
1. 这个脚本到底解决了什么问题第一次接触共享账号这个概念是在一个资源交流群里。当时有人发了一个链接说“装个油猴脚本就能一键登录不用自己注册”。我当时的反应是这东西靠谱吗后来自己折腾了一遍发现背后的逻辑其实不复杂就是把一套已经登录好的会话凭证通过脚本注入到浏览器里让浏览器“以为”你已经登录过了。Exhentai-Shared-Account 这个项目本质上就是一个 Tampermonkey 用户脚本。它的核心功能非常直接当你访问目标站点时脚本会自动把预先配置好的 Cookie 写入当前域名下然后刷新页面你就直接进入了登录状态。整个过程不需要你手动输入账号密码也不需要你理解 Cookie 的工作原理——点一下等两秒页面就变了。这个脚本适合什么人用我总结了三类第一类是对用户脚本完全不了解、但想快速体验一下的新手第二类是有一定前端基础、想研究 Cookie 注入和会话保持机制的人第三类是想自己搭一套类似共享方案、需要参考实现思路的开发者。如果你属于这三类中的任何一类下面的内容应该都能帮到你。需要提前说明的是这类脚本的核心原理涉及浏览器 Cookie 操作和用户脚本注入理解这些原理比单纯“抄一个脚本”更有价值。我在实际使用和拆解的过程中踩过不少坑也总结了一些文档里不会写的细节后面会逐一展开。2. 核心原理拆解Cookie 注入到底是怎么回事2.1 Cookie 是什么为什么它能让你“免登录”要理解这个脚本首先得搞清楚 Cookie 在登录流程里扮演什么角色。简单来说当你在一个网站上输入账号密码并点击登录后服务器验证通过会返回一个带有 Set-Cookie 头的响应。浏览器收到这个头之后会把对应的键值对存储到本地并在后续对该域名的每一次请求中自动带上这些 Cookie。服务器看到请求里带着有效的会话 Cookie就知道“这个人已经登录过了”于是直接返回登录后的页面内容。整个过程就像你去健身房前台给你发了一个手环之后你进出各个区域只需要刷手环不需要每次都去前台报手机号。所以共享账号的核心思路就是把别人已经登录好的那个“手环”也就是 Cookie复制一份给你你戴上它服务器就认你了。Exhentai-Shared-Account 脚本做的事情就是帮你自动完成“戴手环”这个动作。2.2 用户脚本的注入时机与执行环境Tampermonkey 这类用户脚本管理器会在页面加载的不同阶段提供钩子。常见的注入时机包括 document-start、document-end 和 document-idle。这个脚本选择在 document-start 阶段执行原因很实际Cookie 必须在页面发起任何网络请求之前就写好否则第一波请求已经发出去了服务器发现你没带有效 Cookie可能直接把你重定向到登录页或者返回错误页面。在 document-start 阶段DOM 还没有构建完成你没法操作页面元素但可以操作 document.cookie。这就是为什么脚本里看不到任何 querySelector 或 addEventListener 的原因——它根本不需要碰页面结构只需要在最早的时机把 Cookie 写进去。这里有一个容易被忽略的细节document.cookie 的写入是同步的但浏览器对 Cookie 的生效有一定的延迟。脚本在写入之后通常会跟一个 location.reload()强制刷新页面让新的 Cookie 在下一轮请求中生效。如果不刷新当前页面的后续请求可能仍然使用旧的 Cookie 状态。2.3 共享账号方案的优势与局限相比自己注册账号共享方案的优势很明显省去了注册流程不需要验证邮箱也不需要等待账号审核。对于只是想快速看一眼内容的人来说门槛几乎为零。但局限同样明显。第一共享账号的 Cookie 有有效期一旦过期所有人都用不了需要有人重新提供。第二多人同时使用同一个会话服务器端可能会检测到异常并发触发风控。第三从安全角度来说你无法确认共享 Cookie 的来源是否可信虽然脚本本身只做 Cookie 写入但 Cookie 本身可能携带其他信息。我在实际使用中最大的感受是这东西适合临时用一下不适合长期依赖。如果你发现自己需要频繁使用还是建议走正规注册流程。3. 脚本实现细节与关键代码解析3.1 脚本头部元数据的作用一个标准的 Tampermonkey 脚本开头必须有一段 UserScript 注释块。这段注释不是普通的注释Tampermonkey 会解析它来决定脚本的运行时机、匹配规则和权限。// UserScript // name Exhentai-Shared-Account // namespace https://example.com/ // version 1.0 // description 一键注入共享账号 Cookie // author anon // match *://*.example-site.com/* // grant none // run-at document-start // /UserScript这里有几个关键字段需要解释。match 决定了脚本在哪些 URL 上生效写得太宽会误伤其他站点写得太窄又可能匹配不到目标页面。grant none 表示脚本不需要调用 Tampermonkey 提供的特殊 API直接在页面上下文中运行。run-at document-start 是最重要的一个设置它确保脚本在页面任何其他脚本之前执行。我见过有人把 run-at 写成 document-end结果脚本执行的时候页面已经发了好几轮请求Cookie 注入完全无效。这个坑很隐蔽因为脚本本身不报错只是“没效果”。3.2 Cookie 写入的具体实现脚本的核心逻辑通常是这样一段代码(function() { use strict; const cookies [ { name: session_id, value: abc123def456, domain: .example-site.com, path: / }, { name: auth_token, value: xyz789, domain: .example-site.com, path: / } ]; cookies.forEach(function(c) { document.cookie c.name c.value ; domain c.domain ; path c.path ; secure; sameSiteNone; }); if (!sessionStorage.getItem(cookie_injected)) { sessionStorage.setItem(cookie_injected, 1); location.reload(); } })();这段代码有几个值得注意的地方。第一domain 前面加了一个点表示该 Cookie 对主域名及其所有子域名都有效。第二secure 标志要求 Cookie 只能通过 HTTPS 传输如果目标站点是 HTTP 的这个标志会导致 Cookie 写入失败。第三sameSiteNone 是为了兼容跨站请求场景但现代浏览器要求 sameSiteNone 必须配合 secure 使用。sessionStorage 的那段判断是为了防止无限刷新。如果不加这个判断脚本每次页面加载都会写入 Cookie 然后刷新刷新后又触发脚本又刷新陷入死循环。用 sessionStorage 做一个标记确保每个会话只刷新一次。3.3 刷新策略与防死循环处理刷新策略是这类脚本最容易出问题的地方。我试过几种不同的方案各有优劣。第一种是直接 location.reload()简单粗暴但如果没有防重复机制就会死循环。第二种是 location.href location.href效果类似但某些浏览器会把它当作导航而不是刷新行为不一致。第三种是检查当前页面是否已经是登录状态如果是就不刷新这需要额外的 DOM 检测逻辑增加了复杂度。最终比较稳妥的方案是 sessionStorage 标记加一次性刷新。sessionStorage 的特点是只在当前标签页有效关闭标签页后自动清除。这意味着你新开一个标签页访问目标站点时脚本会重新注入并刷新一次之后在同一个标签页内就不会再刷新了。注意如果你在多个标签页同时打开目标站点每个标签页都会独立执行一次注入和刷新。这本身不是问题但如果共享 Cookie 本身已经失效你会看到多个标签页同时报错体验比较糟糕。4. 从零开始搭建完整实操流程4.1 环境准备与脚本管理器安装第一步是安装 Tampermonkey 扩展。Chrome、Edge、Firefox 等主流浏览器都支持直接在扩展商店搜索安装即可。安装完成后浏览器工具栏会出现一个 Tampermonkey 图标点击后选择“添加新脚本”就能进入脚本编辑界面。这里有一个新手常问的问题Tampermonkey 和 Violentmonkey 有什么区别两者功能高度重叠Tampermonkey 的生态更成熟文档更全遇到问题更容易搜到答案。Violentmonkey 开源程度更高代码可审计性更好。对于这个脚本来说两者都能正常运行选哪个看个人偏好。安装完成后建议在 Tampermonkey 的设置里开启“高级模式”这样可以看到更多配置选项比如脚本的更新策略、注入时机等。默认的“新手模式”会隐藏一些高级功能虽然不影响基本使用但调试时会不太方便。4.2 获取并配置共享 CookieCookie 的来源通常有两种一种是项目维护者定期在某个页面更新可用的 Cookie 字符串另一种是社区成员自发分享。无论哪种来源你拿到的通常是一段类似这样的文本session_idabc123; auth_tokenxyz789; other_keyother_value你需要把这段文本拆解成脚本能识别的格式。手动拆解容易出错我一般用一个小工具函数来处理function parseCookieString(str) { return str.split(;).map(function(pair) { const parts pair.trim().split(); return { name: parts[0], value: parts.slice(1).join(), domain: .example-site.com, path: / }; }); }注意 value 部分用了 slice(1).join()这是因为有些 Cookie 的值本身包含等号如果直接 split() 取第二项值会被截断。这个细节在 Cookie 值包含 Base64 编码时特别重要因为 Base64 字符串经常以等号结尾作为填充。配置完成后把脚本里的 cookies 数组替换成解析后的结果保存脚本然后访问目标站点测试。4.3 验证注入是否生效验证方法有几种。最直接的是打开浏览器开发者工具切换到 Application 面板在左侧找到 Cookies 分类展开目标域名看看你配置的那些 Cookie 键值对是否已经存在。如果 Cookie 存在但页面仍然显示未登录可能的原因包括Cookie 已过期、Cookie 的 domain 配置不正确、目标站点使用了额外的验证机制比如指纹校验。这时候可以尝试在 Network 面板里查看实际发出的请求对比请求头里的 Cookie 字段和你配置的是否一致。我遇到过一次比较诡异的情况Cookie 明明写进去了请求头里也带了但服务器就是不认。后来发现是系统时间不对导致 Cookie 的过期时间判断出了问题。把系统时间校准后一切正常。这个坑排查了很久因为从表面上看完全找不到原因。5. 常见问题与排查技巧实录5.1 Cookie 写入失败的原因分析Cookie 写入失败是最常见的问题表现是脚本执行了但 Cookie 没出现在开发者工具里。根据我的经验原因可以归为以下几类问题现象可能原因排查方法Cookie 完全没写入domain 配置错误检查 domain 是否与当前访问域名匹配Cookie 写入后立即消失secure 标志与 HTTP 冲突确认站点协议HTTP 站点去掉 secure部分 Cookie 写入成功单个 Cookie 格式错误逐个检查 name/value 是否包含非法字符写入成功但请求不带path 配置错误确认 path 为 / 或与请求路径匹配刷新后 Cookie 丢失服务器设置了 HttpOnlyHttpOnly Cookie 无法通过 JS 写入其中 HttpOnly 是一个硬性限制。如果目标站点的会话 Cookie 被标记为 HttpOnly那么通过 JavaScript 的 document.cookie 是无论如何也写不进去的。这种情况下共享账号方案在纯脚本层面就走不通了需要考虑其他方式。5.2 页面刷新死循环的解决思路死循环的表现是页面不断刷新根本停不下来。根本原因是脚本在每次页面加载时都执行了刷新操作而刷新又触发了脚本。解决思路是引入一个“只执行一次”的标记。sessionStorage 是最常用的选择因为它的生命周期刚好和标签页一致。也可以用 localStorage 加时间戳的方式但 localStorage 是跨标签页共享的可能会导致新开的标签页不执行注入。const INJECT_KEY exhentai_injected_at; const lastInject sessionStorage.getItem(INJECT_KEY); const now Date.now(); if (!lastInject || now - parseInt(lastInject) 60000) { sessionStorage.setItem(INJECT_KEY, now.toString()); location.reload(); }这段代码的意思是如果从未注入过或者距离上次注入超过 60 秒就执行注入并刷新。60 秒的间隔是为了防止 Cookie 过期后需要重新注入时被标记挡住。5.3 共享 Cookie 失效后的应对策略共享 Cookie 失效是必然的只是时间早晚的问题。失效的表现通常是Cookie 还在但访问目标站点时被重定向到登录页或者返回 403 错误。这时候你需要获取新的 Cookie。如果项目维护者提供了自动更新机制脚本可能会定期从某个远程地址拉取最新 Cookie。如果没有就只能手动更新。我在实际使用中总结了一个小技巧把 Cookie 的获取和更新做成一个半自动流程。用一个简单的本地 HTML 页面粘贴新的 Cookie 字符串点击按钮生成格式化后的脚本代码然后复制粘贴到 Tampermonkey 里。虽然还是要手动操作但比直接编辑脚本里的数组要快得多也不容易出错。提示更新 Cookie 后记得清除 sessionStorage 里的注入标记否则脚本会认为已经注入过了不会重新执行。在开发者工具的 Application 面板里可以手动清除 sessionStorage。6. 安全边界与使用建议6.1 共享账号的固有风险共享账号最大的风险在于 Cookie 的不可控性。你拿到的 Cookie 可能来自任何人虽然它只是一个会话凭证不包含你的个人信息但使用它意味着你的所有操作都发生在别人的会话上下文里。从技术角度来说服务器端可以看到同一个会话 ID 来自不同的 IP 地址、不同的浏览器指纹。如果站点有风控机制可能会把这个会话标记为异常导致所有人都用不了。这也是为什么共享账号通常“活不长”的原因。另外如果你在使用共享账号的同时登录了自己的其他服务虽然 Cookie 是按域名隔离的但浏览器指纹、IP 地址等信息的关联性仍然存在。对于安全要求高的场景建议使用独立的浏览器配置文件。6.2 脚本权限的最小化原则Tampermonkey 脚本可以申请各种权限比如跨域请求、访问浏览器存储、读写剪贴板等。这个脚本只用了 grant none意味着它不需要任何特殊权限只在页面上下文中运行。这是最安全的配置。如果你看到某个类似脚本申请了 grant GM_xmlhttpRequest 或者 grant GM_setValue就要多留一个心眼。跨域请求权限意味着脚本可以向任意服务器发送数据存储权限意味着脚本可以在你的浏览器里持久化保存信息。不是说这些权限一定有问题但你需要知道它们的存在。我在审查任何用户脚本时第一件事就是看头部注释里的 grant 和 match。match 决定了脚本能接触哪些网站grant 决定了脚本能做什么。两者结合基本就能判断出脚本的行为边界。6.3 什么情况下不建议使用这类方案有几种情况我建议直接放弃共享账号方案。第一如果你需要长期稳定使用共享 Cookie 的失效频率会让你不胜其烦。第二如果你对隐私比较在意使用来源不明的 Cookie 始终存在不确定性。第三如果目标站点有严格的并发检测共享账号可能触发封禁影响到其他正常用户。说到底这个脚本解决的是一个“临时、快速、低成本”的需求。把它当作一个应急方案或者学习素材是合适的把它当作长期依赖就不太明智了。7. 从脚本到方案可扩展的思路7.1 自动更新 Cookie 的可行路径如果你确实需要长期使用可以考虑给脚本加一个自动更新机制。基本思路是脚本在注入 Cookie 之前先向一个预设的远程地址发起请求获取最新的 Cookie 字符串解析后再写入。这个远程地址可以是一个静态的 JSON 文件托管在任意支持 HTTPS 的静态服务器上。脚本用 fetch 获取内容解析后注入。这样你只需要定期更新那个 JSON 文件所有使用脚本的人都能自动拿到最新 Cookie。async function fetchLatestCookies(url) { try { const resp await fetch(url ?t Date.now()); const data await resp.json(); return data.cookies; } catch (e) { console.error(Cookie 获取失败, e); return null; } }加时间戳参数是为了绕过缓存确保每次都拿到最新版本。这个方案的缺点是依赖外部服务如果服务挂了脚本也就失效了。7.2 多站点共享方案的通用化改造这个脚本的思路其实不局限于某一个站点。任何基于 Cookie 会话的网站都可以用类似的方式实现共享登录。改造的关键在于把域名、Cookie 名称、注入时机等参数抽出来做成可配置的。我后来把脚本改成了一个通用版本用一个配置对象来描述不同站点的注入规则const SITE_CONFIGS { site-a: { match: *://*.site-a.com/*, cookies: [session_id, auth_token], reloadDelay: 0 }, site-b: { match: *://*.site-b.com/*, cookies: [sid, uid], reloadDelay: 500 } };这样只需要维护一份配置就能覆盖多个站点。当然每个站点的 Cookie 格式和注入要求可能不同实际使用时还是需要针对性地调整。7.3 用户脚本开发的通用经验折腾这个脚本的过程中我积累了一些用户脚本开发的通用经验这里一并分享。第一永远在脚本最外层包一个 IIFE立即执行函数表达式避免变量污染全局作用域。第二用 use strict 开启严格模式很多隐蔽的错误会在严格模式下暴露出来。第三对于可能失败的操作比如网络请求、Cookie 写入一定要加 try-catch否则一个未捕获的异常会导致整个脚本停止执行。第四在脚本里加 console.log 输出关键步骤调试的时候会方便很多发布前再删掉或者用条件判断控制。这些经验看起来简单但真正在项目里坚持做到的人不多。我见过太多脚本因为一个未处理的异常在某个特定页面上完全失效而作者自己都不知道。最后再分享一个小技巧Tampermonkey 的脚本编辑器支持直接调试你可以在代码里打 debugger 语句打开开发者工具后脚本会在这里暂停然后你可以单步执行、查看变量值。这比用 console.log 打印要高效得多尤其是在排查复杂逻辑的时候。