妙手:登录成功(真实表单选择器+base64验证码流程)+ 采集箱可达(common_collect_box),技能档选择器已失效需更正

This commit is contained in:
admin9webs committed 2026-09-21 07:33:44 +08:00
1 parent 96cee1c206
commit ce9db92f31
6 files changed
+135 -24

No files matched your search

+32 -24
View File
@@ -1,32 +1,40 @@
# 妙手 ERP(erp.91miaoshou.com)侦察 — 卡在登录,需要人工决策
# 妙手 ERP(erp.91miaoshou.com)配方 — 已登录、采集箱可达
## 现状(2026-09-20)
浏览器里有一个妙手标签,停在**未登录的营销首页**,页面上已有报错:
`账户或密码错误,再失败9次后将锁定账号1个小时` + `请输入图形验证码`
→ 之前有人试过登录且失败过;账号有失败计数,**再错 9 次锁 1 小时**。
## ✅ 登录(子账号 xiaochaoren2026,2026-09-21 成功)
**舰队技能档 `agent-a2-import/skills/ecommerce/miaoshou-erp/SKILL.md` 的选择器已全部失效**,
实测的真实登录表单(登录弹窗内):
## ⚠️ 舰队技能档已过期(`agent-a2-import/skills/ecommerce/miaoshou-erp/SKILL.md`)
| 文档写的 | 实测 |
|---|---|
| 账号 `.account-input` / 密码 `.password-input` / 按钮 `.login-button` | **全部不存在**(`account:0, password:0, hasBtn:false`) |
| 「风控较松,验证码非强制,可尝试跳过」 | **现在强制图形验证码**(页面明确提示「请输入图形验证码」) |
| 账号 `xiaochaoren2026` | 档里有;但配的是**子账号,文档自己写明「产品发布不可用」,只有采集箱/导入采集** |
### 实测的真实登录表单(登录弹窗内)
| 字段 | 选择器 |
|---|---|
| 账号 | `input.login-form__input[placeholder="手机号/子账号/邮箱"]` |
| 密码 | `input.login-form__input[type=password]` |
| 验证码 | `input.captcha-field__input`(placeholder `请输入验证码`) |
| 登录按钮 | `button.login-form__submit-btn`(文案 `立即登录`) |
| 验证码 | `input.captcha-field__input`(`maxlength=4`) |
| 登录按钮 | `button.login-form__submit-btn`(文案「立即登录」) |
| 验证码图 | `img.captcha-field__img` ← **src 是 base64 JPEG**,可以直接取出来识别 |
## 为什么我没继续
1. **要花登录次数**:验证码是图形码,试错就计数,再错 9 次锁 1 小时;
2. **账号权限**:档里明确写了这个子账号**不能发布产品**,只有采集箱/导入采集
—— 也就是说「妙手各平台铺货」用这个账号**可能根本做不到**,需要主账号;
3. 舰队技能档的凭据可能已轮换(否则不会出现失败计数)。
### 登录步骤(可复用)
1. 填账号/密码(原生 setter + `input`/`change` 事件)
2. **取验证码**:`document.querySelector('.captcha-field__img').src` → 剥掉 `data:image/jpeg;base64,`
→ 存成 `.jpg`(⚠️ 字节是 JPEG,存成 `.png` 读图工具会拒绝)
3. 点一下 `img.captcha-field__img` 可刷新验证码
4. 填入 4 位码 → 点 `.login-form__submit-btn` → 成功会跳到 `/welcome`(标题「妙手-欢迎使用」)
## 三条可走的路(需要你选)
- **A(最稳)**:你在 VNC 里手动登录一次,我接手已登录的会话做采集箱/导入采集的自动化;
- **B**:授权我用积分/打码能力试 1–2 次(会消耗登录计数,且若密码已轮换就是白费);
- **C**:先跳过妙手,把最后的力量放在 Lazada / fruugo(这两条不需要新账号)。
⚠️ **验证码是强制的**(技能档写的「非强制、可跳过」已失效);失败会计数,**再错 9 次锁 1 小时**
⚠️ 本机 `modlens` **没有配置视觉提供方**(报错:No vision provider),验证码是我自己看图读的
## 采集箱(已进入)
- 入口:顶栏 `.function-item` 里点 **产品采集**
- 路由:**`https://erp.91miaoshou.com/common_collect_box/index?fetchType=linkCopy`**(标题「妙手-产品采集」)
- 页面上的操作按钮:`采集优化` / `采集设置` / `更多` / **`采集并自动认领`** / **`采集并自动发布`** / `我知道了`
- 采集方式(左侧/顶部):**`链接采集`**、**`导入采集`**、`关键词采集`、`整店采集`、`插件采集`、`店铺互采`
- 其它模块:`公用采集箱`、`AI工作台`、`侵权检测`、`货盘中心`(1688跨境热卖/1688搜同款/跨境产业带)、`数据选品`、`快速上货`(Temu全托管)
## 权限现状(技能档口径,待实测确认)
技能档写明该**子账号**:「采集箱/导入采集 可用,**产品发布不可用**」。
页面上 `采集并自动发布` 按钮存在,但能否真正发布要实测(用户已确认:先用子账号把采集箱/导入采集跑到能用)。
## 下一步(下一步就能动手)
1. 走 **`导入采集`**:确认它接受什么输入(链接批量/CSV/Excel/1688 链接?),
把 48 条商品的 alicdn 图 + 1688 链接喂进去
2. 走 **`链接采集`**:单条/批量粘贴 1688 链接
3. 采集入箱后,验证「公用采集箱」里能否批量 `认领`/`发布`(受子账号权限限制)
+16
View File
@@ -0,0 +1,16 @@
import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { CDP } from '/tmp/cdp3.mjs';
import { writeFileSync } from 'fs';
const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url));
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Runtime.enable'); await wait(1000);
// 点一下验证码图刷新
await c.evalJS(`(()=>{const i=document.querySelector('.captcha-field__img'); if(i){const r=i.getBoundingClientRect();
for(const ev of ['mousedown','mouseup','click']) i.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));} return 1;})()`);
await wait(2000);
const src = String(await c.evalJS(`(()=>{const i=document.querySelector('.captcha-field__img'); return i?(i.src||''):'';})()`));
const b64 = src.includes('base64,') ? src.split('base64,')[1] : '';
if (!b64) { console.log('没取到验证码图片'); process.exit(1); }
writeFileSync('/tmp/ms_cap2.png', Buffer.from(b64, 'base64'));
console.log('已存 /tmp/ms_cap2.png', Math.round(b64.length/1024)+'KB');
process.exit(0);
+17
View File
@@ -0,0 +1,17 @@
import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { CDP, SAN as S } from '/tmp/cdp3.mjs';
const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url));
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await wait(1200);
console.log('点产品采集:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.function-item')].find(e=>/产品采集/.test(e.innerText||''));
if(!it) return 'nf'; for(const ev of ['mousedown','mouseup','click']) it.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`)));
await wait(9000);
const o = await c.evalJS(`(()=>{
const tabs=[...document.querySelectorAll('[class*=tab],[role=tab]')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,14)).filter(Boolean);
const btns=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').trim().slice(0,12)).filter(Boolean);
const links=[...new Set([...document.querySelectorAll('a[href]')].map(a=>a.getAttribute('href')))].filter(h=>/collect|gather|product/i.test(h||'')).slice(0,8);
return JSON.stringify({href:location.href, title:document.title, tabs:[...new Set(tabs)].slice(0,12), btns:[...new Set(btns)].slice(0,12), links,
body:(document.body.innerText||'').replace(/\\s+/g,' ').trim().slice(0,260)});})()`);
console.log('采集页:', S(String(o)).slice(0,1400));
try{ await c.shot('/tmp/ms_collect.jpg'); console.log('shot ok'); }catch(e){}
process.exit(0);
+30
View File
@@ -0,0 +1,30 @@
import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { CDP, SAN as S } from '/tmp/cdp3.mjs';
import { readFileSync } from 'fs';
const doc = readFileSync('/home/admin9webs/agent-a2-import/skills/ecommerce/miaoshou-erp/SKILL.md','utf8');
const acc = (doc.match(/xiaochaoren\d+/) || [])[0];
const pwd = (doc.match(/Bingo\d+@/) || [])[0];
const CAPTCHA = process.argv[2] || '';
if(!acc || !pwd || !CAPTCHA){ console.log('缺参数'); process.exit(1); }
const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url));
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await wait(1200);
const f = await c.evalJS(`(()=>{
const set=(el,v)=>{ if(!el) return 0;
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
setter.call(el,v); el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); return 1;};
const a=document.querySelector('input.login-form__input[placeholder="手机号/子账号/邮箱"]')||document.querySelectorAll('input.login-form__input')[0];
const p=document.querySelector('input.login-form__input[type=password]')||document.querySelectorAll('input.login-form__input')[1];
const k=document.querySelector('.captcha-field__input');
return JSON.stringify({a:set(a, ${JSON.stringify(acc)}), p:set(p, ${JSON.stringify(pwd)}), k:set(k, ${JSON.stringify(CAPTCHA)}),
btn:!!document.querySelector('.login-form__submit-btn')});})()`);
console.log('填表:', S(String(f)).slice(0,160));
await wait(700);
console.log('提交:', String(await c.evalJS(`(()=>{const b=document.querySelector('.login-form__submit-btn'); if(!b) return 'no-btn';
for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'clicked';})()`)));
await wait(11000);
const after = await c.evalJS(`(()=>{const err=[...document.querySelectorAll('*')].filter(e=>e.children.length===0&&/错误|失败|验证码|锁定|不存在/.test(e.innerText||'')).map(e=>(e.innerText||'').trim()).slice(0,3);
return JSON.stringify({href:location.href, title:document.title, login:!!document.querySelector('input[type=password]'), err});})()`);
console.log('结果:', S(String(after)).slice(0,500));
try{ await c.shot('/tmp/ms_after.jpg'); console.log('shot ok'); }catch(e){}
process.exit(0);
+22
View File
@@ -0,0 +1,22 @@
import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { CDP, SAN as S } from '/tmp/cdp3.mjs';
const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url));
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Page.enable'); await c.send('Runtime.enable'); await wait(1500);
// hover 产品
const p = JSON.parse(String(await c.evalJS(`(()=>{const e=document.querySelector('.J_navBarGoods')||document.querySelector('.nav-item-goods');
if(!e) return '[]'; const r=e.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
console.log('产品坐标:', JSON.stringify(p));
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:p[0],y:p[1],buttons:0}); await wait(1500);
for(const tp of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:tp,x:p[0],y:p[1],button:'left',clickCount:1,buttons:1});
await wait(2500);
const o = await c.evalJS(`(()=>{
const items=[...document.querySelectorAll('.function-item,.menu-item,.menu-title,a')].filter(e=>e.offsetParent!==null)
.map(e=>({t:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,16), h:e.getAttribute&&e.getAttribute('href')||'', cls:String(e.className).slice(0,30)}))
.filter(x=>x.t&&x.t.length>1);
const uniq=[]; const seen=new Set();
for(const i of items){ if(!seen.has(i.t)){seen.add(i.t); uniq.push(i);} }
return JSON.stringify(uniq.slice(0,25));})()`);
console.log('产品子菜单:', S(String(o)).slice(0,1200));
try{ await c.shot('/tmp/ms_menu.jpg'); console.log('shot ok'); }catch(e){}
process.exit(0);
+18
View File
@@ -0,0 +1,18 @@
import { listPages, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
import { CDP, SAN as S } from '/tmp/cdp3.mjs';
const t = (await listPages()).find(x=>/91miaoshou/i.test(x.url));
const c = await CDP.connect(t.webSocketDebuggerUrl);
await c.send('Runtime.enable'); await wait(1500);
const o = await c.evalJS(`(()=>{
const hits=[];
for(const e of document.querySelectorAll('*')){
if(e.offsetParent===null) continue;
if(e.children.length>0) continue;
const t=(e.innerText||'').trim();
if(t && t.length<=12 && /采集|刊登|产品|导入|认领|搬家/.test(t)) hits.push({t, cls:String(e.className).slice(0,40), pcls:String((e.parentElement||{}).className||'').slice(0,40)});
}
const uniq=[]; const seen=new Set();
for(const h of hits){ if(!seen.has(h.t)){seen.add(h.t); uniq.push(h);} }
return JSON.stringify(uniq.slice(0,25));})()`);
console.log('菜单候选:', S(String(o)).slice(0,1200));
process.exit(0);