Add archived/miaoshou-collect-box

This commit is contained in:
2026-07-10 16:10:56 +08:00
parent da372a4458
commit f6947fba97
@@ -0,0 +1,369 @@
---
name: miaoshou-collect-box
category: cross-border-ecommerce
description: Explore and interact with Miaoshou ERP's "通用采集箱" (Common Collect Box) — URLs, collection methods, import paths, and login automation.
---
# 妙手ERP 通用采集箱 (Common Collect Box)
## Overview
Miaoshou ERP (`erp.91miaoshou.com`) has a **Common Collect Box** (`通用采集箱`) at a fixed URL. This skill documents how to navigate to it, the collection methods available, and how to interact with the import/collection UI programmatically via DrissionPage.
## Login
- URL: `https://erp.91miaoshou.com/auth/login`
- Credentials: `xiaochaoren2026 / [REDACTED]`
- CAPTCHA: **Base64 inline image** (`data:image/png;base64,...`) rendered in `.captcha-img`
- Unlike Dianxiaomi (which uses a separate PNG endpoint), Miaoshou embeds the captcha directly.
- Extract with JS: `document.querySelector('.captcha-img').src`
- Decode base64, save to file, run `ddddocr.classification()`
- Apply digit cleaning: `raw.translate(str.maketrans('oliOSsZz', '01105522'))`
- Form selectors:
- Account: `css:.account-input`
- Password: `css:.password-input`
- Captcha text: `css:.captcha-text`
- Captcha UUID: `input[name="captchaUuid"]` (must be set/synced)
- Submit: `css:.login-button`
- Success redirects to: `https://erp.91miaoshou.com/welcome`
- Cookie file: `~/.hermes/cookies/miaoshou_cookies.json` (autoLoginToken is critical)
## Common Collect Box URL
Direct link (works after login):
```
https://erp.91miaoshou.com/common_collect_box/index
```
Title: `妙手-产品采集`
## Collection Methods Discovered
The page exposes **6 collection methods** as tab-like options:
| Method | Chinese Name | Notes |
|--------|-------------|-------|
| Link Collection | 链接采集 | Enter public product URLs (1688, Taobao, etc.). System scrapes the page. **Requires publicly accessible pages** — internal systems (like AtomK behind login) will NOT work. |
| Import Collection | 导入采集 | **Bulk import via file upload** (template-based). This is the viable path for transferring data from AtomK or other internal systems. |
| 1688 AI Selection | 1688Ai选品 | AI-assisted product selection from 1688 |
| Inventory Collection | 货盘采集 | Collect from inventory/supplier pallets |
| Keyword Collection | 关键词采集 | Search keyword-based batch collection |
| Whole-Store Collection | 整店采集 | Enter a store URL to collect all products |
| Plugin Collection | 插件采集 | Browser extension-based collection |
| Store Cross-Collection | 店铺互采 | Cross-store collection |
## Import Collection (导入采集) — 4 Sub-Methods
When you click "导入采集", the page switches to `?fetchType=importCopy` and shows **4 sub-tabs**:
| Sub-Method | Chinese Name | What It Does |
|-----------|-------------|-------------|
| Import Link Collection | 导入链接采集 | Upload an **XLS file** with product URLs. Miaoshou scrapes each URL. |
| Excel Spreadsheet Import | Excel表格导入 | Upload a pre-structured Excel with full product data (details TBD). |
| Local Material Package | 本地素材包导入 | Upload a local asset package (images + data). |
| Jushuitan Material Package | 聚水潭素材包导入 | Import from Jushuitan ERP format. |
### Import Link Collection Template (导入链接采集)
This is the **most important** one for AtomK-style workflows.
**Template file**: `导入产品链接模板.xls` (downloadable via "下载导入模板" link in the modal)
**File format**: `.xls` (older Excel 97-2003, NOT xlsx) — verified April 2026. `.xlsx` triggers `解析文件数据错误`.
**CRITICAL**: The template is **strictly 5 columns**. Any extra column causes failure.
| Column | Field | Required | Notes |
|--------|-------|----------|-------|
| A | 链接地址(必填) | ✅ Yes | Product detail page URL. **Must be publicly accessible**. Login-gated pages (like AtomK behind login) will fail. |
| B | 产品标题 | No | **AI titles ARE preserved** — verified. Overrides scraped 1688 title. |
| C | 价格(RMB | No | **AI prices ARE preserved** — verified. Overrides scraped 1688 price. |
| D | 促销价(RMB | No | Only used when publishing to **Lazada**. |
| E | 提示:促销价仅可用于采集到Lazada | No | Literal hint text — include this column to match the official template exactly. |
**Empirically verified findings (April 2026)**:
- ✅ 5-column `.xls` with exact headers above → `解析成功 N/0/N`
- ❌ 6+ columns (e.g., adding "产品描述") → `导入文件未识别到:【产品主编号】表头`
- ❌ Adding "产品主编号" column → `解析文件数据错误`
-`.xlsx` format → `解析文件数据错误`
- ✅ AI title from XLS **overrides** scraped 1688 title in the final product
- ✅ AI price from XLS **overrides** scraped 1688 price
- ✅ Sheet name can be `Worksheet` or `Sheet1` (doesn't matter with correct 5-col headers)
- ✅ Encoding can be `utf_16_le` or `utf-8` (doesn't matter with correct 5-col headers)
**Important**: The error *"导入文件未识别到:【产品主编号】表头"* is a **misleading** catch-all for "your columns don't match the expected 5-column template". Do NOT add a "产品主编号" column — doing so triggers `解析文件数据错误`. Always use the exact 5-column format. The system does not actually use or store a "产品主编号" from Excel; SKU must be filled later via the edit dialog.
**Critical limitation**: The "导入链接采集" method fundamentally relies on **web scraping** each product URL. If the URL requires authentication (e.g., AtomK product pages behind `atomlisting.com` login), Miaoshou cannot fetch the data. The 5-column XLS only passes the link + AI title + AI price; Miaoshou still scrapes the 1688 page for images, description, specs, etc.
#### File Upload Mechanics
When the import modal is open:
- The upload area is a hidden `<input type="file" class="jx-upload__input">` inside a `.pro-upload` / `.jx-upload--text` container.
- **Selector**: `css:input[type=file]` or `css:.jx-upload__input`
- **Accepts**: `.xls,.xlsx,.csv` (but only `.xls` works reliably)
- Use `file_input.input('/path/to/file.xls')` to set the file path programmatically.
- After upload, the file name appears in `.jx-upload-list li`.
- **The confirm button is inside the modal dialog** — search within `.jx-dialog` for buttons with text `确认`.
- **After clicking confirm, the modal closes and the file appears in the "已导入的文件列表" table.** Wait ~15s for parsing, then **refresh the page** to check status.
- **The page does NOT auto-update after confirm.** You must navigate back to `?fetchType=importCopy` or use `page.get()` to refresh and see the parse result.
#### Complete Upload Flow (Verified April 2026)
```python
# 1. Navigate to import page
page.get("https://erp.91miaoshou.com/common_collect_box/index?fetchType=importCopy")
time.sleep(10)
# 2. Click Excel import button
page.run_js("""
var all = document.querySelectorAll('span, div, button, a');
for (var i=0; i<all.length; i++) {
var txt = all[i].innerText || '';
if (txt.includes('Excel表格导入') && txt.length < 20) {
all[i].click(); break;
}
}
""")
time.sleep(5)
# 3. Upload file
file_inputs = page.eles('css:input[type="file"]')
file_inputs[0].input('/path/to/import.xls')
time.sleep(5)
# 4. Click confirm
page.run_js("""
var btns = document.querySelectorAll('button');
for (var i=0; i<btns.length; i++) {
if (btns[i].innerText && btns[i].innerText.trim() === '确认') {
btns[i].click(); break;
}
}
""")
# 5. Wait for backend parsing, then refresh to check result
time.sleep(20)
page.get("https://erp.91miaoshou.com/common_collect_box/index?fetchType=importCopy")
time.sleep(10)
# 6. Check result in page body text
body_text = page.run_js("return document.body.innerText;")
# Look for: '解析成功' or '解析失败' near the filename
```
#### Known Error Patterns
| Error Message | Meaning | Fix |
|---------------|---------|-----|
| `导入文件未识别到:【产品主编号】表头` | Column count ≠ 5 or headers don't match | Ensure **exactly 5 columns** with headers: `链接地址(必填)`, `产品标题`, `价格(RMB`, `促销价(RMB`, `提示:促销价仅可用于采集到Lazada`. Do NOT add "产品主编号" — it's a misleading error. |
| `解析文件数据错误` | Wrong format or added "产品主编号" | Use `.xls` (not `.xlsx`). Remove any "产品主编号" column. Ensure exact 5-column headers. |
| `解析失败` (with `0/N/N`) | URLs accepted but scraping failed | Linked pages are unsupported (e.g., COS-hosted custom HTML, login-gated pages). Use standard 1688/Taobao/Pinduoduo URLs. |
## Collect Box Product Edit Dialog (采集箱产品编辑弹窗)
After a product is imported and **claimed to a platform** (e.g., Temu全托管), it appears in the **已认领** tab. Each row has an **编辑** button that opens a modal dialog for editing the product's full data.
This dialog is the key to filling AI-generated fields (SKU, description, keywords) that cannot be imported via the 5-column XLS template.
### Dialog Structure
- **Root dialog**: `.jx-dialog.pro-dialog.collect-box-editor-dialog-V2` (1880×953)
- **Editor body**: `.collect-box-editor` (1559×953)
- **Tabs**: 基本信息, 产品属性, 销售属性, 产品图片, 产品视频, 认证说明, 物流信息, 货源链接
### Critical Field Mappings (Verified April 2026)
The editor uses Vue.js with custom `jx-input` components. Direct `.value` assignment works but **must be followed by event dispatching** for Vue to sync state.
| Field | DOM Element | Selector Strategy | Notes |
|-------|------------|-------------------|-------|
| 产品标题 | `input[type="text"]` (multiple) | Find first input whose `.value` contains the title text | Title is pre-filled from import; usually does NOT need updating. |
| **产品主编号 (SKU)** | `input[type="text"]` at **index 1** | `editor.querySelectorAll('input')[1]` | Placeholder: `请输入产品主编号,不填将自动生成`. This is the **most reliable** field to target. |
| **简易描述** | `textarea` at **index 0** | `editor.querySelectorAll('textarea')[0]` | Plain text. Fills to Shopee/Wish/Joom/Mercado Libre/Ozon/Walmart product description. |
| **详细描述** | `iframe` at **index 0** (TinyMCE-like) | `editor.querySelectorAll('iframe')[0]``contentDocument.body` | Rich text editor. Used for Lazada/速卖通/TikTok/Coupang/eBay/Allegro. Body is `contenteditable`. |
### Vue State Sync (Mandatory)
Miaoshou uses Vue.js. Simply setting `.value` is **not enough** — the change must be propagated to Vue's reactive state. After setting any value, dispatch:
```javascript
el.value = 'your-value';
el.dispatchEvent(new Event('input', {bubbles: true}));
el.dispatchEvent(new Event('change', {bubbles: true}));
el.dispatchEvent(new Event('blur', {bubbles: true}));
el.dispatchEvent(new KeyboardEvent('keydown', {key: 'a', bubbles: true}));
el.dispatchEvent(new KeyboardEvent('keyup', {key: 'a', bubbles: true}));
```
Without this, clicking **保存修改** will silently discard your changes.
### Opening the Edit Dialog Programmatically
Products are rendered in a **virtual scroll table** (`.pro-virtual-table__row`), not a standard `<table>`.
```python
# 1. Navigate to claimed tab
page.get('https://erp.91miaoshou.com/common_collect_box/items')
# ... click 已认领 tab ...
# 2. Find the row by collect_box_id and click 编辑
page.run_js("""
var rows = document.querySelectorAll('.pro-virtual-table__row, .pro-virtual-scroll__row');
for (var i=0; i<rows.length; i++) {
var txt = rows[i].innerText || '';
if (txt.includes('3516358992')) { // your collect_box_id
var btns = rows[i].querySelectorAll('button');
for (var j=0; j<btns.length; j++) {
if (btns[j].innerText && btns[j].innerText.trim() === '编辑') {
btns[j].click();
break;
}
}
}
}
""")
time.sleep(10) # Dialog takes time to render
editor = page.ele('css:.collect-box-editor')
```
### Filling Each Field
#### SKU (产品主编号)
```python
editor.run_js(f"""
var editor = document.querySelector('.collect-box-editor');
var skuInput = editor.querySelectorAll('input')[1];
skuInput.value = {json.dumps(sku)};
skuInput.dispatchEvent(new Event('input', {{bubbles: true}}));
skuInput.dispatchEvent(new Event('change', {{bubbles: true}}));
skuInput.dispatchEvent(new Event('blur', {{bubbles: true}}));
skuInput.dispatchEvent(new KeyboardEvent('keydown', {{key: 'a', bubbles: true}}));
skuInput.dispatchEvent(new KeyboardEvent('keyup', {{key: 'a', bubbles: true}}));
""")
```
#### Simple Description (简易描述)
```python
editor.run_js(f"""
var editor = document.querySelector('.collect-box-editor');
var ta = editor.querySelectorAll('textarea')[0];
ta.value = {json.dumps(description)};
ta.dispatchEvent(new Event('input', {{bubbles: true}}));
ta.dispatchEvent(new Event('change', {{bubbles: true}}));
ta.dispatchEvent(new Event('blur', {{bubbles: true}}));
""")
```
#### Rich Description (详细描述 — iframe)
```python
html_content = f'<p><span style="font-size: 12.0pt;">{description.replace(chr(10), "</span></p><p><span style=\\"font-size: 12.0pt;\\">")}</span></p>'
editor.run_js(f"""
var editor = document.querySelector('.collect-box-editor');
var iframe = editor.querySelectorAll('iframe')[0];
var doc = iframe.contentDocument || iframe.contentWindow.document;
var body = doc.querySelector('body');
body.innerHTML = {json.dumps(html_content)};
body.dispatchEvent(new Event('input', {{bubbles: true}}));
""")
```
### Saving
```python
page.run_js("""
var btns = document.querySelectorAll('button');
for (var i=0; i<btns.length; i++) {
if (btns[i].innerText && btns[i].innerText.includes('保存')) {
btns[i].click();
break;
}
}
""")
time.sleep(5)
```
### Verification
After saving, re-open the edit dialog and read back the values to confirm persistence:
```python
verify = editor.run_js("""
var editor = document.querySelector('.collect-box-editor');
var inputs = editor.querySelectorAll('input');
var textareas = editor.querySelectorAll('textarea');
var iframes = editor.querySelectorAll('iframe');
var result = {};
if (inputs.length > 1) result.sku = inputs[1].value;
if (textareas.length > 0) result.desc = textareas[0].value;
for (var i=0; i<iframes.length; i++) {
try {
var doc = iframes[i].contentDocument || iframes[i].contentWindow.document;
var body = doc.querySelector('body');
if (body) { result.rich_desc = body.innerText; break; }
} catch(e) {}
}
return result;
""")
# verify.sku should match, verify.desc should contain AI text
```
### AtomK → Miaoshou Batch Import Script
A production-ready end-to-end script exists at `/home/ubuntu/auto_miaoshou_import.py`:
**What it does:**
1. Logs into AtomK API, fetches claimed products + their AI-generated listings
2. Generates a 5-column `.xls` file (`utf_16_le` encoding, `Worksheet` sheet name)
3. Launches headless Chromium, loads Miaoshou cookies
4. Navigates to `?fetchType=importCopy`, clicks `Excel表格导入`
5. Uploads the `.xls` file, clicks `确认`
6. Refreshes the page after 20s to verify `解析成功`
7. Logs results and updates state file
**State tracking:** `/home/ubuntu/.hermes/miaoshou_import_state.json`
- Tracks already-imported `product_id`s to avoid duplicates
- Updated after each successful run
**Cron job:** `miaoshou-atomk-auto-import` (job_id: `2b6587d6895b`)
- Schedule: `0 * * * *` (every hour on the hour)
- Batch size: 6 products per run
- Logs to: `/home/ubuntu/.hermes/miaoshou_import.log`
**Usage:**
```bash
# Manual run
python3 /home/ubuntu/auto_miaoshou_import.py
# Check cron status
hermes cron list
```
| Scenario | Viable? | Recommended Path |
|---------|---------|-----------------|
| AtomK product pages are **public** | ❌ No | Even **public** AtomK HTML pages (hosted on Tencent COS) fail with `解析失败 0/3`. Miaoshou's scraper is hardcoded for domestic e-commerce platforms (1688, Taobao, Pinduoduo), not custom HTML pages. |
| AtomK product pages require **login** | ❌ No | Same as above — plus authentication barrier. |
| **5-column XLS** import (link + AI title + AI price) | ✅ Yes | `解析成功`. Titles and prices from Excel **override** scraped 1688 data. Verified: AI title `Romantic Heart...` appears instead of 1688 original. |
| 5-column XLS + extra columns (desc, SKU, images, EAN, etc.) | ❌ No | Triggers `导入文件未识别到:【产品主编号】表头`. Adding "产品主编号" triggers `解析文件数据错误`. |
| ".xlsx" format | ❌ No | Triggers `解析文件数据错误`. Must use `.xls` (xlwt). |
| **Full AI data via dual-browser automation** | ✅ **Yes** | **Proven workflow (April 2026)**:<br>1. Import via 5-col XLS (link + AI title + AI price)<br>2. Claim product to target platform<br>3. Open collect box edit dialog<br>4. Auto-fill SKU, 简易描述, 详细描述 (rich text) via DrissionPage + JS<br>5. Save and verify<br>Reference script: `/home/ubuntu/miaoshou_atomk_bridge.py` |
| Direct platform upload (bypass Miaoshou) | ⚠️ Partial | Generate Temu/AliExpress CSV from AtomK API → upload to platform directly. Bypasses Miaoshou entirely but loses Miaoshou's inventory/order management. |
## Page Structure Notes
- Left sidebar contains: `通用功能`, `产品采集`, `公用采集箱`, `AI工作台`, `侵权检测`, `货盘中心`, etc.
- Supported platforms displayed as tags: 1688, 义乌小商品城, 浙宝网, Vvic, 搜款网, 网商园, 货捕头, etc.
- Bottom action bar: `采集并自动认领`, `采集并自动发布`
- A modal/popup may appear on first visit with collection settings — dismiss with "我知道了".
## Browser Setup (DrissionPage)
```python
from DrissionPage import ChromiumPage, ChromiumOptions
co = ChromiumOptions()
co.set_browser_path('/home/ubuntu/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome')
co.set_argument('--headless=new')
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
co.set_argument('--window-size=1440,900')
co.set_user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36')
co.set_local_port(get_free_port())
page = ChromiumPage(addr_or_opts=co)
```
## Pitfalls
- **Cookie expiration**: The saved cookie (autoLoginToken) expires relatively quickly. Re-login with captcha is often needed.
- **Base64 captcha session**: Must extract the captcha image from the **same browser session** (via JS `document.querySelector('.captcha-img').src`) — do NOT use `requests` to fetch it separately, or the captcha UUID will mismatch.
- **Tab clicking**: The collection method tabs (链接采集, 导入采集, etc.) are `<span>` elements inside radio-button groups, not standard `<button>` or `<a>` tags. Standard `.ele('xpath://*[contains(text(), "导入采集")]').click()` may fail. Use JS click via `page.run_js()` or search for the actual `<button>` inside the tab component.
- **Import dialog**: Clicking "导入链接采集" opens a modal dialog. The template download link "下载导入模板" triggers a browser download to the default Downloads folder. In headless mode, the file goes to `/home/ubuntu/Downloads/导入产品链接模板.xls`.
- **XLS format only**: The import link template is `.xls` (Excel 97-2003). Using `.xlsx` will likely fail.
- **"暂无当前模块权限"**: Some accounts may see "You don't have permission for this module" if the collect box feature is not enabled for the sub-account. Contact the main account admin to enable.
- **SPA navigation**: Clicking menu items may not trigger full navigation. Use `page.get('https://erp.91miaoshou.com/common_collect_box/index?fetchType=importCopy')` for direct access.
- **Link scraper limitation**: The 导入链接采集 method requires **publicly accessible** product detail pages. Any page behind authentication (AtomK, internal ERPs) will result in empty/failed collection.
## Next Exploration Steps (when needed)
1.~~Click the "导入采集" tab and inspect the file upload input~~ (Done)
2.~~Download the template to understand required columns~~ (Done — 5-column `.xls`: link, title, price, promo_price, hint)
3.~~Analyze "Excel表格导入" template for full-data import~~ (Done — the XLS template is corrupt/unreadable; full-field bulk import is **not viable**. The working path is 5-col import + edit-dialog automation.)
5.~~Automate the file upload and submission~~ (Done — upload via `file_input.input()` + confirm button in modal + page refresh to check result.)
6.~~Automate the edit dialog post-import fill~~ (Done — SKU, 简易描述, 详细描述 all verified saving correctly.)
7.~~Build end-to-end automation~~ (Done — script at `/home/ubuntu/auto_miaoshou_import.py` fetches AtomK API data, generates 5-col XLS, uploads via browser, and tracks state. Cron job `miaoshou-atomk-auto-import` runs hourly.)
8.~~Set up cron job for hourly batch import~~ (Done — job `miaoshou-atomk-auto-import` runs at `0 * * * *`, imports 6 products per run, skips already-imported IDs via `/home/ubuntu/.hermes/miaoshou_import_state.json`.)
9. 🔴 **Future**: Extend edit-dialog automation to fill **产品图片** (replace 1688 images with AI-generated ones from AtomK `platform_fields.pictures`), **产品属性** (material, shape, brand), and **关键词** fields.
10. 🔴 **Future**: Build batch loop that processes multiple `collect_box_id`s end-to-end (import → claim → edit → save).
11. 🔴 **Future**: Investigate if `快速上货` (Quick Listing) offers a more direct API or form for bulk AI data injection, bypassing the collect box entirely.