--- name: ozon-operations description: "Ozon e-commerce operations: scraping product pages, seller API (import/pricing/attributes), and CDP auto-surf data collection." version: 1.2.0 author: Hermes Agent license: MIT metadata: hermes: tags: [ozon, e-commerce, scraping, seller-api, cdp, russia, cross-border] related_skills: [proxy-management, camoufox-google-2fa-login, node-playwright-web-automation, cross-border-social-marketing] --- # Ozon Operations Three workflows for working with Ozon (Russian e-commerce platform): scraping, seller API, and automated data collection. ## Workflow Overview | Workflow | Purpose | Method | |----------|---------|--------| | **Product Page Scraping** | Extract product data from ozon.ru | CamouFox + Russia proxy + slider CAPTCHA | | **Seller API** | Import, pricing, attributes, health analysis | REST API (v3) | | **CDP Auto-Surf** | Continuous product data collection | CDP tunnel, random page hopping | | **CDP Seller Login** | Automate Ozon Seller web UI via Bridge CDP | CDP Input.dispatchMouseEvent | | **Product Health Audit** | Analyze product availability & fix issues | v3 API + availability triage | | **Messenger Management** | Read & manage platform notifications | CDP evaluate (read-only); manual mark-read only | ## Section 0: CDP Interaction with Ozon Seller UI Ozon Seller (`seller.ozon.ru`) is a React SPA that rejects standard JS `click()` and requires **CDP `Input.dispatchMouseEvent`** for button interactions. Never use `element.click()` or `dispatchEvent(new MouseEvent('click'))` — these are silently ignored by React/Vue synthetic event handlers. **Reliable click pattern:** ```python # Get button position, then: cdp('Input.dispatchMouseEvent', {'type': 'mouseMoved', 'x': x, 'y': y}) cdp('Input.dispatchMouseEvent', {'type': 'mousePressed', 'x': x, 'y': y, 'button': 'left', 'clickCount': 1}) time.sleep(0.15) cdp('Input.dispatchMouseEvent', {'type': 'mouseReleased', 'x': x, 'y': y, 'button': 'left', 'clickCount': 1}) ``` **Also needed:** `Input.insertText` for form fields (textareas/inputs ignore `value=` assignment + event dispatch in React). **UI limitations:** - Product filter tabs (错误, 待修改, etc.) cannot be activated via URL params or DOM clicks — they use internal React state - Product detail pages don't support deep-linking (`/app/products/CODE` renders empty) - Product list uses lazy loading — scrolling down doesn't load more; must navigate page-by-page - "生成密钥" button opens an inline form (not a modal); the form uses permission checkboxes + text input **See:** `references/ozon-cdp-interaction.md` **Sidebar navigation URLs** and common 404 traps are documented in `references/ozon-seller-navigation.md` — use for direct CDP navigate between sections without clicking. ## Section 1: Product Page Scraping Scrape Ozon.ru product pages using CamouFox anti-detection browser with Russian proxy and slider CAPTCHA auto-solve. **When to use:** Extracting product data (title, price, rating, images) from specific Ozon product pages. **Key challenges:** - Anti-bot detection (Cloudflare, slider CAPTCHA) - Geo-restricted content (Russia-only products) - Dynamic pricing and availability **Quick start:** ```bash # Use Russian SOCKS5 proxy with CamouFox python3 scripts/ozon_scraper.py --proxy socks5://ru-proxy:1080 --url "https://ozon.ru/product/..." ``` **Pitfalls:** - Must use Russian proxy for full product data - Slider CAPTCHA requires CamouFox (not standard Playwright) - Request frequency triggers rate limiting **See:** `references/ozon-ru-scraper.md` for full scraping guide, proxy selection, and CAPTCHA handling. ## Section 2: Seller API Ozon Seller API for product import, review, pricing, categories, attributes, and product health analysis. **When to use:** Programmatic product management on Ozon — importing listings, updating prices, checking review status, auditing product health. ### API Key Acquisition API keys for Ozon stores are stored in AtomK backend: ```python # Login to AtomK r = requests.post('https://atomlisting.com/api/v1/auth/login', json={'username': 'admincao', 'password': 'Tt123456!'}, verify=False) token = r.json()['token'] # Get stores with extra_data.api_key r = requests.get('https://atomlisting.com/api/v1/stores', headers={'Authorization': f'Bearer {token}'}, verify=False) for store in r.json(): extra = store.get('extra_data', {}) # extra['api_key'] — Ozon API Key # extra['client_id'] — Ozon Client-Id ``` **Pitfall:** API keys can be **deactivated** by Ozon. If the API returns `{"code":7,"message":"Api-key is deactivated"}`, you must generate a new key at `seller.ozon.ru/app/settings/api-keys` using the CDP mouse-event pattern (Section 0). ### Product Health Audit (v3 API) Use `POST /v3/product/list` + `POST /v3/product/info/list` to audit all products. The critical field is **`availabilities`**, NOT `status.state`: ```python av = product.get("availabilities", [{}])[0] status = av.get("availability") # "AVAILABLE", "UNAVAILABLE", "HIDDEN" reasons = [r["human_text"]["text"] for r in av.get("reasons", [])] ``` **Common states:** | State | Meaning | Typical fix | |-------|---------|-------------| | `AVAILABLE` | Active for sale | None | | `HIDDEN` | "Нарушен запрет на копирование" — copy prohibition | Replace images/description with original content | | `UNAVAILABLE` | "Неактуальный товар" — stale product | Update stock, pricing, or re-activate | | `NO_DATA` | Empty availabilities array | May need manual review | **Full audit script:** See `references/ozon-product-audit.md`. **Key features:** - v3/import for product creation (use `desc_cat_id` + `type_id` for categories) - Attribute API: use `id` not `attribute_id`, dictionary attributes need `dict_id` - Pricing: must be in CNY - Review: check image-to-category matching **Quick start:** ```bash curl -s "https://api-seller.ozon.ru/v3/product/import" \ -H "Client-Id: $CLIENT_ID" \ -H "Api-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d @product_import.json ``` **Pitfalls:** - Category: use `description_category_id` + `type_id`, NOT `category_id` - Attributes: use `attribute.id`, NOT `attribute.attribute_id` - Dictionary attributes: must include `dictionary_attribute_id` (alias `dict_id`) - Price must be in CNY - v3/import is the current endpoint — earlier versions are deprecated **See:** `references/ozon-seller-api.md` for full API reference with import, pricing, and attribute guides. ## Section 4: Messenger & Notifications Management The Ozon Seller messenger (`/app/messenger?group=*`) shows system notifications, support chats, and buyer messages. Unread counts appear on the dashboard and top nav. ### Group Structure | Group | URL param | Typical content | |-------|-----------|-----------------| | 主要 (Main) | `?group=main` | Platform announcements, policy updates | | 客服 (Support) | `?group=support_v2` | Automated system messages (product issues, archivals) | | 通知 (Notifications) | `?group=system` | FBS orders, returns, quality alerts | | 推广 (Promotions) | `?group=promotion` | Marketing tools, ad features | ### Critical Limitations **No programmatic mark-read available.** The Ozon messenger React SPA: - Has no "mark all as read" button in any group - Ignores all JS `element.click()`, `dispatchEvent(MouseEvent)`, and CDP mouse events — clicks are consumed but read state never changes - No discoverable REST API (all attempts return 404; uses gRPC) - Direct CDP navigate to messenger URLs causes SPA hydration failure (shell loads but Vue doesn't bootstrap) **Only manual mark-read works** — user must click messages in their Desktop browser. ### Navigation Access must go through the SSO flow — direct CDP navigate to messenger routes fails: 1. Navigate to `seller.ozon.ru/app/registration/signin` 2. SSO auto-login → company selection → "下一步" 3. Dashboard loads with top-nav messenger links showing unread counts 4. Click the count badge to enter messenger (triggers proper SPA routing) The dashboard (`#__ozon`) and messenger (`#app`) use different SPA frameworks — see `bridge-cdp-agent` skill reference `references/ozon-dual-spa-frameworks.md`. ### Reading Content Message content IS accessible via CDP evaluate — the DOM shows message text in `.m9d-c2` elements. Use this to extract and summarize visible messages even though you can't mark them as read. ```javascript // Get visible message previews in current group Array.from(document.querySelectorAll('.m9d-c2')).map(e => ({ text: (e.innerText || '').substring(0, 100), rect: e.getBoundingClientRect() })) ``` ### Pitfalls - Dashboard and messenger are different SPAs — Vue instance location differs (`#__ozon.__vue__` vs `#app.__vue__`) - Sidebar group labels are `` inside `
`, not clickable buttons - Unread count badges are `
` — read these to track remaining unread - The settings button at position (474, 178) opens a chat-settings panel that blocks the message list - Page.navigate to messenger URL returns minimal shell (~58 chars); only client-side routing via Vue $router.push() works Auto-surf Ozon.ru via CDP tunnel — randomly hops between product pages, extracts data, saves to JSONL. Runs continuously. **When to use:** Continuous background collection of Ozon product data for market research, price monitoring. **Key patterns:** - CDP tunnel for browser connection - Random page navigation (surf pattern) - Extract: SKU, price, rating, seller, stock, delivery info - Append to JSONL file - Graceful shutdown on interrupt **Quick start:** ```bash python3 scripts/cdp_ozon_collector.py --output products.jsonl ``` **Pitfalls:** - CDP connection may drop — implement reconnection - Respect rate limits — add delays between page loads - JSONL append mode — don't rewrite entire file each iteration **See:** `references/cdp-ozon-collector.md` for full collection pipeline.