我经常在 Pinterest 上找图片和视频素材。图片保存起来很直接,视频却经常停在“浏览器里能看,手上只有一个 URL”这一步。为了把一条公开视频存到本地,还得再找下载站或者跑一段脚本;偶尔用一次问题不大,用得多了就很烦,尤其是有些下载站的广告比下载按钮还积极。

所以我写了 Pinch。它和常见 Pinterest 下载工具的区别比较明确:只处理单个公开的视频 Pin,不做图板、个人页、图片和批量抓取;整个解析过程都在浏览器里运行,没有后端服务,也不需要代理,可以直接部署到 GitHub Pages。
我主要想解决两个问题:一是把单个公开 Pin 里的渐进式 MP4 找出来,省掉来回倒腾 URL 的步骤;二是验证这件事能不能只靠一个静态网页完成。静态解析现在已经跑通,不过下载能力只覆盖 MP4;HLS 只做到识别、探测和报错,还不能把 .m3u8 转成可下载的 MP4。
怎么用
在线页面放在 GitHub Pages,打开后只有一个输入框。Pinch 不要求登录,也不上传视频;浏览器会直接向 Pinterest 请求公开数据。

把公开 Pin 的完整 URL 粘进去,点击 Get MP4。解析成功后,页面会显示视频预览、分辨率、格式,以及当前浏览器可以执行的操作。
下面这次测试找到了一个 720 × 1280 的 MP4。结果页显示的是 Open MP4,因为 Pinterest CDN 没有允许当前网页直接读取视频字节;点开原始视频后,再使用浏览器自己的保存命令即可。

如果视频响应允许跨域读取,按钮会显示 Download MP4,Pinch 会直接保存原始 MP4,不转码,也不重新压缩。页面显示哪个按钮,取决于浏览器实际探测到的结果。
静态网页怎么找到视频
一开始我试过直接读取 Pinterest 的 Pin 页面。页面返回的 HTML 里确实有不少数据,但浏览器受同源策略限制,无法从 GitHub Pages 读取它的响应内容。用命令行能看到 HTML,不等于网页里的 JavaScript 也能看到,这是做纯前端版本时遇到的第一个限制。
后来我找到了 Pinterest 小组件使用的公开 JSON 接口。它不需要登录、Cookie 或 API 密钥,而且允许其他网页跨域读取。Pinch 从输入的 URL 中提取数字 Pin ID,请求这份 JSON,再从普通 Pin 和 Story Pin 的嵌套结构里收集视频源。
公开 Pin URL
→ 提取数字 Pin ID
→ 请求公开的 widget JSON
→ 收集并归一化视频源
→ 校验媒体域名和 HTTPS 协议
→ 去重并优先选择渐进式 MP4
→ 探测浏览器能否读取视频响应
解析时不能只看 Pinterest 返回的顶层字段。有些 Story Pin 的顶层会显示 videos: null,真正的视频地址却放在更深的 video_list 里。Pinch 会递归检查这些结构,但最终只接受 Pinterest 视频 CDN 上的 HTTPS MP4 或 HLS 地址,其他域名即使混在返回数据里也不会进入结果。
这个 widget 接口是 Pinterest 的公开接口,但不是它承诺长期稳定的官方开发 API,数据结构以后可能变化。因此我把它封装在独立的 PinResolver 后面,解析逻辑也有脱敏测试样本。上游发生变化时,修改解析器就行,不需要把 UI 一起重写。
MP4 为什么有时只能打开
静态网页找到 MP4 地址以后,还有一道浏览器跨域限制。一个跨域视频可以在 <video> 标签里正常播放,但同一页面里的 fetch() 不一定能读取它的响应体。播放视频和让 JavaScript 拿到文件字节,在浏览器看来是两种权限。
Pinch 会先发送一个很小的 Range 请求探测媒体是否可读,然后按结果选择操作:
- 响应可读时,获取原始 MP4 字节并通过 Blob 下载,按钮显示 Download MP4;
- 视频可以播放但响应体不可读时,不会把“打开链接”算作下载成功,而是显示 Open MP4,让用户在原始视频页面里保存;
- 视频源不可用时,直接显示错误。
我也测试过给链接加 download 属性、使用 no-cors、Service Worker 等做法。它们都不能把不可读的跨域响应变成可读响应,所以纯静态页面无法强制把所有 Pinterest MP4 都保存成文件。加一个后端代理可以绕开浏览器这一层限制,但那会带来服务器、带宽、日志和素材中转,已经不是我想做的这个工具了。
MP4 与 HLS
Pinch 选择视频源时会优先使用渐进式 MP4。只要公开 Pin 里有 MP4,就走上面的预览和保存路径,这也是当前版本实际验证过的主要流程。
如果公开 Pin 没有 MP4,只有 .m3u8,Pinch 会识别出它是 HLS,并探测播放列表能否读取。目前无论播放列表被 CORS 拦截,还是能够读取但转封装流程尚未验证,页面都会返回明确错误,不会把 HLS 当成已经支持的下载能力。
Pinch 没有接入 ffmpeg.wasm,因为浏览器拿不到 HLS 播放列表和分片时,转封装库没有数据可处理。当前版本有 MP4 就走 MP4;只有 HLS 时会报错。以后如果能稳定读取完整的 HLS 资源,再考虑浏览器端无转码封装。
为什么只做单个公开 Pin
我在实现时参考过开源项目 pinterest-downloader 对单个 Pin、Story Pin 和 video_list 的处理,不过没有把它的图板、个人页、图片和批量下载功能搬过来。Pinch 从一开始就只想处理“我手上已经有一个公开 Pin URL,现在要把里面的视频存下来”这个场景。
当前支持的是路径中带数字 ID 的公开 Pinterest Pin 链接,包括常见子域和地区域名;pin.it 短链、私密 Pin、登录态、图板、个人页和批量抓取都不在范围内。工具不配置第三方 CORS 代理,也不处理 DRM 或访问控制绕过。
范围收窄以后,页面可以保持简单,安全边界也更容易检查。外部 JSON 一律按未知数据处理,媒体地址限定在 Pinterest CDN,标题和描述通过 textContent 写入页面,不直接插入 HTML。生产构建没有运行时 npm 依赖,静态文件部署到 GitHub Pages 就能运行。
代码与地址
Pinch 使用 TypeScript 和 Vite 编写。仓库里有 URL 校验、JSON 提取、视频源排序、媒体探测、下载路径和 DOM 行为测试,页面流程也使用 Playwright 在 Chrome、Firefox 和 WebKit 下检查。依赖 Pinterest 线上行为的测试单独运行,避免外部接口变化影响默认测试结果。
如果你也会从 Pinterest 保存单个公开视频,可以直接试一下。遇到解析失败或者 Pinterest 数据结构变化,也欢迎在评论区留言或在 GitHub 提 Issue。下载前请确认自己拥有相关内容,或者已经获得使用授权。
