豆备 DOUBAK
← 开发日志

他不是没登录,是豆瓣把他送去了手机版

安卓 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 的判定规则被果断撤下。它不是「还没写」,是这个方案在那儿不成立 ——而保留它只会留下一条永远无法触发、且前提虚假的冗余规则。

同一个形状这几天已经栽过两次:当一条规则有好几种情形而你只画得下两栏时, 读者补全的那几格会按他自己的直觉落位,而直觉往往与设计相反。

这一条只解决内容那一侧

页面回到桌面模板,分类器、抽取器、路线表就都对得上了。但离屏文档在移动版浏览器上 扛不扛得住一场几小时的抓取,没有实测,所以没有写成「手机上可以用了」。

而报告人能走到「无法判断登录状态」这一步本身是个有用的数据点:离屏文档、网络请求、 请求头改写在那台设备上都是活的——这在此之前完全不知道。

← 更新:Firefox 能抓、能存,就是交不出一个文件夹 更早:一个复选框,三处静默 →