news 2026/10/13 6:48:38

gdown 浏览器 Cookie 提取方案剖析:以符号级 Vendor 方式引入 yt-dlp 的 Cookie 模块

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gdown 浏览器 Cookie 提取方案剖析:以符号级 Vendor 方式引入 yt-dlp 的 Cookie 模块
  • CLI
  • 开发工具

【免费下载链接】gdown

Google Drive public file downloader when curl/wget fails.

项目地址:https://gitcode.com/gh_mirrors/gd/gdown
点击查看免费下载

导读

Google Drive 会对访问量过大的公共文件触发“Too many users have viewed or downloaded this file recently”限流,但对已登录账户却照常放行。gdown 通过一项名为「符号级 Vendor(symbol-level vendoring)」的架构决策,把 yt-dlp 的浏览器 Cookie 提取模块以最小代价引入项目:既没有新增运行时依赖,也让gdown --cookies-from-browser firefox URL开箱即用。本文基于仓库中的架构决策记录 docs/adr/0002-vendor-ytdlp-cookie-extraction.md,结合gdown/_vendor/下的生成代码与手写 shim、CLI 入口和测试用例,讲清这条决策从“为什么”到“怎么用”再到“如何维护”的完整链路。

背景:Drive 限流与“登录账户可下载”的矛盾

Google Drive 对热门的公共文件执行限流:当短时间内访问人数过多时,服务端会拒绝匿名下载。ADR 记录于 2026-09-04 实测验证:一个被限流的 Newer College rosbag 文件,不带 Cookie 时下载失败,而换上已登录浏览器的 Google Cookie 后即可正常下载。

在此之前,gdown 其实已经支持 Cookie,但使用门槛很高:用户必须先用浏览器扩展把 Cookie 导出成 Netscape 格式的cookies.txt,再手动放进缓存目录。yt-dlp 与 gallery-dl 早已确立了更顺手的 CLI 约定——--cookies FILE指定文件、--cookies-from-browser BROWSER直接从浏览器读取——而 gdown 的诉求是uvx gdown必须无需任何额外步骤就能工作。

从技术实现看,读取浏览器 Cookie 库并不轻松:要碰 SQLite 数据库、按平台取密钥(Linux 的 GNOME Keyring / KWallet、macOS 的 Keychain、Windows 的 DPAPI)、再走 AES 解密。浏览器提取是 gdown 中唯一一个纯 CLI 场景的功能,而 gdown 的大部分安装形态是把 gdown 当库使用,因此为它引入新的运行时依赖很难有正当理由。

备选方案评估:为什么最终选择 Vendor

ADR 逐一推演了四条替代路线并给出否决理由:

方案评估结论否决原因
依赖 browser-cookie3否决LGPL 许可证;自带 3~6 个传递依赖(其中 2 个是 C 扩展);上游自 2024 年 12 月起基本停更。每个库的使用者都要为一个 CLI 功能买单,且 LGPL 代码无法在日后想卸掉依赖时复制进 MIT 项目
直接依赖 yt-dlp否决为了一个函数引入约 3 MB 的依赖,代价不成比例
从 gallery-dl 复制代码否决gallery-dl 是 GPL-2 许可证,不能并入 MIT 项目
从 yt-dlp Vendor采用cookies.py与纯 Python 的aes.py以 Unlicense(公有领域)发布,仅需标准库加系统工具,且上游持续维护
作为可选 extra 发布否决extras 只会“加依赖”,无法表达精简的gdown[core],uvx gdown也拿不到该功能

另有一个所有方案都无法绕开的硬约束:Google 的 App-Bound Encryption(应用绑定加密)使得没有任何方案能在 Windows 上无管理员权限读取 Chrome 127+ 的 Cookie;而 Firefox 在所有平台上都可以读取。这个约束直接决定了后面的平台策略。

决策:符号级 Vendor 与一键再生成脚本

最终决策是:对 yt-dlp 的 Cookie 模块做符号级提取(symbol-level extraction)。仓库中的 scripts/vendor_ytdlp_cookies.py 是这个流程的执行者,其工作机制清晰可见:

  • 锁定一个 yt-dlp 发布 tag(脚本默认值TAG = "2026.08.19");
  • 从 yt-dlp 仓库在线下载yt_dlp/cookies.py与yt_dlp/aes.py两个模块;
  • 以extract_cookies_from_browser和SUPPORTED_BROWSERS为根(ROOTS),用ast遍历其顶层定义引用的闭包,只保留可达的符号;
  • 把保留的源码连同必要的标准库 import 写进唯一一个生成文件 gdown/_vendor/_ytdlp_cookies.py。

该生成文件以头注释明示来源与纪律:# Generated by scripts/vendor_ytdlp_cookies.py from yt-dlp 2026.08.19.、# Do not edit; rerun the script to upgrade.、# yt-dlp is public domain (Unlicense).,并带有# ruff: noqa、# fmt: off,从工具链上杜绝手工改动。升级流程就是换一个更新的 tag 重跑脚本,例如:

uv run python scripts/vendor_ytdlp_cookies.py 2026.09.01

脚本在写入后会做一次自检:比对 shim 中是否缺少提取代码引用的名字,若缺则报错并提示需要扩展 shim(见下文)。

生成文件的头部可以看到完整的浏览器清单与入口函数:

CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi', 'whale'} SUPPORTED_BROWSERS = CHROMIUM_BASED_BROWSERS | {'firefox', 'safari'} def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger(), *, keyring=None, container=None): if browser_name == 'firefox': return _extract_firefox_cookies(profile, container, logger) elif browser_name == 'safari': return _extract_safari_cookies(profile, logger) elif browser_name in CHROMIUM_BASED_BROWSERS: return _extract_chrome_cookies(browser_name, profile, keyring, logger) else: raise ValueError(f'unknown browser: {browser_name}')

手写 shim:把“纯复制”与“手工维护”分开

提取出的 yt-dlp 代码会 import 十几个 yt-dlp 内部符号(它的 logger、进度条打印机、subprocess 包装器,以及若干小的字符串辅助函数)。这些符号放在紧邻生成文件的手写 shimgdown/_vendor/_ytdlp_shim.py 中,实现两个目标:

  1. 生成文件保持纯复制:它本身绝不掺入手写代码,升级时可整文件替换;
  2. 手写面保持最小:shim 全文约一百余行,且文件头部明确写着“每个名字的调用形态尽量贴近上游,仅供提取代码使用,gdown 自身不直接使用”。

shim 提供的替代实现包括:_YDLLogger(debug/info/warning/error 四种日志通道)、MultilinePrinter与QuietMultilinePrinter(进度输出)、Popen(基于subprocess.Popen的run类方法,统一 stdout/stderr 与超时语义)、Cryptodome(AES = None,让提取的 AES 代码在没有 pycryptodomex 时走纯 Python 路径)、以及compat_ord、error_to_str、is_path_like、str_or_none、try_call、write_string、normalize_url、sanitize_url等辅助函数。

值得注意的一个设计细节:shim 中有一个模块级探测函数_probe_secretstorage(),它在 import 时尝试加载secretstorage模块,失败时记录原因字符串并置secretstorage = None、_SECRETSTORAGE_UNAVAILABLE_REASON。这使提取的 Chromium 解密代码在 GNOME Keyring 缺失时能给出明确诊断,而不是神秘报错。

CLI 集成:一次复制、之后免 flag

两个命令行参数沿用了 yt-dlp 的命名,定义于 gdown/main.py:

  • --cookies FILE:读写 Netscape 格式 Cookie 文件,默认~/.cache/gdown/cookies.txt;
  • --cookies-from-browser BROWSER:从指定浏览器复制 Google Cookie 到 Cookie 文件,choices直接取自生成文件的SUPPORTED_BROWSERS(在BROWSERS常量中排序导出);
  • --no-cookies:完全不读不写 Cookie,且与上述两个参数互斥(解析期即报错)。

CLI 的核心流程是:下载开始前只做一次浏览器 Cookie 导入,之后库层始终保持单一的 Cookie 来源(同一个 Cookie 文件),后续运行无需再带任何参数。调用链位于 gdown/download.py 的_import_cookies_from_browser():

  1. 函数内部延迟导入vendor 模块——只有真的用到浏览器提取时才加载,保证库用户零成本;
  2. 调用extract_cookies_from_browser(browser, logger=logger)提取全部 Cookie,然后只保留google.com及.google.com域的条目;
  3. 通过专门的_CookieExtractionLogger收集 keyring 与解密告警;一旦记录到 error,即使有“看起来解密成功”的 Cookie 也整体放弃(防止空口令回退把坏数据写进文件),抛DownloadError;
  4. 对 Chromium 家族做过期时间换算:Chromium 以 1601 年起算的微秒存储到期时间,需要expires // 1_000_000 - 11_644_473_600转成 Unix 时间戳,避免把已过期 Cookie 存成“远未来”;
  5. 合并写入 Cookie 文件并返回导入数量。

__main__.py中的处理逻辑(gdown/main.py)随后给出反馈:成功时打印Saved N Google cookies from <browser> to <file>;找不到任何 Google Cookie 时提示“先在浏览器中登录 Google,或改用其它浏览器”并退出码 1;导入失败时打印错误详情,若在 Windows 且浏览器不是 Firefox,还会追加提示:

Chrome 127+ on Windows blocks other programs from reading its cookies. Try --cookies-from-browser firefox.

一条完整可用的命令如下(README 中的示例见 README.md):

# 以已登录账号身份下载,直接复用浏览器的 Cookie gdown https://drive.google.com/uc?id=FILE_ID --cookies-from-browser firefox # 用独立文件保存/读取 Cookie,覆盖默认路径 gdown https://drive.google.com/uc?id=FILE_ID --cookies ./cookies.txt # 完全禁用 Cookie gdown https://drive.google.com/uc?id=FILE_ID --no-cookies

Cookie 文件的生命周期:会话 Cookie 与权限安全

“Cookie 文件里存着整个 google.com 会话(含 Gmail 与账户信息)”是必须严肃对待的事实。代码层面有两处对应的硬措施(gdown/download.py):

  • _load_cookies()以ignore_discard=True, ignore_expires=True加载,因为Google 的登录状态有一部分活在会话 Cookie 里,而 Netscape 格式默认会丢弃它们;加载后把expires == 0的会话 Cookie 改为不过期(标准库否则会视为已过期),再统一清理真正过期的条目;
  • _save_cookies()在写入任何字节之前就用mode=0o600创建文件并os.chmod(..., 0o600),确保只有当前用户可读。README 明确要求把这个文件当成密码对待,需要吊销时就删除该文件并在浏览器中退出 Google 登录。

测试用例印证了这两点:tests/test_download.py 中覆盖了「多次导入不重复、合并保留已有 Cookie」「导入后文件权限为 0o600」「keyring 失败时保留原有的好会话、绝不写入坏数据」「Chromium 过期时间换算」「扩展导出 Cookie 的会话保留」等场景。

GNOME Keyring:用 optional extra 补最后一公里

Linux 上 Chromium 系浏览器的 Cookie 密钥存放在系统钥匙串中,yt-dlp 的惯例是通过secretstorage这个 Python 包访问 GNOME Keyring。gdown 遵循同样的 optional-extra 约定(pyproject.toml):

[project.optional-dependencies] secretstorage = ["secretstorage"]

主依赖列表保持原样(beautifulsoup4、filelock、requests[socks]、tqdm等,见 pyproject.toml),没有为 CLI 功能新增任何运行时依赖。安装方式:

pip install 'gdown[secretstorage]' # 或在不污染环境的情况下临时使用 uvx --from 'gdown[secretstorage]' gdown --cookies-from-browser chrome URL

Firefox 在 Linux 上不需要任何额外依赖(其 Cookie 数据库本身未加密),这也是建议优先使用 Firefox 的原因之一。若secretstorage缺失,shim 中的探测会生成类似“as thesecretstoragemodule is not installed. Install withpip install 'gdown[secretstorage]'”的提示,直接告诉用户该装什么。

平台限制速查

平台可用性
所有平台Firefox 均可读取,无需额外依赖
macOSChromium 系首次会请求一次登录密码以解锁 Keychain;Safari 需要为终端授予“完全磁盘访问权限”(Full Disk Access)
LinuxChromium 系若使用 GNOME Keyring 需gdown[secretstorage];KWallet 走kwallet-query(经 dbus)
WindowsChrome 127+ 因 App-Bound Encryption 无法被其他程序读取,官方提示改用 Firefox

后果与维护契约

这条 ADR 决策写下了一组可以验证的后果:

  1. uvx gdown --cookies-from-browser firefox URL无额外依赖、无额外配置即可工作;
  2. 约 1600 行生成代码常驻仓库(当前生成文件实际约 1650 行),被ruff: noqa/fmt: off排除在 lint 与类型检查之外,且只在重跑脚本时变化;
  3. 上游一旦重命名内部符号,两处会同时“大声失败”:一是脚本结束时的 shim 缺失名报告,二是生成模块的 import 错误——这正是扩展 shim 的信号;
  4. 支持浏览器清单与锁定的 yt-dlp 发布版保持一致(升级 tag 即随上游更新);
  5. 浏览器清单由SUPPORTED_BROWSERS一处定义,CLI 的choices与测试中的断言(如 tests/test___main__.py 的test_browser_choices_match_vendored_extractor)都指向同一个来源,防止两者漂移;
  6. Windows Chrome 用户得到的是指向 Firefox 的明确错误提示,而不是神秘的解密失败。

整体来看,这一设计把“功能完整”与“依赖克制”同时做到了:提取代码是上游公有领域源码的机械副本,可审计、可整体升级;手写 shim 是唯一需要人工维护的薄接口;CLI 与库层通过 Cookie 文件解耦,库用户与uvx用户各取所需。如果你想深入了解实现细节,可以从 scripts/vendor_ytdlp_cookies.py(生成器)、gdown/_vendor/_ytdlp_shim.py(shim)、gdown/download.py(导入与存取逻辑)和 tests/test_download.py(行为契约)四条线索继续深入。

  • CLI
  • 开发工具

【免费下载链接】gdown

Google Drive public file downloader when curl/wget fails.

项目地址:https://gitcode.com/gh_mirrors/gd/gdown
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

共享单车大数据毕业设计:从Hive到可视化看板全流程实践

共享单车这个题目&#xff0c;在毕业设计里算是长盛不衰的经典选题。每年都有大量学生选它&#xff0c;但坦率讲&#xff0c;真正能把这个题目做出彩的并不多。大多数人的做法是&#xff1a;拉一份数据&#xff0c;用Pandas跑几个统计图&#xff0c;再加个地图撒点&#xff0c;…

作者头像 李华
网站建设 2026/10/13 6:45:46

5G+智慧文旅落地实操:专网切片、边缘GPU与客流融合避坑指南

简介&#xff1a;本资源是一份聚焦5G技术与文旅产业深度融合的行业应用型课件&#xff0c;面向通信、文旅、政企解决方案等领域的从业者、高校师生及数字化转型研究者&#xff0c;旨在系统解析5G如何破解出行难、停车难、入园难、赏景难、如厕难等文旅服务痛点&#xff0c;并支…

作者头像 李华
网站建设 2026/10/13 6:45:45

27. 数据产品- BI进阶 - AI 应用实战终篇-从 RAG 架构到企业级平台落地

文章目录前言开篇总结&#xff08;总述&#xff09;第一节 多模型兼容配置&#xff1a;云AI API KEY配置调用 本地化部署模型调用双模式第二节 批量导入管理&#xff1a;文件上传、解析与切片全流程第三节 元数据管理&#xff1a;结构化 非结构化双体系管控第四节 企业智能问…

作者头像 李华
网站建设 2026/10/13 6:44:20

编程智能体三件套:管钱、管事、管时间的插件实践

1. 为什么想起来给编程智能体配三件套先说背景。我大概是今年年初开始重度使用 DeepSeek 的编程智能体做日常开发的&#xff0c;干了几周之后&#xff0c;整体感觉是&#xff1a;能力够用&#xff0c;但有两个很要命的问题。第一是成本感缺失。智能体跑起来之后&#xff0c;我经…

作者头像 李华
网站建设 2026/10/13 6:43:57

时序网络瓦解Ⅱ

一、研究背景 【注意时序网络需要结合最大连通分量、时间可达性、时序巨分量等来反应网络】 总结: 时序网络的定义:节点与连边会随时间变化而变化 时序网络注意事项:需要考虑什么时间与什么节点连接,连接多久。 时许网络瓦解注意事项:需要考虑在什么时间与什么节点断开可以…

作者头像 李华