一个主页的视频怎么批量下载?自研采集任务队列的一次设计复盘

一个主页的视频怎么批量下载?自研采集任务队列的一次设计复盘 一个主页的视频怎么批量下载自研采集任务队列的一次设计复盘有人问我能不能把一个博主的参考视频全存下来做拆解我说简单手动下呗。两个小时后我存完了 43 条却不记得任何一条讲了什么——只记得手腕在重复点、等、点、等。我不服气把当时能找到的工具挨个试了一遍最长的那条链路我点了 60 多次下载按钮。做完这件事我坐在那里发了会儿呆一个真人对着页面一小时的工作量机器本该十分钟跑完中间那 50 分钟的差距全是人肉调度器在替软件打工。做工具的人最怕的就是这个——于是接下来的两周我把整主页批量下载重做成了一条采集任务队列。这篇文章是一次完整的设计复盘需求怎么拆、状态机怎么定、并发怎么被教做人、边界在哪里。⚠️合规前提本文讨论的采集只针对公开可见内容目的仅限个人学习与自有素材备份全程遵守各平台服务条款与频控规则不涉及存储、传播或商用他人版权作品。 文章目录一. 需求拆解单条、整主页、合集是三个产品 二. 为什么手动点不动登录态、翻页与风控 三. 任务队列设计排队-解析-下载-重试的状态机 四. 进度可视化为什么每条任务都要能单独重试 五. 自动入库下载完成只是开始 六. 踩过的坑并发被限流、分页参数突变 ️七. 边界与合规只做公开内容仅供个人学习 ⚖️常见问题 FAQ八. 写在最后 参考文献一. 需求拆解单条、整主页、合集是三个产品 “主页视频批量下载听起来是一个功能拆开其实是三种形态工程含义完全不同单条是一次解析一次下载”整主页是先枚举、再展开任务合集创作者把视频归成的专题列表介于两者之间但它的分页机制最不稳定。做设计前先把这三件事分开建模是这次复盘的第一个结论。维度单条下载整主页批量合集下载输入一条链接/分享文案作者主页链接枚举全部投稿合集链接按集合内条目展开枚举来源无需枚举作者作品公开分页接口合集分页接口游标参数易变任务规模1几十到几百条通常几条到几百条主要风险播放地址时效过期枚举漏页、中途被限流分页参数突变导致重复/漏取工程重点延迟解析、TTL 控制按视频 ID 全量去重快照式枚举先定清单再排队典型场景存一个参考镜头拆一个对标账号存下一个系列教程需求确认后剩下所有设计都围绕一个词排队。二. 为什么手动点不动登录态、翻页与风控 手动点 60 次下载累的不是手是三个机器本来能消化、但人肉处理不了的环节。**登录态。**人在浏览器里是登录状态脚本不是。这里我做过一次权衡要不要引导用户把账号授权给工具去模拟登录结论是不做。批量采集的正当场景是公开内容与自有素材备份不该依赖他人账号的私密会话工具只处理匿名状态下的公开分页账号相关操作留给用户自己。少碰一点凭证就少一分信任成本。**翻页。**刷过抖音或快手主页的人都知道列表是无限滚动的一次只加载二十来条剩下的靠滚动触发。手动下载的本质是人肉当翻页器。队列化的做法是把枚举和下载拆成两个阶段先沿公开分页把视频 ID 清单完整拉下来再逐条展开成下载任务。**风控节流。**人点按钮的频率是随机、缓慢、带停顿的脚本是恒定频率的一眼假。所以工程上我们不追求最快只追求最像一个有节制的人在浏览请求间隔加随机抖动量大自然慢。思考 慢一点没关系反正挂机跑 不止是体验问题。节流是这套系统里少数合规与稳定性双赢的设计把频率压到人工浏览的量级既降低被风控标记的概率也让失败率显著下降。后来限流事故证明这条是整套设计里最值得守住的纪律。三. 任务队列设计排队-解析-下载-重试的状态机 队列的骨架是一张状态机。六态queued排队→parsing解析→downloading下载→done完成外加retrying退避待重试与failed终态可人工重试。领取槽位 解析出播放地址 流式落盘 [queued] ──────────────▶ [parsing] ────────────▶ [downloading] ────────────┐ ▲ │ 解析失败 │ 网络中断 │ 全部字节到齐 │ ▼ ▼ ▼ │ ┌──────────────────────────────────────┐ [done] │ │ [retrying] 指数退避抖动 │ │ 元数据落库 │ └──────────────────────────────────────┘ ▼ │ │ 重试次数 N → 回到 parsing/downloading (自动入库见第五节) │ 人工重试任意终态可回│ 达到上限 ▼ └───────────────────────────────▶ [failed] ◀── 确定性失败内容非公开/地址已失效# 状态机骨架状态迁移只能由调度器驱动UI、计费、入库都只是状态的投影ALLOWED{QUEUED:{PARSING,FAILED},DOWNLOADING:{DONE,RETRYING},RETRYING:{PARSING,DOWNLOADING,FAILED},DONE:{ARCHIVED},# 自动入库是 done 的延伸态FAILED:{QUEUED},# 只能人工触发回队永不自动迁移# ...}deftransition(task,event):targetclassify(task,event)# 限流/超时暂时失败404/非公开确定性失败iftargetnotinALLOWED[task.state]:raiseIllegalTransition(task)# 脏迁移直接拒绝坏不了账本task.statetarget audit_log.append(task.id,target)# 每次迁移落本地日志崩溃恢复 扣费证据链两个设计决策事后看最关键其一延迟解析——播放地址有时效签名任务在队列里排久了会 403所以解析动作推迟到领取槽位的那一刻做其二classify 先于 retry——重试的前提是分得清暂时和彻底否则就是把用户往限流枪口上再推一把。四. 进度可视化为什么每条任务都要能单独重试 队列跑起来之后用户对确定性的需求远大于对速度的需求。批量任务天然参差两百条里有 195 条顺利完成、5 条卡在解析——如果界面只有一个总进度圈转用户只能全部重跑而这恰恰是风险最高的操作。所以列表里每一条任务都是独立个体各自的实时进度、各自的失败原因文案被限流稍后自动重试和内容不可见需你确认是两种语气、各自的重试按钮。配合按次付费的下载失败不扣次数规则重试对用户是零成本的对系统则是把判断失败的权力还给用户。思考 失败原因要不要把技术细节全摊开 摊一半。原始错误码用户看不懂纯安慰话术又不可信。我们的折中是人话结论 可展开详情第一行永远是下一步该做什么详情留给愿意深究的人。五. 自动入库下载完成只是开始 done不是终点。一条存完就躺在下载文件夹里改名为video(23).mp4的素材和没下载没有区别。所以状态机里 done 后面还有一个archived字节落盘后立即写元数据来源平台、原链接、发布日期、时长按平台/类型建索引去重校验进本地素材库。这一步背后是一句我们内部反复说的话下载是动作素材库才是资产。影栈是面向创作者的素材库产品——短视频素材资产管理平台。它把抖音、B站、小红书、快手等平台获取的图文、视频、音频素材统一管理起来智能集合筛选、项目工作区、素材对比同步播放、一键拖入剪辑软件让创作者的每一次收藏都变成可复用的资产。这条采集队列与素材库已经做进了影栈桌面客户端macOS / Win10/11客户端页https://yc.codexaiplus.com/client 目前公测预约中、安装包尚未公开公测期功能免费素材文件始终保存在用户本地目录库丢了可以重建文件永远是你自己的。六. 踩过的坑并发被限流、分页参数突变 ️坑一并发开太大被平台教做人。第一版我理直气壮开了 6 个并发下载觉得这是对技术的尊重。跑 80 条任务前 30 条生龙活虎之后整条链路的请求开始成批 403——不是单任务失败是枚举接口也开始返回空列表。那一刻我才明白风控不针对某个请求针对的是这个来源的整体行为速率。修复方案没有魔法信号量把同时在途的任务压到 2退避加抖动宁可慢不可炸。MAX_INFLIGHT2# 实测下来的最温柔并发再高就开始被标记slotasyncio.Semaphore(MAX_INFLIGHT)asyncdefrun(task):asyncwithslot:# 并发闸同一时刻最多两个任务在下载forattemptinrange(task.MAX_RETRY):try:awaitstream_to_disk(task)# 流式写盘长视频内存占用恒定returntransition(task,DONE)except(RateLimited,Timeout):# 暂时性失败指数退避 随机抖动awaitasyncio.sleep(min(BASE*2**attempt,CAP)random()*JITTER)transition(task,RETRYING)except(Gone,NotPublic):# 确定性失败立刻终态绝不重试骚扰returntransition(task,FAILED)returntransition(task,FAILED)# 达到上限 → 交还人工决策坑二合集分页参数突变。某天起快手某类合集的枚举开始大量重复入库查下来是分页游标结构变了旧参数被接口宽容地忽略于是每一页都返回第一页。教训枚举阶段必须以视频 ID 去重为准绳而不是信任分页参数本身并且快照式先固定清单再建任务防止枚举中途列表变化导致错位。思考 被限流了为什么不换个伪装头硬冲 因为那是把工具往灰产的方向推。被限流说明当前行为已经越过了平台认为舒适的边界正确响应是减速、退避、把失败诚实地告诉用户而不是军备竞赛。我们宁可接受今天这 5 条没下成也不透支这个工具明天还能不能用。七. 边界与合规只做公开内容仅供个人学习 ⚖️最后是把贯穿全文的边界写成白纸黑字这也是产品设计的一部分不是免责声明的点缀只处理公开内容需要登录可见、粉丝专属、会员专属、已删除或权限变更的视频枚举阶段就不会进入任务清单确定性失败直接终态用途限定仅供个人学习与自有素材备份比如存自己账号的历史作品不鼓励任何形式的搬运、二次分发与商用频率自律并发与节流策略本身就是一种态度——我们对平台的姿势是安静路过的访客不是强攻本地为王素材存在用户自己的磁盘上我们不做服务端中转存储数据不离开用户的设备。做采集类功能边界画得越清楚产品能走的路越长。常见问题 FAQQ1一个主页的视频能全部批量下载下来吗不能保证全部。只有匿名状态下公开可见、且分页接口能枚举到的内容会进任务队列被作者删除、设为权限可见、或平台限制公开展示条目的拿不到也不该拿。Q2合集下载和整主页批量有什么区别主页是账号维度的枚举合集是专题维度的枚举一个账号可以有多个合集。两者共用同一条任务队列区别只在枚举来源合集的分页游标更容易变化见第六节踩坑。Q3采集抖音、快手会不会很容易触发风控取决于行为速率。任务队列默认低并发、带随机抖动、失败指数退避把请求节奏压到接近人工浏览的量级被限流的任务会自动转入重试等待不会持续骚扰平台。Q4某条任务失败了会白扣次数吗不会。按次付费的规则里写死了下载失败不扣次数计费动作后置到任务到达完成状态之后失败任务可以随时单独重试。八. 写在最后 写完这套队列我偶尔还会想起那 60 多次点击。做工具的人有种很朴素的执念看见重复动作就难受看见人肉调度器就手痒非要把人从机械劳动里换出去不可。但这个项目教给我的另一件事是克制——技术的边界不在能不能在该不该只做公开内容只服务个人学习与自有备份慢一点、安静一点。把力气花在秩序上而不是花在越界上这大概就是我们理解的把收藏变成资产。关于影栈影栈桌面客户端正在迭代文中这套采集队列与本地素材库macOS / Win10/11公测预约中安装包未公开公测期功能免费官网 https://yc.codexaiplus.com/client 。每一次收藏都算数。参考文献[1] Node.js 官方文档. “Stream.” nodejs.org. https://nodejs.org/api/stream.html[2] Bull 项目. “A job queue for Node.js backed by Redis.” GitHub. https://github.com/OptimalBits/bull[3] yt-dlp 项目. “A feature-rich command-line audio/video downloader.” GitHub. https://github.com/yt-dlp/yt-dlp[4] AWS Architecture Blog. “Exponential Backoff and Jitter.” aws.amazon.com. https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/