bigywater.tech
爬虫工程 · 66

User-Agent 与联系方式:服务端日志里看到什么

爬虫发出的每个请求都会在目标服务器的日志里留下一行。站长从这一行里能看到什么、认得出你是谁吗?这篇在本机 nginx 的访问日志里把常见库的 UA、自报身份的写法和几个边界情况逐个看一遍。

python-requests/2.34.2
requests 不改 UA 时服务器日志里的样子(curl_cffi 默认则根本没有 UA 头)
8100 对 8200 字节
nginx 默认配置下,UA 8100 字节返回 200,8200 字节返回 400
1 对 3
同一个 IP 发来 900 个请求,按 IP 统计是 1 行,按 UA 统计是 3 行

默认 UA

实测 · 本机执行输出
  "GET /a-requests HTTP/1.1" 200 3 "-" "python-requests/2.34.2"
  "GET /b-urllib HTTP/1.1" 200 3 "-" "Python-urllib/3.14"
  "GET /c-aiohttp HTTP/1.1" 200 3 "-" "Python/3.14 aiohttp/3.14.3"
  "GET /d-curl_cffi HTTP/1.1" 200 3 "-" "-"
  "GET /e-curl_cffi-chrome HTTP/1.1" 200 3 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"
  "GET /f-no-user-agent-header HTTP/1.1" 200 3 "-" "-"

requests、urllib、aiohttp 的默认 UA 都带库名和版本(python-requests/2.34.2、Python-urllib/3.14、Python/3.14 aiohttp/3.14.3);curl_cffi 默认不带 UA 头;用 impersonate='chrome' 则是它自带的那个 Chrome 版本(实验里 146,和我机器上的 Chrome 154 不同)。

自报身份

实测 · 本机执行输出
  combined:"GET /with-id HTTP/1.1" 200 3 "-" "MyCrawler/1.0 (+https://example.org/bot.html; ops@example.org)"
  combined:"GET /without-from HTTP/1.1" 200 3 "-" "MyCrawler/1.0 (+https://example.org/bot.html; ops@example.org)"
  withfrom:127.0.0.1 "GET /with-id HTTP/1.1" 200 ua="MyCrawler/1.0 (+https://example.org/bot.html; ops@example.org)" from="ops@example.org"
  withfrom:127.0.0.1 "GET /without-from HTTP/1.1" 200 ua="MyCrawler/1.0 (+https://example.org/bot.html; ops@example.org)" from="-"

把爬虫名、说明页网址和联系邮箱写进 User-Agent,整串都会出现在站长的访问日志里。From 头也能带邮箱,但常见的日志格式不记它,要站长专门在 log_format 里加 $http_from。

长度

实测 · 本机执行输出
  UA 长  1000 字节:HTTP/1.1 200 OK
  UA 长  4000 字节:HTTP/1.1 200 OK
  UA 长  8000 字节:HTTP/1.1 200 OK
  UA 长  8100 字节:HTTP/1.1 200 OK
  UA 长  8200 字节:HTTP/1.1 400 Bad Request
  UA 长 16000 字节:HTTP/1.1 400 Bad Request

nginx 默认的 large_client_header_buffers 是 4 个 8k,一个请求头要放进一个缓冲:实验里 UA 8100 字节返回 200,8200 字节返回 400。别的服务器、CDN 的限制不同,我没有测。

非 ASCII

实测 · 本机执行输出
  requests 中文(str):UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-1: ord;日志:[]
  requests 中文(bytes,UTF-8):发出,状态 200;日志:['"GET /enc-requests HTTP/1.1" 200 3 "-" "\\xE7\\x88\\xAC\\xE8\\x99\\xAB/1.0"']
  requests 含换行:InvalidHeader: Invalid leading whitespace, reserved character(s), or return;日志:[]
  urllib 中文(str):UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-1: ord
  aiohttp 中文(str):发出,状态 200;日志:['"GET /enc-aiohttp HTTP/1.1" 200 3 "-" "\\xE7\\x88\\xAC\\xE8\\x99\\xAB/1.0"']

requests 和 urllib 把头值按 latin-1 编码,传中文 str 会抛 UnicodeEncodeError;requests 传 UTF-8 的 bytes、aiohttp 传 str 能发出,但服务器日志里是 \xE7\x88… 这样的转义,站长看不懂。含换行的头值 requests 会拒绝(InvalidHeader)。UA 用 ASCII 写。

和 robots.txt 的关系

实测 · 本机执行输出
  只传产品名 MyCrawler        can_fetch("/public") = True, can_fetch("/private/x") = False
  传完整 UA 字符串             can_fetch("/public") = True, can_fetch("/private/x") = False
  传 Mozilla 兼容写法         can_fetch("/public") = False, can_fetch("/private/x") = False
  传 mycrawler/1.0        can_fetch("/public") = True, can_fetch("/private/x") = False

robots.txt 里 User-agent 写的是产品名,客户端用什么字符串去匹配取决于实现。urllib.robotparser 取 UA 第一个 “/” 之前的部分(实验里 MyCrawler/1.0 (+…) 匹配到了 mycrawler 组),而 Mozilla/5.0 (compatible; MyCrawler/1.0) 这种写法取到的是 mozilla,落到 * 组。UA 以产品名开头,和你实际用的 robots 解析器对得上(第 26 篇讲了解析器的差别)。

站长怎么区分

实测 · 本机执行输出
  按来源 IP 统计:{'127.0.0.1': 900}
  按 User-Agent 统计:
     300  python-requests/2.34.2
     300  MyCrawler/1.0 (+https://example.org/bot.html; ops@example.org)
     300  Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36

一个出口 IP 后面可能有很多客户端,站长在日志里常常是按 UA 区分的。UA 只是默认库名时,站长只能知道“有人用 requests”;写了网址和邮箱,站长至少有地方可以联系。是否有人去看,我没有办法验证。

分步演示

User-Agent 与日志
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / requests 2.34.2 / aiohttp 3.14.3 / lxml 6.1.1(系统 Python);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、断连、压缩、重定向等行为,并记录收到的原始请求;计时用 time.perf_counter,数字每次运行会有小幅抖动。服务器是本机 nginx 1.31.6(只监听 127.0.0.1,combined 日志格式加一个记录 $http_from 的自定义格式),各客户端为 requests 2.34.2、urllib、aiohttp 3.14.3、curl_cffi 0.15.0;UA 超长的请求用裸套接字发;各情形跑一次(第六节每种 300 次)
    • 没有验证:真实站点对 UA 的处理(有的站按 UA 屏蔽、有的站根本不看);CDN、WAF 对超长 UA 或空 UA 的处理;邮件地址写进 UA 之后是否真有人联系;Apache、IIS 等其它服务器的头长度限制和日志格式;HTTP/2 下的头压缩对 UA 长度的影响;urllib.robotparser 以外的 robots 解析器(如 Protego)对 UA 的匹配方式
    京ICP备2026067502号-1京公网安备11010502063487号