cvl_csrf_token 是有了, 但是 acf_ccn 还没有

This commit is contained in:
yml2213
2026-06-24 08:15:15 +08:00
parent 4813eb039d
commit ae92cd2441
2 changed files with 164 additions and 1 deletions
+58 -1
View File
@@ -54,6 +54,8 @@ class DouyuLogin:
VERIFY_API = "https://passport.douyu.com/wgapi/member/passport/remotelogin/verify"
LOGIN_CALLBACK_API = "https://www.douyu.com/api/passport/login"
WEBLOGIN_API = "https://msg.douyu.com/webLogin"
CSRF_API = "https://www.douyu.com/japi/carnival/nc/common/generateCsrf"
CSRF_REFERER = "https://www.douyu.com/pages/live-peace-handbook/web/shop?ditchname=pass0&roomId=9263298"
LOGIN_REFERER = (
"https://passport.douyu.com/index/login?"
"passport_reg_callback=PASSPORT_REG_SUCCESS_CALLBACK&"
@@ -732,7 +734,62 @@ class DouyuLogin:
except Exception as e:
logger.warning(f"WebLogin请求失败(不影响登录): {e}")
# 收集所有Cookie
# 登录成功后访问斗鱼 Web CSRF 接口,补齐 cvl_csrf_token;响应里的 acf_ccn 也会进入 session cookie jar。
self._generate_csrf_cookie()
return self._format_cookie_string()
def _generate_csrf_cookie(self) -> str:
"""
访问斗鱼 generateCsrf 接口,从 Set-Cookie 中同步 cvl_csrf_token。
requests.Session 会自动接收响应中的 Set-Cookie,所以这里不只保存
cvl_csrf_token;接口额外下发的 acf_ccn 等 Cookie 也会保留到最终 CK。
"""
logger.info("补齐CSRF Cookie...")
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.6,en;q=0.5',
'Origin': 'https://www.douyu.com',
'Referer': self.CSRF_REFERER,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
# 覆盖登录接口的默认表单头,尽量贴近浏览器抓包。
'Content-Type': None,
'X-Requested-With': None,
}
response = self._request('post', self.CSRF_API, headers=headers)
response.raise_for_status()
body = response.text.strip()
if not body:
raise ValueError("生成CSRF失败: 响应为空")
try:
payload = response.json()
except json.JSONDecodeError as exc:
preview = body[:200].replace("\n", "\\n")
raise ValueError(f"生成CSRF失败: 响应不是有效 JSON: {preview}") from exc
if payload.get('error') != 0:
raise ValueError(f"生成CSRF失败: {payload.get('msg', '未知错误')}")
cookies = self.session.cookies.get_dict()
csrf_token = cookies.get('cvl_csrf_token', '')
if not csrf_token:
raise ValueError("生成CSRF失败: 响应没有 cvl_csrf_token")
if cookies.get('acf_ccn'):
logger.info("已同步 acf_ccn Cookie")
else:
logger.warning("generateCsrf 响应未同步 acf_ccn Cookie")
logger.info("CSRF Cookie已补齐")
return csrf_token
def _format_cookie_string(self) -> str:
"""把当前 session 中的 Cookie 格式化为可导出的 CK 字符串。"""
cookies = self.session.cookies.get_dict()
# 格式化Cookie字符串
+106
View File
@@ -0,0 +1,106 @@
目标 ck
cvl_csrf_token=62083f188904477e9bc1c5a70537145c; acf_ccn=8b025ffcbf403d5e0d880ad6327bb3cf; PHPSESSID=86k3denvoc1j16rl1fsv5l2mc2; acf_auth=5a8eUzBoDc8uyLasCP7LXaO9a2vUK%2FDaNFwYi0MVdUFMpUcMBfsoZfifJZKCQC8WgJuKq7WB78VZrRlRXnZUSt87TZM0tLNdnplqtuL87c7idAqo%2Fal57ls; acf_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjk0MjQxNjQ1MSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImR5Iiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo2OTE2MTY4NSwic3RrIjoiNGZkNDY5ZDE2OWY3NzM0YiIsImV4cCI6MTc4MjMyMjc2OSwiaWF0IjoxNzgxNzE3OTcwLCJrZXkiOiJkeS1qd3QtbWQ1In0.NDc0YzMyNjQ0OGIzNTkxYzkxNGQ1NDk0Y2U0NTk1NTU; acf_dmjwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjk0MjQxNjQ1MSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImRtIiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo2OTE2MTY4NSwic3RrIjoiNGZkNDY5ZDE2OWY3NzM0YiIsImV4cCI6MTc4MjMyMjc2OSwiaWF0IjoxNzgxNzE3OTcwLCJrZXkiOiJkeS1qd3QtbWQ1In0.NWM1ZDgwZjE1N2ZhNTE3NDUxNmQ4ZjljMzI5ODQ0ZjM; dy_auth=29bfEdmmHp50M9Zr2gxQ3%2F2AJD2DAdCK6I%2FoX92U5n13i64R%2B%2FywJZf%2FqG4%2BCJvbUQmFaUSu3dFbJj4%2Be1gU1DpW6ligJYaJLnNuVbmKMeZCtayBmGtns6A; wan_auth37wan=cf514c616a0fzsvhClbvRbju0sSZb4aDqHQQabpd66aLoVky4bvRRXYDfYMX3iSUNiDuh%2BHt%2B20QXW0f5JLZ6lBtOW8LIIwb31iX8OiZurGB0aztSKA; acf_uid=942416451; acf_username=942416451; acf_nickname=%E7%94%A8%E6%88%B74512197651; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F18_; acf_ct=0; acf_ltkid=69161685; acf_biz=1; acf_stk=4fd469d169f7734b; acf_devid=9869cce23d84da0fd8b1cd20162b3def
======
自己跑的
dy_accounts_main=; dy_auth=0208CrOVxM5sDK9%2F5WrkVdZhuB4HSkgVod%2F7g6BhzIwaz0KjIUNW%2FkwQP2aNlqlgUCmz40FFwmZlH%2FV%2BKs%2FIfw9NgitHrJUx8w%2F8H1H2Dati3ezwI%2BOR6WYLeQ; wan_auth37wan=36a436a51f20daPv%2B2XfNSB6x4uSc4S%2Bu1ygA0cT%2FjaoFSAvJcFQtC6dlTLGIzT2xhSM8Al1gmJumyROhJOrnC5NFtcWbzxzPwbJEetjQKR58fRcTCcj; LTP0=eyJhbGciOiJtZDUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsicGFzc3BvcnQiXSwiY3QiOjAsImN0aW1lIjoxNzgyMjMwOTMwLCJleHAiOjE3OTc5OTg5MzgsImtleSI6ImR5LWp3dC1tZDUiLCJsdGsiOiIzOWU4ZDZhYzBiZGYwNTEzIiwibHRraWQiOjcxODUwNjE0LCJzdWIiOiJsdCIsInVpZCI6MTAzMDAyMzc0MH0.MjQ3NGQzNWQ1MjkyOGQ1NzUxZjA1OTNiYzAxMmE5MWY; last_login_way=nickname; PHPSESSID=8a7ldg51tuapcngdkdice6pab0; acf_auth=dc71BJkxslSfnGLHIcCLvMmxXDfqt72pB0VW53Z30z7RZfS5NSZRGFvrNShEuX6k0YxPv%2B71Cnhl6O%2FTRQYciZ9l7RhRMqs0ykAhm%2Ftb1ulawonh0V2hoBmU0w; acf_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjEwMzAwMjM3NDAsImN0IjowLCJzdWIiOiJzdCIsImF1ZCI6WyJkeSIsIm5vbmUiXSwiYml6IjoxLCJsdGtpZCI6NzE4NTA2MTQsInN0ayI6IjliNDEyMGE2MTFkMDA3NjAiLCJleHAiOjE3ODI4MzU3MzAsImlhdCI6MTc4MjIzMDkzMCwia2V5IjoiZHktand0LW1kNSJ9.ZmE0Y2IwMDIzMmZhZTQzZGIzMmMzNmY2ZWY1NGNjNjQ; acf_dmjwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjEwMzAwMjM3NDAsImN0IjowLCJzdWIiOiJzdCIsImF1ZCI6WyJkbSIsIm5vbmUiXSwiYml6IjoxLCJsdGtpZCI6NzE4NTA2MTQsInN0ayI6IjliNDEyMGE2MTFkMDA3NjAiLCJleHAiOjE3ODI4MzU3MzAsImlhdCI6MTc4MjIzMDkzMCwia2V5IjoiZHktand0LW1kNSJ9.M2QyMWMxYjI2NjY5OTlkMGYyOGVlYTU3ZmRlZTg4YzE; acf_uid=1030023740; acf_username=1030023740; acf_nickname=%E7%94%A8%E6%88%B74965059115; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=71850614; acf_biz=1; acf_stk=9b4120a611d00760; acf_devid=a72ea6ef2f1f6c51a823d212390ce718
dy_accounts_main=; dy_auth=7febuXzoBCnE7TEoIP28mhEf2OXn6l%2FSwL56eAJA1LYlNzSpDua7PtYlV8gxpxxlPOCFRYwTRw%2BDh6X19MJetWfzOTasWoyu1kxEMg5uitOBAWm5uI%2BqS3M; wan_auth37wan=66e4873d1a6dU7jaNtlupCZ78lHwB7SzAUisHHNHyQlnH3heDOvP4b4CkEPOxy%2FBqT3HGEMra%2FAeflM6D6hfrMpJfXJGoNYwCF08uCnTsYWDiGGHWVE; LTP0=eyJhbGciOiJtZDUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOlsicGFzc3BvcnQiXSwiY3QiOjAsImN0aW1lIjoxNzgyMjU5OTI5LCJleHAiOjE3OTgwMjc5NDQsImtleSI6ImR5LWp3dC1tZDUiLCJsdGsiOiIwOWJlYWZlZTM2MDBhMjBkIiwibHRraWQiOjU1ODI4NjkxLCJzdWIiOiJsdCIsInVpZCI6NzIyMDUwMDMxfQ.YjVlY2FjNTZkNzIxYjhjMDRjODZhNzE0ZDA3MmQ3OWE; last_login_way=nickname; PHPSESSID=oquhqrc9b6bkfvldj6n7mh9tg2; acf_auth=50c0Hnuwt0o08C%2FaUYAUoiY8QbryaO2fmxkd%2Frn7uktr5WqvmLxNHcpHAcEcgZVmmr29iqA2YTs1adLO%2B4R4NI7Hm8EgLqiKsTw8s152W32rEA9UN7JEL3w; acf_jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjcyMjA1MDAzMSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImR5Iiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo1NTgyODY5MSwic3RrIjoiMDJhMzM5MjE5YWQxYTdhNSIsImV4cCI6MTc4Mjg2NDcyOSwiaWF0IjoxNzgyMjU5OTI5LCJrZXkiOiJkeS1qd3QtbWQ1In0.YWNlM2FkYTliYjEwMjk5NzdjMDA0Zjc2NGJkNTEwNmQ; acf_dmjwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJtZDUifQ.eyJ1aWQiOjcyMjA1MDAzMSwiY3QiOjAsInN1YiI6InN0IiwiYXVkIjpbImRtIiwibm9uZSJdLCJiaXoiOjEsImx0a2lkIjo1NTgyODY5MSwic3RrIjoiMDJhMzM5MjE5YWQxYTdhNSIsImV4cCI6MTc4Mjg2NDcyOSwiaWF0IjoxNzgyMjU5OTI5LCJrZXkiOiJkeS1qd3QtbWQ1In0.ZDljYzlkNTZjNzNhYzMwZjAzNzY5NjQxZmJhM2FmODk; acf_uid=722050031; acf_username=722050031; acf_nickname=%E7%94%A8%E6%88%B76893423879; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F19_; acf_ct=0; acf_ltkid=55828691; acf_biz=1; acf_stk=02a339219ad1a7a5; acf_devid=d5d7050d31074c949bd5394f1241000c; cvl_csrf_token=347aeed3a7974d668de371c1a3213c18
========
我看了 [对比 ck.txt](/Users/yml/codes/douyu_login_py/对比%20ck.txt),结论是:两组 CK 的核心登录字段都有,但不是同一个账号/同一次登录态;自己跑出来的 CK 少了目标里的 2 个字段,多了 3 个字段。
**字段差异**
目标 CK 独有:
```text
cvl_csrf_token
acf_ccn
```
自己跑的 CK 独有:
```text
dy_accounts_main=
LTP0
last_login_way=nickname
```
其中 `dy_accounts_main=` 是空值,实际意义可能不大;`LTP0` 是 passport 侧的长期 token,目标 CK 里没有。
**相同字段但值不同**
这些字段两边都有,但值都不一样:
```text
PHPSESSID
acf_auth
dy_auth
wan_auth37wan
acf_jwt_token
acf_dmjwt_token
acf_uid
acf_username
acf_nickname
acf_avatar
acf_ltkid
acf_stk
acf_devid
```
账号信息也不同:
```text
目标 uid: 942416451
自己 uid: 1030023740
目标 nickname: 用户4512197651
自己 nickname: 用户4965059115
```
**JWT 载荷对比**
`acf_jwt_token` / `acf_dmjwt_token` 的结构是一致的,区别主要是账号、`ltkid`、`stk`、签发时间、过期时间:
```text
目标:
uid = 942416451
ltkid = 69161685
stk = 4fd469d169f7734b
iat = 2026/6/18 01:39:30
exp = 2026/6/25 01:39:29
自己:
uid = 1030023740
ltkid = 71850614
stk = 9b4120a611d00760
iat = 2026/6/24 00:08:50
exp = 2026/7/1 00:08:50
```
`acf_jwt_token` 的 `aud` 都是 `["dy", "none"]``acf_dmjwt_token` 的 `aud` 都是 `["dm", "none"]`,这部分没问题。
**关键判断**
自己跑出来的 CK 看起来是完整登录态,但和目标 CK 的“形态”不完全一样:主要缺 `cvl_csrf_token` 和 `acf_ccn`。如果某些接口依赖 CSRF 或风控相关 cookie,这两个缺失可能会导致请求失败;如果只是普通登录态校验,核心字段已经都有。