引号之间那一段,是客户端自己写的
有次翻 Nginx 的 access.log,想确认反复来取接口的到底是谁:搜索引擎的蜘蛛、微信内置浏览器里的真人,还是拿脚本把自己写成 Chrome 的人。证据只有引号之间那段 User-Agent,而它是客户端自己拼出来、原样发上来的——「像 Chrome」和「是 Chrome」是两个命题:前者查字面,后者要字面以外的东西。
先摆尺度:8 条样本最长 274 字符、最短 curl/8.5.0 的 10 字符,合计 935。段落数量与顺序全由发出去那一端决定。
图 1|8 条样本的字符数,条形按字符数等比。
六个维度各读哪一段,值从哪张表来
| 维度 | 值从哪里来 | 读不到时显示 |
|---|---|---|
| 浏览器 / 客户端 | BROWSERS 32 条自上而下取首个命中 | 未识别 |
| 渲染内核 | 独立一条链:Trident、Edge、Chromium 裸子串、Gecko | —(非浏览器 UA) |
| 操作系统 | Windows NT 映射表 7 条命中 | —(无法判断) |
| 设备类型 | iPad、iPhone、是否带 Mobile,再问爬虫 | —(无法判断) |
| CPU 架构 | Intel Mac 优先,arm64 对 x64,WOW64 另判 | —(未声明) |
| 语言环境 | 8 条里只有安卓微信给出 zh-cn | 其余 7 条 —(未声明) |
安卓微信那条同时有 AppleWebKit/537.36 与 MicroMessenger/8.0.40.2420,客户端给 微信(内置浏览器) 8.0.40.2420,内核却给 Blink(Chromium 内核) / 537.36;Safari/Mac 是 WebKit(Safari 内核) / 605.1.15,两根轴各判各的。
同一条字符串,位次决定它变成谁
- MicroMessenger 排第 1 条,安卓微信那条里的 Chrome/77.0.3969.139 轮不到。
- Edg 第 13 条早于旧版 Edge 第 14 条;Chrome 第 28 条、Firefox 第 30 条、Safari 第 31 条。
- 爬虫表 21 条,兜底那条排在具名蜘蛛与程序库之后。
- 程序库也走爬虫表:curl/8.5.0 第 13 条、python-requests/2.31.0 第 15 条。
图 2|浏览器表 32 条里各类客户端的命中位次,条形按位次等比。
爬虫那侧优先级更高:先跑 detectBot,有返回就不看浏览器表。伪造那条 compatible; Spybot/1.0 夹着合法的 Chrome/131.0.0.0,本可命中第 28 条,卡片却写 自动化程序 / 爬虫、主版本号 bot——命中 21 条里最后的兜底 /(bot|spider|crawler|fetcher|httpclient)\b/i。只写 compatible; GenericClient/1.0 时兜底不响,回落 Chrome 131.0.0.0,操作系统与设备都是 —(无法判断)。
内核栏为什么会自己冒出 Blink
detectEngine 的 Chromium 分支是 /Edg|OPR|Vivaldi|Chrome|CriOS|Chromium/ 裸子串,没有 i 修饰符。小写因此不算:3 条只含 pledge、edgeless、knowledge-sync 的 UA 被它命中的条数是 0,最后那条照报 Gecko 132.0。
大写就中招:4 条含大写子串的自建 UA 被改判的条数正是 4——机型名 OPRO-X、设备名 Chromecast、EdgeSync-Agent/2.0 全报成 Blink(Chromium 内核)且版本为空;一条 Firefox 128.0 只因尾部多了 MicrosoftEdgeRuntime 就从 Gecko 变 Blink,第 5 条真 Edge 对照一致。「内核有名字、版本为空」就是信号。
版本号里那些下划线是谁换掉的
iOS 捕获 17_4 换点后显示 iOS 17.4,macOS 的 10_15_7 显示 10.15.7。Windows 那张映射表只有 7 条命中,10.0 对 Windows 10 / 11 直到 5.2 对 Windows Server 2003,取不到就回落「较老版本」:NT 11.0、6.4、5.0 都算较老版本,真 Windows 11 走的反而是 10.0。微信 8.0.40.2420 的 4 段全保留,(0x28002837) 不参与版本;rv:132.0 优先于 20100101。
第三张表只解释字面,不下结论
TOKENS 34 条只说明每段是什么意思。对 125 字符那条 Edge/Win 做高亮,命中 9 个片段,25 条没匹配上或被区间重叠去掉。两条注释回答开头:Mozilla/5.0「几乎所有浏览器都固定写 5.0,不代表浏览器品牌」,(KHTML, like Gecko) 是「WebKit 的伪装声明」。这 34 条我背不下来,通常把整行丢进User-Agent 解析逐段标出来。
「到底是谁」这一栏答不了。要把蜘蛛和伪装者分开,得拿同一行日志里的 IP 去对归属地与反向解析,IP 归属地查询给的机房线索才回答「这台机器有没有可能是百度出口」。模板里 Sec-CH 与 Client Hints 各出现 0 次,所以冻结在 108.0.0.0 的精简版只能读成 Chrome 108.0.0.0;尾部挂着编码过的一段,先按 Base64 编解码解一遍。
Q:UA 能不能伪造?
能,不需要技巧——那串字符本来就是客户端拼的。有爬虫关键词就落到 自动化程序 / 爬虫,没有就回落 Chrome 131.0.0.0。它读字面不读来源,只靠这一栏做访问控制都能被绕过。
Q:为什么 Chrome 的 UA 里写着 Safari?
兼容遗留。Chromium 系结尾都带一段 Safari/537.36,注释写着「Chromium 结尾也带一段,是兼容遗留」。内核那栏不看名字,Chrome、Edge、安卓微信都报 Blink(Chromium 内核) / 537.36。
Q:识别爬虫和屏蔽爬虫是一回事吗?
不是。21 条爬虫表只回答「这串字符像不像爬虫」,第 21 条是 bot、spider、crawler、fetcher、httpclient 的泛化匹配;取舍靠 IP 与反向解析。写成 Googlebot 的脚本照样被认成蜘蛛。
Q:页面说明里的微信版本为什么和卡片不一样?
代码捕获 8.0.40.2420 这 4 段,卡片就显示它;工具说明里的 MicroMessenger/8.0.40 少了一段。同一份说明还提小程序,而模板里 miniProgram 出现 0 次,那条 UA 只是被读成 微信(内置浏览器)8.0.44。