首页 / 资讯中心 / 文章详情

网盘自动签到脚本:从接口抓包到定时任务长期维护

网盘自动签到脚本:从接口抓包到定时任务长期维护 ★ FEATURED ARTICLE
每天点一次签到按钮二十秒的事但连续做了两百多天之后我因为出差换了台电脑中间断了一天之前攒的连续天数清零一个月能换的那点空间奖励也跟着泡汤。那一刻我才认真琢磨网盘自动签到这件事——它不是懒不懒的问题而是人去做周期性的、低价值的、容错为零的操作本身就不合理。这篇就把我从抓接口、写脚本、挂定时到长期维护的完整过程摊开讲包括第一版跑通的样子、后面踩的坑、以及哪些地方我最后选择了放弃自动化。适合会一点 Python、手里有常开设备或者愿意用托管流水的朋友也适合完全没写过自动化脚本但想弄明白一个签到脚本到底在干什么的读者。1. 连续签到断了一天之后我才决定把它写成脚本1.1 签到奖励的真实价值与手动操作的隐性成本先把账算清楚。多数网盘类服务的签到奖励无非是三种一点点容量、一点点下载额度、一点点积分换会员时长。单次价值很低低到你会觉得忘了就忘了。但它的设计逻辑恰恰是反过来的——奖励不靠单次给靠连续天数给。签到第 1 天给 10MB连续 30 天可能给 1GB断一天回到起点。也就是说这个机制的收益曲线是阶梯式上翘的前期几乎为零后期才值钱而你真正的成本不在点一下在于每天记得点一下。人做这种事的失败率有多高我自己的记录是两百多天断一次一年按 1.5 次算等于每年有 1.5 次把阶梯归零。更麻烦的是心理成本每天睡前想一遍今天签到了吗这种低频但长期的牵挂比实际点那一下累多了。所以自动化的真正收益不是省下二十秒是把这件必须记着的事从脑子里彻底摘出去。这个动机决定了后面所有技术选择的方向——可靠性优先于炫技可维护优先于一次写爽。还有一个容易被忽略的点签到任务往往不止一个。同一家服务可能有网盘签到、会员签到、任务中心签到、小程序签到各自奖励不同。手动做是四次点击写脚本也是四个接口边际成本几乎为零。这也是我最终下决心写的原因——一次性投入长期四条链路一起跑。1.2 把点一下拆成四段可编程的链路在动手写之前我先在纸上把整个过程拆了一遍。任何一次签到无论界面长什么样底层都是这四段身份证明服务端怎么知道你是你。落到 HTTP 层面就是 Cookie、Token、请求头里的设备标识这些东西。请求构造签到这个动作对应哪个接口要带什么参数其中有些参数是动态生成的时间戳、随机串、签名。结果判定接口返回什么算成功什么算今天已经签过什么算你没登录。执行时机与反馈什么时候跑跑挂了怎么知道。这四段里第一段和第三段决定了脚本的寿命第二段决定了脚本能不能跑起来第四段决定了你晚上睡不睡得着。我见过很多人卡在第二段——费劲把签名算法逆向出来了结果因为 Cookie 三天两头失效再也没打开过那个脚本。所以我的建议是把百分之六十的精力放在登录态管理和失败通知上剩下百分之四十才是逆向接口。这个比例听起来反直觉但你真正跑上三个月就明白了。2. 动手前的侦察把一次签到请求完整看清楚2.1 用浏览器开发者工具锁定目标接口打开网页版网盘登录按 F12 调出开发者工具切到 Network网络面板。这里有两个必做的设置一是把过滤类型切到 Fetch/XHR把图片、字体、统计脚本这些噪音过滤掉二是勾上 Preserve log保留日志因为很多站点点完签到会跳转或者刷新不勾的话请求记录会被清空你什么都抓不到。准备好之后点一次签到按钮然后盯住请求列表里新冒出来的那一条。正常情况下你会看到一个很明显的候选请求路径里带sign、checkin、task、reward之类的词请求方法是 POST 或者 GET响应体是一小段 JSON。如果不确定是哪条就用排除法——按域名过滤掉统计类的第三方域名剩下的自家域名里通常只有十几条。找到之后右键选择复制为 cURL粘到文本编辑器里。这一步非常关键因为它把 URL、方法、请求头、请求体全部完整保留下来了。我习惯把它存到一个capture/目录里命名成checkin-20240115.txt日期带上将来接口改版的时候可以对比着看。这个习惯后来救了我好几次。提示抓包时把浏览器的扩展插件全部临时关掉尤其是那些会改写请求头的插件。我一开始排查了半天为什么脚本签名一直错最后发现是插件往请求里塞了一个额外参数。2.2 请求三件套登录态、签名参数、业务字段拿到 cURL 之后把请求拆成三部分看。登录态部分主要在请求头里。Cookie 通常是核心一段几十到几百字符的字符串往往由四五个键值对组成其中一两个是真正的会话凭证另外几个是设备标识或者用户 ID。我的做法是先整段照抄跑通之后再做减法——逐个删掉 Cookie 里的键值对看哪个删了会导致失败通常会发现只有两三个是必需的其余是统计用的。这个减法很有价值因为必需的键越少你后面维护时重新获取 Cookie 的成本越低。签名参数部分一般在 URL 查询串或者请求体里。常见的字段名是sign、signature、_sign、token、nonce、timestamp、ts、appid、channel。这里有个特别容易踩的坑时间戳单位。有的是秒级十位有的是毫秒级十三位差三个数量级签名算出来必然对不上而且报错信息往往只告诉你签名错误不会告诉你是时间戳的问题。我第一次就是在这上面栽的对着算法反复验算了两小时。业务字段部分就是跟签到本身相关的参数比如任务 ID、签到类型、版本号。这些通常比较固定照抄就行。唯一要注意的是有些站点会传一个页面加载时下发的随机串这种就需要先请求一次前置接口拿串。2.3 签名算法的定位思路顺着调用栈往上找不是所有签到接口都有签名。如果抓下来的请求里没有明显是 hash 的字段恭喜你直接跳过这一节。如果有就得找算法。我的流程是这样的在开发者工具的 Sources源代码面板里全局搜索sign这个关键词CtrlShiftF搜出来的结果一般会命中前端打包后的 JS 文件。这些文件是压缩过的一行几千字符先点一下格式化按钮那个{}图标让它变回可读的样子。然后在搜索结果里找形如sign: xxx或者xxx.sign 的赋值语句在那一行打上断点回到页面上再点一次签到按钮。断点触发后调用栈Call Stack面板会列出一串函数名。从最顶上往下翻你会看到一个函数它的入参里包含了你刚才在请求里看到的那些业务字段——那个函数就是负责拼装参数的。这时候把它的逻辑读一遍通常是参数按 key 的 ASCII 码升序排列拼成k1v1k2v2的形式末尾拼上一段固定字符串盐值然后做一次 MD5 或者 SHA256。常见的变体有拼串时对值做了 URL 编码盐值不是固定的而是从别的地方取的哈希结果要求大写参数里要排除掉空值。这些细节只要照着原逻辑一比一复刻基本不会错。我用 Python 复刻的时候习惯先写一个函数把浏览器里断点看到的原始拼串打印出来和自己算的拼串逐字符比对确认完全一致了再去算哈希。这样即使出错也能立刻定位是拼串错还是哈希错。import hashlib from urllib.parse import quote def build_sign(params: dict, salt: str) - str: # 与前端保持完全一致ASCII 升序、跳过空值、值做 URL 编码 items [(k, v) for k, v in params.items() if v not in (None, )] items.sort(keylambda kv: kv[0]) raw .join(f{k}{quote(str(v), safe)} for k, v in items) salt print([DEBUG] raw , raw) # 和浏览器里的拼串比对 return hashlib.md5(raw.encode(utf-8)).hexdigest()3. 第一版脚本先跑通再谈优雅3.1 目录结构与配置分离第一版脚本我写得非常不优雅所有东西堆在一个文件里Cookie 硬编码在代码顶部。这样跑了两周之后每次 Cookie 失效我都得改代码、重新上传、重新部署烦到不行。后来做了第一件事就是拆配置。现在的目录结构是这样pan-checkin/ ├── config.json # Cookie、任务 ID、通知地址 ├── checkin.py # 主流程 ├── signer.py # 签名算法 ├── notify.py # 通知封装 ├── logs/ │ └── 2024-01-15.log └── requirements.txtconfig.json长这样{ accounts: [ { name: 主账号, cookie: SESSIONxxxx; uid123456; DEVICEyyyy, user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., tasks: [daily_sign, task_center], delay_range: [3, 12] } ], notify_url: https://example.invalid/hook/xxxx, retry: {times: 3, backoff: 30} }关键设计有两个。一是账号数组即使现在只有一个账号也用数组包起来将来加账号不用改结构。二是随机延迟区间delay_range决定了多个任务之间间隔多久这个后面第 5 节会详细说为什么必须有。配置文件本身不进版本库我用环境变量在运行时注入避免 Cookie 被提交到代码仓库里——这种事我见得太多了有人把自己账号的 Cookie 连同脚本一起公开出去等于把家门钥匙贴在门上。3.2 核心签到函数与响应判定主流程的逻辑很简单读配置、建会话、逐个任务发请求、判定结果、汇总、通知。真正需要花心思的是判定。import json, time, random, logging import requests def run_task(sess, account, task, conf): url, payload build_request(task, account) r sess.post(url, datapayload, timeout15) body r.text[:500] try: data r.json() except ValueError: return unknown, f非 JSON 响应: {body} code data.get(errno, data.get(code)) msg str(data.get(errmsg, data.get(msg, ))) if code in (0, 200): return success, msg or 签到成功 if 已签到 in msg or 重复 in msg: return duplicated, msg # 幂等视为成功 if code in (401, 1001) or 未登录 in msg: return need_login, msg # 需要人工介入 if sign in msg.lower() or 签名 in msg: return sign_error, msg # 算法或参数变了 return failed, fcode{code} msg{msg} body{body}注意我把返回状态分成了六类而不是简单的成功/失败。这个分类是长期维护的关键——不同的状态对应完全不同的处理方式success和duplicated都不需要打扰你need_login必须立刻通知sign_error说明接口层面变了需要你回来改代码failed可能是网络抖动重试就行。如果只有一个布尔值你收到失败通知之后还得自己去翻日志猜原因。duplicated这个分支特别重要。因为定时任务可能因为各种原因被触发两次比如手动补跑如果不把今天已经签过当成成功你会收到一堆误报然后你就开始无视通知了——这是自动化脚本死掉的第一大原因通知疲劳。3.3 重试、退避与已签到的幂等处理重试这件事我踩过的坑比想象中多。最初的写法是无脑重试三次间隔固定 5 秒。结果有一次服务端返回的是操作过于频繁脚本又连着重试三次直接把账号推进了短时限制名单后面两天所有请求都被拒。后来的做法是分错误类型决定要不要重试| 错误类型 | 是否重试 | 策略 | | 连接超时 / DNS 失败 | 是 | 指数退避10s、40s、120s | | 5xx 服务端错误 | 是 | 退避最多两次 | | 401 / 未登录 | 否 | 立即停止发通知 | | 签名错误 | 否 | 立即停止发通知 | | 频繁 / 限流 | 否 | 立即停止当日不再跑 | | 已签到 | 否 | 标记成功不再请求 |def with_retry(fn, times3, base10): for i in range(times): status, msg fn() if status ! retryable: return status, msg wait base * (4 ** i) random.randint(0, 5) logging.warning(第 %d 次重试等待 %ds, i 1, wait) time.sleep(wait) return failed, 重试耗尽指数退避的基数选 10 秒、公比 4不是什么精妙的调参纯粹是因为服务端的限流窗口通常是分钟级的间隔太短没有意义而超过两分钟的重试又失去了当天补上的价值。这个数值你可以根据自己的服务特性调整但原则是宁可一次不补不要连续猛冲。4. 让它自己醒来定时执行的几种落地方式4.1 本机 cron最朴素也最可控最省事的方案就是在你常开的机器上用 cron。写好一行 crontab剩下的交给系统。SHELL/bin/bash PATH/usr/local/bin:/usr/bin:/bin 17 8 * * * cd /opt/pan-checkin sleep $((RANDOM % 1500)) /usr/bin/python3 checkin.py logs/$(date \%F).log 21这一行里有三个细节值得说。第一时间选在 8:17 而不是 8:00。整点是最拥挤的时刻很多人的脚本都挂在整点服务端在那一分钟的压力最大出错的概率也最高。往后错开十几分钟成功率高不少。具体错开多少看你心情只要别是 00 分就行。第二sleep $((RANDOM % 1500))这个随机延迟。它让每天的实际执行时刻落在 8:17 到 8:42 之间浮动。固定时刻的请求在服务端日志里是一个非常明显的特征——同一个账号、每天同一分同一秒、连请求头都一模一样。这种规律性对账号没有任何好处。第三$(date \%F)里的反斜杠。cron 里%是特殊字符代表换行必须转义成\%否则整行会被截断你会得到一个看起来配置了但什么都不执行的诡异状态。我第一次配的时候在这上面浪费了半小时因为 cron 不报错日志里什么都没有。cron 的缺点是依赖机器常开。笔记本合盖休眠、系统更新重启、家里跳闸任何一次都会让当天的任务错过。我后来在脚本里加了一个补跑逻辑启动时先检查今天有没有成功记录没有就立刻跑一次。这样机器重启之后 cron 没触发也没关系开机就补上。4.2 容器与轻量设备把脚本挪出主力机主力机上跑脚本有两个问题一是环境依赖容易和别的项目打架二是你会忍不住去改它。用容器包一层能解决第一个问题顺便让部署变成可复制的。FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . ENV TZAsia/Shanghai CMD [python, checkin.py]docker run -d --name pan-checkin \ --restart unless-stopped \ -e PAN_COOKIESESSIONxxxx; uid123456 \ -v /opt/pan-checkin/logs:/app/logs \ pan-checkin:latest--restart unless-stopped这个参数很重要它保证宿主机重启后容器会自己起来。ENV TZ也要设否则容器里默认是 UTC你写每天 8 点跑实际会在北京时间 16 点跑而你的日志时间戳全是 UTC排查时会非常迷惑。更彻底一点的做法是把脚本扔到一台低功耗的小主机或者常开的路由器上用一个精简的定时器触发。这样做的好处是它跟你的工作机彻底解耦你不会因为重装系统、换电脑、清理环境而把它弄丢。代价是那台设备本身也会挂所以补跑逻辑依然要有。4.3 托管流水线免维护但有时区与密钥的坑如果你实在没有常开设备用代码托管平台提供的定时流水线功能是个不错的选择。写一个配置文件声明每天某个时间跑一次这个命令平台会按时启动一个临时环境执行。name: daily-checkin on: schedule: - cron: 17 0 * * * # 平台默认 UTC对应北京时间 08:17 workflow_dispatch: jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.11 - run: pip install -r requirements.txt - run: python checkin.py env: PAN_COOKIE: ${{ secrets.PAN_COOKIE }}这里有三个坑必须提醒。时区。平台几乎都用 UTC 作为定时基准17 0 * * *才是北京时间 8:17。我第一次配的时候写了17 8 * * *结果每天下午 4 点跑签到是成功了但提前量没了如果当天接口出问题我下班前根本没机会处理。密钥管理。Cookie 一定要放在仓库的加密变量Secret里不要写在配置文件里提交上去。哪怕仓库是私有的也养成这个习惯。调度延迟。托管平台的定时任务在高峰期可能延迟几十分钟才启动这是正常现象不能指望它精确到分钟。如果你的签到窗口很窄比如必须在某个时间点前完成这条路就不合适。三种方案横向对比| 方案 | 首次搭建成本 | 长期维护成本 | 断电或重启的影响 | 适用场景 | | 本机 cron | 低写一行就行 | 中Cookie 失效要手动改 | 当天可能错过需补跑 | 有常开电脑或小主机 | | 容器 宿主机 | 中要写 Dockerfile | 低配置集中管理 | 重启后自动恢复 | 已有家用服务器 | | 托管流水线 | 低写 YAML | 中密钥与时区要理清 | 不受本机影响 | 没有常开设备 |我最后的选择是容器 一台低功耗小主机理由是它完全脱离我的日常设备我换电脑、重装系统都不会影响它而且日志在我自己的硬盘上排查起来顺手。5. 长跑阶段的真实故障登录态、风控与误报5.1 登录态失效的三种典型表现脚本跑起来之后百分之八十的维护工作都跟登录态有关。Cookie 的有效期从几天到几个月不等取决于服务端的策略和你的使用行为。失效的表现有三种处理方式完全不同。第一种接口直接返回未登录。最友好的一种状态码是 401 或者业务码里带请先登录。脚本能准确识别发通知给你你重新登录一次、复制 Cookie、更新配置完事。第二种接口返回成功但奖励没到账。这是最阴险的。请求被重定向到了一个登录页返回的是 HTML而你的脚本把 HTTP 200 当成了成功。我第一次遇到这个问题之后加了一条硬性校验响应必须是 JSON且必须包含预期的业务字段否则一律判为异常。这个校验后来拦住了好几次类似情况。第三种部分接口失效。一个 Cookie 可能对签到接口有效对任务中心接口已经失效了。如果你的脚本把多个任务打包成一个整体一个失败就整体失败你会很难定位。所以任务应该独立判定、独立汇总。延长 Cookie 寿命有一些实操技巧保持和手动登录时一致的 User-Agent这个真的有用我见过 UA 不匹配导致 Cookie 被提前作废的情况不要在多台设备上频繁切换同一个账号请求时带上 Referer指向你平时访问的页面。这些细节看起来无关紧要但它们让请求看起来像正常访问减少被判定为异常的概率。5.2 频率与指纹别让脚本看起来像机器一个签到的正常人类行为长什么样大致是早上打开 App 或者网页滑动几下点签到可能再看两眼别的页面然后退出。而一个粗糙的脚本长什么样每天同一秒发起请求只请求签到接口不做任何其他动作请求头里少了三四个浏览器会带的字段结束后立刻断开连接。这两者的差别服务端不需要多复杂的模型就能分辨。我不是说要你去伪造一整套浏览器行为那属于另一个层面的工程对签到这件事来说完全没必要。但有几点是低成本高收益的错开时间前面说的随机延迟让每天的执行时刻浮动。保留正常的请求头User-Agent、Referer、Accept 这些照抄浏览器不要只留一个 Cookie 就发请求。任务之间加间隔多个任务不要在同一毫秒发出去间隔几秒到十几秒的随机值。控制总量每天就一次签到加两三个任务不要额外去请求接口探活、刷新、拉数据能少一次就少一次。我特别想说的是最后一点。有些人喜欢写一个每小时检查一次看看签到成功没的循环这纯粹是给自己找麻烦。签到是幂等操作一天跑一次、失败重试一次足够了。多出来的请求不会让结果更好只会让你的账号在服务端统计里变得显眼。5.3 通知设计只在需要人介入时吵你通知的策略直接决定了这个脚本能活多久。我见过太多人一开始把通知配得特别全成功也推、失败也推手机每天响一次一周之后就开了免打扰一个月之后再也不看。这时候脚本挂了三个月他都不知道。我的原则是默认静默只在需要人动手的时候响。具体规则| 状态 | 是否通知 | 通知内容 | | success | 否 | 只在日志里记一行 | | duplicated | 否 | 同上标记为幂等成功 | | need_login | 是且高优先级 | 标题写明需要更新登录态 | | sign_error | 是 | 附上原始响应片段方便定位 | | failed 且重试耗尽 | 是 | 附上最后一次的完整响应 | | 连续 3 天未成功 | 是且单独触发 | 说明脚本可能已经整体失效 |最后一条是关键补充。因为有些故障是被静默吞掉的——比如脚本根本没跑起来cron 被删了、容器挂了这种情况下你连失败通知都收不到。所以需要一个独立的心跳检查记录每次运行的时间戳如果超过 26 小时没有新的成功记录就触发一条脚本可能停了的通知。这个检查可以放在同一个脚本的最开头也可以单独做一个更轻量的任务。def check_heartbeat(state_file, hours26): last json.load(open(state_file))[last_success] gap (time.time() - last) / 3600 if gap hours: notify(f签到脚本可能已停摆已 {gap:.1f} 小时无成功记录)6. 我踩过的几个坑与长期维护的取舍6.1 一次看起来成功的假签到这个坑值得单独讲因为它差点让我白跑两个月。现象是日志里每天都是签到成功通知也从来没响过但我某天进网页一看连续天数只有 3 天。排查过程是这样的。第一步看日志脚本记录的是code0看起来完全正常。第二步看保存的原始响应僵住了——响应体里根本没有奖励相关的字段只有{code:0,msg:ok}。第三步用同样的 Cookie 在浏览器里手动点一次返回的却是完整的奖励详情。差别在哪把两边的请求头逐行 diff发现脚本少传了一个X-Requested-With头服务端把它当成了普通页面请求走了一个静默通过但什么都不做的分支仍然返回 200 和 code 0。这件事给我两个教训。第一判定成功不能只看状态码要校验业务字段。改成响应里必须出现奖励数值或者连续天数之后这类问题就不会再漏过。第二保留原始响应。如果我只存了一个布尔值这个问题永远查不出来。现在我的日志里每条记录都带着截断到 500 字符的原始响应日志文件大一点但值。6.2 接口改版后的快速定位流程服务端改版是必然会发生的频率大概半年到一年一次。改版之后脚本会突然全部返回sign_error或者干脆 404。这时候的排查流程我固化成了四步第一步确认是不是普遍现象。去网页版手动点一次签到如果手动也失败那是服务端的问题等就行不用改代码。第二步重新抓一次包和旧包对比。这就是前面说的capture/目录的价值。把新旧两份 cURL 并排放在一起逐行看差异URL 变了没、请求体多了什么字段、签名参数名改了没、请求头少了什么。绝大多数改版都只是加减字段看一眼就清楚。第三步如果签名算法变了重新定位。重复第 2.3 节那套流程打点、看调用栈、找到新的拼串逻辑。这一步最费时间但因为有旧版本作为对照通常能发现只是盐值换了或者排序规则从升序改成了降序。第四步改完先在本地跑一次确认奖励真的到账。不要改完看到 code0 就部署去网页上确认一下连续天数加了一天。这一步多花三十秒能避免你带着一个假成功的脚本再跑一个月。6.3 什么时候该放弃自动化最后说说边界。这东西写着写着容易上头我一度想把所有带签到字样的服务都接进来后来及时刹住了。有几种情况我会明确选择不写脚本接口有强人机校验的。如果签到前需要拖动滑块、点选图片、输入短信验证码那说明服务方明确不希望被自动化。这种情况下硬要做成本极高而且不体面直接放弃手动点。频率限制严格的。有些服务对签到接口做了很紧的限制稍微频繁一点就会触发短时封禁。这种服务即使写出来收益也覆盖不了风险——你可能因为一次重试把账号搞进限制名单耽误的是正常使用。单账号之外的场景。这个必须说清楚签到自动化的合理范围是你自己常用的账号。批量注册账号刷奖励、把脚本分享出去让别人挂几百个号这既违反服务条款也把风险转嫁给了别人。我写的所有脚本都只服务我自己的两三个账号多一个都不加。收益已经趋近于零的。有些服务后期把签到奖励砍到几乎没有这时候继续维护脚本的时间成本已经超过了奖励本身。我去年就下线了两个这样的任务删代码的那一刻还挺轻松的——维护的东西越少剩下那部分才能跑得久。回过头看整个项目真正的难点从来不是怎么算签名那部分我当时花了一个周末就搞定了。难的是让它持续地、安静地跑上一年中间不用你操心太多。这意味着你要把大量的精力放在失败判定、通知策略、登录态管理、补跑逻辑这些不性感的地方。我现在这套东西大概是三百多行 Python加上一个 Dockerfile 和一个配置文件跑在角落里一台小主机上每天早上的随机某个时刻跑一次一年下来主动找我的次数不超过十次基本都是要更新 Cookie。这个状态我觉得就挺好了——工具应该退到背景里而不是变成另一件需要你每天惦记的事。
阅读完成 · 觉得有帮助?
咨询建站