Batch AI rewriting of Webasyst / Shop-Script product cards using any OpenAI-compatible LLM (DeepSeek, OpenAI, OpenRouter, local Ollama…).
The tool scans your catalog, detects which product descriptions are still in the old format, rewrites each one through an LLM following a strict template, posts the result back through the Webasyst REST API, and validates the change on the live page — automatically, in controllable batches, with full rollback.
Русская версия: README.ru.md
Manually rewriting hundreds of product cards is weeks of monotonous work. This project turns it into a supervised pipeline:
sitemap → scanner → inventory → LLM copywriter → poster → validator
↑ │
└──────── state + backups ─────┘
It was built for a real catalog of 728 products and processed the whole
catalog in batches of 50 with zero data loss: every update is backed up before
it is written, and every card is re-checked on the live site before it is
marked as done.
- Catalog scanner — walks the shop sitemap, downloads product pages and extracts title, features, old description and summary (multithreaded).
- Deterministic rewrite detection — a product needs a rewrite when its
description lacks configurable CSS markers (e.g.
faq-item,facts-wrapper). - LLM copywriter — strict prompt with a few-shot example; the model returns
JSON (
description_html,summary_text, and optional SEO meta tags). - SEO-safe posting —
meta_title/meta_descriptionare written only into fields that are currently empty; existing SEO is never overwritten. - Rollback — the previous state of every product is saved to
data/backups/before each update. - Validation — after posting, the live page is re-fetched to confirm the new-format markers actually appeared (with retries for cache).
- Resumable runs — progress lives in
data/state.json; a re-run skipsdoneproducts and retrieserrorones. - WAF-friendly API client — sends
access_tokenin the query string with a browser User-Agent, which works on hostings that block theAuthorization: Bearerheader (e.g. reg.ru). - Provider-agnostic LLM — any OpenAI-compatible endpoint, including local Ollama models.
| Stage | Module | Description |
|---|---|---|
| 1. Scan | src/scanner.py |
Sitemap → data/inventory.json with needs_rewrite flags |
| 2. Generate | src/llm_client.py + prompts/product_prompt.md |
LLM returns structured JSON with new HTML |
| 3. Post | src/poster.py |
Backs up current state, then calls shop.product.update |
| 4. Validate | src/validator.py |
Re-fetches the page and checks the markers |
| 5. Orchestrate | src/pipeline.py |
Runs the cycle in batches and persists statuses |
| 6. Report | src/report.py, src/make_report.py |
Exports lists of processed URLs |
Product statuses: pending → generated → posted → done (or error).
- Python 3.10+
- A Webasyst / Shop-Script site with REST API access (
api.php) - An OpenAI-compatible LLM endpoint + API key
# 1. Clone and install
git clone https://github.com/YanChi-pixel/webasyst-ai-rewriter.git
cd webasyst-ai-rewriter
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
# 2. Configure
copy .env.example .env # Windows
# cp .env.example .env # macOS / Linux
# …then edit .env and fill in your values
# 3. Scan the catalog
python -m src.scanner
# 4. Run a batch of 50 products
python -m src.pipeline 50
# 5. Build a report of processed URLs
python -m src.make_reportEach script can also be run directly from the src/ directory
(e.g. python src/pipeline.py 50) — the modules support both launch modes.
Webasyst uses OAuth 2.0. The easiest way to get a token is the client flow: the token lands in the browser address bar.
-
Log into the shop admin (
https://your-shop.example.com/webasyst) as a user with product permissions. -
In the same browser open:
https://your-shop.example.com/api.php/auth?client_id=ai_rewriter&client_name=Rewriter&response_type=token&scope=shop&redirect_uri=https%3A%2F%2Fyour-shop.example.com%2F&format=json -
Approve access.
-
The browser redirects to
https://your-shop.example.com/#access_token=TOKEN— copy the value ofaccess_token(up to the first&). -
Paste it into
.envasWEBASYST_TOKEN.
Verify the token:
GET https://your-shop.example.com/api.php/shop.product.getInfo?id=1&access_token=TOKEN
An invalid_token error means the token is wrong; product data means it works.
Why the token is in the query string? Some hostings run a WAF that blocks the
Authorization: Bearerheader (andcurl's User-Agent). Sendingaccess_tokenin the query string with a browser User-Agent is the form that passes the filter. This is already handled insidesrc/webasyst_api.py.
All settings live in .env (see .env.example):
| Variable | Default | Meaning |
|---|---|---|
WEBASYST_BASE_URL |
— | Shop base URL, no trailing slash |
WEBASYST_TOKEN |
— | OAuth access token |
WEBASYST_CLIENT_ID |
ai_rewriter |
Client id used in the authorize URL |
SITEMAP_PATH |
/sitemap-shop.xml |
Path of the sitemap with product URLs |
NEW_MARKERS |
faq-item,facts-wrapper |
CSS classes that mark a rewritten card |
LLM_API_BASE |
https://api.deepseek.com |
OpenAI-compatible endpoint |
LLM_API_KEY |
— | API key (also reads DEEPSEEK_API_KEY) |
LLM_MODEL |
deepseek-chat |
Model name (also reads DEEPSEEK_MODEL) |
SCAN_LIMIT |
0 |
Max products to scan (0 = all) |
SCAN_WORKERS |
8 |
Parallel page downloads during scan |
BATCH_SIZE |
50 |
Default products per pipeline run |
DELAY_SECONDS |
2 |
Pause between products |
The config is defensive: it also accepts the legacy DEEPSEEK_* variable names
and strips an accidental /chat/completions suffix from the base URL.
python -m src.scannerProduces data/inventory.json. Each item contains url, product_id,
title, needs_rewrite, features, old_description, summary. Products
with needs_rewrite: true are the ones the pipeline will process.
python -m src.pipeline 50 # process up to 50 pending products
python -m src.pipeline # process BATCH_SIZE products from .envA lock file (data/pipeline.lock) prevents two concurrent runs. Progress is
written to data/state.json after every product, so interrupting the process
loses nothing — the next run continues where it stopped.
python -m src.report # all done URLs → data/report_links.txt
python -m src.make_report # last complete batch → data/report_batchN.txtBefore every update the current product state is saved to
data/backups/product_<id>_<timestamp>.json. To roll a product back, take the
old description / summary / SEO fields from that JSON and write them back
through shop.product.update (see src/webasyst_api.py).
- Rewrite markers — set
NEW_MARKERSin.envto the CSS classes that identify your "new format" description. If at least one marker is missing, the product is treated as needing a rewrite. - Copywriter prompt — edit
prompts/product_prompt.md(niche-neutral by default). For a stronger few-shot anchor, copy one of the ready-made niche prompts fromprompts/examples/(textiles, electronics, furniture) intoproduct_prompt.md. - Theme selectors — the scanner's HTML extraction assumes the Shop-Script
mastershoptheme (#product-description,#product-options,product-card__summary,data-product). If your theme differs, adjust the selectors insrc/scanner.pyandsrc/validator.py.
webasyst-ai-rewriter/
├── src/
│ ├── config.py # .env loader with defensive defaults
│ ├── webasyst_api.py # Webasyst REST client
│ ├── scanner.py # sitemap → inventory
│ ├── llm_client.py # OpenAI-compatible LLM client
│ ├── poster.py # posting + backups
│ ├── validator.py # marker validation
│ ├── pipeline.py # orchestrator
│ ├── report.py # done-URLs report
│ └── make_report.py # per-batch report
├── prompts/
│ ├── product_prompt.md # niche-neutral system prompt
│ └── examples/ # ready-made prompts: textile / electronics / furniture
├── examples/
│ └── inventory.example.json
├── .github/
│ └── workflows/ci.yml # lint + compile + smoke import test
├── CHANGELOG.md
├── .env.example
└── requirements.txt
{"error":"token_required"}— normal without a token; it means the API endpoint is alive. SupplyWEBASYST_TOKEN.- HTTP 403 from the API — the hosting WAF is blocking the request form.
Make sure
access_tokenis in the query string and a browser User-Agent is used (handled by the client). Do not usecurlagainst such hostings. - Windows console shows
������— console encoding (cp1251) cannot render Cyrillic; this is cosmetic. Read UTF-8 files (data/…) instead of the console output. invalid_token— the token is wrong or expired; re-issue it via the client flow above.- LLM returns no JSON — the client raises
LLMErrorand retries on 429/5xx. Check your API key balance and the model name. - Hallucinated product facts — the prompt forbids inventing facts, and the
few-shot example anchors the model. For extra safety, add a deterministic
post-check: key numbers from
featuresmust appear indescription_html.
- Run a small pilot batch (3–5 products) before processing the whole catalog.
- Keep
data/backups/until you are confident the new texts are good. - Do not commit
.env— it is ignored via.gitignore. - The tool writes
meta_title/meta_descriptiononly into empty fields; existing SEO is never touched.
This tool writes to a production store via the official REST API. Use it on a staging copy first, back up your data, and respect the rate limits of both your LLM provider and your hosting. You are responsible for the content the LLM generates and for reviewing it before publishing at scale.