From ce9db92f31b562efb0e2f893562a4fafee8051d8 Mon Sep 17 00:00:00 2001 From: admin9webs Date: Mon, 21 Sep 2026 07:33:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A6=99=E6=89=8B=EF=BC=9A=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=88=90=E5=8A=9F=EF=BC=88=E7=9C=9F=E5=AE=9E=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=99=A8+base64=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=EF=BC=89+=20=E9=87=87=E9=9B=86=E7=AE=B1?= =?UTF-8?q?=E5=8F=AF=E8=BE=BE=EF=BC=88common=5Fcollect=5Fbox=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E6=8A=80=E8=83=BD=E6=A1=A3=E9=80=89=E6=8B=A9=E5=99=A8?= =?UTF-8?q?=E5=B7=B2=E5=A4=B1=E6=95=88=E9=9C=80=E6=9B=B4=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 妙手建品/RECIPE.md | 56 +++++++++++++++++++++++------------------ 妙手建品/ms_cap2.mjs | 16 ++++++++++++ 妙手建品/ms_collect.mjs | 17 +++++++++++++ 妙手建品/ms_login2.mjs | 30 ++++++++++++++++++++++ 妙手建品/ms_menu.mjs | 22 ++++++++++++++++ 妙手建品/ms_nav2.mjs | 18 +++++++++++++ 6 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 妙手建品/ms_cap2.mjs create mode 100644 妙手建品/ms_collect.mjs create mode 100644 妙手建品/ms_login2.mjs create mode 100644 妙手建品/ms_menu.mjs create mode 100644 妙手建品/ms_nav2.mjs diff --git a/妙手建品/RECIPE.md b/妙手建品/RECIPE.md index 32fa6e3..26d54c4 100644 --- a/妙手建品/RECIPE.md +++ b/妙手建品/RECIPE.md @@ -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. 采集入箱后,验证「公用采集箱」里能否批量 `认领`/`发布`(受子账号权限限制) diff --git a/妙手建品/ms_cap2.mjs b/妙手建品/ms_cap2.mjs new file mode 100644 index 0000000..457fdbc --- /dev/null +++ b/妙手建品/ms_cap2.mjs @@ -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); diff --git a/妙手建品/ms_collect.mjs b/妙手建品/ms_collect.mjs new file mode 100644 index 0000000..df1e6de --- /dev/null +++ b/妙手建品/ms_collect.mjs @@ -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); diff --git a/妙手建品/ms_login2.mjs b/妙手建品/ms_login2.mjs new file mode 100644 index 0000000..a06aa7e --- /dev/null +++ b/妙手建品/ms_login2.mjs @@ -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); diff --git a/妙手建品/ms_menu.mjs b/妙手建品/ms_menu.mjs new file mode 100644 index 0000000..db42ed7 --- /dev/null +++ b/妙手建品/ms_menu.mjs @@ -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); diff --git a/妙手建品/ms_nav2.mjs b/妙手建品/ms_nav2.mjs new file mode 100644 index 0000000..7dd5df2 --- /dev/null +++ b/妙手建品/ms_nav2.mjs @@ -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);