我用 AI 做了一个 New Yorker 朗读音频下载扩展——因为「Listen」按钮没有下载键

《纽约客》(The New Yorker)对它的朗读音频相当用心。几乎每篇特稿都带一个 “Listen” 播放器,能把整篇文章念出来——通勤、散步、洗碗时都好用。但这个播放器没有下载按钮。如果你想把某一期存下来离线听——长途航班、地铁没信号的路段、一台不能开浏览器的老 MP3 播放器——官方没有任何办法。

我不是职业开发者。但我手边有一个 AI 助手,一个安静的下午,还有一个挥之不去的问题:这段音频到底存在哪里? 这是一次完整的记录——我们如何在页面里找到那个地址、为什么它居然能直接下载,以及最后做出来的那个小小的 Manifest V3 扩展。

开始之前——几句实话。

  • 这是一篇纯技术分享,按原样提供。这个扩展只指向一个媒体地址,而这个地址本就是文章页面在你自己浏览器里加载的。它没有破解任何东西,没有绕过任何登录,也没有碰任何加密。
  • 仅限个人使用。请在你本就有权访问的文章上使用。不要二次传播、重新上传或转卖下载到的音频。请遵守 The New Yorker 的服务条款,以及作品的版权。
  • 如果你喜欢这本杂志,最便宜的正版渠道是真实存在的:The New Yorker 经常有约每周 1 美元的订阅优惠。这是我会对任何人说的那个选择。这个工具只是方便你听已经读过的内容——不是用来替代订阅的。

最后这一点对我挺重要:音频之所以存在,根本原因是那些新闻报道。力所能及的话,请为它付费。

为什么它能下载——“顿悟”时刻

我一开始用的是你预期的方式:打开文章源码,搜索 .mp3 和 .m4a。什么都没有。音频不是 HTML 里的一个静态文件链接,而是由页面携带的一段 JSON 交给播放器的。在源码里搜 audio,命中了一个 relatedAudio 对象:

"relatedAudio": {
  "name": "The New Age of Reading",
  "embedUrl": "https:\u002F\u002Fembed-audio.cnevids.com\u002Fpermalink\u002Fnarrated\u002F6ace2d7e2ff8076a7e442422",
  "durationInMs": 4083000
}

两点很关键:

  • 音频由 Condé Nast 的 cnevids 平台托管(embed-audio.cnevids.com)。
  • 播放器的 embedUrl 是一个 “permalink”——永久链接式的地址。

出于直觉,我直接请求了这个地址。它返回的就是 MP3 文件本身:约 81 MB、160 kbps、48 kHz、约 68 分钟——和 durationInMs 完全吻合。没有 token、没有 cookie、没有鉴权握手。付费墙挡的是文章文字;而朗读音频是页面自己的播放器以一份公开的媒体文件加载的。所以“下载它”本质上就是“向播放器请求的同一个地址发起请求”。没有任何东西被破解。

比这个工具本身更值得记下的经验是:大量网页音视频播放器都是这个模式。你交给播放器的那个地址,往往就是媒体的真实地址。找到它,剩下的只是工程问题。

我到底做了什么——一个小小的 Manifest V3 扩展

既然地址就在那里,这个扩展的全部工作就是:打开任意一篇 New Yorker 文章 → 找到音频地址 → 点一下 → 存下 MP3。Manifest V3,所以 Chrome 和 Edge 都能跑。三个小部件:

1) content.js——找地址 + 画按钮

New Yorker 文章是服务端渲染的,所以 relatedAudio 这段 JSON 已经在 DOM 里了。content 脚本取出 document.documentElement.outerHTML,跑一条正则,同时兼容两种形态的地址:正常的 https://embed-audio… 和源码里 \u002F 转义过的形态。

const EMBED_RE = /https?:(?:\\u002F|\/)+embed-audio\.cnevids\.com(?:\/|\\u002F)+permalink(?:\/|\\u002F)+narrated(?:\/|\\u002F)+[a-f0-9]+/i;
function normalize(url){ return url.replace(/\\u002F/g,"/").replace(/\\\//g,"/"); }
const m = document.documentElement.outerHTML.match(EMBED_RE);
const embedUrl = m ? normalize(m[0]) : null;

如果找到了,就在页面右下角注入一个悬浮按钮,加一条小提示做反馈,然后把地址和清理过的文章标题发给 background 脚本。

2) background.js——让浏览器去下载

下载本身跑在 service worker 里,用 chrome.downloads.download。刻意地,扩展不自己 fetch 这个文件——它把地址交给浏览器自己的下载通道。这意味着请求经由浏览器的网络栈发出(系统代理、Cookie 都包含在内),既没有 CORS 问题,也不用写任何代理代码。它还会清理文件名,把文件存进一个名为 New Yorker Audio/ 的子目录:

const filename = "New Yorker Audio/" + safeName(msg.title) + ".mp3";
chrome.downloads.download({ url: msg.url, filename, conflictAction: "uniquify" }, (id) => {
  if (chrome.runtime.lastError) sendResponse({ ok:false, error: chrome.runtime.lastError.message });
  else sendResponse({ ok:true, id });
});

3) popup.html / popup.js——工具栏面板

在文章页上点工具栏图标,弹窗会问 content 脚本找到了什么,然后提供一个下载按钮。同一个地址、同一套流程——只是给更喜欢工具栏、而不是悬浮按钮的人多一个入口。

(三张 PNG 图标是用几行 Pillow 画出来的——深海军蓝方块上,一个播放三角叠一个下载箭头。没什么花哨的。)

我踩过的坑

1) 那个“不是 URL 的”转义 URL

源码里把地址写成 https:\u002F\u002Fembed-audio…。直接把这个字符串交给下载器会失败——\u002F 是一个六个字符的字面转义,不是斜杠。修复就一行:在使用地址前,把 \u002F(以及 JSON 里的 \/ 形态)还原成 /。我在正则阶段就规避了它,所以它从没变成运行时的 bug。

2) 网络在(本地)骗我

这一点跟环境有关,如果你想复现,值得提一句:在我的机器上,命令行访问 www.newyorker.com 会超时(HTTP 000),但浏览器里打开正常。原因是浏览器走了我的系统代理(Clash,在 127.0.0.1:7890),而 curl 没走。本地调试的修复只是加 -x http://127.0.0.1:7890。有意思的是:音频 CDN embed-audio.cnevids.com 直连就能到,不需要代理。这些在扩展里都不影响,因为我们把下载委托给了 chrome.downloads,让浏览器自己处理网络——但这正是那种如果你在错误的层上调试、会白白吃掉一整个下午的幽灵。

如何安装

  1. 下载并解压下面的文件夹。
  2. 打开 edge://extensions(或 chrome://extensions)。
  3. 打开右上角的“开发人员模式”。
  4. 点击“加载解压缩的扩展”,选择解压出来的文件夹——也就是包含 manifest.json 的那个。
  5. 打开任意一篇带 “Listen” 播放器的 New Yorker 文章。页面右下角会出现一个悬浮按钮;点它,MP3 就会落到 Downloads/New Yorker Audio/ 里。

注意:现代 Chrome 和 Edge 不会安装拖进去的 .crx 文件——“加载解压缩的扩展”才是可靠路径。

下载——New Yorker Audio Downloader(v1.0.0)

解压即用扩展文件夹 + 说明 · 约 9 KB · Edge / Chrome

下载 .zip

按原样提供,不附带任何担保。请只在你有权访问的内容上使用,并遵守 The New Yorker 的服务条款与作品版权。如果你喜欢这本杂志,正版订阅约 1 美元/周——那才是值得选的优惠。

Bluesky 讨论

在 Bluesky 上查看

暂无回复。去 Bluesky 上抢先评论吧!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部