Продавцы в трансграничной торговле часто размещают один и тот же товар на Shopee, Lazada, Amazon, TikTok Shop и в собственном интернет-магазине, причём на нескольких языках. Языковая модель может за одну ночь подготовить черновики заголовков, буллетов (bullet points) и описаний для сотен товаров по таблице с их характеристиками. В этой статье мы напишем небольшой скрипт на Python, который читает CSV-файл, запрашивает у локальной модели в Ollama (или у любого OpenAI-совместимого ИИ API) карточку товара на каждом языке и записывает результаты в новый CSV-файл: вы проверяете его, а затем импортируете в шаблоны маркетплейсов.
Создайте таблицу, где каждому товару соответствует одна строка, и сохраните её как products.csv в кодировке UTF-8 (в Excel: CSV UTF-8). Названия столбцов выбирайте сами: модели передаются все столбцы, а скрипту нужен столбец sku с уникальными значениями:
sku,name,category,specs,keywords
LS-01,Bamboo laptop stand,Office,"6 height levels; fits 10-17 inch laptops; foldable; 1.1 kg; natural bamboo","laptop stand, bamboo, ergonomic"
BT-07,Wireless earbuds,Audio,"Bluetooth 5.3; 30 h playback with case; IPX5; USB-C charging","wireless earbuds, bluetooth, sport"
Чем точнее характеристики (материал, размеры, совместимые модели, комплектация), тем лучше и надёжнее результат: скрипт требует от модели использовать только эти факты.
apt install -y python3-venv
python3 -m venv /opt/listing
/opt/listing/bin/pip install openai
Python-библиотека openai работает и с OpenAI-совместимым эндпоинтом Ollama, и с большинством ИИ-провайдеров, поэтому один и тот же скрипт можно запускать как с локальной, так и с облачной моделью. Настройка Ollama описана в статье о запуске локальной LLM с Ollama.
cat > /opt/listing/listing.py <<'EOF2'
import csv, json, os, re, sys, time
from openai import OpenAI
client = OpenAI(base_url=os.environ.get("AI_BASE_URL", "http://127.0.0.1:11434/v1"),
api_key=os.environ.get("AI_API_KEY", "ollama"))
MODEL = os.environ.get("AI_MODEL", "qwen2.5:7b")
LANGS = [l.strip() for l in os.environ.get("LANGS", "English").split(",")]
SYSTEM = """You write e-commerce product listings for online marketplaces.
Use only the product facts provided. Never invent features, materials,
certifications, prices or delivery promises. No exaggerated claims.
Return JSON only, in this format:
{"title": "...", "bullets": ["...", "...", "...", "...", "..."], "description": "..."}
title: at most 120 characters, main keyword first.
bullets: 5 short points, each starting with the benefit.
description: 80 to 150 words of plain text."""
def ask(product, lang):
prompt = f"Write the listing in {lang}.\nProduct facts:\n{json.dumps(product, ensure_ascii=False)}"
for attempt in range(3):
r = client.chat.completions.create(model=MODEL, temperature=0.4, messages=[
{"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}])
m = re.search(r"\{.*\}", r.choices[0].message.content or "", re.S)
try:
data = json.loads(m.group(0))
if data.get("title") and isinstance(data.get("bullets"), list) and data.get("description"):
return data
except (AttributeError, ValueError):
pass
time.sleep(2)
raise RuntimeError(f"no valid answer for {product['sku']} / {lang}")
src, dst = sys.argv[1], sys.argv[2]
done = set()
if os.path.exists(dst):
with open(dst, encoding="utf-8-sig") as f:
done = {(r["sku"], r["language"]) for r in csv.DictReader(f)}
with open(src, encoding="utf-8-sig") as f:
products = list(csv.DictReader(f))
new_file = not os.path.exists(dst)
with open(dst, "a", newline="", encoding="utf-8-sig") as f: # utf-8-sig: Excel shows every language correctly
w = csv.writer(f)
if new_file:
w.writerow(["sku", "language", "title", "bullets", "description"])
for p in products:
for lang in LANGS:
if (p["sku"], lang) in done:
continue
d = ask(p, lang)
w.writerow([p["sku"], lang, str(d["title"]).strip(),
"\n".join(str(b).strip() for b in d["bullets"]), str(d["description"]).strip()])
f.flush()
print(p["sku"], lang, "done", flush=True)
EOF2
Скрипт запрашивает ответ в формате JSON, проверяет его и при неудаче повторяет запрос, всего до трёх попыток. Готовые строки записываются сразу, а при повторном запуске товары и языки, которые уже есть в выходном файле, пропускаются, поэтому прерванное задание просто продолжается с того места, где остановилось.
cd /data/listings
LANGS="English,Thai,Malay,Japanese" nohup /opt/listing/bin/python /opt/listing/listing.py \
products.csv listings.csv > listing.log 2>&1 &
tail -f listing.log
На CPU-сервере модель 7B обычно тратит на одну карточку несколько десятков секунд, так что 500 товаров на четырёх языках — работа на ночь. Чтобы использовать облачную модель, задайте адрес API, ключ и название модели любого OpenAI-совместимого API:
AI_BASE_URL=https://ai.example.com/v1 AI_API_KEY=sk-your-key AI_MODEL=cloud-chat \
LANGS="English,Thai" /opt/listing/bin/python /opt/listing/listing.py products.csv listings.csv
Пользуйтесь услугами ИИ-провайдеров только из поддерживаемых ими регионов и в рамках их условий.
listings.csv в Excel или Google Таблицах и отфильтруйте строки по языку.SYSTEM тон вашего бренда и правила маркетплейса, например список слов, которые вы никогда не используете, британский или американский вариант английского или запрет на эмодзи.SYSTEM одну одобренную карточку в качестве примера — модели очень точно копируют её структуру и тон.qwen2.5:14b, заметно лучше пишут на тайском, малайском и вьетнамском, но работают медленнее и требуют больше памяти.Маркетплейсам важны точность и соблюдение правил, поисковым системам — полезность контента. Точные, конкретные карточки, проверенные человеком, проблем не вызывают; массово созданный текст с ошибками или выдуманными заявлениями — это риск, кто бы его ни написал.
Модель три раза подряд вернула ответ, который не является корректным JSON. Запустите ту же команду ещё раз (обработка продолжится с места остановки), снизьте температуру или возьмите модель побольше.
Да. Как массово вырезать товары и создавать главные изображения на белом фоне, описано в статье о массовом удалении фона с фото товаров с помощью rembg.
Если проблема не решена, создайте тикет — техническая поддержка IMIDC работает 24/7. Укажите IP сервера, используемую модель, выполненную команду и последние строки listing.log, чтобы мы быстрее нашли причину.