我们的立场很明确:我们是 Geonode,我们销售代理服务,而关于用户代理的问题通常会伴随着“为什么我被封了”这样的疑问出现。 坦率地说,用户代理是可用信号中较弱的一种,如果请求的 TLS 指纹显示的情况与浏览器字符串不符,那还不如干脆不伪装——你制造了一种真实浏览器绝不会产生的不一致性。 下文对此有专门论述。比人们预期更常奏效的策略恰恰相反:表明身份、提供联系网址,并保持合理行为。匿名自动化请求被封锁的概率远高于已识别的自动化请求,而表明身份是无需成本的。
语法
curl -A "MyBot/1.0" https://example.com
curl 手册 中对 -A, --user-agent <name>
的说明如下:“指定要发送给 HTTP 服务器的 User-Agent 字符串。若要对字符串中的空格进行编码,请用单引号或双引号将字符串括起来。”
文档中也说明了默认行为:“默认情况下,curl 使用 curl/VERSION
,例如 User-Agent: curl/8.22.0
。”
因此,如果不指定任何选项,你发出的每个请求都会声明自己是 curl 及其版本。有些服务器会根据这一点做出不同的响应,在断定某个网站出现故障之前,了解这一点很有必要。
使用通用标头的等效写法:
curl -H "User-Agent: MyBot/1.0" https://example.com
两者结果相同——手册中指出该标头“也可通过 --header
或 --proxy-header
选项设置”。-A
更简洁;-H
则与其他标头的设置方式保持一致,这在通过编程生成命令时尤为重要。
如果多次设置该选项,“将采用最后设置的值”。
完全移除请求头
这是一个人们往往不知道的情况,而手册对此区别有明确说明:
若向
--user-agent传递空参数(" "),则会将该标头完全从请求中移除。若希望保留空白标头,可将其设置为单个空格(" ")。
curl -A "" https://example.com # no User-Agent header at all
curl -A " " https://example.com # User-Agent: (empty value)
这两种请求在实质上是不同的。不发送任何标头与发送空标头并不相同,服务器能够区分它们。
这两种情况都很不寻常,而“不寻常”本身就是一种信号。完全没有用户代理的请求比标识为 curl 的请求更为罕见,因此删除标头以显得不那么引人注目,通常会适得其反。
-H 也采用相同的机制,手册中解释了这两种情况的语法:“要删除内部标头,请在冒号右侧提供一个无内容的替换值,例如:-H "Host:"”,而值空的标头则需要分号——-H "X-Custom-Header;" 会发送 X-Custom-Header:。
为什么默认值很重要
curl/8.22.0 是一个完全正常的字符串,但它会产生相应的影响。
**有些服务器会直接屏蔽它。**针对已知自动化工具制定的通用规则既容易编写,又十分常见,因此你很可能会遇到这种情况。
有些服务器会提供不同的内容。 例如简化后的标记、不含依赖 JavaScript 的部分,有时甚至会显示完全不同的页面。
有些服务器会记录该请求但不采取任何行动。 这是迄今为止最常见的情况。
部分 CDN 将其视为众多信号之一,而非单独的决策依据。
实际后果是,“在我的浏览器中能正常运行,但在 curl 中却不行”这一现象可能有多种原因,而用户代理只是其中之一。 在更改用户代理之前,请先确认差异是否确实源于 JavaScript——curl 不会执行 JavaScript,因此无论发送何种请求头,客户端组装的页面在 curl 中看起来都会近乎空白。这并非阻塞性问题,也没有任何用户代理能解决它。
为什么冒充浏览器往往适得其反
在粘贴 Chrome 字符串之前,这部分内容值得一读。
用户代理是一种声明。现代反机器人系统会根据证据来验证该声明,而相关证据不胜枚举:
TLS 指纹。 客户端协商 TLS 的方式——密码套件顺序、扩展、支持的组——会生成一种通常被概括为 JA3 或 JA4 的签名。curl 的指纹与 Chrome 不同,且任何标头都无法改变这一点。 一个声称来自 Chrome 但使用 curl 的 TLS 握手过程的请求,比一个老实声称来自 curl 的请求更容易被识别,因为真正的 Chrome 绝不会产生这种组合。
标头集及其顺序。 浏览器会按特定顺序发送一组具有特征的标头——Accept、Accept-Language、Accept-Encoding、Sec-Fetch-* 等。而 curl 通常只发送三到四个。一边声称是 Chrome,一边发送 curl 的标头集,这显然自相矛盾。
HTTP 版本与行为。 连接复用、多路复用、标头压缩的细节。
行为。 真正的浏览器会加载 CSS、图片和脚本。一个只加载一个 HTML 文档而不再加载其他内容的客户端,无论它自称什么,都不像浏览器。
因此,真实的层次关系是:准确的用户代理表现一致且不引人注目;伪造的用户代理则表现不一致且引人注目。如果你确实需要类似浏览器的请求,你需要的是浏览器——Playwright 或类似工具——而不是一个头部。我们在 使用 Playwright 截屏 中探讨了相关的权衡取舍。
存在一个狭窄的、合理的折中方案:测试你自己网站对用户代理的处理方式,或者获取某个网站针对移动客户端提供不同内容的情况。这些是对你自身系统的验证,或是良性的内容协商,因此是可以接受的。
应发送什么内容
对于自动化客户端,被拦截概率最低的字符串是明确说明自身身份的字符串。
curl -A "AcmePriceBot/1.2 (+https://acme.example.com/bot)" https://example.com
该规范包含三个部分:名称、版本号,以及一个可让他人了解你的身份及联系方式的 URL。其有效性在于,它为网站运营者提供了除拦截之外的其他选择。 来源不明的请求模式需要被阻止;而身份明确且带有联系页面的爬虫则需要进行评估,通常会选择允许其访问。
有三项切实的实际好处,且均属真实:
**robots.txt
可以专门针对你。** 规则是通过用户代理标识符进行匹配的,因此网站可以给予你的爬虫一种不给予其他所有人的特权。如果你是匿名的,这便无法实现。
**网站运营方可以在封禁前与您联系。**这种情况比人们预期的要常见,而且比三周后才发现被封禁要好得多。
它支持主动申请访问权限。“我们是身份已确认的 AcmePriceBot 爬虫;以下是我们的工作内容”——这样的对话往往能取得实质性进展。 而“我们是一个未标识的脚本”则不然。
请配合符合声明的行为:遵守 robots.txt
,尊重 Crawl-delay
和 Retry-After
,保持适度的访问频率,并进行缓存以避免重复抓取未更改的资源。
在脚本中保持设置的一致性
如果涉及的命令不止一条,请将字符串集中放在一个地方。
UA="AcmePriceBot/1.2 (+https://acme.example.com/bot)"
curl -sS --fail --location \
--user-agent "$UA" \
--connect-timeout 5 --max-time 30 \
"$URL"
或者在 curl 配置文件中设置一次,这样可以确保每次调用都保持一致,而无需重复指定该参数:
# bot.conf
--user-agent "AcmePriceBot/1.2 (+https://acme.example.com/bot)"
--location
--show-error
curl -K bot.conf "$URL"
关于 ~/.curlrc
的特别提醒:该设置适用于该用户的 所有 curl 调用,包括您未编写的命令。若在此处设置机器人用户代理,则您发出的每个临时请求都会被识别为您的爬虫,这会在数月后导致结果混乱。对于任何与工作负载相关的设置,请使用命名配置文件并通过 -K
进行配置。
如果某个工作负载需要使用多个用户代理,请将它们存放在数组中,并有针对性地进行选择,而非随机选择——在同一会话中随机轮换会导致客户端看起来像是在访问过程中更换了浏览器,这会造成不一致,而非有效伪装。
检查实际发送的内容
这是一个值得养成的习惯,因为对请求头的假设出错的情况出乎意料地频繁。
curl -v -A "MyBot/1.0" https://example.com 2>&1 | grep -i '^> user-agent'
详细输出会发送到 stderr,因此使用了 2>&1。> 这几行就是 curl 实际发送的内容。
或者让服务将内容回显回来:
curl -sS -A "MyBot/1.0" https://httpbin.org/user-agent
这比听起来更重要,因为手册中描述了一种特定行为:“如果你添加的自定义标头名称与 curl 会使用的内部标头名称相同,则会使用你外部设置的标头,而不是内部标头”。因此,将 -A 与 -H "User-Agent: ..." 结合使用时,其中一个会悄无声息地生效,而要确定是哪一个,就需要仔细查看。 手册中的建议值得重申:“除非完全清楚自己在做什么,否则不应替换内部设置的头部。”
通过代理时同样需要进行此检查,因为 --proxy-header 会在代理连接上设置头部,而非目标请求上——当用户设置的头部似乎未送达时,这一区别往往会让人感到困惑。
解析用户代理字符串
值得了解这一点,一方面是因为你可能需要构建一个用户代理字符串,另一方面是因为其格式能解释为什么浏览器的字符串看起来如此奇怪。
现代 Chrome 的字符串大致如下:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
其中几乎没有任何内容是真实的。它既不是 Mozilla,也不是 Safari,而且 AppleWebKit/537.36 这个域名已经多年未更新了。 该字符串是二十年来内容协商的“化石记录”:每款浏览器都会添加其前辈的标识符,以便服务器在检测到竞争对手时,能为其提供页面中的“优质版本”。其结果就是,这种格式几乎不包含任何可靠信息,却仍被大量软件所解析。
其底层的结构语法很简单:一串Product/Version标识符,每个标识符后可选地跟一个用括号括起的注释。这就是全部规范,也是为什么像AcmePriceBot/1.2 (+https://acme.example.com/bot)这样的字符串是语法正确的——一个产品标识符、一个版本号,以及一个包含URL的注释。URL前缀+是一种约定而非强制要求,且被广泛认可。
**关于移动端的问题。**许多网站会向移动客户端提供不同的标记内容,而区分标志通常是字符串中某处的 Mobile。 如果你确实需要检查页面在移动访客端上的渲染效果,发送移动用户代理属于常规的内容协商,而非冒充行为——尽管一如既往地需要注意:在具有桌面视口且连接形式为桌面的情况下,仅凭移动字符串作为依据仅是半个论据,真正的移动浏览器在其他多个方面也会有所不同。
以及版本冻结的趋势。 浏览器一直在逐步减少该标头中暴露的详细信息,转而将功能信息转移到结构化的客户端提示中。其实际影响是,用户代理解析作为信息来源正逐渐衰落——包括那些决定如何处理您请求的网站,这也是将其作为制定策略依据的信号过于薄弱的另一个原因。
当用户代理并非问题所在时
列出这些情况,是因为人们通常会首先尝试更改用户代理,但这样做并无帮助。
当内容由 JavaScript 渲染时。 curl 不会执行脚本。几乎为空的响应意味着页面是在客户端组装的,解决方法在于浏览器或底层 API,而非请求头。
当您受到速率限制时。 429 涉及的是请求量,而非身份。 降低请求速度可能有效;但更改用户代理无济于事。
**当问题出在地址上时。**如果整个 IP 范围被封锁,那么无论请求头如何设置,来自该范围的所有请求都会失败。
当需要身份验证时。 401 需要凭据。
当 TLS 指纹暴露了你的身份时。 上文已提及,这也是仅靠标头模拟浏览器身份往往难以奏效的原因。
当网站根本不希望接收自动化流量时。 有些网站会在其条款中明确说明这一点并加以执行。修改标头并不能改变条款,而一个已明确告知你不要抓取其内容的网站,给你的其实是明确的信息,而非需要破解的谜题。
快速区分这些情况的诊断方法是:使用普通浏览器通过同一连接请求相同的 URL。如果浏览器能正常访问而 curl 无法访问,请逐个比较两个请求的头部信息——如果唯一关键的差异恰好是你无法通过命令行更改的,那就是答案。
大家还问
如何在 curl 中设置 User-Agent?
curl -A "MyBot/1.0" URL,或者等效地使用 curl -H "User-Agent: MyBot/1.0" URL。如果字符串中包含空格,请用引号括起来。如果多次指定,则以最后一个值为准。
curl 的默认 User-Agent 是什么?
curl/VERSION —— 例如 curl/8.22.0。除非您覆盖或移除它,否则每次请求都会发送该值,且某些服务器会根据该值做出不同的响应。
如何在 curl 中移除 User-Agent 头部?
curl -A "" URL 可完全移除该头部。curl -A " " URL 则会发送一个值为空的 User-Agent,这属于另一种请求方式。请注意,不发送 User-Agent 比发送 curl 的默认值更为罕见,因此反而会引起更多关注,而非减少关注。
我应该用 curl 伪造浏览器的 User-Agent 吗?
通常不建议。当请求的 TLS 指纹、标头集及其顺序完全与 curl 一致时,却使用浏览器字符串,这种不一致性是真实浏览器绝不会产生的,这反而会让你更容易被识别,而非更难被识别。如果你需要模拟浏览器的请求,请直接使用浏览器。
更改 User-Agent 能否避免被封禁?
仅靠这一招很少奏效。它有助于应对那些一概拒绝已知工具的规则,但对速率限制、基于 IP 地址的封禁、TLS 指纹识别或行为分析毫无作用。通过联系 URL 如实标识自己,往往比伪装更有效。
机器人的 User-Agent 应该是什么样子的?
名称、版本和一个联系 URL:AcmePriceBot/1.2 (+https://acme.example.com/bot)。这使得 robots.txt 能针对你进行具体处理,让运维人员能够联系你而非直接封禁,并赋予你请求访问权限的正当理由。
我可以为每次请求设置不同的 User-Agent 吗?
可以——-A 适用于调用本身,因此每次提供不同的值,或者使用 --next 在一个命令中执行多个带有不同选项的操作。避免在同一会话中随机轮换,否则会使客户端看起来像是在访问过程中频繁更换浏览器。
为什么我的自定义 User-Agent 没有显示?
最可能的原因是你通过不同方式设置了两次,因为 curl 会使用你外部设置的标头而非其内部标头,且最后定义的值优先。请通过 curl -v ... 2>&1 | grep -i '^> user-agent' 检查实际传输的内容。
总结
在 curl 中设置用户代理只需一个参数,而参数值的选择比语法更重要。
默认设置会如实声明 curl,而这种诚实的做法是站得住脚的:一致、不起眼,有时还能让网站对你给予合理的对待。虽然可以移除该标头——空参数会完全省略它,单个空格会使其变为空值——但这两种做法都比默认设置更不常见,这与人们通常的意图恰恰相反。
复制浏览器字符串是人们常见的本能反应,但也是最不可靠的选择,因为这种声明是可以被验证的。TLS 指纹、标头组成与顺序,以及是否获取页面的子资源,都会与之产生矛盾,而这种矛盾比直接承认更容易被识别出来。如果确实需要模拟浏览器请求,那么浏览器才是合适的工具。
有一种比大多数人预期的更有效的方法,而且完全免费:一个名称、一个版本号,以及一个能让他人了解你是什么的 URL。 这能让 robots.txt 准确识别你,让运维人员通过邮件与你联系而非直接封禁,并将“原因不明的流量”转化为“有所有者的爬虫”——当有人决定如何处理你时,这无疑是一个更有利的处境。
