一个月前我写过一篇采集方法论,然后我把它忘了
先说一件不太体面的事。
2026 年 8 月 27 日,我写过一篇文章,讲怎么从零搭一套多源采集管线。那篇东西不差——21 个渠道逐条实测过,去重、并发、追溯都写了,是我和另一个 AI 双线跑完一个真实项目之后的复盘。后来 9 月 16 日站上做减法,把它和一批东西一起下架了。
一个月后,我为了收一组 Instagram 参考图,让一个 AI 去干同样性质的活。它跑了两个小时,交回来的东西我没法用:数字失控、判据说谎、该保留的被当成垃圾。我当时的原话是「我无法接受你不按照我的诉求去办事」。
那篇被我自己下架的文章里,写着它正在踩的每一个坑。
所以这篇文章分两部分:前面是那份方法论,我把它从仓库历史里捞回来重新整理了;后面是这次实战补上的东西——有些是旧文没讲到的,有些是讲到了但我没当回事的。
如果你也打算做一个自己的素材库,这篇可以当操作手册用。工具在文末,方法不受版权保护,拿走就是。
把链接丢上来,图会铺到一张桌上。
帖子 / Reels / 分享口令
点「粘贴示例」→「识别并提取」,看图是怎么摊开的。
打开真正的取图页 ↗一、先分清「产地」和「工具」
我犯过的第一个错,是一搜到某个站就盯着它猛抓。结果是收回来一堆重复、低质的图,还白白触发风控。
复盘之后发现,问题不在「不会下」,在于没先分清原料产地和收割工具。这是两件独立的事:
- 选对产地——哪些站才是这类素材的高质量大本营;
- 掌握工具——能不能批量、能不能去重、能不能追溯。
然后是个很关键的醒悟:那些看起来很精致的参考站,自己大多也是从少数几个上游源拿图。所以与其扒一个站,不如直接掌握它的上游源。
这条到现在都是整套方法论的地基。扒站得到的是别人的加工结果,端上游源得到的是原料。
二、可批量源才是真主力
把渠道逐个实测之后,结论其实很集中——真正「开箱即用、能批量、能去重」的没几个。
| 渠道 | 能拿什么 | 关键条件 |
|---|---|---|
| Pixiv | 二次元插画 | ajax 接口无需登录,下载带 Referer |
| Wallhaven | 壁纸 | 公开 API,可按分辨率/纯度过滤 |
| Civitai | AI 生图 | 返回自带内容 hash,天然好去重 |
| IG(gallery-dl + cookie) | Kpop 官方原图 | 一次拉到过 3.5 万张 |
| LOL DataDragon | 游戏官方原画 | 官方渠道,干净 |
而那些「看着美好」的站,实测大多是坑:
- Pexels / Pixabay / Unsplash:质量顶级,但要 API key(注册即给,不难,但多一道手续);
- DeviantArt / 图搜类:403 / 401 / 要 OAuth;
- B 站专栏:有优质大图,但会撞 412 风控;
- 某些聚合站:成分太杂,不适合当主路。
一句话:少而精的可批量源,好过一堆看着好但只能硬刚反爬的站。
三、管线:并发 + 去重 + 追溯
真正把量堆起来的骨架就三段:
研究方法 → 评估可行性 → 并发采集 → 去重归档 → 上线管理 → 规模化其中三件事最关键。
① 并发
单线程抓太慢。多线程(每主题 10 个 worker)比单线程快一个量级不止。
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=10) as ex: for i in ids: ex.submit(download_one, i, dirp, existing)② 去重——这条我这次又栽了
重复下载的根因,是用 URL 去重。 同一张图换个 URL、翻一页、换一种命名,就识别不出来了。
def url_dedup(url): return sha256(url).hexdigest() # ❌ 同图不同 URL 就漏def content_dedup(buf): return sha256(buf).hexdigest() # ✅ 跨源/跨轮都判重正确的做法是按文件字节算 sha256。 跨源、跨轮、跨主题,绝不会重复入库。
这次实战的教训:我遇到的不是「重复」,是它的反面——同一张图有两种文件命名(一个来自帖子网格、一个来自轮播第一张)。按文件名判,它们是两张;按内容 hash 判,它们是一张。去重和查重都得落到字节上,不是名字上。
③ 可追溯
每个主题目录放一份 manifest.json,记录来源、作者、hash、时间。收藏的东西要知根知底。
{ "file": "a3f9c1d2e8b7...", "source": "instagram/@handle", "post_id": "3784443148073331773", "media_id": "3784443134089551574", "sha256": "a3f9c1d2e8b7...", "fetched_at": "2026-10-15T02:18:44"}四、限流:认清形态,别把 404 当没数据
这一段是旧文里最值钱的部分,也是这次被原样踩掉的坑。
def http_get(url): for attempt in range(1, 5): try: r = fetch(url) if r.status == 200: return r except HTTPError as e: if e.code in (404, 500): # ⚠️ 软限流:不是真 404! sleep(2 ** attempt); continue if is_cf_challenge(r): # 硬限流:换出口 IP rebuild_proxy(); continue if e.code == 429: # 按 Retry-After 等 sleep(retry_after(e)); continue raise三种限流,三种反应,别混:
| 形态 | 含义 | 正确反应 |
|---|---|---|
| 404 / 500 | 软限流,不是没数据 | 指数退避重试 |
cf-mitigated 挑战页 | 硬限流,出口被盯上 | 换出口 IP |
| 429 | 频率超限 | 读 Retry-After 老实等 |
这次实战补的:这次遇上的 401 带着
require_login: true,看着像「会话失效」,实际是节点级限流。判定方法是拿同一套 cookie 换出口节点试——换个节点秒开 200,原节点要等十几分钟。换节点比等冷却快约 50 倍。教训:401 不一定是登录问题。先怀疑出口,再怀疑 cookie。 那次差点把好好的 cookie 全重导一遍。
还有一个更阴的:零结果要拒绝写表。
扫描失败返回 0 条,如果代码照常把「0 条」写进真值表,然后返回成功——你就得到一份看起来很正常的空数据,后面所有校验都基于它,全部通过。
这次实战的铁律:扫描零结果,必须拒绝写表并报错。绝不能用空表覆盖好表之后返回成功。这个 bug 让我在「一切正常」的假象里待了很久。
五、缺口判定:别用「总格数减格号」
这是这次实战里我最后悔没早点做对的一件事,旧文里没写。
一个帖子有多少张图、我们拿到了几张——看起来是个减法,实际很容易做错。
错误做法:
missing = [i for i in range(expected) if i not in have] # ❌拿「帖子总格数」减「我们有第几格」。问题在于:它从没查过那一格到底是什么媒体。
Instagram 的帖子第 7 格常常是视频,而我们故意不下载视频(下载了也只是封面帧,不是照片)。于是这个判据永远报「缺第 7 张」,而你点开帖子一看——第 7 格确实是个视频,我们一张照片都没缺。
正确做法:对着真值逐格验。
for idx, slide in enumerate(truth_slides): if slide["type"] == 2: # 视频格:不计入缺口 video_skipped += 1 continue if idx not in have_idx: # 照片格缺了:这才是真缺 missing.append(idx)三层分类,必须分清楚:
| 标记 | 含义 | 要处理吗 |
|---|---|---|
complete | 照片格全在 | 不用 |
video_skipped | 帖内视频位,故意没下 | 不用,视频不是照片 |
boundary | 窗口从这帖中间切开,更老的部分在归档区 | 不用,是设计行为 |
size-unknown | 没有元数据,判不了 | 老实说判不了 |
missing | 照片格真缺了 | 这才是要补的 |
这次实战最严重的一个错:真相表覆盖不到的帖,我的代码走了「判不了」的分支,却把
complete算成了True。结果满屏绿标「完整」,真缺的帖子被藏在了「一切正常」里。铁律:信息不足时必须输出「判不了」,绝不能输出「完整」。用默认值冒充验证通过,比直接报错害人得多。
六、窗口:裁剪 + 钉住
素材库会长得很大。如果你的目标是「保留最新 N 张」,就需要裁剪——但裁剪有个大坑。
裁剪会误删你之前选过的图。
这次实战的做法:所有「已选」记录单独存一份清单(
picked/manifest.json),裁剪时以它为准:
- 落在窗口内的已选图:正常留着;
- 落在窗口外的已选图:钉住(pin),不参与任何裁剪。
另外,「移动」而不是「删除」——裁剪掉的东西进
_removed/,随时可捞。素材库不做不可逆操作。
七、分级:给「以后要用」一个出口
一开始我做的是二元判断:要 / 不要。
问题很快暴露:「不确定但舍不得删」的图没有地方放。 判「要」太满,判「不要」心疼。
于是改成分级:
S = 顶(首选素材)A = 高(优质备用)B = 中(有意思,留着)C = 不用(判过了,文件还在)关键设计:C 不代表删除。 文件一直都在,只是「判过了」。这样你可以放心大胆地判,不会有亏损感。
另外,已选的图要锁死——分级界面里它们只读,方向键按不动。不然你手滑一下,之前的选择就变了。
八、验收:不要相信「看起来能跑」
这一节是旧文里另一块值钱的东西。
「看起来能跑」是最不可靠的信号。三重校验,缺一不可:
- 截图——看视觉;
- 推进——很多站有加载动画,静态截图只能拍到一片黑,要用滚轮事件驱动;
- DOM 校验——截图小不等于页面坏了,WebGL 在无 GPU 环境里不画,但结构是对的。
chrome --headless=new --dump-dom http://127.0.0.1:8101/ > dom.html逻辑是:截图看视觉,滚轮推进度,DOM 查结构。
九、踩过哪些坑(都是学费)
挑几个最典型的,括号里是这次有没有复发:
- URL 去重 = 重复的根因 → 换成内容 sha256(复发了,这次是反过来:该合的没合)
- 脚本里写中文路径会乱码(PowerShell 按 GBK 读坏)→ 含中文的脚本一律用 Python
- 翻页参数被覆盖(
page=dapi被再拼一次覆盖成 HTML)→ 改用pid - 限流的三种形态要认清(复发了)
- cookie 写过期的值会静默失效(写
1会报 “expired at 1970”) - 账号存在 ≠ 有作品(很多号只是注册了,几乎没发图)
- 大库会让管理工具卡死(几万张大图建预览会超时)→ 用轻量小目录,别让工具处理全库
- 零结果写表 = 埋雷(这次新踩的)
十、这篇文章想说的
如果只留一句话,我会说:抓上游源,别扒单个站。
但这次我更想说的是另一件事。
上面这些教训,我都写过。一个月前就写过。然后我让另一个 AI 去干同样的事,它把每一条都原样踩了一遍——而我当时没想起这篇文章。
所以经验这件事,不是「有」就行了。得放在够得着的地方,得在开工之前读一遍。
这份方法论现在的地位变了:它不再是一个项目的记录,它就是这套活该怎么干的说明书。以后要干这个,先读它。
剩下的几条原则,更像是给自己立的规矩:
- 先研究再动手:把渠道逐个实测(连通 / 可批量 / 限流),挑出能跑的,而不是一看到就冲。
- 内容去重 + 可追溯:绝不重复入库,且每张图都能溯源。
- 工具要匹配对象:Node 抓某个源超时就换 PowerShell;本机被风控标记就走代理出口;长跑配看护。
- 先给用户看,再放量:先出一小批真成果,风格质量对味了,点头之后再规模化。
- 进度诚实:没验证的事不说成已验证,没修的 bug 不说成已修。
十一、和「取图」的关系
这篇文章和 取图 是同一件事的两个尺度。
取图解决的是单次提取:你刷到一组图,把链接丢进去,图摊在桌上,挑几张下载。它是「手边就能用」的那个工具。
这篇文章解决的是批量管线:当你要的不是一组,而是某个来源的几百上千张,而且要求去重、可追溯、可重跑——取图那种交互式的做法就不够了,你需要一套能跑一整夜、出错了能重来的管线。
两者本质是同一件事:把散落在各个 App 里的图,收到自己能掌控的地方。 差别只是规模。
如果你只是偶尔收图,取图 就够了。如果你要做素材库,从这里往下。
工具清单
旧文那套工具在 D:\Saki\design-lib\(38 个脚本,逆向站复刻用),这篇讲的是采集侧的,在 .ai-work/ 下:
| 用途 | 脚本 |
|---|---|
| 全量真值扫描 | hy_sweep.py |
| 缺口权威审计 | hy_audit.py |
| 真相表校验 | hy_verify_truth.py |
| 按窗口补口 | hy_fetch800.py |
| 合格性验收 | hy_accept.py |
| 窗口裁剪 | hy_revert_overfetch.py |
| 最终检查 | hy_final_check.py |
全部可复用,改改入口就能指向别的账号或别的平台。
本站协议见 规矩。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

































































