闲鱼淘货
Overview
把「我想在闲鱼买个便宜的 X」变成一份可核实、带证据、标好风险的候选清单。
核心判断:闲鱼的标题和参数区都是卖家手填的,都会错;只有卖家自己拍的实物截图(关于本机 / 系统报告 / 发票 / 机身细节)才算证据。整个流程就是不断把「卖家声称」降级、把「实拍证据」升级。
同理心提示:用户的真实目标不是"抓到 500 条数据",而是"别买亏"。宁可给出 3 台说得清的,也不要给 30 台没核实的。
使用边界(务必遵守)
本 skill 服务于个人买家的购买决策:按需求做少量查询、核实卖家自己公开的图片与描述、输出候选清单。
- 仅查询用户自己可见的公开商品页;不做批量采集、不做全站爬取、不做数据转售。
- 不绕过、也不帮助绕过平台的风控、登录校验或访问频率限制;遇到验证码或风控提示即停下,交人工处理。
- 查询量保持"人肉看货"的量级(单次任务几十台以内),不要连续高频跑。
- 抓到的图片与文本仅用于本次购买决策,不落库、不二次分发。
- 任一条件不满足时,中止任务并向用户说明,而不是想办法绕过去。
依赖与前置条件
本 skill 不能单独工作,它依赖另一个 skill:web-access。 抓取统一走 web-access 提供的 CDP Proxy(默认 http://127.0.0.1:3456),它连的是用户日常已登录的 Chrome。环境里没有 web-access(或等价的 CDP Proxy)时,本 skill 所有采集脚本都连不上,请先安装依赖。开工前确认:
- 先加载
web-accessskill,按其说明启动 proxy。若未启动,脚本会直接连不上。 - 闲鱼搜索页未登录会被挡,必须用带登录态的浏览器。
- Chrome 136+ 会忽略
--remote-debugging-port;需走chrome://inspect/#remote-debugging开关,且首次连接会在浏览器弹授权框,须人工点「允许」,否则 WebSocket 握手静默挂起(TCP 已连接但无响应,很难排查)。 - 若之前用
--remote-debugging-port启动过 Chrome,要完全退出并不带参数重启,再勾开关,否则开关不生效。
⚠ 两个高频环境坑,先处理再开工:
- 环境里的
http_proxy会污染所有 curl 诊断。若 shell 里设了http_proxy/https_proxy(实测某台机器上是http://127.0.0.1:1748),
于是curl http://127.0.0.1:3456/targets返回的其实是那个代理的报错(upstream connect failed: ... (os error 10061)),
看起来像 cdp-proxy 挂了或浏览器没连上,实际完全是假象。诊断 localhost 一律加--noproxy '*'。
注意:node 的fetch不走环境代理,所以脚本内部调用(harvest/scan_details/fetch_images)不受影响,只有你自己手敲 curl 排障时会被坑。 - proxy 被"起完就死":若用
spawn(detached:true)+unref()起 proxy,在某些 Agent 沙箱里 detached 子进程会随命令结束被杀 →
下一条命令立刻connection refused,表现为"代理时好时坏"。
正确做法:用 Bash 工具的run_in_background直接常驻跑node scripts/cdp-proxy.mjs,然后照常用--noproxy '*'验证/health。
该后台任务在整个会话内保持存活,任务结束不必主动停(重启需重新授权 CDP)。
平台层细节(选择器、翻页控件、价格筛选、字段提取)见依赖 skill web-access 内的 <web-access>/references/site-patterns/goofish.com.md(路径相对 web-access 技能目录,不在本包内),那是权威来源,且随平台改版持续更新。本 skill 的脚本已把选择器内联,可直接跑;该文档用于排障和理解。
五阶段流程
阶段 1 · 需求翻译与关键词铺设
先把用户的话翻译成可搜索和可过滤两层条件:
| 层 | 例子(以二手笔记本为例) | 用途 |
|---|---|---|
| 可搜索 | macbook air 2020、A2179、macbook air i3 2020 |
喂给搜索框 |
| 可过滤 | 年份=2020、容量=256G、内存≥16G、价格≤1500 | 采集后本地筛 |
| 不可搜索 | 内存 16G、是否原装屏、电池健康度 | 必须逐台进详情页看 |
关键词分三类,各有取舍:
- 品名 + 年份/代际(
macbook air 2020)—— 主力产出词,覆盖率最高。 - 型号码(
A2179、A2251)—— 精准,但噪音极大:型号字符串会命中屏幕总成、主板、C 壳、内存颗粒,甚至完全无关的商品(实测A2251底下有翡翠手镯、跑鞋、车牌号)。用型号码时必须叠加「含品类词 + 排除配件词」。 - 配置词(
16g、16GB)—— 基本搜不出来,卖家标题几乎不写。想找高配只能靠详情页参数区逐个筛,这是全流程最贵的一步,要提前跟用户交代成本。
深度取舍:单关键词最多 50 页(1500 条)封顶;命中率随深度快速衰减(实测 p1–3 ≈59%、p10–12 ≈52%、p25 之后 ≈5% 并趋近 0)。有效深度就是前 15 页左右,再往后翻是纯浪费。先跑 3 页采样,把产出高的词挑出来再加深,比全词平推更省时间。
执行:scripts/harvest.mjs(见下方「脚本」)。一个关键词一个 tab,串行跑最稳。
阶段 2 · 列表初筛(不进详情页)
列表页的标题是完整的(详情页参数区才需要「品\n牌\n:」那种逐字换行解析),所以第一轮筛洗在列表层就能干掉大部分噪音,别急着逐台开详情页。
用两组正则:
- 必须含:品类词 + 关键规格词。
- 必须排除:配件/故障/引流词。排除词要写具体,写「坏|碎|裂」会漏掉大量真实表述。一份可复用的起步清单:
总成|主板|液晶|屏幕总成|拆机|尸体|料头|上半部|下半部|上半身|C壳|D壳|A壳|B壳|颗粒|外壳|键盘|电池|风扇|排线|批发|打包|标价非|勿拍|引流|拍照专用|仅展示|代购|监管机|绕过|ID锁|有锁|丢失模式
屏幕不显示|屏幕爆|黑屏|花屏|漏液|漏夜|竖线|一条线|亮线|条纹|压伤|老化|不亮|无显示|进水|泡水|水渍
筛完把结果落成 JSON,保留 id/title/price/loc/href,去重按 id。
阶段 3 · 详情页核实
对筛后的候选逐台开详情页,提取两类信息:
A. 参数区(品类相关,如品牌/型号/内存/硬盘/屏幕尺寸/拆修)——注意:参数区也是卖家自己填的,同样会错,批量铺货商家尤其明显。参数区与文案打架时,两边都存疑,等阶段 4 的截图来定。
B. 文案与卖家信号——N人想要/N浏览/卖家昵称/来闲鱼 N 年/卖出 N 件/好评率。商家 vs 个人看「卖出件数」:卖出上千件的不是个人闲置。
两个必须做的清洗(否则会误杀好货):
- 否定消解:文案里的「无进水 / 无拆修 / 未修过 / 没摔过」是加分项,不是硬伤。先剥离
无|没|未|不前缀再匹配故障词,否则好机器会被当成坏的排除。 - 验机套话豁免:商家文案模板里的「拆开核实硬件真实度(如电池鼓包、进水等)」「已检测功能正常」这类句式命中排除词,需单独剔除。
文案不要截断:致命伤常埋在最后一句(实测有台机器前面写「功能全正常、成色几乎全新」,末尾才是「屏幕爆了黑屏」;另一台末尾是「通电开机屏幕没反应」)。抓取时保留全文,别只取前 60 字。
字段解析坑:参数区文本是逐字换行(品\n牌\n:\nApple/苹果),正则要写成 品\s*牌\s*:\s*([^\n]+)。
执行:scripts/scan_details.mjs(单 tab 串行,约 5–6s/台,支持断点续跑——中途挂了重跑会自动跳过已完成的)。
阶段 4 · 实物图下载与看图取证
这是唯一能推翻前面所有结论的一步,也是这个 skill 最值钱的部分。
必须用容器选择器抓图,绝不能用「全页 img + 宽高过滤」:
['[class*="item-main-window--"] img', '[class*="carouselItem"] img',
'[class*="item-main-window-list"] img', '[class*="item-main-container--"] img']
页面底部的「为你推荐」(容器 feeds-item-wrap-- / item-feeds-list-container--)和卖家头像(item-user-info-avatar--)会被全页选择器一起抓进来——实测第一版 7 台里错了 6 台,抓回来的是明星海报、表情包、婴儿照。
拿高清原图:DOM 里 currentSrc 常是缩略图 ...jpg_110x10000Q90.jpg_.webp,把尺寸段替换为 _790x10000 即得原图;不能删掉尺寸段(直连会 404)。同一张图有多个尺寸变体,按资产 ID 去重:s.match(/(O1CN[0-9A-Za-z]+)/)。img.alicdn.com 是公开 CDN,不需要登录态。
执行:scripts/fetch_images.mjs(按 id 列表批量抓)→ scripts/sheet.py(多图拼成一张联系表总览,便于一次看完)。
看图取证点(跨品类通用):能证明"机器是什么"和"来自哪里"的截图优先。
- 系统/设置里的设备信息页 → 真实型号、CPU、内存、序列号
- 健康度/使用量页(如电池的循环计数 + 全负荷容量)→ 老化程度
- 底壳/接缝特写 → 螺丝孔位、脚垫、撬痕 → 是否拆修
- 原始购买凭证/发票 → 购买日期与销售方 → 可与卖家所在地、序列号交叉验证
判读经验:
- 图片数量本身就是透明度信号:1 张 = 高风险,3 张 = 信息不足,7–9 张 = 卖家相对坦荡。
- 电池截图是二手 Mac 的第一优先证据(比「关于本机」更能定价值)。要卖家拍「系统报告 → 电源」,
读三项:循环计数、全负荷容量 (mAh)、状态。健康度 = 全负荷容量 ÷ 设计容量。
常用设计容量:A2179 / A1932 = 4379 mAh(49.9Wh,电池型号 A1965);
A2337(M1 Air) = 4380 mAh。循环 < 300 且健康 > 85% 算良好;> 800 次或状态「建议维修」要压价或放弃。
⚠ 「系统报告→电源」里的「序列号」是电池模组的序列号(如 D86…),与整机序列号(如 FVF…)不同,
两者不一致是正常的,不是换过电池的证据。 - 截图的拍摄时间戳要读:手机翻拍图常带水印时间(如 iQOO「2026.08.09 21:29」)。
时间距今越远,电池/循环数据的参考价值越低;且标题栏若仍显示「某某's MacBook Air」,
说明卖家还没做交机准备(未退 ID、未抹机)→ 要求当面退 ID + 抹除,否则有激活锁风险。 - 屏幕上的白色痕迹:条状、在深色区域最亮、随拍摄角度变化 → 灯管反光,不是缺陷;圆形/不规则、位置固定、不随角度变化 → 才是坏点或压痕。
- 手机翻拍的截图(底部带闲鱼 App 的「查看原图」控件)可信度打折,但要警惕它恰恰是卖家唯一一张能证明配置的图。
- 实测战果:标题写 16G 的机器,唯一一张图显示 8GB;标题写「i5 四核」的机器,卖家自己放的「关于本机」截图是「双核 Core i3」。两台都是靠看图才抓出来的,纯文本筛选完全抓不到。
阶段 5 · 风险分级与报告
按证据强度分四档,每台都必须能说清"凭什么":
| 档 | 判据 |
|---|---|
| ✅ 首选 | 参数区 + 文案 + 实拍截图三处一致;来源可溯(发票/序列号);卖家信号正常 |
| 🟡 可考虑 | 配置属实但有明确瑕疵(外观磕碰、颜色非首选、电池老化) |
| ⚠ 待补证 | 图太少 / 无系统信息 → 先让卖家补截图再谈价,不要在信息不足时推荐 |
| ✗ 排除 | 标题与实拍不符、故障机、配件机、带锁/监管机 |
报告输出用 assets/report-template.html 作骨架(含样式与卡片结构),产出单文件 HTML(图片用相对路径引用本地下载的图)。每台机器必须带:
- 价格、地区、卖家信号
- 核实到的关键参数 + 证据截图(不要只写结论,要放图)
- 一句「判定」:便宜在哪儿 / 贵在哪儿,把差价折算成预期成本(例:"比 X 便宜 250 元,但换一块电池就要 300+")
- 闲鱼商品页链接:
https://www.goofish.com/item?id=<数字id>(用户一定会点,别漏)
★ 交付硬要求:每个商品链接必须配「复制」按钮。(2026-09-23 实测,用户必然要在手机闲鱼 App 里打开)
用户看报告是在电脑上,但真正下单在手机 App —— 所以"能复制"比"能点"更重要。
- 每个链接旁给两个按钮:复制链接(
https://www.goofish.com/item?id=xxx)和 复制ID(纯数字)。 - 顶部再给一个一键复制全部链接(按 URL 去重,一行一条)。
- 页面写明用法:复制 → 打开闲鱼 App → 顶部搜索框粘贴 → 搜索(App 常自动识别剪贴板);
并明确劝阻直接点链接 —— 闲鱼防外链极严,微信/浏览器里点开大概率提示「页面不存在」或跳应用商店。 - 实现要点(都在
assets/report-template.html之外的注入脚本里,可直接抄):
1. 用 JS 在a[href*="goofish.com/item"]后insertAdjacentElement('afterend', ...)注入按钮,不要手工改几十处 HTML。
2. 复制链三级兜底:navigator.clipboard.writeText→document.execCommand('copy')→ 弹窗给一个已全选的 textarea 让用户 Ctrl+C。
(预览面板是 iframe,clipboard 权限常被拦,兜底必须有。)
3. 验证必须用真实手势:execCommand('copy')依赖 user activation,用 JS 程序化.click()测会误判为失败。
正确做法是用 CDP/clickAt(真实鼠标事件)点一下,再看按钮是否进入「已复制」成功态、兜底弹窗是否没出现。
4. 顺手校验渲染:document.images里naturalWidth==0的数量应为 0(相对路径图别裂)。 - 主题:
report-template.html是深色皮肤。若当前 IDE 是浅色主题,把 CSS 变量整体换成浅色版(浅底深字),
报告在预览面板里的可读性比"照搬模板"更重要。
跨品类通用原则
从真实抓取中总结,适用于任何品类:
- 信息可信度阶梯:卖家标题 < 参数区 < 卖家实拍的系统截图 < 平台验货宝官方报告。核实永远往后一层走。
- 实测(2026-09-18~23):某台 MacBook Air 的标题和参数区都写 256G,只有卖家实拍的「关于本机」截图显示 512G,
最后验货宝报告确认是 512G —— 前两层同时错,第三层才是对的。别因为标题和参数区一致就相信它。
- 卖家标错配置对买家可能是有利的:该机一直按 256G 定价议价,标题写 256G 导致浏览/想要数偏低(373 浏览、8 人想要),
最终被以 ¥1300 成交(512G 同款市场最低 ¥1598)。所以「价格异常低」不必然是机器有问题,也可能是卖家自己没搞清配置
—— 但仍要按第 3 条查清原因再下单。 - 高配搜不出来:卖家只写主销配置。想找高配必须逐台看详情页,要提前给用户报价(时间成本)。
- 价格异常低必有原因:无一例外。找到原因再决定值不值。
- 图数量 = 透明度:卖家放几张图,反映他敢让你看多少。
- 凭证能交叉验证:发票的购买日期/销售方城市,与卖家所在地、序列号互相印证,比任何自述都硬。
- 商家 vs 个人:看卖出件数、好评率、来闲鱼年数。卖出上千件 + 文案模板化 = 商家铺货。
- 反常即停:结果不符合常识时(比如"筛选后 0 条"、"高配数量为 0")先停下来查为什么,不要顺着往下写。曾经因为一个字段名写错导致筛选结果全为 0,白跑一轮。
- 关键属性常常不在标题里:卖家只写主销卖点。先找出"决定这件东西能不能用"的那个属性——能不能从标题看出来?能(容量、型号)就直接列表层筛;不能(电脑的内存、登山杖的手柄类型)这一步就是全流程的成本中心,要提前跟用户交代耗时,并优先用实物图而不是文案来补。
脚本
脚本内联了全部页面选择器,可直接运行。注意:某些 Agent 的 bash 沙箱里缺 coreutils(ls/head/sleep/dirname 不可用),文件与进程操作建议一律用 node;若命令 stderr 报 dirname: command not found,无害,可忽略。
scripts/cdp.mjs — Proxy 客户端(调试用)
node cdp.mjs targets # 列出已打开的 tab
node cdp.mjs new <url> # 新开 tab,返回 targetId
node cdp.mjs eval <target> <js文件|-> # 在页面里执行 JS(从文件读,避免转义地狱)
node cdp.mjs scroll <target> <y|bottom|top>
node cdp.mjs shot <target> <file> # 截图
node cdp.mjs close <target>
scripts/harvest.mjs — 搜索采集
node harvest.mjs <target> <关键词> <页数> <价格下限> <价格上限>
# 例:node harvest.mjs T1 "macbook air 2020" 15 300 1500 > out.json
输出 {keyword, items:[{id,title,price,loc,href}]}(JSON 走 stdout,进度走 stderr)。
scripts/scan_details.mjs — 详情页参数扫描
node scan_details.mjs <target> <ids.json> <out.json> [limit]
# ids.json: ["123","456"] 或 [{"id":"123"},...]
支持断点续跑:已抓过的 id 会跳过。每 10 台落盘一次。约 5–6s/台。
scripts/fetch_images.mjs — 批量抓实物图
node fetch_images.mjs <id1> <id2> ... # 或 --file ids.json
# 每台新开 tab → 抓容器内高清图 → 下载到 ./imgs/<id>/ → 关 tab
# 同时写 manifest.json
scripts/sheet.py — 多图拼联系表
python sheet.py <图片目录> <输出.jpg> [列数=5] [缩略宽=260] [缩略高=300]
输出一张带编号的拼图总览,便于一次性看完某台机器的所有图。需要 Pillow(pip install pillow)。
常见坑速查
| 现象 | 原因 / 对策 |
|---|---|
| 抓回的图是明星海报/表情包 | 用了全页 img 选择器,撞进「为你推荐」容器 → 改用容器选择器 |
| 图片只有几 KB、看不清 | 拿到的是缩略图 → 尺寸段替换为 _790x10000 |
| 图片全部 404 | 把尺寸段整个删掉了 → 必须保留 _xxx_ 段 |
/eval 返回 {error:"Uncaught"} |
① 顶层重复声明了同名 const/let(同页面上下文复用)→ 改用 window.__X =;② 点击分页/确认按钮触发重渲染,这是正常的,重试即可;③ 脚本自身有语法错误——注意 EXTRACT 等模板字面量里的 // 注释中绝不能写 \n、\t 这类转义,求值后会变成真实换行把注释截断,导致整个脚本 SyntaxError(曾因此 20/20 台全挂,改用独立 try 包字段无效,因为这是解析期错误)。诊断法:把源码塞进 window.__S,再 (0,eval)(window.__S) 包 try 捕获真实 e.message |
| 翻页无效 | &page=N 参数不生效,必须点分页控件 |
| 价格筛选后 URL 没变 | 正常,过滤在页内生效,无参数可复用 |
| 结果里混进远超预算的商品 | 先采集了未筛选的第 1 页 → 必须先设价格筛选再采集。筛选会改变结果集与排序,未筛选那批的 id 与筛选后不重合,去重清不掉(实测混进上千元的货)。harvest.mjs 已修正,输出时也会自检区间外条数 |
| 筛选结果全为 0 | 先查字段名是否写错(曾因漏返回 ssd256 白跑一轮),再查正则是否误伤 |
| 好机器被判成故障机 | 否定消解没做(「无进水」被当成「进水」) |
| 详情页正文混进别人的商品 | 未按 为你推荐 / 聊一聊 截断 |
| 参数区只抓到「牌/质/色」这种残缺键名 | 参数区的键是逐字换行的(品\n牌\n:\nApple)→ 正则必须允许键名跨行:([^\n:]{1,14}(?:\n[^\n:]{1,14}){0,3})\n:\n |
| 正文里出现「描述不符包邮退」等平台文案 | 平台 UI 文字混进 innerText → 分析前先剥离(见 playbook 第五节) |
| 判不出卖家是个人还是商家 | 文案不可信(「个人闲置」被商家大规模套用)→ 只看卖家卖出件数,≥300 件基本是商家 |
| 连接到 proxy 后无响应 | Chrome 首次 CDP 连接的授权框没点「允许」 |
curl 连 3456 报 upstream connect failed ... 10061 |
环境的 http_proxy 污染,不是代理挂了 → 加 --noproxy '*' 复测 |
| 代理第一次能用、下一条命令就 connection refused | detached 起的 proxy 被命令结束回收了 → 用 run_in_background 常驻跑 cdp-proxy.mjs |
node: command not found |
PATH 没生效/被覆盖 → 用本机 node 的绝对路径调用(如 $(command -v node) 的结果;WorkBuddy 环境里在 ~/.workbuddy/binaries/node/versions/<ver>/node.exe) |
Resources
references/playbook.md—— 完整方法论、正则清单、字段解析模板、报告结构详解scripts/—— 采集 / 扫描 / 抓图 / 拼图脚本assets/report-template.html—— 报告骨架(样式 + 卡片结构)