他不是没登录,是豆瓣把他送去了手机版
安卓 Edge 上一按开始就是「无法判断登录状态」。拒绝本身是对的——错的是那句话把人指向了一条修不好的路:他会一遍遍重新登录,去修一个不存在的登录问题。
安卓 Edge 上的一条 issue:一按「开始抓取」就是 「无法判断登录状态,拒绝开始抓取」。回帖只有一句: 「豆瓣网站正常登录的。」
他是对的。
豆瓣把他送去了手机版
真实成因是豆瓣按 User-Agent 里的手机标记,把请求跳到了
m.douban.com。而那张 12.5 KB 的壳页上,扩展用来判断登录状态的东西
一个都没有:
| 要找的 | 桌面版 | 手机版那张壳页 |
|---|---|---|
nav-user-account | 有 | 0 处 |
/accounts/logout | 有 | 0 处 |
nav-login(未登录的标志) | 有 | 0 处 |
| 四种取数字用户 ID 的写法 | 有 | 全是 0 |
「已登录」和「未登录」两个标志都找不到,所以判定是「说不准」, 而说不准就不开工——这条是对的。登录状态不明就开抓,可能把只有公开视图的那一份 当成完整数据存进档案,而档案是不可逆的那一步。
拒绝是对的,那句话把人送上了一条修不好的路。 他会一遍遍重新登录,去修一个根本不存在的登录问题。
这个项目里早就有一条同形的规矩:「读不到用户 ID」与「会话过期了」必须分开说 ——前者是豆瓣改版,后者才是没登录,混成一句会让用户反复重登。 而「判断不出来」是这两者之外的第三种,当时没跟上。
线索一直都在手边:网络层返回了最终落在哪个网址、HTTP 状态、跳了几次、多少字节, 一个不缺——只是在最后一步,只有网页正文被传了进去,其余全丢了。
判据是 UA 里的那个词,而且加不管用,只有删管用
逐项发真实请求量过:
| 发出去的 UA | 拿到哪个站 |
|---|---|
| 安卓 Chromium | 手机版 |
| 同上,外加一个「我不是手机」的请求头 | 手机版(那个头它不看) |
同上,去掉 Mobile 这个词 | 桌面版 |
桌面 UA 加上 Mobile | 桌面版(加了没用) |
| 安卓平板 Chromium(本来就没这个词) | 桌面版 |
| Firefox 安卓手机 | 手机版 |
| Firefox 安卓平板 | 手机版 |
「加了没用」那一行否掉了最省事的做法——拼一个桌面 UA 塞进去。
而「安卓平板」那一行给出了做法:删掉 Mobile 得到的不是编造的 UA,
是同一个浏览器在安卓平板上的真实 UA:同系统、同浏览器、同一份 TLS 指纹。
不得伪造 UA
数据格式规范里有一句方向相反的硬规矩:
抓取时浏览器的真实 User-Agent,原样记录。生产者不得伪造 UA: 伪造出的 UA 与 TLS 指纹及其他请求头不一致,反而更易被风控识别。
那句话防的是「装成另一个浏览器」,而它给出的理由正好就是判据:不一致才危险。
删一个词,处处一致;拼一个 (X11; Linux x86_64) 配安卓的 TLS 指纹,
正是这个项目最不能承担的那种「更容易被盯上」——把用户的豆瓣账号搞封是不可接受的结果,
不是可以权衡的风险。
所以规则只删词,永远不拼。认不出的形状一律不动,并如实说「没动」。
iOS 的 Mobile/15E148 是个带版本号的整体,删掉得不到任何真实存在的 UA
——那就不动它。编一个 UA,比不支持这台设备糟糕得多。
那张表原本只有两行,而两行会被读成二分法
写这段说明的时候,表里原本只有「Firefox 手机 → 删掉 → 桌面版」那两行。 第三行(平板也被跳)与第四行(只有货真价实的桌面 UA 才行)一进来,结论就反了: Firefox 安卓上不存在一个满足这条不变量的 UA。
于是那条 Firefox 判据被撤了下来。它不是「还没写」,是这个方案在那儿不成立 ——而留着它就等于留下一条永远不会触发、且第一句就是假的判据。
同一个形状这几天已经栽过两次:当一条规则有好几种情形而你只画得下两栏时, 读者补全的那几格会按他自己的直觉落位,而直觉往往与设计相反。
这一条只解决内容那一侧
页面回到桌面模板,分类器、抽取器、路线表就都对得上了。但离屏文档在移动版浏览器上 扛不扛得住一场几小时的抓取,没有实测,所以没有写成「手机上可以用了」。
而报告人能走到「无法判断登录状态」这一步本身是个有用的数据点:离屏文档、网络请求、 请求头改写在那台设备上都是活的——这在此之前完全不知道。