Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

逆向百度网盘签到自动化

网上流传的百度网盘签到脚本几乎都用的同一套接口,拿来实测直接报 400。排查发现这批接口已经被百度下线了一年左右,现行可用的参数组合完全不同。本文记录从排查到复现的完整过程,以及防封号的请求编排设计。

背景

百度网盘会员有一套成长值体系,每天可以白嫖两笔成长值:

  • 签到:每日签到领成长值(连续签到有递增奖励)
  • 答题:每日一题,接口直接返回答案,提交就给分

老版本脚本(各大签到聚合项目抄来抄去的版本)都是这三个请求:

1
2
3
GET https://pan.baidu.com/rest/2.0/membership/level?app_id=250528&web=5&method=signin
GET https://pan.baidu.com/act/v2/membergrowv2/getdailyquestion?app_id=250528&web=5
GET https://pan.baidu.com/act/v2/membergrowv2/answerquestion?app_id=250528&web=5&ask_id=xxx&answer=x

带 BDUSS cookie 实测第一个请求直接挂:

1
2
HTTP 400
{"error_code":100002,"error_msg":"Param error","request_id":"..."}

排查过程

第一步是分清”cookie 失效”还是”接口变了”。拿同一个 cookie 打两个不同端点:

端点 参数 结果
/rest/2.0/membership/user?method=query web=5 200,返回真实会员数据(等级 9、成长值 69925、年度 SVIP)
/rest/2.0/membership/level?method=query web=5 400 + 100002 Param error
/rest/2.0/membership/level?method=signin web=5 400 + 100002 Param error

结论很清晰:cookie 是好的(user 接口正常吐数据),是 membership/level 这个端点的参数契约变了。再换成无 cookie 请求 level 端点,同样 400——它压根没走到鉴权那步,参数校验就拒了。

换 web=1、POST、加 clienttype=0 等参数变体全部无效,这条老路彻底死了。

GitHub 考古

去翻还在维护的签到项目,Sitoi/dailycheckin 的 issue #534(2025-10)就在报”百度网盘签到无反应”,至今没修——全量代码搜索发现几十个项目还在抄这套死掉的接口。

唯一有价值的是 xvkong233/qlscript 的 baidu_netdisk.py:作者在 2026-10 用 App 13.34.3 真机抓包重做了整套实现,注释里连反欺诈指纹、风控错误码表都写全了。从中拿到关键结论:

1
2
# 成长值通道现行参数组合
QUERY_PARAMS = {"app_id": "250528", "web": "1", "clienttype": "5", "clientinfo": "wap"}

老接口 web=5 被服务端下线,现行可用的是 web=1&clienttype=5&clientinfo=wap(wap 网页端通道)。

现行接口

以下全部实测验证过(2026-10-28)。请求头只需要三条:

1
2
3
4
5
6
7
HEADERS = {
"User-Agent": "Mozilla/5.0 (Linux; Android 16; Xiaomi 17 Pro Max) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36",
"Referer": "https://pan.baidu.com/wap/svip/growth/task",
"Accept": "application/json, text/plain, */*",
"X-Requested-With": "XMLHttpRequest",
}

1. 签到状态查询(只读)

1
GET /rest/2.0/membership/level?app_id=250528&web=1&clienttype=5&clientinfo=wap&method=signinlist
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"data": {
"list": [
{"id": 1, "is_signin": false, "points": 2},
{"id": 2, "is_signin": false, "points": 4},
{"id": 7, "is_signin": false, "points": 14}
],
"signed_cnt": 0,
"today_signed": false
},
"error_code": 0,
"error_msg": ""
}

today_signed 是当日是否已签的权威判定,signed_cnt 是连续签到天数,list 是本月签到日历(id 即第几天,points 是当日可领成长值)。

2. 签到(写)

1
GET /rest/2.0/membership/level?app_id=250528&web=1&clienttype=5&clientinfo=wap&method=signin
1
2
3
4
5
{
"error_code": 0,
"error_msg": "",
"result": {"points": 2}
}

签完再查一次 signinlist,日历第一天 is_signin 变 true、signed_cnt 变 1。重复请求时返回 error_code: 421001(error_msg 含 repeat)。

3. 每日答题(这组接口用老参数 web=5 反而活着)

取题:

1
GET /act/v2/membergrowv2/getdailyquestion?app_id=250528&web=5
1
2
3
4
5
6
7
8
9
10
{
"data": {
"answer": 1,
"answer_status": 1,
"ask_id": 1237,
"question": "做成长值任务是否可以加速提升会员等级?",
"score": 3
},
"errno": 0
}

answer 字段直接就是正确答案的序号——出题接口就是答案接口。answer_status: 1 表示今天已答过。

提交(把取到的 ask_id 和 answer 原样带回去):

1
GET /act/v2/membergrowv2/answerquestion?app_id=250528&web=5&ask_id=1237&answer=1

成功响应含 data.score;重复提交返回 errno: 9502。

4. 会员信息(顺带验证签到收益)

1
GET /rest/2.0/membership/user?app_id=250528&web=5&method=query

成长值在 level_info 里:current_level(等级)、current_value(成长值)。实测签到 +2 后这里从 69925 变 69927,数字对得上。

关键细节

把 cookie 字符串塞进 requests.Session().cookies(domain 填 .baidu.com),而不是拼到请求头里。原因:请求过程中服务端会下发 BAIDUID、PANWEB 等 Set-Cookie,会话 jar 会自动带上后续请求,写死 Cookie 头这些就全丢了,会话上下文不完整,风控侧看起来就是异常客户端。

cookie 要求很低:成长值/答题/信息查询这条链路只要 BDUSS 一个字段就够(实测无 STOKEN 全通)。整串浏览器 cookie 复制进来也行。

判定位汇总

场景 判定
签到状态 data.today_signed
签到成功 error_code == 0,积分在 result.points
重复签到 error_code == 421001 或 error_msg 含 repeat/已签
cookie 失效 error_code == -6 或 error_msg 含”登录”
取题成功 errno == 0,答案在 data.answer/data.ask_id
已答过 data.answer_status == 1(取题时)或 errno == 9502(提交时)
答题成功 响应含 data.score

Python 实现

自包含版本,cookie 作为参数传入,结果直接打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import re
import time
import requests

API_BASE = "https://pan.baidu.com"
# 2026-10 实测:签到通道必须用 web=1 + clienttype=5 + clientinfo=wap,
# 网上流传的 web=5 组合已被服务端下线(400 + 100002 Param error)
SIGNIN_PARAMS = {"app_id": "250528", "web": "1", "clienttype": "5", "clientinfo": "wap"}
GROWTH_PARAMS = {"app_id": "250528", "web": "5"}

UA = ("Mozilla/5.0 (Linux; Android 16; Xiaomi 17 Pro Max) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36")


def build_session(cookie_str: str) -> requests.Session:
"""cookie 放入会话 jar 而非写死 Cookie 头,
保证服务端运行中下发的 cookie(BAIDUID/PANWEB 等)能在后续请求回传"""
session = requests.Session()
session.headers.update({
"User-Agent": UA,
"Referer": "https://pan.baidu.com/wap/svip/growth/task",
"Accept": "application/json, text/plain, */*",
"X-Requested-With": "XMLHttpRequest",
})
for part in re.split(r"[;\n]+\s*", cookie_str):
if "=" not in part:
continue
name, value = part.split("=", 1)
if name.strip():
session.cookies.set(name.strip(), value.strip(), domain=".baidu.com", path="/")
return session


def pan_get_json(session, path, params):
resp = session.get(API_BASE + path, params=params, timeout=15)
if resp.status_code != 200:
raise RuntimeError(f"HTTP {resp.status_code}")
return resp.json()


def growth_signin(session) -> str:
"""成长值签到:先读 signinlist 判断今日是否已签(避免重复写请求触发风控),
未签才调用 signin"""
data = pan_get_json(session, "/rest/2.0/membership/level",
dict(SIGNIN_PARAMS, method="signinlist"))
if data.get("error_code") != 0:
return f"签到状态查询失败: {data.get('error_msg') or data}"
inner = data.get("data") or {}
if inner.get("today_signed"):
cnt = inner.get("signed_cnt")
return f"今日已签到, 已连续签到 {cnt} 天" if cnt else "今日已签到"

time.sleep(2)
result = pan_get_json(session, "/rest/2.0/membership/level",
dict(SIGNIN_PARAMS, method="signin"))
code = result.get("error_code")
if code == 0:
points = (result.get("result") or {}).get("points")
cnt = inner.get("signed_cnt")
msg = f"签到成功, 获得成长值: {points if points is not None else '未知'}"
if cnt:
msg += f", 已连续签到 {cnt + 1} 天"
return msg
error_msg = str(result.get("error_msg") or "")
if code == 421001 or "repeat" in error_msg.lower() or "已签" in error_msg:
return "今日已签到(重复请求)"
if code == -6 or "登录" in error_msg:
return "签到失败: cookie(BDUSS) 已失效"
return f"签到失败: {error_msg} (error_code={code})"


def answer_daily_question(session) -> str:
"""每日答题:出题接口直接返回答案,照抄提交即可;
answer_status=1 表示今天已答过,直接短路,不再发提交请求"""
data = pan_get_json(session, "/act/v2/membergrowv2/getdailyquestion", dict(GROWTH_PARAMS))
if data.get("errno") not in (0, None):
return f"获取日常问题失败: {data.get('show_msg') or data.get('errno')}"
inner = data.get("data") or {}
ask_id, answer = inner.get("ask_id"), inner.get("answer")
if not ask_id:
return "未找到日常问题或答案"
if inner.get("answer_status") == 1:
return "今日已答题"
if answer is None:
return "今日题目暂无答案, 跳过答题"

time.sleep(2)
result = pan_get_json(session, "/act/v2/membergrowv2/answerquestion",
dict(GROWTH_PARAMS, ask_id=ask_id, answer=answer))
if result.get("errno") == 9502:
return "今日已答题"
score = (result.get("data") or {}).get("score")
if score is not None:
return f"答题成功, 获得成长值: {score}"
show_msg = (result.get("data") or {}).get("show_msg") or result.get("show_msg") or ""
return f"答题未生效: {show_msg or result}"


def get_user_info(session) -> str:
"""查询会员等级与成长值"""
data = pan_get_json(session, "/rest/2.0/membership/user",
dict(GROWTH_PARAMS, method="query"))
info = data.get("level_info") or {}
level, value = info.get("current_level"), info.get("current_value")
if level is None or value is None:
return "获取用户信息失败: 响应中未找到等级/成长值字段"
return f"当前会员等级: {level}, 成长值: {value}"


def baidupan_checkin(cookie_str: str) -> str:
"""流程: 成长值签到 → 每日答题 → 查询会员等级与成长值"""
session = build_session(cookie_str)
results = [growth_signin(session)]
time.sleep(3)
results.append(answer_daily_question(session))
time.sleep(1)
results.append(get_user_info(session))
return "\n".join(results)


if __name__ == "__main__":
cookie = "BDUSS=xxxxxxxx" # 浏览器登录 pan.baidu.com 后复制,仅 BDUSS 必需
print(baidupan_checkin(cookie))

运行效果

1
2
3
4
$ python baidupan_checkin.py
签到成功, 获得成长值: 2, 已连续签到 1 天
今日已答题
当前会员等级: 9, 成长值: 69927

第二次运行(幂等路径,只有只读请求):

1
2
3
4
$ python baidupan_checkin.py
今日已签到, 已连续签到 1 天
今日已答题
当前会员等级: 9, 成长值: 69927

防封号设计

签到类自动化最大的风险不是接口,是请求行为把自己送进风控。这套实现的约束:

  1. 先查后写:signinlist 是只读查询,先看 today_signed,已签就绝不再发 signin 写请求。写请求每天最多出现一次
  2. 幂等短路:答题先看 answer_status,已答过不发提交请求;服务端兜底的 421001/9502 也按”已签/已答”归为幂等成功,不重试
  3. 请求量恒定:每天全流程 35 个请求(3 只读 + 最多 2 写),间隔 23 秒,不并发、不重试轰炸
  4. 浏览器同源:UA/Referer 对齐 wap 成长值任务页,cookie 走会话 jar 保持会话连续性

即便脚本一天跑多次,重复执行时全部走只读路径,行为等价于多刷新了几次页面。

边界:没做的事

百度任务体系还有几条通道,逆向结论记录在这里但没有自动化:

  • 任务中心签到(/coins/taskcenter/signin,金币/任务系统 H5):必须带 z/channel/offlinepackage 等完整客户端参数,其中 z 是 sofire 反欺诈指纹(com.baidu.sofire.ac.FH.gz),由设备+登录态在本机算出,不能跨账号/跨设备复放,拿别人的抓包值只会收到 errno=2。要自动化这条通道得上 Frida 在真机现场取指纹,成本完全不成比例
  • PC 积分签到(/coins/pc/signin):需要伪装 PC 客户端 Electron UA,奖励是金币不是成长值
  • 补签(/coins/taskcenter/suppsignin):与任务中心共用设备链和 z 指纹,同上不碰

成长值签到+答题这条 BDUSS 就能走的通道,是整套体系里唯一不需要对抗设备指纹的,性价比最高。

Reference