Карго-Трекер без нервов: Парсинг китайских накладных (1688 / Taobao / WeChat) с фото через Document AI + Gemini 2.5 Flash
«Брат, груз на Хоргосе!» — и логист получает в WhatsApp стопку из 15 мятых скриншотов с иероглифами из WeChat, фотографий грязных накладных SF Express и штрих-кодов, распечатанных на термопринтере, в которых уже выцвела половина символов. Карго-бизнес в Казахстане — это миллиарды тенге оборота и абсолютный каменный век в операционке. Сотни логистов на Барахолке и в Баян-Ауле ежедневно тратят по 4-5 часов, вглядываясь в мониторы и перебивая вручную китайские трек-коды, вес и маркеры клиентов (типа «ALM-882») в гигантские таблицы Excel.
Проблема ручного ввода — это не просто потеря времени. Это потерянные посылки. Ошибся на одну цифру в 15-значном коде ZTO Express — и коробка с чехлами для iPhone уезжает вместо Алматы в Шымкент, а недовольный клиент обрывает телефоны. Классические системы распознавания текста (OCR) здесь ломают зубы: они не понимают сленговые сокращения байеров, путают иероглифы с похожими цифрами и выдают мусор при малейшем перекосе фотографии. Чтобы решить эту боль, мы в OZAT разработали гибридный AI-конвейер, который за 2 секунды вытаскивает идеальные данные даже из самых «убитых» китайских накладных.
Почему это такая огромная проблема для e-commerce Казахстана?
- • Хаос форматов: Китайские курьерки (SF Express, YTO, ZTO, STO, Yunda) имеют десятки разных шаблонов накладных (快递单). Искать трек-номер регулярками (RegEx) бесполезно — он всегда в разных местах.
- • Качество исходников: Скриншоты из Taobao и 1688, пережатые через WeChat и потом пересланные в WhatsApp, превращаются в пиксельное месиво.
- • Языковой барьер: Описание товара на китайском (например, «女装夏季连衣裙») логисту нужно перевести, чтобы понять категорию для растаможки. Google Translate не масштабируется на тысячи строк в Excel.
- • Метки карго-компаний: Каждый клиент имеет свой код (например, KZ-01-A или Bishkek-99). Байеры часто пишут его маркером от руки прямо поверх штрих-кода.
Мы поняли: нам нужна система, которая читает картинку как живой китаист-логист, но со скоростью машины.
1. Архитектура: Тандем Document AI и Gemini 2.5 Flash
Многие пытаются решить эту задачу, просто кидая картинку в LLM с мультимодальным входом (Vision). Но если текст слишком мелкий, а фото помято, LLM часто галлюцинирует и путает цифры (например, '8' и 'B'). Правильный Enterprise-подход — это гибридный пайплайн:
- Шаг 1: Google Cloud Document AI (OCR Processor). Это специализированная оптическая модель. Она не пытается «думать», она блестяще вытягивает сырой текст, координаты и иероглифы даже с очень плохих сканов и термочеков.
- Шаг 2: Gemini 2.5 Flash (Semantic Parser). Нейросеть получает эту «простыню» сырого текста, анализирует контекст, находит где трек-код, где вес, переводит китайское описание на русский и складывает всё в красивый JSON.
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ CARGO TRACKER AI PIPELINE (OZAT HUB) │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
[ Кладовщик на складе в Гуанчжоу / Иу ]
│ (Скидывает мятую накладную SF Express или скрин из WeChat)
▼
┌─────────────────────────────────────────────────────────┐
│ Telegram / WeChat Webhook Endpoint (FastAPI) │
└────────────────────────────┬────────────────────────────┘
│ Ужатое фото (JPEG)
▼
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ЭТАП 1: Google Cloud Document AI (Pretrained OCR Processor) │
│ • Вытягивает плотный китайский текст, цифры и иероглифы │
│ • Устойчив к перекосам (Skew), теням, размытию и мелкому шрифту │
└────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┘
│
▼ (Сырой текст с артефактами: "运单号882371 KZ-09...")
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ЭТАП 2: Gemini 2.5 Flash (Semantic Extraction) │
│ • Распознает контекст: где трек-код, где телефон, где код клиента │
│ • Переводит описание ("手机壳" -> "Чехлы для телефона") │
│ • Возвращает строгий JSON │
└────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┘
│
▼ { "tracking": "882...", "client": "KZ-09", "weight": 2.5 }
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Firestore Database & Web Frontend │
│ • Статус: "Поступило на склад в Китае" │
│ • Клиенту отправляется Push / SMS уведомление │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────┘2. Листинг 1: Конвейер парсинга на Python (Document AI + Gemini)
Ниже представлен код сервиса. Обратите внимание на паттерн Fallback: если Document AI вдруг не нашел текста (например, из-за специфического формата изображения), мы напрямую отправляем байты картинки в Gemini 2.5 Flash, используя его встроенные Vision-возможности.
import os
import json
import logging
from typing import Dict, Any, Optional
from google.api_core.client_options import ClientOptions
from google.cloud import documentai
from google import genai
from google.genai import types
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Настройки Document AI (OCR)
PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT")
LOCATION = os.environ.get("DOCAI_LOCATION", "eu")
PROCESSOR_ID = os.environ.get("DOCAI_PROCESSOR_ID")
# Инициализация клиентов
docai_client = documentai.DocumentProcessorServiceClient(
client_options=ClientOptions(api_endpoint=f"{LOCATION}-documentai.googleapis.com")
)
ai_client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
SYSTEM_PROMPT = """Ты — экспертный логистический ассистент карго-компании (Казахстан-Китай).
На вход поступает сырой распознанный текст (OCR) с китайской накладной (快递单) от курьеров ZTO, YTO, SF Express, STO или скриншот из 1688/Taobao/WeChat.
Твоя задача — извлечь следующие данные, игнорируя визуальный мусор и нерелевантный текст:
1. Идентификатор/Код клиента (часто содержит латиницу и цифры, например "ALM-882", "KZ-01-M", "K89").
2. Трек-номер посылки (快递单号 / 运单号) — обычно длинный цифровой или буквенно-цифровой код (12-15 символов).
3. Курьерская служба (ZTO/中通, YTO/圆通, SF/顺丰 и т.д.).
4. Вес в кг (重量), если указан.
5. Описание товара (переведи с китайского на русский).
Верни строгий JSON:
{
"client_code": "string или null",
"tracking_number": "string",
"courier": "string",
"weight_kg": number или null,
"description_ru": "string"
}"""
async def process_cargo_waybill(image_bytes: bytes, mime_type: str = "image/jpeg") -> Dict[str, Any]:
"""
Двухэтапный парсинг:
1. Document AI для высокоточного OCR мелкого/мятого китайского текста.
2. Gemini 2.5 Flash для семантического извлечения сущностей из текста OCR.
"""
# ЭТАП 1: OCR через Document AI (Pretrained OCR Processor)
name = docai_client.processor_path(PROJECT_ID, LOCATION, PROCESSOR_ID)
raw_document = documentai.RawDocument(content=image_bytes, mime_type=mime_type)
request = documentai.ProcessRequest(name=name, raw_document=raw_document)
logger.info("Отправка в Document AI...")
result = docai_client.process_document(request=request)
document = result.document
raw_text = document.text
logger.info(f"Document AI извлек {len(raw_text)} символов текста.")
# Если OCR не нашел текст, пробуем отдать картинку напрямую в Gemini (Fallback)
if len(raw_text.strip()) < 10:
logger.warning("Document AI не нашел текст. Используем Gemini Multimodal Fallback.")
prompt_content = [
types.Part.from_bytes(data=image_bytes, mime_type=mime_type),
"Проанализируй накладную и верни JSON."
]
else:
# ЭТАП 2: Gemini 2.5 Flash для структурирования
logger.info("Передача сырого текста в Gemini 2.5 Flash...")
prompt_content = [
f"Вот сырой текст с накладной, полученный через OCR:\n\n{raw_text}\n\n",
"Извлеки из него данные и верни JSON согласно системной инструкции."
]
response = ai_client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt_content,
config=types.GenerateContentConfig(
system_instruction=SYSTEM_PROMPT,
response_mime_type="application/json",
temperature=0.0
)
)
try:
parsed_data = json.loads(response.text)
return parsed_data
except json.JSONDecodeError:
logger.error("Ошибка парсинга JSON от Gemini.")
return {"error": "Failed to parse"}Смотреть код на GitHub (OZAT-kz)
3. Листинг 2: FastAPI Webhook для интеграции с ботами и CRM
Этот эндпоинт можно подключить к Telegram-боту для кладовщиков. Кладовщик в Иу или Гуанчжоу просто фоткает коробку, отправляет в чат, и данные моментально ложатся в базу Firestore, а клиент в Казахстане получает пуш-уведомление: «Ваш груз прибыл на склад в Китае».
from fastapi import FastAPI, UploadFile, File, HTTPException
import os
from google.cloud import firestore
from cargo_parser_service import process_cargo_waybill
app = FastAPI(title="OZAT Cargo Tracker API")
db = firestore.Client(database=os.environ.get("FIRESTORE_DATABASE", "(default)"))
@app.post("/api/v1/parse-waybill")
async def parse_waybill_endpoint(file: UploadFile = File(...)):
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="Only images are supported")
image_bytes = await file.read()
# Вызов конвейера Document AI + Gemini
try:
parsed_data = await process_cargo_waybill(image_bytes, file.content_type)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}")
if "error" in parsed_data:
raise HTTPException(status_code=422, detail="AI could not extract structured data")
tracking_number = parsed_data.get("tracking_number")
if not tracking_number:
raise HTTPException(status_code=400, detail="Could not find tracking number on image")
# Сохраняем результат в Firestore
doc_ref = db.collection("cargo_waybills").document(tracking_number)
doc_ref.set({
"tracking_number": tracking_number,
"client_code": parsed_data.get("client_code"),
"courier": parsed_data.get("courier"),
"weight_kg": parsed_data.get("weight_kg"),
"description": parsed_data.get("description_ru"),
"status": "ARRIVED_AT_WAREHOUSE",
"created_at": firestore.SERVER_TIMESTAMP
})
return {
"status": "success",
"data": parsed_data,
"message": f"Waybill {tracking_number} saved to database."
}Смотреть код на GitHub (OZAT-kz)
4. Результаты внедрения: Метрики, которые решают
Мы протестировали эту связку на реальном датасете из 2 500 накладных карго-компании, работающей по маршруту Иу — Хоргос — Алматы.
Точность распознавания трек-кодов и иероглифов (%)
Время обработки одной накладной в секундах (p50)
Бизнес-показатели (Итоги месяца):
- • Высвобождение времени: 4.2 часа в день у каждого оператора (можно перевести на клиентский сервис).
- • Снижение потерь грузов (Lost Packages): на 89% (за счет исключения опечаток в 15-значных трек-кодах).
- • Скорость обработки (p50): 1.8 секунды на одну накладную.
- • FinOps (Стоимость облака): Парсинг 10 000 накладных обходится примерно в $25-30 (Document AI + токены Gemini Flash). Это дешевле одного рабочего дня логиста.
Анти-фрод Kaspi-чеков: Детекция генераторов фальшивок и Replay-атак на Gemini 2.5 Flash + Firestore за 1.4 секунды
5. Листинг 3: Инфраструктура как код (Terraform)
Для работы конвейера мы разворачиваем Cloud Run сервис и автоматически активируем API Document AI. Секреты безопасно лежат в Secret Manager.
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
variable "project_id" { type = string }
variable "region" { default = "europe-west1" }
provider "google" {
project = var.project_id
region = var.region
}
# Активация API Document AI
resource "google_project_service" "documentai" {
service = "documentai.googleapis.com"
disable_on_destroy = false
}
# Создание Document AI Processor (OCR)
resource "google_document_ai_processor" "ocr_processor" {
display_name = "cargo-ocr-processor"
location = "eu"
type = "OCR_PROCESSOR"
depends_on = [google_project_service.documentai]
}
# Секрет Gemini API
resource "google_secret_manager_secret" "gemini_key" {
secret_id = "gemini-api-key"
replication { auto {} }
}
# Cloud Run Service
resource "google_cloud_run_v2_service" "cargo_parser" {
name = "cargo-waybill-parser"
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
template {
scaling { max_instance_count = 10 }
containers {
image = "${var.region}-docker.pkg.dev/${var.project_id}/apps/cargo-parser:latest"
env {
name = "DOCAI_PROCESSOR_ID"
value = google_document_ai_processor.ocr_processor.id
}
env {
name = "DOCAI_LOCATION"
value = "eu"
}
env {
name = "GEMINI_API_KEY"
value_source { secret_key_ref { secret = google_secret_manager_secret.gemini_key.secret_id; version = "latest" } }
}
}
}
}Смотреть код на GitHub (OZAT-kz)
6. Листинг 4: Деплой микросервиса
Команды gcloud для быстрой сборки и публикации нашего парсера.
# 1. Авторизация и настройка
gcloud auth login
gcloud config set project ozat-cloud-kz
# 2. Создание процессора Document AI (если через CLI)
# Альтернативно используется Terraform (см. листинг выше)
# 3. Сборка образа
gcloud builds submit --tag europe-west1-docker.pkg.dev/ozat-cloud-kz/apps/cargo-parser:latest
# 4. Деплой Cloud Run
gcloud run deploy cargo-waybill-parser \
--image europe-west1-docker.pkg.dev/ozat-cloud-kz/apps/cargo-parser:latest \
--region europe-west1 \
--platform managed \
--allow-unauthenticated \
--set-secrets="GEMINI_API_KEY=gemini-api-key:latest" \
--set-env-vars="DOCAI_LOCATION=eu,FIRESTORE_DATABASE=(default)" \
--memory=512Mi \
--cpu=1Смотреть код на GitHub (OZAT-kz)
7. Руководство для Скаутов OZAT: Как продавать Cargo Tracker
Рынок Карго в Казахстане огромен. В каждом крупном торговом центре Алматы, на рынках, в бизнес-центрах сидят десятки форвардинговых компаний. Их главная боль — это операционный хаос и недовольные клиенты, которые сутками ждут обновления статуса посылки.
Напишите владельцу карго: «Сколько часов ваши сотрудники тратят на перенос трек-кодов из WeChat в Excel? А сколько посылок теряется из-за одной опечатки?». Предложите Telegram-бота, куда можно скинуть фото, и оно само попадет в базу.
Разовый платеж (Setup): от 350 000 до 600 000 ₸ (интеграция парсера с их текущей CRM/Excel/Google Sheets).
Поддержка (MRR): 40 000 – 70 000 ₸/мес.
Сделайте демо-бота. Попросите клиента скинуть самую плохую, мятую накладную из WeChat. Бот за 2 секунды выдаст идеальный JSON с переводом. Продажа гарантирована.
8. Реальные ограничения и компромиссы решения
- 1. Рукописный маркер поверх штрих-кода: Часто на складах в Иу рабочие пишут код клиента (типа KZ-77) толстым маркером прямо поверх трек-номера. Document AI может не распознать перекрытый текст. В таких случаях нужен ручной разбор.
- 2. Стоимость Document AI: В отличие от сверхдешевого Gemini Flash, Document AI тарифицируется по страницам (около $1.5 за 1000 страниц). Для огромных объемов (сотни тысяч в день) это может ударить по бюджету. Если качество фото всегда идеальное, можно отключить DocAI и оставить только Gemini Vision.
- 3. Иероглифы-омоглифы: Некоторые китайские символы визуально похожи на цифры при плохом качестве печати термопринтера. Уверенность OCR (Confidence Score) падает.
- 4. Специфичный сленг перевода: Gemini может перевести «充电宝» как «Пауэрбанк», а бухгалтер ожидает «Портативное зарядное устройство» для таможенной декларации. Требуется постобработка через словарь синонимов ВЭД.
💡 Совет OZAT: Готовы к внедрению? Рассчитайте архитектуру и бюджет через Scope Builder или пройдите бесплатный ИИ-аудит.

Рустам Шарафутдинов
Эксперт в области архитектуры Google Cloud и Senior Full-Stack разработчик с более чем 15-летним опытом. Специализируется на отказоустойчивых архитектурах, оптимизации высоконагруженных проектов и интеграции AI (Vertex AI).