Инженерный хаб

Карго-Трекер без нервов: Парсинг китайских накладных (1688 / Taobao / WeChat) с фото через Document AI + Gemini 2.5 Flash

03.09.2026
Шарафутдинов Р.

«Брат, груз на Хоргосе!» — и логист получает в 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.
Архитектурная схема / Data Flow Pipeline (ASCII)
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│                          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). Это дешевле одного рабочего дня логиста.

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

Рынок Карго в Казахстане огромен. В каждом крупном торговом центре Алматы, на рынках, в бизнес-центрах сидят десятки форвардинговых компаний. Их главная боль — это операционный хаос и недовольные клиенты, которые сутками ждут обновления статуса посылки.

1. Триггер продаж

Напишите владельцу карго: «Сколько часов ваши сотрудники тратят на перенос трек-кодов из WeChat в Excel? А сколько посылок теряется из-за одной опечатки?». Предложите Telegram-бота, куда можно скинуть фото, и оно само попадет в базу.

2. Средний чек внедрения

Разовый платеж (Setup): от 350 000 до 600 000 ₸ (интеграция парсера с их текущей CRM/Excel/Google Sheets).
Поддержка (MRR): 40 000 – 70 000 ₸/мес.

3. Вау-эффект на демо

Сделайте демо-бота. Попросите клиента скинуть самую плохую, мятую накладную из 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).

Экспертность: GCP, Kubernetes, Микросервисы, React, Node.js

Комментарии (0)