ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Fantia付费内容批量下载:Tampermonkey用户脚本实战指南

Fantia付费内容批量下载:Tampermonkey用户脚本实战指南 简介一款面向 Fantia 平台用户的 Tampermonkey 用户脚本核心功能是在 Fantia 图片框上自动添加下载按钮点击后即可把页面内全部图片打包成 ZIP 并下载保存有效解决图片批量下载与离线归档问题。资源压缩包体积很小共 2 个文件分别是 1 个 JavaScript 脚本和 1 个 Markdown 说明文档整体仅 3KB结构非常精简适合熟悉用户脚本机制的读者直接安装或按需修改。需要特别留意的是作者经测试后明确建议在 Firefox 中运行Chrome 可能因浏览器自身跨域图片的 CORS 限制而无法正常打包相关提示已写入说明文档。目前已有 10586 人学习下载脚本可从 Greasyfork 安装源代码同步托管在 GitHub兼顾使用与二次开发需求。对经常浏览 Fantia 并希望快速批量保存图片的用户而言这是一份轻量且实用的工具资源。1. 这个标题在解决什么Fantia 付费内容的批量下载到底卡在哪看到标题里的 Fantia-Downloader-tampermonkey你多半是来给「在 Fantia 上把图批量存下来」找个省力方案而 Tampermonkey 正是让这个方案跑起来的浏览器环境。问题是Fantia 的创作者主页上内容按帖子分发图片要真金白银开订阅才看得到浏览器既没有「整页一键存图」右键一张张另存又累到怀疑人生。这个标题对应的路线就是写一个贴着页面跑的自动化脚本帮你把订阅权限内看到的图片批量落盘。它适合两类人一是开了订阅想把内容留档的普通用户二是帮创作者做备份归档的人。注意它解决的是「重复劳动」不是绕过权限——你的账号能看见什么脚本才能下什么。2. 环境准备Tampermonkey 装好后还有 3 个权限声明要确认2.1 为什么不用现成下载器而要一个脚本先摆一个对比你就明白为什么这类下载器全是「页面脚本」而不是独立软件。方案登录态处理订阅权限识别跨域抓图上手成本独立下载器要手动导 Cookie过期还要重来不识别靠你手动拿来链接可以但要配很多源站规则高浏览器扩展自带 Cookie但要写 background 通信能识别但权限模型较重可以中高Tampermonkey 用户脚本天然复用页面 Cookie零配置直接读 DOM看到即允许靠 GM 系列 API低关键差别在登录态。Fantia 的内容是按账号订阅关系开放的独立下载器最常见的问题就是「抓回来一堆登录页的 HTML」。用户脚本跑在页面上下文里浏览器已经把 Cookie、订阅关系都处理好了脚本只是从渲染后的 DOM 里拿数据踩坑面一下少了一半。另一个现实原因是这类需求的分享方式几乎都是「扔一个用户脚本文件给你」Tampermonkey 是事实标准装脚本比装扩展更接近原作者的发布形态。局限性也要说清楚。浏览器环境的脚本有内存和速度天花板一次抓几百张图没问题几万张就不适合了另外脚本依赖页面结构Fantia 改一次版选择器就要跟着改。所以这篇文章讲的是「中等规模、自用留档」的靠谱做法不是批量采集生产线。2.2 装好 Tampermonkey 并添加第一个脚本的步骤环境搭建本身不复杂但权限声明容易漏。先按下面步骤走一遍在浏览器应用商店搜索 TampermonkeyChrome、Edge、Firefox 都有官方版本并安装。点击工具栏的油猴图标选择「添加新脚本」进入编辑器。清空模板内容粘贴下面的 metadata 块然后按 CtrlS 保存// UserScript // name Fantia 页面下载助手 // namespace local // version 0.1.0 // description 在 Fantia 帖子页面上注入批量下载按钮 // match https://fantia.jp/* // grant GM_xmlhttpRequest // grant GM_download // grant GM_setValue // grant GM_getValue // run-at document-idle // connect fantia.jp // connect cdn.fantia.jp // /UserScript这段头部信息有四个参数必须说明。match决定了脚本在哪些网址生效https://fantia.jp/*表示主站所有页面都跑不需要精确到某个帖子路径——因为脚本要在帖子页注入按钮在创作者主页也可能要干活。grant是权限清单GM_xmlhttpRequest 用于跨域拉图GM_download 负责把文件写进磁盘GM_setValue 和 GM_getValue 用来记住下载进度缺哪个功能就报哪个错。connect是跨域请求的白名单主站和图片 CDN 都要写进去具体 CDN 域名以你打开浏览器开发者工具「网络」面板里实际出现的为准我这里写的是常见值。run-at document-idle表示等 DOM 基本渲染完再执行避免太早注入按钮被页面脚本清掉。保存后脚本不会立刻出现在页面上因为里面还没有实际逻辑。你可以在 https://fantia.jp 随便打开一个公开页面点油猴图标确认这个脚本显示为「正在运行」。如果没生效多半是match写错或者浏览器没刷新页面。2.3 确认生效范围与登录态脚本生效不等于能下载还有一道隐藏门槛登录态。打开任意一个你已订阅的创作者帖子页在页面上右键「检查」看 Elements 面板里帖子的正文容器是否存在。如果能看到付费内容对应的图片节点说明脚本有资格干活如果页面跳转到登录页或提示订阅过期那脚本做什么都是白搭。这里有个小技巧Tampermonkey 的管理面板里可以打开「配置模式」它会帮你分析当前页面被哪些脚本匹配、匹配了哪条规则。遇到「脚本装了但不出现按钮」的情况先看这里而不是去改代码。2.4 权限不足时的报错特征还有一种常见的「假失效」脚本图标亮了但点击按钮毫无反应。打开浏览器控制台F12如果看到GM_download is not defined或GM_xmlhttpRequest is not defined说明grant声明和实际调用不一致。Tampermonkey 的规则是脚本里用到任何 GM_ 系列 API都必须在grant里显式声明否则 API 是 undefined。曾有人为了省事把grant none写在头顶结果所有跨域请求全部失败这是一个非常典型的玄学坑。3. 跑通第一条下载链路抓原图 URL、命名与落盘的最小脚本3.1 页面里的图是分层的缩略图、预览图和原图用户脚本的本质是「把页面里已有的数据重新组织」所以第一步要搞清楚 Fantia 的图片在 DOM 里长什么样。看一段典型结构img srchttps://cdn.fantia.jp/thumb/0001_thumb.jpg >const toOriginal (url) { let out url.replace(/\/thumb\//, /contents/); out out.replace(/_(s|m)(?\.\w$)/, ); return out; };这个函数是示例不是通用银弹——因为每个平台的命名规则都有差异。正确姿势是用浏览器开发者工具对比缩略图和原图的实际请求地址找出路径规律再写正则。你要记住的判断标准只有一个还原后的 URL 能不能在浏览器里直接打开能打开且尺寸足够大才算还原成功。3.2 注入下载按钮并收集 URL 清单最小可用的脚本先把「收集图片地址」这件事做对。下面是一段简化的采集逻辑(function () { use strict; const collectImageEntries () { const out []; const seen new Set(); document.querySelectorAll(img).forEach((img) { // 优先级data-original 原图 >const downloadOne (entry) new Promise((resolve, reject) { GM_download({ url: entry.url, name: entry.filename, saveAs: false, timeout: 30000, headers: { Referer: location.origin location.pathname, }, onload: () resolve(), onerror: (err) reject(err), }); });参数含义要拆开讲。name是保存到磁盘的文件名可以带相对路径比如2025-archives/posts/123_001.jpg浏览器会自动创建目录。saveAs: false表示不弹「另存为」对话框直接存进浏览器的默认下载目录批量下载时这是必须的否则每张图都弹窗你根本点不过来。timeout设 30 秒网络慢的图超过这个时间就报错重试。headers里带 Referer 是防盗链的常规操作很多图站会校验来源不带 Referer 的请求会被 403 拒绝。这个 Referer 建议用location.origin因为图片也可能在创作者主页被加载来源始终是 Fantia 主站域名。为什么不推荐用 fetch blob 的方式因为 fetch 受 CORS 限制图片 CDN 通常不会给你开放跨域头就算用 GM_xmlhttpRequest 拿到二进制再转 ObjectURL 下载批量下几十张图时浏览器内存会一路飙升还容易触发「此网站正尝试下载多个文件」的拦截。GM_download 是把下载任务交给浏览器下载管理器内存压力小得多。真正的坑在于 GM_download 在 Firefox 里需要显式grant GM_download而部分 Chromium 内核浏览器对它的存在时代理了下载行为表现是「文件秒下完但打不开」——这种情况我在第 5 章细讲。命名规则也要提前定好不要直接用图里的原始文件名。因为同一张图在不同帖子里的原始文件名可能一样直接重名会互相覆盖。推荐模板帖子 ID 序号。帖子的 ID 可以从 URL 里取比如https://fantia.jp/posts/123456就是 123456。拼接成123456_001.jpg这样既保证唯一性又方便回查是哪一帖的图。4. 调到顺手再放量并发、间隔、命名模板与过滤4.1 配置面板与 4 个必调参数脚本跑通之后你面临的现实问题是一个帖子几十张图一次全下会卡页面批量下会被浏览器拦创作者主页往下滚还有更多帖子。所以要把几个关键参数抽出来做成可配置项。我一般采用「脚本顶部常量 页面小面板」的组合核心参数就四个参数建议默认值作用maxConcurrent2同时下载的最大任务数超过容易触发站点限流requestInterval800ms每个请求完成后的等待时间给服务器喘口气timeout30000ms单张图的超时阈值超时计入失败并重试filenameTemplate{postId}_{index}.{ext}落盘文件名的格式必须保证不同文件不重名并发数默认只给 2很多人上来就填 8结果是请求发太快服务器开始返回 429 或直接拒绝响应整个队列连环失败。这个参数不是越高越好你总觉得「慢了」但实测中并发 23 配合 800ms 间隔下载 200 张图的时间只比并发 8 慢 20% 左右稳定性和失败率却好得多。间隔参数同理它主要防的是「同一 IP 短时间高频请求被限流」一旦限流后面所有图都会失败重试反而更慢。4.2 过滤规则怎么写日期、帖子、格式有了参数还要有过滤逻辑不然脚本会把整个主页的公开图全拉下来。最常用的三条过滤规则按日期只下载最近 7 天或某个日期之后发布的帖子里的图。实现思路是页面里帖子列表会显示日期把日期文本解析成时间戳后和当前时间做差超过阈值就跳过。按帖子只在当前打开的帖子页生效创作者主页只列出图片清单但不自动下载。这个需求常见于「我只想备份某一位创作者的某一帖」。按格式只下载图片跳过视频和压缩包。因为有些帖子里挂着视频GM_download 虽然能下但视频文件动不动几百 MB不适合用浏览器脚本批量拉。过滤条件的判定要在收集阶段就做而不是下载阶段。因为过滤掉的资源连清单都不要进这样能保证下载队列干净。我给这组规则配置了优先级日期 格式 帖子。也就是说先按日期砍掉过期老帖再对剩下的做格式筛选最后如果启用了「仅当前帖」模式就直接锁死容器范围。4.3 并发队列与断点记忆批量下载的骨架是并发队列配合断点记录才能保证中途挂断不从头再来。一段常用的实现async function runQueue(tasks, limit) { const results []; const pool []; let i 0; const worker async () { while (i tasks.length) { const index i; const task tasks[index]; const done GM_getValue(doneMap, {}); if (done[task.url]) { results[index] { status: skipped }; continue; } try { await downloadOne(task); // 内部带重试逻辑 const map GM_getValue(doneMap, {}); map[task.url] 1; GM_setValue(doneMap, map); results[index] { status: ok }; } catch (e) { results[index] { status: fail, err: e }; } } }; for (let k 0; k limit; k) pool.push(worker()); await Promise.all(pool); return results; }这里有两个设计点值得解释。第一断点续传的本质是「记住已成功下载的 URL」而不是「检查本地文件是否存在」。浏览器脚本没有能力直接读磁盘目录用 GM_setValue 把完成的 URL 记在本地存储里是最可靠的。下次跑同一批清单时命中的 URL 直接跳过实现幂等。第二limit个 worker 共享同一个i指针谁空闲谁拿下一个任务避免任务分配不均导致部分 worker 空闲、部分顶着满负荷。断点记录有一个边界坑如果你改了命名模板同样的 URL 下次会生成新文件名但doneMap里还记着旧 URL导致这次什么都不下。解决方法是把模板版本号也存进去改模板时清空一次记录。GM_setValue 的存储量有限制几万条记录没问题几十万条就要考虑换数组缓冲了自用场景基本到不了这个量级。5. 常见问题与避坑缩略图混入、拦截、漏图和权限失效这个方案最气人的不是「下不下来」而是「下下来了才发现不对」。第四张开始全是缩略图、文件全是 0 字节、滚动加载后直接漏一批——这些我全踩过。下面四条是按翻车频率排的每条都按「现象 → 原因 → 解决」写清楚。5.1 下回来的图全是几百 KB 的缩略图现象下载目录里文件都在但凡是正文图片统统是 400 × 300 左右的小图根本没法用。原因抓取优先级里img.src排到了最前面而 Fantia 页面的缩略图铺得满屏都是。尤其是有 srcset 的图片浏览器会根据视口宽度自动选一张合适尺寸的渲染脚本看到的是渲染后的缩略图 URL而不是原图链接。另一种情况是懒加载>const observer new MutationObserver(() { const fresh collectImageEntries(); if (fresh.length 0) { queue.push(...fresh.filter(e !seenSet.has(e.url))); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true });注意attributes: true也要开着因为有些懒加载是直接把>const dryRun true; // 改 false 才会真正下载 if (dryRun) { const entries collectImageEntries().map(e ({ filename: buildFilename(e), sizeKB: 未请求, url: e.url, })); console.table(entries); } else { await runQueue(collectImageEntries(), maxConcurrent); }6.2 选择器失效的自查两步Fantia 一旦改版最常见的症状是「按钮在清单空」。此时不要急着改代码先做两步自查。第一步在浏览器控制台手动执行document.querySelectorAll(img[data-original]).length看返回的数字是否大于 0。如果返回 0说明style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表