爬虫发出的每个请求都会在目标服务器的日志里留下一行。站长从这一行里能看到什么、认得出你是谁吗?这篇在本机 nginx 的访问日志里把常见库的 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 的限制不同,我没有测。
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 写。
只传产品名 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") = Falserobots.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”;写了网址和邮箱,站长至少有地方可以联系。是否有人去看,我没有办法验证。