Design & media

国内网络环境下用 yt-dlp 下载视频

Try it

在中国大陆网络环境下用 yt-dlp 下载视频(含 Windows GUI 封装)。当用户要下载 YouTube、Bilibili、SoundCloud 等 yt-dlp 支持的站点内容,或遇到「yt-dlp 装不上 / 下载报 502 / Tunnel connection failed / ffmpeg not found / 缺 JS runtime / TLS fingerprint / watch?v=&list= 把整个列表都下了 / Sign in to confirm you're not a bot / The page needs to be reloaded / 下到一半突然下不动 / 下回来的视频糊、有拖影残影 / 下载速度特别慢」等问题时使用。

What it does

国内环境下 yt-dlp 有**七个必踩的坑**,按顺序处理即可一次跑通。 一~四是"跑不起来",五~六是"跑起来了但画质糊/速度慢",七是必备参数。

The skill document

国内环境 yt-dlp 下载工作流

国内环境下 yt-dlp 有七个必踩的坑,按顺序处理即可一次跑通。 一~四是"跑不起来",五~六是"跑起来了但画质糊/速度慢",七是必备参数。

一、安装 yt-dlp:不要走 GitHub

GitHub Release 资源域名 objects.githubusercontent.com 在国内不通, https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe 会直接超时(curl 返回 000)。

# 正确做法:走 pypi 镜像
python -m pip install -U yt-dlp -i https://pypi.tuna.tsinghua.edu.cn/simple
# 调用方式(Scripts 目录常不在 PATH 上)
python -m yt_dlp --version

二、找出口:先探测,别假设

不要假设用户有 Clash(7890)。穷举探测 + 记录实际端口:

import socket
for port in (10808, 1080, 1081, 7897, 7891, 20170, 20171):
    with socket.socket() as s:
        s.settimeout(0.2)
        if s.connect_ex(("127.0.0.1", port)) == 0:
            print("socks5://127.0.0.1:%d" % port)
  • Shadowsocks / ssx-win:本地端口是 SOCKS5(常见 10808)。配置在安装目录的 gui-config.json 的 localPort 字段(注意 shareOverLan)。
  • Clash / mihomo:7890 是 HTTP 代理,不是 SOCKS5,要写 http://。
  • 验证出口:curl -x socks5h://127.0.0.1:10808 https://api.ipify.org(socks5h 让 DNS 也走代理)。

关键陷阱:如果环境里已经存在 http_proxy / https_proxy(例如某些 IDE 注入的服务代理), yt-dlp 在没有显式 --proxy 时会继承它,表现是莫名的 Tunnel connection failed: 502 Bad Gateway。 显式传 --proxy 后必须清掉子进程的代理环境变量:

def child_env(proxy):
    env = dict(os.environ)
    env["PYTHONIOENCODING"] = "utf-8"
    env["PYTHONUTF8"] = "1"
    if proxy:
        for k in ("http_proxy","https_proxy","all_proxy",
                  "HTTP_PROXY","HTTPS_PROXY","ALL_PROXY"):
            env.pop(k, None)
    return env

三、ffmpeg:必须实跑验证,不能只看文件存在

  • scoop 卸载不干净会留下空壳 shim:scoop\shims\ffmpeg.exe 还在 PATH 上,但 scoop\apps\ffmpeg\current\ 里只剩 LICENSE/README,没有 bin 目录,调用报 Could not create process。所以必须真跑 ffmpeg -version 判断。
  • pip install imageio-ffmpeg 可以救急,但它自带的二进制文件名带版本号 (ffmpeg-win-x86_64-v7.1.exe),yt-dlp 的 --ffmpeg-location 认不出 → 报 Postprocessing: ffmpeg not found。要复制一份改名为 ffmpeg.exe。
  • 推荐:代理通了之后直接从 gyan.dev 下完整包(含 ffprobe,yt-dlp 需要): https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip 用 Python zipfile 解压即可,不必装 7z。

四、JS runtime:新版 yt-dlp 必需

没有 JS runtime 会告警 YouTube extraction without a JS runtime has been deprecated, and some formats may be missing。

不必装 deno——现成的 node 就能顶上(默认只启用 deno,其他要显式指定):

--js-runtimes node:C:\path\to\node.exe

五、画质:高清只在 HLS 里,而默认选择器一定拿不到它

同一分辨率常常同时有 DASH(https / mp4_dash) 和 HLS(m3u8) 两套流,码率差 7~9 倍:

270 mp4 1920x1080 | ~5.30GiB  4370k  m3u8  | avc1.640028   <- 真高清,只在 HLS
137 mp4 1920x1080 |  595.52MiB 480k  https | avc1.640028   <- DASH,被 YouTube 阉割

⚠️ 2026 修正(重要):上面「DASH 被阉割、HLS 才是真高清」是过时误判。 实测拉 manifest 算 clen/dur 证明:无 PO Token 时,HLS 的 270 与 DASH 的 137 是同一转码流, 真实码率都只有 ~485 kbps(270 那个 4370k 是峰声明 tbr 虚高)。 也就是说,只切 HLS 不提升画质。真正的解锁条件是 PO Token(见第十五节): 装上 PO Token provider 后,-F 里 270 才会变成真·1080p@4107k(4.1 Mbps), 比无 token 的 485k 高约 8 倍。下面「显式偏好 m3u8」的修法依然成立且必要 (PO Token 解锁高码率档后,选择器必须能挑到 HLS 那路才不会退化回 DASH 低码率), 但它只是「必要条件」,PO Token 才是充分条件。

为什么默认选择器一定会挑错(本节最关键)

yt-dlp 默认排序字段为 quality → res → fps → hdr:12 → vcodec → channels → acodec → size → br → asr → proto。

HLS 的 270 与 DASH 的 137 在 quality/res/fps/vcodec 上全部打平,接着比 size:

fmtqualityresvcodecfilesize / filesize_approxtbrprotocol
270 (HLS)91080avc1.640028None / None4370km3u8_native
137 (DASH)9.01080avc1.640028624,445,061 / 624,443,846480khttps

HLS 是分片流,压根没有 filesize(两个字段都是 None),被判为劣于 DASH 的 624MB —— size 这一关就把高清淘汰了,后面的 br(4370k > 480k) 根本轮不到比; proto 排在最后,也救不回来。这就是"参数看着没问题、下回来却一直糊"的机制。

修法:选择器里显式偏好 m3u8,并逐级兜底

-f "bv*[height<=?1080][protocol^=m3u8]+ba[acodec^=mp4a]/bv*[height<=?1080][protocol^=m3u8]+ba/bv*[height<=?1080]+ba[acodec^=mp4a]/bv*[height<=?1080]+ba/b[height<=?1080]/b"
--merge-output-format mp4

同一视频实测对比(关键:是否带 PO Token 决定码率上限):

选择器选中无 PO Token有 PO Token(装好 provider 后)
旧:bestvideo[height<=?1080][vcodec^=avc1]+bestaudio[acodec^=mp4a]137+140~609 kbps(137 仅 ~480k)仍被限在同一低档
新:上面的 m3u8 偏好串270+140 / 270+234~614 kbps(270 实际仅 ~485k,tbr 4370k 是峰声明虚高)~4.1 Mbps(270=4107k,真高清)

结论:m3u8 偏好选择器让 yt-dlp「能挑到 HLS 那路」,但 HLS 那路本身有没有高码率, 取决于 PO Token。无 token 时挑到 270 也是 485k(和 DASH 137 一样糊);有 token 时 270 才是 4.1Mbps。

验证到底选中哪路(比抽帧更省事、可自动化):

python -m yt_dlp -f "<选择器>" --simulate \
  --print "%(format_id)s | %(resolution)s | %(tbr)s kbps | %(protocol)s" URL
# 期望 protocol 是 m3u8_native(HLS);若出现 https 就是退化到低码率了

音频仍建议用 DASH 的 ba[acodec^=mp4a](140, 129k)——HLS 那对音频 (233/234)码率未知、只有 "Default, low/high" 字样,不如它稳妥。 例外:纯屏幕录制 / 幻灯片课程可用 ffmpeg -ss 600 -i x.mp4 -frames:v 1 -y f.png 抽帧 人工确认后容忍低码率,但默认应取 HLS 高清。

六、HLS 下载慢的根因:分片在串行等 RTT(不是限速)

别把它当成 YouTube 限速或节点太差。用纯网络层基准(绕开 yt-dlp 提取)可直接证伪: 拿 master playlist → 选最高带宽变体 → 媒体 playlist → 分片地址,然后用不同并发拉同样的分片:

并发14816324864
MiB/s0.130.490.892.434.405.575.80(饱和)

吞吐随并发近乎线性增长直到饱和 ⇒ 带宽有余量,瓶颈是每个分片都要等一个 RTT (实测单片约 640KB 却要 ~1.7s,绝大部分是往返延迟)。

修法:加大 --concurrent-fragments,并显式锁定 native 下载器:

--concurrent-fragments 32 --downloader m3u8:native
  • 只有 native(HlsFD) 下载器支持并发分片;用 ffmpeg 拉 HLS 时 -N 无意义。
  • YouTube 的 HLS 协议就是 m3u8_native,默认下载器本来就是 native,但 -N 默认是 1。
  • native 走 yt-dlp 自带网络栈,支持 SOCKS5,不必再往子进程塞 http_proxy (ffmpeg 拉 HLS 才需要,所以锁定 native 反而更干净)。
  • 端到端实测:-N 32 达 5.19 MiB/s(进度显示 frag N/2380 即已走 HLS), 对比 -N 1/4 的 ~0.11–0.49 MiB/s,约 10~40 倍。
  • --sleep-requests 只影响提取阶段,不会拖慢分片下载(downloader/hls.py 里没有 sleep)。

测速小坑(受控沙箱里):curl -o 文件 可能恒为 0 字节、-w %{size_download} 也不可信 (写盘被拦)。测带宽要走 stdout 管道(curl -s URL | wc -c)或直接用 Python 读 pipe。 复用一次 -j 导出的格式 JSON(里面含已签名的 playlist 地址)做基准, 可完全不触发 YouTube 提取,既快又不会踩到 bot 风控。

七、其他必备参数

--download-archive /archive.txt   # 断点续传核心:重跑自动跳过已下完的
--newline                              # 进度逐行输出, 便于 GUI/日志解析
--windows-filenames --trim-filenames 120
--retries 10 --fragment-retries 10 --concurrent-fragments 32 --downloader m3u8:native --sleep-requests 1

八、Windows GUI 封装要点

  • Windows Python 官方安装包的 tkinter 在 C:\Python3xx\,而 uv/独立分发的 managed Python 通常没有 tkinter。若界面和后端在不同解释器,写一个 find_ytdlp_python(): 逐个候选用 python -c "import yt_dlp" 探测,取第一个成功的。
  • 用 pythonw.exe 启动可无控制台窗口(配 .bat 双击)。
  • 后台线程跑 subprocess.Popen,stdout=PIPE, stderr=STDOUT, text=True, encoding="utf-8", errors="replace", bufsize=1,主线程用 root.after(80, drain) 消费 queue.Queue。
  • 停止进程要 taskkill /F /T /PID(yt-dlp 会派生 ffmpeg,terminate() 杀不干净)。
  • 高频进度行用正则拦下喂进度条,别全塞进 Text 控件(会很卡):
    • Downloading item (\d+) of (\d+)
    • ^\[download\]\s+([\d.]+)% of\s+~?\s*([\d.]+)(\w+)
  • 探测 ffmpeg 时单次超时别给太长(6 秒足够):候选里有坏 shim 时,15 秒 × 若干候选会让 "环境自检"干等半分钟,界面一直停在"正在检测"。
  • 面向"支持所有站点"的界面应留三个逃生口:浏览器 Cookie 下拉(登录/会员内容)、 指纹伪装下拉(值从 --list-impersonate-targets 动态填)、附加参数输入框 (任意 yt-dlp 参数)。切分附加参数别用 shlex.split——它会把 Windows 路径的 \ 当转义符吃掉,用 re.findall(r'"([^"]*)"|(\S+)', text)。
  • 主线程 after 轮询的 id 要存下来,关窗口时 after_cancel,否则销毁后 Tcl 会吐 invalid command name ..._drain_queue。
  • argparse 的 nargs="?" 位置参数会被未知选项的值劫持(--playlist-items 5 里的 5 会被当成 URL)。透传参数统一用 -- 分隔后手工切分 sys.argv。

九、链接范围:watch?v=X&list=Y 是个大坑(实测结论)

同一个播放列表,URL 里有 list= 但主体是 watch?v= 时行为完全不同:

链接不加开关时 yt-dlp 的行为
watch?v=ID只下这一个
watch?v=ID&list=PL...&index=3下整个列表 10 个,而且完全忽略 index=3
playlist?list=PL...下整个列表
@channel/videos整批(playlist_index 从 1 开始)
ytsearch5:关键词前 5 条搜索结果

所以链接同时含 v= 和 list= 时必须显式表态:

--yes-playlist      # 要整列表
--no-playlist       # 只要 v= 指的那一个
--playlist-items 3   # 只下第 3 条; 也支持 1-3,6

反向实测:--no-playlist 对纯列表 / 频道链接无效(照样全下),所以可以无脑带上, 不会误伤——这让"范围开关"对任意链接都能统一处理。

命名模板要跟着范围走,否则单视频下载会生成 NA - 标题.mp4 (playlist_index 缺失时渲染成 NA):

named = "%(playlist_index)03d - %(title)s" if want_playlist else "%(title)s"

集合型链接的识别(决定要不要序号化 + 默认整批):list=、/playlist、/@、 /channel/、/c/、/user/、/results?、/videos、/streams、/shorts、 space.(B站空间)、/favlist、/medialist、ytsearch。注意 /shorts/ID 是单个视频, 只有以 /shorts 结尾才是合集。

十、TLS 指纹伪装:部分站点必需,但对 YouTube 有害

装 curl_cffi 后 yt-dlp 能伪装浏览器 TLS 指纹,解决 Vimeo 之类站点的 This request has been blocked due to its TLS fingerprint:

python -m pip install -U curl_cffi -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m yt_dlp --list-impersonate-targets   # 列出可用目标
python -m yt_dlp --impersonate Chrome-136:macos-15 URL

必须记住的两条实测结论:

  1. 别对 YouTube 用 --impersonate —— 会立刻触发 Sign in to confirm you're not a bot(开了伪装反而把自己判成机器人)。 所以只能做成可选开关,默认关。
  2. 直接写 --impersonate chrome 时 yt-dlp 会解析成"最新版 Chrome" (如 chrome-150:macos-26),可能超出 curl_cffi 提供的版本。 应当从 --list-impersonate-targets 的输出里取具体目标。
  3. 数据中心 IP 被站点拒绝时,伪装也救不了(会报 403 + your IP may be blocked), 只能换节点。

十一、报错 → 下一步(GUI/CLI 都该自动提示这些)

yt-dlp 报错关键字真正原因用户该做什么
TLS fingerprint指纹被拦开指纹伪装;若附带 IP blocked 则换节点
Sign in to confirm you're not a bot风控传 --cookies-from-browser chrome;若有开伪装则先关掉
HTTP Error 403IP 被拒 / 需登录换节点或加 Cookie
members-only / Private video会员/私有用已订阅账号的浏览器 Cookie
Unsupported URL链接不是视频页换成具体视频页,别用站点首页
blocked in your country地区限制换该地区节点
The page needs to be reloaded / 格式只剩 storyboard 图片该 IP 被 YouTube 限流(非 cookie / 非参数问题)等冷却或换节点,别改参数(见第十三节)
n challenge solving failed 单独出现只是 warning,非致命无需处理

十二、站点覆盖:1752 个提取器 / 1310 个站点

python -m yt_dlp --ignore-config --list-extractors        # 全量, 离线可跑
python -m yt_dlp --ignore-config --list-extractors | grep -i bili   # 过滤

带冒号的是同站点细分类型(youtube:tab = 频道页、BiliBiliBangumiSeason 等)。 给工具做"支持哪些站点"的搜索框比写死列表靠谱:--list-extractors 的输出是唯一事实来源。

十三、YouTube「下载量触发型」IP 限流 —— 最耗时的误判陷阱

现象(症状会伪装成"参数问题"):

  • 本来能下的 IP,下载累计约 1~1.5GB 后开始失败;
  • 报 The page needs to be reloaded(web 客户端),--list-formats 只剩 storyboard 图片;
  • 连完全公开的视频(如 dQw4w9WgXcQ)都拿不到任何格式;
  • 伴随 n challenge solving failed: Some formats may be missing。

铁证判别法(务必先做这一步,别急着改参数): 对比「1 小时前能下」与「现在不能下」两次运行的 yt-dlp 版本 + 完整参数是否一致。 若一致、唯一变量是时间 ⇒ 就是 IP 被限流,与 cookie / player_client / ejs / yt-dlp 版本全部无关。 (若连 title 都取不到,基本可确诊。)

正解:等冷却(约 30–60 分钟)后同一节点同参数自动恢复实测成立;或换一个没被限的节点。 "换节点"与"等冷却"本质等价,都是绕开限流——不必执着换新节点。

动手前先查出口 IP(30 秒,能省下几小时乱试):

curl -s -x socks5h://127.0.0.1:10808 https://ipinfo.io/json
# 看 org 里的 ASN:AS20473(Vultr)/AS14061(DigitalOcean)/AS16509(AWS) 等
# 都是数据中心 IP,触发风控的概率远高于住宅 IP

实测教训:节点会悄悄回落到数据中心节点(如 Vultr 64.177.45.2), 此时 visionos 等客户端直接返回 playability status: LOGIN_REQUIRED → 报 Sign in to confirm you're not a bot。先确认出口 IP 再谈参数,否则会误判成参数问题。

已被实测排除、不要再试(这些是本坑里最浪费时间的方向):

  • --remote-components ejs:github:对限流无效。
  • player_client=tv/tv_embedded/web_safari/ios/android_vr:对限流无效; 且 ios 客户端不支持 cookie 会被静默跳过(Skipping client "ios" since it does not support cookies), 结果只剩 storyboard。默认 web 客户端就是最优选择。
  • 升级/覆盖 yt-dlp(已是当日最新 2026.08.19、含 master 源码覆盖):无效。

防触发:批量下载时加 item 间间隔,显著降低再触发概率:

--sleep-interval 30 --max-sleep-interval 60

配合 --download-archive,被限后已完成项不会重下,冷却后直接续跑。

与 bot 风控的区别(别混为一谈):

症状根因解法
Sign in to confirm you're not a bot风控,要求登录态传 cookie(--cookies cookies.txt),cookie 一上就消失
page needs to be reloaded / 只剩图片 / 连公开视频都无格式IP 被限流等冷却或换 IP,cookie 救不了

Chrome cookie 的现实:Chrome 127+ 的 App-Bound Encryption 使 --cookies-from-browser chrome 在本机 DPAPI 解密失败(Failed to decrypt with DPAPI; Chrome 未关时则是 Could not copy Chrome cookie database)。 唯一可行路径:用扩展「Get cookies.txt LOCALLY」导出 Netscape 明文 cookies.txt, 再用 --cookies 加载(明文免解密,绕过 App-Bound)。

十四、合并后进程异常退出:查「文件删除保护 hook」

症状(极易与"IP 限流"混淆,但性质完全不同):

  • 日志有 [Merger] Merging formats into "xxx.mp4"、该条目其实已下完;
  • 但随后没有下一条目的 Downloading item N+1,进程直接以非 0 退出;
  • 播放列表只下到中途某条就整体停了;
  • 日志末尾出现删除/钩子提示,如 [safe-delete][SAFE_DELETE_BULK_CONFIRM_REQUIRED] {... "targets":[ "...f137.mp4" ]}。
  • 没有 bot / 403 / reload / n-challenge 相关报错。

原因:yt-dlp 合并完成后默认会删除中间流文件 (Deleting original file ...f137.mp4 (pass -k to keep))。 在带 safe-delete / 批量删除确认的受控环境(沙箱、安全策略)里,这个删除被拦截, yt-dlp 收到错误即异常退出,后续条目不再处理。

判别技巧:把日志按 Downloading item 数一遍——如果最后一条 item 的 Merging 之后 再无 item,说明就是被"删除"打断,而不是网络问题。

14.1 根因:删除 hook 是「缺省即启用」,删变量 ≠ 关闭(大坑)

WorkBuddy(以及同类受控宿主)通过两层注入删除保护:

通道文件开关判定
Pythoncli/vendor/shim/sitecustomize.py_SAFE_DELETE_ENABLED = os.environ.get("CODEBUDDY_SAFE_DELETE_ENABLED") != "0"
Nodecli/vendor/shim/node-safe-delete-shim.cjsSAFE_DELETE_ENABLED = process.env.CODEBUDDY_SAFE_DELETE_ENABLED !== '0'

两边都是 != "0" —— 意味着变量不存在时判定为「启用」。

⚠️ 最容易踩的错:以为"把 CODEBUDDY_SAFE_DELETE_* 从子进程 env 里 pop 掉就禁用了"。 恰恰相反 —— pop 掉等于走「缺省启用」分支,保护照旧生效。必须显式设成字符串 "0"。

Python 侧的 hook 是无条件 monkey-patch os.remove / os.unlink / os.rmdir / shutil.rmtree / pathlib.Path.unlink|rmdir(见 sitecustomize.py 末尾 if _SAFE_DELETE_ENABLED: 分支),且它由 managed python 的 .pth 加载, 清 PYTHONPATH 拦不住(这点也要注意,别以为改 PYTHONPATH 就够了)。

14.2 正解:子进程 env 显式关闭

env = dict(os.environ)
env["CODEBUDDY_SAFE_DELETE_ENABLED"] = "0"          # 关键:设成 "0",不是 pop
env["CODEBUDDY_SAFE_DELETE_BULK_THRESHOLD"] = "0"   # 顺带让批量阈值失效
for k in ("NODE_OPTIONS", "ELECTRON_RUN_AS_NODE", "BASH_ENV", "ENV"):
    env.pop(k, None)                                 # node 侧 shim 也一并废掉

必须用批量删除验证(只测单文件会漏!单个删除走回收站是静默成功的, 只有 ≥ 阈值(默认 50) 才弹确认窗):

# 建 200 个文件, 子进程里一次 rmtree, 看是否 0.3s 内干净完成且无窗口
subprocess.run([sys.executable, "-c",
    "import sitecustomize,shutil;"
    "print(sitecustomize._SAFE_DELETE_ENABLED);"   # 期望 False
    f"shutil.rmtree({path!r})"], env=env)

实测效果:子进程 _SAFE_DELETE_ENABLED = False,200 文件 0.3s 删净, download_hd 30GB 残留 0.1s 删净,全程无 genie-trash 窗口。

判别日志时注意:hd_download.log 是追加模式,旧运行的 [safe-delete][SAFE_DELETE_BULK_CONFIRM_REQUIRED] 会留在文件里。 要按最后一次 [启动] 标记行号切开再 grep,别误判成没修好:

LAST=$(grep -an "^\[启动\]" hd_download.log | tail -1 | cut -d: -f1)
tail -n +$LAST hd_download.log | grep -ac 'safe-delete'   # 期望 0

14.3 降级方案:-k(空间换安宁)

若无法控制子进程 env(或删除仍被拦),才用 -k 保留中间文件:

yt-dlp ... -k     # 保留 .f137.mp4 / .f140.m4a,不删

代价:每条残留视频流 + 音频流,磁盘占用翻倍,全部下完后统一清理:

rm -f download/*.f137.mp4 download/*.f140.m4a   # 成品 .mp4 不受影响

判断依据:磁盘余量 < 成品预估总量 × 2 时不要用 -k。 例:C 盘剩 43G、HD 全量约 30GB → 用 -k 会撑爆,必须走 14.2 的正解。

十五、PO Token:2026 年 YouTube 高清的真正命门

现象

即使带了完整 cookie(含 __Secure-3PSIDTS),yt-dlp -F 最高也只到 1080p@255k(itag 137, H.264 低码率档), 无 1440p/4K、无高码率 VP9/AV1。换 player_client、切 HLS 都无效 → 这就是「画质糊」的硬上限。

根因

2025–2026 YouTube 对高码率格式强制 PO Token(Proof of Origin)鉴权:

  • 无 cookie → 直接 bot 墙(Sign in to confirm you're not a bot)。
  • 有 cookie → 过 bot 墙,但 cookie 只做登录态、过不去 attestation 门,只给低码率 H.264 档。
  • 高码率档(真·1080p 高码率、1440p、4K)需要 PO Token 才释放。

⚠️ 270 的真实身份(2026-09 实测铁证,推翻本 skill 早期结论):270 是 137 的 HLS 重封装, 不是独立高码率流。抓它的 m3u8 分片 URL 会看到 sgovp/clen%3D65782132%3Bdur%3D2060.733%3Bitag%3D137——clen(=62.7MB) 正是 137 的字节数、 itag 直接写着 137。yt-dlp 界面里 270 ... ~1009.12MiB 4107k 是 filesize_approx 估算值 (tbr × duration),全是虚高;实际下完只有 62.7MB / 255k。 所以「-F 里看到 270 / 4107k」≠ 解锁了高清,别被它骗。 同理:判断真实码率永远看 ffprobe 实测(-show_entries stream=bit_rate),不要信 -F 的 tbr。

解法(三步,缺一不可)

  1. 装 provider 到 yt-dlp 同环境(managed python):
    python -m pip install yt-dlp-get-pot-rustypipe      # 自包含 botguard 挑战,推荐
    # 备选:bgutil-ytdlp-pot-provider(http 后端需外部服务器、script 后端缺脚本,
    #       国内公共服务器基本被墙,不如 rustypipe 省事)
    
  2. 【必踩大坑】pip 装的只是 Python 包装器,真正的二进制要单独下载。 不装的话 -F 只会打印 [pot:rustypipe-botguard] No valid rustypipe-botguard v1 binary found, 且 PO Token Providers: 里该项标成 (external, unavailable) → 等于没装。
    # 从 codeberg 下 Windows x64(国内需走代理)
    curl -L -x socks5h://127.0.0.1:10808 -o rpb.zip \
      https://codeberg.org/ThetaDev/rustypipe-botguard/releases/download/v0.1.2/rustypipe-botguard-v0.1.2-x86_64-pc-windows-msvc.zip
    python -c "import zipfile; zipfile.ZipFile('rpb.zip').extractall('.')"   # 解出 rustypipe-botguard.exe
    ./rustypipe-botguard.exe --version   # 必须输出 `rustypipe-botguard-api 1`(源码 _BOTGUARD_VERSION="1")
    
    放哪:provider 按 [rustypipe-botguard, ./rustypipe-botguard] 查找 → PATH 或 yt-dlp 的 cwd (run_hd.py 的 cwd 就是脚本目录,直接丢工作目录即可);也可 --extractor-args "youtube:rustypipe_bg_bin=<绝对路径>" 显式指定。
  3. 【中国大陆专属坑,最耗排障时间】代理必须走 http,不能是 socks5。
    • provider 源码 _SUPPORTED_PROXY_SCHEMES = ("http", "https")。实测 ALL_PROXY=socks5://… 直连会报 http: error sending request for url (https://www.youtube.com/api/jnn/v1/Create) —— 这个 Rust 二进制不认 socks5。
    • 而本机(Shadowsocks)通常只有 socks5 端口 10808 → 必须起一个本地 http→socks5 桥 (见 http2socks.py,零依赖、自己实现 SOCKS5 握手),再让 yt-dlp 用 http 桥:
      pythonw http2socks.py &                  # 监听 127.0.0.1:8118 -> 转发到 socks5 127.0.0.1:10808
      yt-dlp --proxy http://127.0.0.1:8118 …   # provider 收到 http proxy 后会给二进制设 ALL_PROXY
      
    • 桥实现要点:CONNECT 请求要读到 \r\n\r\n 为止再回 200 Connection Established; 只读第一行会把剩余请求头泄进隧道,表现为 TLS 握手失败(curl exit 35)。
    • child_env() 若把 HTTP_PROXY/HTTPS_PROXY 剥离或设成 socks5,provider 同样拿不到可用代理。 现已改为:proxy 是 socks5 且本机 8118 桥在监听 → 自动把 PO helper 的 HTTP_PROXY/HTTPS_PROXY/ALL_PROXY 指向 http://127.0.0.1:8118(桥);是 http 代理则直用;两者皆无则退回 socks5h(优雅降级,不崩)。 run_hd.py 的 ensure_bridge() 会在启动时自动拉起 http2socks.py(无窗口),无需手动开桥。

验证是否真解锁(关键:用「对照视频」,别看目标视频的 270)

目标视频可能本身就只有低码率档,此时「有没有 PO Token」在 -F 上看着一模一样。 正确姿势:拿一个已知有 4K 的视频当基准。

yt-dlp -F --cookies cookies.txt --proxy http://127.0.0.1:8118 --js-runtimes node: \
  "https://www.youtube.com/watch?v=dQw4w9WgXcQ" | grep -E "1080|1440|2160"
# 出现 137(3038k) / 248(1080p VP9) / 271(1440p) / 313(2160p@13466k) = 链路完全打通
  • 对照视频正常、目标视频却仍只有 137(255k) → 该视频在 YouTube 侧就只有这一档 (源本身低码率 / 未做 VP9 转码),换工具、换 cookie、换 player_client、装 PO Token 都救不了。 此时正解是找官方原始文件或联系作者,别再折腾下载器(2026-09 的「Vibe Coding 课程」就是这个情况: 对照视频 4K 13Mbps 正常,该课程 1080p 实测只有 255–485k,270 只是 137 的壳)。
  • 实测码率永远以 ffprobe -select_streams v:0 -show_entries stream=bit_rate 成品.mp4 为准。

选择器(PO Token 就绪后)

沿用第五节「显式偏好 m3u8」的串即可。注意:对本身低码率的视频,该串会挑到 270 (内容 = 137),这已是该视频的上限,不是选择器的问题。 完整调用示例(守护/批量见 run_hd.py):

python -m yt_dlp -f "bv*[height<=?1080][protocol^=m3u8]+ba[protocol^=m3u8]/bv*[height<=?1080][protocol^=m3u8]+ba/bv*[height<=?1080]+ba/b[height<=?1080]/b" \
  --merge-output-format mp4 --concurrent-fragments 32 --downloader m3u8:native \
  --js-runtimes node: --cookies cookies.txt --proxy http://127.0.0.1:8118 URL

十六、后台跑批不弹黑窗:CREATE_NO_WINDOW,不要 DETACHED_PROCESS

守护/批量脚本用 subprocess.Popen(..., creationflags=DETACHED_PROCESS) 虽然能脱离终端, 但 DETACHED_PROCESS 意味着进程完全没有控制台 → 之后启动的 yt-dlp.exe / node.exe(JS challenge)/ ffmpeg.exe 这类控制台程序会被 Windows 自动分配一个全新的可见控制台窗口, 表现为下载期间一直挂着一个空白黑窗(stdout 已重定向进日志,所以窗口里什么都没有)。

CREATE_NO_WINDOW = 0x08000000
subprocess.Popen(child, ..., creationflags=CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP)
# 守护自身的 subprocess.run(yt-dlp) 也加 creationflags=CREATE_NO_WINDOW,双保险

CREATE_NO_WINDOW = 进程拥有一个隐藏控制台,所有后代继承同一个控制台 → 永不弹窗, 且不影响已重定向到日志的 stdout/stderr。验证:tasklist /v 里该进程 Window Title 显示 N/A。

十七、H 盘上的 *.part-Frag / 0KB 文件是正常的

yt-dlp 的 HLS 分片下载器(--concurrent-fragments 32)会为每个并发分片建一个 .part-Frag 临时文件,用 seek 定位写入。资源管理器里看到一堆 0KB 是:

  1. 分片刚被创建、数据还没落盘(写入有缓冲);2. 资源管理器的大小列有缓存(F5 刷新即变)。 这些都是临时的,该集下载 + 合并完成后自动清理。只有当某集已完成却仍残留大量 Frag 时才算异常 (强烈断线留下的垃圾,直接删)。

Related skills

Generate and edit Draw.io, Mermaid, and Excalidraw diagrams from natural language using a structured JSON spec.

by nssa.io1.0k installs47 stars

Fetch raw ad creative, app, ranking, and revenue data from AdMapix as structured JSON.

by fly0pants4.3k installs296 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai22 installs8 stars

Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.

by Iván559 installs18 stars

Run Git operations — commits, branches, merges, rebases, conflict resolution, and recovery — with safety rules enforced.

by Iván535 installs31 stars

More from oracis

Browse all skills

把本地 Skill 打包并发布到 SkillHub(腾讯 skillhub.cn)与 ClawHub,覆盖预检、发布、状态回读与竞品对标全链路,并在上传被拒时用二分/ddmin 脚本把「服务端 WAF 拦内容(566)」与「包结构/字段问题」精确区分开。当用户说「发布技能到市场」「上架 skill」「SkillHub 提交失败」「Failed to fetch」「566」「上传 zip 报错」「技能审核状态」「版本号被拒」时使用。也讲清「入库成功 ≠ 已公开」——用 `mine` 的 `reviewStatus`、重发探测 `409 VERSION_EXISTS`、`/versions/<v>/signature` 三个探针判断版本到底走到哪一步。

by oracis1 installs1 stars

平台只有网页后台、没有上传 API 时,用本机已装的 Chrome/Edge + CDP 自动化走完上传/发布/填表流程——复用真实登录态、零安装(不下载 500MB Chromium)、能给文件选择框塞文件;并实测富文本编辑器(公众号 / CMS / 邮件模板)里到底哪些 CSS 能活下来,以及在决定是否接入平台自带的「一键排版 / AI 美化」前先抓它输出做结构化 diff。当用户说「只能网页上传」「后台发布」「网页填表自动提交」「发布到技能市场/各平台控制台」「复用已登录的浏览器」「公众号排版样式不生效/保存后样式没了」「一键排版要不要用」「点了按钮没反应/提交没成功也没报错」时使用。

by oracis2 installs

一键安装并实测验证已验证可用的免费 LLM 到 WorkBuddy/Claude Code,无需注册、无需 API Key。当用户说「装免费模型」「加免费模型」「免费 LLM」「免费模型一键装」「验证免费模型可用吗」「免费模型怎么接入」「帮我配几个免费模型」「监控免费模型」「免费模型挂了吗」「kilo 网关」「kilo-auto」「nemotron 免费」「space-bunny」「智谱 glm 免费」「zhipu 免费」「glm-4-flash」「glm-4.7-flash」「glm-z1-flash」「glm-4v-flash」「探测国外免费模型」「国外平台免费 llm」「优先用免费模型」「读图桥接」「vision bridge」「给 glm-4-flash 加读图」「不读图的模型怎么看图」「阶跃星辰」「stepfun」「step 5」「step-5-preview」「限时免费模型」「ccproxy 读图转发」「给 claude code 加读图」时使用。也用于验证已有免费模型配置是否真的可用(tool calling 是否支持、是否限流、是否支持读图),以及定期 watch 监控免费模型是否下线/限流/失去工具调用能力。

by oracis1 installs

聚合搜索百度网盘/夸克网盘/阿里云盘等网盘分享链接。当用户想按作者名、课程名、剧名、书名、软件名找网盘下载路径,或提到「网盘搜索」「PanSou」「盘搜」「找到下载链接」「夸克/百度/阿里网盘资源」时使用。包含已验证的 PanSou 协议端点、绕过系统代理的必要写法、以及可直接运行的本地 Web 应用 panradar.py。

by oracis2 installs

在 WorkBuddy(Windows)环境里交付零依赖本地 Web 应用,并绕开官方测试技能完全不覆盖的本机专属坑。当用户要在本机跑起来一个本地 Web 应用 / 单文件工具 / 案例库 / 看板 / 小工具,或要验证刚写完的 HTML/JS/Python 交付物真的能工作时使用。独家覆盖:Bash 缺 coreutils、bash 是 WSL 启动桩、rm 是坏 shim、PowerShell 不回显 stdout、cwd 漂移、预览面板靠 HEAD 探活——这些在干净 Linux 环境里遇不到。

by oracis1 installs

把B站横屏中长视频(读书解读/知识类)自动改造为微信视频号9:16竖屏短视频切片。当用户说"B站视频转视频号""横屏转竖屏切片""读书视频切片""视频号短视频制作""横屏改竖屏"时使用。覆盖下载→转写→选段→横转竖→烧字幕→封面全流程, 含本机环境坑与画质/字幕优化。

by oracis1 installs