Files
atomk-hermes-skills/skills/cross-border-ecommerce/ozon-operations/SKILL.md
T

222 lines
9.8 KiB
Markdown

---
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 `<SPAN class="m9d-z8">` inside `<DIV class="m9d-z5">`, not clickable buttons
- Unread count badges are `<DIV class="m9d-z7">` — 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.