news 2026/10/31 4:44:02

插画作品批量下载与长期备份:会话、限流和断点续传

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
插画作品批量下载与长期备份:会话、限流和断点续传

做插画备份这件事,我前后折腾过三轮。第一轮用最土的办法,手动右键另存,存了不到两百张就放弃了;第二轮写了个脚本硬跑,跑到第三天账号被临时限制,本地还留下一堆 0 字节的 jpg;第三轮才把整件事拆成“会话管理 + 节奏控制 + 落盘结构”三块,做成一个能挂后台、断了能续、跑一个月不用管的东西。这篇就把这套东西完整拆开讲——如何把关注的画师的历史作品和后续新作批量下载到本地,怎么把登录会话维持住不失效,为什么人机验证压根不该去“过”而应该去“避”,以及那个每次换机器必跳出来的 SSL 证书报错到底该怎么归类和修。

适合谁来读:写过一点 Python、能看懂 requests 基本用法、但一上规模就翻车的人。纯新手也能跟着抄,但建议先把第 2 章和第 5 章看完再动手,那两章决定了你这套东西能不能长期跑;老手可以直接跳到第 3、4 章看我踩过的具体坑。

1. 动手前先算清楚:6 万张作品、13 万个文件、100GB 磁盘

很多人写爬虫的第一步是打开编辑器,我的第一步是拿计算器。这不是强迫症,是血泪——我第二轮翻车有一半原因就是跑到第三天才发现磁盘只剩 4G,脚本开始疯狂写半截文件。

1.1 先给需求做个规模估算

假设你关注了 300 位画师,这个数字不算多,常年混插画社区的人关注列表轻松过千。按人均 200 件作品算,总作品量是 6 万;其中大概 15% 是多图作品,人均 3 页,那么图片文件数约等于:

60000 + 60000 × 0.15 × (3 - 1) ≈ 78000

再算上缩略图和头像,实际落在 10 万到 13 万个文件之间。单张原图平均按 800KB 估(有的插画师一张 5MB,有的 300KB,取中位),总容量 60GB 到 100GB 量级。

这两个数字直接决定了后面所有设计:

规模指标估算值对应的设计决策
作品数6 万必须有本地清单表,不能靠文件夹名反查
文件数10 万+单目录不能放超过 2 万个文件,否则 ls、资源管理器都会卡死
总容量60-100GB落盘前先检查剩余空间,低于阈值暂停
单次请求耗时200-800ms全量跑完至少 8-15 小时,必须支持断点续传

我第一次跑的时候没做目录分片,13 万个文件全堆在一个文件夹里,结果文件管理器打开要转圈半分钟,脚本里用os.listdir判断是否已下载也要几秒一次。后来改成按画师 ID 分目录,单个目录最多几百个文件,一切就顺了。

1.2 一次性全量备份和长期增量同步,是两套架构

这两个需求长得像,实现差别很大,最忌讳混在一起写。

一次性全量备份的优化目标是吞吐:可以容忍较高的并发,可以不做精细的去重,跑完就完事,代码写崩了重跑一遍也无所谓。这种场景反而不需要复杂的清单表,一个简单的“文件存在就跳过”的判断就够了。

长期增量同步的优化目标是稳定和低打扰:每天或每周跑一次,只拉新增作品,请求量必须压到极低,不能让站方觉得你在刷。这种场景清单表是必需品,而且要记录每次同步的时间点和游标。

我最终的方案是做成同一个程序、两种模式:

  • --mode full:首次初始化,把关注列表里所有画师的历史作品全拉一遍,并发拉高,跑通宵。
  • --mode incr:日常增量,只查每个画师作品列表的第一页,比对本地最大作品 ID,有新的才往下翻。

增量模式的日请求量大概是这样:300 位画师 × 每人 1 次列表请求 = 300 次,加上平均每天 50 件新作品的详情和图片,总共不到 500 次请求。这个量级分散在一天里,跟正常用户刷页面的行为没区别。

1.3 明确哪些事不做

这一段我必须写在最前面,因为它是整个项目能不能长期活下去的前提。

不碰需要额外权限才能看的内容。有些作品是限定可见的,有些高清原图是付费订阅才能拿的。这些东西哪怕技术上能碰到,也不该碰——它既违反站方规则,也是对创作者最直接的伤害。我的脚本里会检查作品的可访问状态字段,不可访问的直接标记跳过并存日志。

不做验证码的自动识别。这一点第 3 章会详细讲,结论先给:技术上不划算,风险上不划算,效果上更不划算。

不做二次分发。下载到本地是自己的备份,打包上传到网盘分享、做成图站、拿去训练模型,这些都不在“备份”的范畴里。我在脚本里给每个画师目录生成一个 README,写明来源和作者信息,就是为了提醒自己这堆东西的归属。

不追求“零遗漏”。有些作品会被作者删除,有些画师会注销账号,有些图片链接会失效。这些是常态,不是 bug。我的清单表里专门有一个status字段记录ok / gone / forbidden,跑完之后统计一下比例,如果gone超过 5%,那说明是我的解析逻辑出问题了,而不是真的删了那么多。

提示:先把“我要什么”和“我不要什么”写进配置文件,跑起来之后再改成本很高。我见过太多人一开始只想备份自己的收藏,跑着跑着就变成“顺手把排行榜也抓了”,然后就被限速了。

2. 登录态是整个项目的地基,也是最容易塌的地方

这一章是全文最核心的部分。后面所有关于反爬、关于人机验证的讨论,前提都是你的登录态是健康的。登录态一旦不稳,你会把所有问题都误判成“被反爬了”,然后去做一堆没用的对抗。

2.1 三条登录路径,我为什么放弃了“模拟填表”

要拿到登录后的会话,理论上只有三条路:

路径实现难度稳定性维护成本我的选择
官方开放授权(OAuth 类)低高极低首选,有就用
手动登录后导出 Cookie 复用低中低次选,实际采用
程序化提交登录表单高低极高放弃

第一条路最好,很多社区都提供开放接口,走标准授权流程拿 token,token 有明确的过期时间和刷新机制,一切都很干净。缺点是权限范围通常受限,有些接口不开放。

第二条路是我实际用的:在浏览器里正常登录,从开发者工具里把 Cookie 导出成 JSON 或者 Netscape 格式的 cookies.txt,脚本启动时加载。这条路的关键认知是——你复用的是“你自己手动登录产生的会话”,不是“破解登录”。登录这一步由人完成,脚本只负责携带这个会话去发请求。这个边界要划清楚。

第三条路我试过,放弃了。原因很实在:登录接口的表单里通常带动态生成的加密字段,这些字段的生成逻辑会随站方前端更新而变化,你每跟一次更新就要重写一次;更麻烦的是,登录接口本身往往就是人机验证的重点防护对象,你在这儿撞墙的概率比在任何地方都高。投入产出比极差。

2.2 Cookie 持久化的几个工程细节

导出的 Cookie 直接塞进 headers 里能用,但很脆。我现在的做法是封装一个 Session 工厂:

import json import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def build_session(cookie_path: str, ua: str) -> requests.Session: s = requests.Session() with open(cookie_path, "r", encoding="utf-8") as f: jar = json.load(f) # [{"name": .., "value": .., "domain": ..}, ...] for c in jar: s.cookies.set(c["name"], c["value"], domain=c["domain"]) s.headers.update({ "User-Agent": ua, "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://<site-domain>/", }) retry = Retry( total=3, backoff_factor=1.5, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"], respect_retry_after_header=True, ) adapter = HTTPAdapter(max_retries=retry, pool_connections=8, pool_maxsize=8) s.mount("https://", adapter) return s

三个细节值得单独说。

第一,UA 必须和导出 Cookie 时的浏览器一致。这是最容易崩的点。我用 Chrome 登录导出 Cookie,脚本里却写了个python-requests/2.31的默认 UA,结果第一个请求就跳登录页。原因很简单:服务端会话记录里存了“这个会话是用什么客户端建立的”,你换了客户端但会话还是那个会话,就是明显异常。

第二,Cookie 不要写进代码,也不要进 git。我把它放在~/.config/mybackup/cookies.json,权限设成 600。曾经有次差点把带会话票据的代码推上去,从那以后.gitignore第一条就是这个文件。

第三,不要打印 Cookie 到日志。调试的时候图方便print(session.cookies.get_dict()),结果日志文件里全是会话票据。日志是要长期留存的东西,这个习惯必须改掉,最多打一下 cookie 的 name 列表和 value 的哈希前六位。

2.3 登录态失效的三种表现,以及怎么自动判定

会话不会突然消失,它是有征兆的。我总结下来是三种:

第一种,接口返回 200,但 body 里带错误字段。很多接口的设计是 HTTP 层永远 200,业务错误放在 JSON 里。这种情况最阴险,因为你的重试逻辑、状态码判断全都认为“成功了”,实际拿到的是个空对象,最后表现为“下载了 0 张图但脚本说全部完成”。

第二种,请求被 302 重定向到登录页。这种好判断,设allow_redirects=False看一眼 Location 就行。

第三种,返回 200 且是个正常 HTML,但其实是通用首页。这种最难判,因为页面结构看起来没错。判断方法是检查响应里有没有你的用户标识,或者检查返回的内容长度是不是和未登录时一样。

我的做法是设一个哨兵请求:每次批量任务开始前,先请求一个需要登录、返回体极小、结果稳定的接口(比如“我自己的账号基本信息”)。判定逻辑:

def check_login_alive(s: requests.Session) -> bool: r = s.get("https://<site-domain>/ajax/me", timeout=10, allow_redirects=False) if r.status_code in (301, 302, 401, 403): return False if r.status_code != 200: return False # 交给上层重试,不当成失效 try: data = r.json() except ValueError: return False # 返回了 HTML,基本就是掉登录了 return bool(data.get("user_id")) # 业务层字段,按实际接口调整

哨兵不通过就直接sys.exit(2),让人工介入去重新导出 Cookie。千万不要写“检测到失效就自动重新登录”的逻辑,那就是第 2.1 节说的第三条路,你会陷进一个无限循环。

注意:哨兵请求不要设成每个请求都跑一遍。那样请求量翻倍,还容易把哨兵接口本身撞出限制。我的做法是每处理完一位画师跑一次,300 位画师一天跑 300 次哨兵,完全在合理范围内。

3. 人机验证的正确解法是“不触发”,不是“过验证”

这个话题很多人上来就问“怎么识别那个点选图”。我的回答一直是一样的:别识别。这一章讲清楚为什么,以及不识别的情况下怎么把活干完。

3.1 先把触发的真实原因找出来

人机验证组件不是随机弹的,它背后有一套风控评分。我观察了几个月,触发率和我以为的因素关系不大,和这几个因素关系很大:

请求速率。这是第一因素,占我遇到的情况的七成以上。具体阈值不好说,但我实测下来,对详情类接口保持 1 秒 1 次、对图片 CDN 保持 3-5 并发,连续跑几个小时都没事;一旦把详情接口压到 1 秒 5 次,十分钟内必弹。

**访

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 4:43:31

OpenClaw飞书机器人安装与高效办公实战指南

1. OpenClaw飞书机器人初体验&#xff1a;为什么你需要这个数字助手&#xff1f;第一次听说OpenClaw飞书机器人时&#xff0c;我正被堆积如山的会议纪要和待办事项压得喘不过气。作为团队负责人&#xff0c;每天要处理几十个群聊消息、整理会议记录、协调成员日程——这些重复性…

作者头像 李华
网站建设 2026/10/31 4:36:39

华为交换机Console口密码配置全解析:从物理层到安全审计

1. 项目概述&#xff1a;为什么一个Console口密码配置值得单独写一篇长文&#xff1f;“华为交换机配置consel口用户密码”——标题里这个拼写错误&#xff08;consel应为console&#xff09;其实特别真实。我第一次在某高校网络实验室帮学生排查设备连不上时&#xff0c;也是盯…

作者头像 李华
网站建设 2026/10/31 4:36:35

AI Agent能力扩展:Skill、MCP与插件如何协同落地

从去年开始我就在折腾AI Agent的能力扩展&#xff0c;越深入越发现一个尴尬的事实&#xff1a;网上绝大多数教程都在争论该选Skill、MCP还是插件&#xff0c;但真正落地时你会发现&#xff0c;这三样东西根本不是同一维度的概念&#xff0c;硬要"三选一"等于让一个人…

作者头像 李华
网站建设 2026/10/31 4:35:21

2026届AI科研平台选择指南与评测

1. 2026届毕业生AI科研平台选择困境与破局作为即将在2026年毕业的科研新生代&#xff0c;我们正站在AI技术革命与学术研究深度融合的历史节点。最近帮实验室三位研一学弟学妹配置科研环境时&#xff0c;发现他们面对琳琅满目的AI科研工具时普遍存在选择困难&#xff1a;有人花两…

作者头像 李华
网站建设 2026/10/31 4:35:06

OpenRig:轻量级AI智能体持久化协作系统

1. 项目概述&#xff1a;当AI智能体不再“单打独斗”&#xff0c;而开始“组队干活”OpenRig这个名字&#xff0c;乍一听像某种开源硬件驱动&#xff0c;或者某个嵌入式开发框架——但其实它指向一个更本质的转变&#xff1a;我们正在从“调用一个AI模型”走向“调度一群AI智能…

作者头像 李华
网站建设 2026/10/31 4:34:09

AI系统安全防护:动态防御与伦理越界风险解析

1. 项目背景与核心议题解析这个标题引发了对AI技术发展边界的深度思考。作为从业十余年的技术观察者&#xff0c;我注意到近年来AI伦理讨论已从学术圈进入公众视野。标题中"越狱"的表述实际上隐喻了AI系统突破预设伦理框架的风险场景&#xff0c;这种技术隐喻在安全领…

作者头像 李华