资讯详情

资讯详情

douyin-downloader 源码走读:单条链接到批量落盘的下载流水线,4 个核心文件讲清楚

douyin-downloader 源码走读单条链接到批量落盘的下载流水线4 个核心文件讲清楚【免费下载链接】douyin-downloaderA practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音批量下载工具去水印支持视频、图集、合集、音乐(原声)。项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloaderdouyin-downloader 是一个无水印的抖音下载命令行工具贴一条链接能下视频、图文、合集、音乐贴用户主页链接能整页批量拉取还能录直播。技术栈很轻——纯 Pythonaiohttp异步请求 aiosqlite去重 rich终端进度条YAML 配置一条命令跑。它解决的是批量把作品稳定存到磁盘这件事重复作品不重下、请求速率不封 IP、登录态失效能自动重登、翻页被风控有浏览器兜底。五分钟跑通装依赖、取 Cookie、下第一条视频环境准备两条命令加一个 CookiePython 3.8 即可核心依赖就是requirements.txt里那一串aiohttp、aiosqlite、rich、pyyaml、gmssl等不需要任何系统级服务git clone https://gitcode.com/GitHub_Trending/do/douyin-downloader cd douyin-downloader pip install -r requirements.txt # 弹出浏览器登录抖音回终端按 EnterCookie 自动写入配置 python -m tools.cookie_fetcher --config config.ymlCookie 是后续一切接口请求的门票tools/cookie_fetcher.py用 Playwright 起一个真实浏览器让你人工登录比手动从 DevTools 复制省事。最小配置六个字段就够仓库里没有预置config.yml复制config.example.yml后按最小集裁剪即可单视频场景只需要link: - https://www.douyin.com/video/7604129988555574538 path: ./Downloaded/ thread: 5 retry_times: 3 database: truelink接受任意可识别链接视频/图文/合集/音乐/用户主页/短链thread是并发数database: true会生成dy_downloader.db用于去重和历史查询。第一次成功看什么输出python run.py -c config.yml # 或者不写配置文件命令行直接追加链接 # python run.py -c config.yml -u https://www.douyin.com/video/7604129988555574538结束时终端会打印 Overall Summary 和Total / Success / Failed / Skipped四项计数Downloaded/下出现以作者名命名的目录里面是日期_标题_aweme_id.mp4。桌面端Douzy把同样的后端做成了可视化工作台图抖音下载链接工作台粘贴视频或主页链接即可开始进度实时可见源码深潜串起抖音下载主链路的 4 个文件cli/main.py一条 URL 怎么变成下载器入口链路是run.py→cli/main.py:main()→asyncio.run(main_async())。每个 URL 在download_url()里走同一条流水线短链先解出真实地址URLParser.parse()分类工厂按类型造下载器执行后把汇总写进download_history表。# cli/main.py — 主调度短链解析 → URL 分类 → 工厂建下载器 → 执行 async with DouyinAPIClient( cookie_manager.get_cookies(), proxyconfig.get(proxy), ) as api_client: if is_short_url(url): resolved_url await api_client.resolve_short_url(normalize_short_url(url)) if resolved_url: url resolved_url parsed URLParser.parse(url) # ... 能力门禁部分类型解析得出来但没有下载器提前拦下 ... downloader DownloaderFactory.create( parsed[type], config, api_client, file_manager, cookie_manager, database, rate_limiter, retry_handler, queue_manager, progress_reporterprogress_reporter, ) result await downloader.download(parsed)DownloaderFactorycore/downloader_factory.py把 URL 类型字符串映射到VideoDownloader/UserDownloader/MixDownloader/MusicDownloader等具体类所有下载器共享同一套构造签名——这样新增一种链接类型时只需要在core/url_parser.py加一个正则分支和工厂里注册一行主流程零改动。URLParser.parse()本身就是一张正则分类表/video/(\d)、/user/([A-Za-z0-9_-])、/collection/(\d)分别抽出aweme_id、sec_uid、mix_id分类结果决定后面的一切。core/downloader_base.py去重先问磁盘再问数据库所有下载器继承BaseDownloader去重决策集中在_should_download()# core/downloader_base.py — 去重判定磁盘文件是主判据数据库只是兜底 await self._ensure_local_aweme_index() if self._is_locally_downloaded(aweme_id): return False if self._redownload_missing_files_enabled() or self.database is None: return True if await self.database.is_downloaded(aweme_id): return False return True本地索引不是凭空来的首次运行时_build_local_aweme_index()用rglob扫整个下载目录从文件名里用 15~20 位数字的正则抽出aweme_id跳过_cover/_music等附属文件和 0 字节文件。为什么磁盘优先因为数据库可能被误删或损坏而磁盘上确实有这个文件是最不会说谎的证据数据库查不到时宁可补下也不能把作品永久误判为已下过。索引构建是同步重活所以放在asyncio.to_thread里跑避免大库扫描冻住事件循环。core/downloader_base.py多镜像轮转和 15 分钟总时限同一个视频的下载地址通常有多个 CDN 镜像play_addr的url_list下载逻辑是每轮按序每个候选试一次整轮失败才退避重试# core/downloader_base.py — 单视频多镜像轮转 兜底总时限 _VIDEO_ITEM_DEADLINE_S 900 async def _download_video_with_fallback( self, candidates, save_path, session, *, aweme_idNone, ) - bool: async def _attempt_round() - bool: for url, headers in candidates: if await self._download_with_retry( url, save_path, session, headersheaders, optionalTrue, retryFalse, on_progresson_progress, ): return True raise RuntimeError(fAll {len(candidates)} video url candidate(s) failed) return await self._run_within_item_deadline( self.retry_handler.execute_with_retry(_attempt_round), save_path )注释里写得很直白play 端点失败多是 302 落到 PCDN 死节点重试同一 URL 有意义直连地址 403/过期则应换下一个候选。两种失败模式用按轮扫 RetryHandler 退避同时覆盖。而没有_VIDEO_ITEM_DEADLINE_S这道 15 分钟总闸重试轮数 × 候选数 × 单次超时最坏能把一条视频拖到 80 分钟整条队列跟着停摆。镜像多时镜像列表本身即重试_download_first_available里use_backoff len(urls) 1避免在已知 403 的死镜像上叠加多轮退避。control/rate_limiter.py抖动为什么必须在锁内RateLimiter是最小间隔 随机抖动的极简实现retry_handler.py配 1s/2s/5s 固定退避二者是所有 API 调用前的两道闸# control/rate_limiter.py — 最小间隔 随机抖动 async def acquire(self): async with self._lock: current time.time() time_since_last current - self.last_request if time_since_last self.min_interval: wait_time self.min_interval - time_since_last await asyncio.sleep(wait_time) await asyncio.sleep(random.uniform(0, 0.5)) self.last_request time.time()默认 2 请求/秒抖动 0~0.5s 在锁内执行是关键锁内睡眠保证下一个协程从本次实际发出时刻起算间隔而不是从抢到锁时刻起算否则 N 个协程排队时真实速率会远超配置值。RetryHandler的延迟表是[1, 2, 5]超过长度后一直用 5smax_retries指首次尝试之外的重试次数。存储侧storage/database.py的aweme表aweme_id TEXT UNIQUE去重建表时顺手开了PRAGMA journal_modeWAL让下载写入和历史读取能并发SQLite 在这里只记历史与增量判定不参与要不要下的主判断。进阶调优批量下载线程数、增量与画质配置thread / rate_limit / retry_times三个数字先记牢thread默认 5下载 worker 池大小见control/queue_manager.py建议 3~8再大不是更快而是更容易触发接口风控。rate_limit默认 2API 请求上限次/秒全量抓主页时别超过 3配合上面的抖动已是相当保守的画像。retry_times默认 3单文件失败退避重试次数媒体下载基本够用了失败大头是镜像死节点那部分靠多候选轮转兜。下载落盘结构folderstyle: true且按模式分组时group_by_mode: true文件按作者/模式/日期_标题_aweme_id三层组织download_manifest.jsonl每行一条作品记录Downloaded/ ├── download_manifest.jsonl └── 作者名/ ├── post/ │ └── 2024-02-07_作品标题_aweme_id/ │ ├── ....mp4 │ ├── ..._cover.jpg │ ├── ..._music.mp3 │ └── ..._data.json ├── like/ ├── mix/ └── music/文件名日期取作品发布时间create_time而非下载时间缺失才回退当天——这样按时间翻档案是准的。增量下载、原画与浏览器兜底increase: post: true # 只下库里没有的新作品依赖 database: true number: post: 0 # 0 不限全量 video_quality: original # 探测上传原片失败退回最高转码档 browser_fallback: enabled: true headless: false # 必须非无头翻页风控后要人工过验证码REST 服务模式--serve --serve-port 8000把同一套后端暴露成/api/v1/download接口job 按 TTL 容量自动剪裁适合挂进自动化流水线图抖音下载任务中心按作品查看任务结果、重试失败项踩坑手册批量下载的高频问题与改法只能抓到 20 条作品翻页不再前进现象主页批量任务停在第 20 条附近日志没有报错。原因接口翻页风控后续页返回重复或空数据。解法确认browser_fallback.enabled: true且headless: false浏览器弹出后人工过掉滑块再让它继续注意兜底目前只对post模式完整验证过like/mix/music主要靠 API 正常分页。任务中途报登录态失效需要重新登录现象批量跑到一半抛出 LoginRequiredError当前 URL 被判失败。原因Cookie/登录态过期API 返回未登录状态码。解法交互环境下cli/main.py:_run_with_relogin()会自动弹浏览器登录一次并重试手动场景直接重跑python -m tools.cookie_fetcher --config config.yml刷新 Cookie。重跑任务想强制重下作品全被 skip现象删了部分文件重跑跳过计数照旧媒体没补回来。原因磁盘文件 数据库双通道去重只删一半不生效。解法两条都删单作品rm -rf Downloaded/作者名/post/*_aweme_id/ sqlite3 dy_downloader.db DELETE FROM aweme WHERE aweme_id aweme_id;下载的 mp4 播放花屏无声文件还不见了现象进度条跑满、时长正常播放器里花屏随后文件被删。原因付费作品下发的是 CENC 加密流_discard_if_encrypted()检测到加密盒会主动删掉并判失败避免留一个放不了的假成功。解法这类内容接口不提供解密密钥下载参数无解换非付费作品或确认账号对该内容有授权。磁盘文件为主判据、数据库只做兜底的双通道去重是这套代码里最值得搬走的实践磁盘状态不会说谎库不确定时宁可补下也不误杀。想自研长跑型抓取工具优先通读core/downloader_base.py。【免费下载链接】douyin-downloaderA practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音批量下载工具去水印支持视频、图集、合集、音乐(原声)。项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →