in

当网站通过标头阻止爬虫时我们应该怎么办

当网站通过标头阻止爬虫时我们应该怎么办

Web 抓取工具识别的主要原因之一是错误配置的请求标头,这可能导致阻止、限制甚至禁止 Web 抓取工具。

标头是每个 HTTP 请求的重要组成部分,因为它提供有关传入请求的重要元信息。这从有关客户端的基本信息(如用户代理字符串)到自定义安全令牌和客户端呈现功能不等。

在本文中,我们将深入了解网络抓取中的请求标头。我们如何通过将我们的请求配置为看起来自然来防止我们的网络抓取工具被识别和阻止。

我们将了解常见的标头及其含义,在配置这部分网络抓取过程时有哪些挑战和最佳实践。

检查浏览器行为

当网络抓取时,我们希望我们的抓取器显示为网络浏览器,因此首先我们应该确保我们的抓取器复制 Chrome 或 Firefox 等网络浏览器发送的通用标准标头。

要了解浏览器发送的内容,我们需要一个简单的回显服务器,它会打印出服务器正在接收的 HTTP 连接详细信息。我们可以用一个简短的 python 脚本来实现:

import socket

HOST = "127.0.0.1" 
PORT = 65432

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    while True:
        conn, addr = s.accept()
        with conn:
            print(f"connected by {addr}")
            data = conn.recv(1024)
            print(data.decode())
            # header
            conn.send(b'HTTP/1.1 200 OK\n')
            conn.send(b'Content-Type: text/html\n')
            conn.send(b'\n')
            # body
            conn.send(b'<html><body><pre>')
            conn.send(data)
            conn.send(b'</pre></body></html>')
            conn.close()

如果我们运行此脚本并在浏览器中转到http://127.0.0.1:65432 ,我们将看到浏览器发送的确切的 http 连接字符串:

Linux 上的 Chrome

GET / HTTP/1.1
Host: 127.0.0.1:65432
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

Linux 上的Firefox

GET / HTTP/1.1
Host: 127.0.0.1:65432
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
GET / HTTP/1.1
Host: 127.0.0.1:65432
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
GET / HTTP/1.1
Host: 127.0.0.1:65432
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Cache-Control: max-age=0

Windows 上的Edge

GET / HTTP/1.1
Host: 127.0.0.1:65432
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Microsoft Edge";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.30
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,fi;q=0.7

MacOS 上的 Chrome

GET / HTTP/1.1
Host: 127.0.0.1:65432
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

MacOS 上的Firefox

GET / HTTP/1.1
Host: 127.0.0.1:65432
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Cache-Control: max-age=0
GET / HTTP/1.1
Host: 127.0.0.1:65432
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15
Accept-Language: en-gb
Accept-Encoding: gzip, deflate
Connection: keep-alive

上面显示了默认标头及其在建立连接时作为第一个请求发送的常见 Web 浏览器的顺序。

使用此信息,我们可以为我们的网络抓取工具构建标头指纹配置文件。与往常一样,我们不希望这个指纹太突出,因此我们的目标应该是复制最常见的平台,例如Windows 上的 ChromeMacOS 上的 Safari

请求标题顺序

我们在上一节中首先注意到的是浏览器按一定顺序返回标头,这是一种经常被忽视的网络抓取工具识别方法。主要是因为许多使用各种编程语言的 http 客户端实现了它们自己的标头排序——这使得网络抓取工具的识别变得非常容易!

例如,Python中最常见的 http 客户端库- requests – 不遵守标头顺序(潜在解决方案请参阅issue 5814),因此可以轻松识别基于它的网络抓取程序。或者,httpx库确实尊重标头顺序,我们可以安全地将它用于网络抓取作为requests替代方案。

为避免因不自然的标头顺序而被检测到,我们应确保使用的 HTTP 客户端尊重标头顺序,并在 web 浏览器中显式排序标头。

例如,如果我们在 Python 中使用httpx,我们可以在 Windows标头及其顺序上模仿 Firefox:

import httpx

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Cache-Control": "max-age=0",
}
print(httpx.get("http://127.0.0.1:65432/", headers=HEADERS).text)

由于 python 字典是有序的,我们可以简单地将我们的头字典传递给我们的客户端,它们将按照这个定义的顺序发送。

接下来,让我们看看这些默认标头,它们是什么意思以及我们如何在网络抓取工具中复制它们。

通用标准标题

Accept

指示我们的 HTTP 客户端接受什么类型的数据。我们通常希望保持它在普通网络浏览器中的原样:

# Firefox
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
# Chrome
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Accept-Encoding

指示 HTTP 客户端支持的编码类型。记下br表示支持较新的brotli编码的值,该编码通常用于识别网络抓取工具。

# Firefox with brotli support
gzip, deflate, br
# Chrome with no brotli support
gzip, deflate

Accept-Language

指示浏览器支持的语言。q如果定义了多种语言(例如) ,请注意指示语言偏好分数的值fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5。在抓取非英语网站时,我们可能需要将此值调整为适当的语言,以避免脱颖而出。

# Firefox
en-US,en;q=0.5
# Chrome
en-US,en;q=0.9

Host

指示我们正在与之交互的服务器名称。大多数 http 客户端会根据给定的 url 自动配置此标头,因此我们无需担心。

Upgrade-Insecure-Requests

指示客户端是否允许 http->https 重定向。如果我们的爬虫与 SSL 斗争,我们可能想试试运气并禁用它以爬取网站的不安全版本(如果可用)。

User-Agent

可以说是网络抓取中最重要的标头。指示哪个设备正在提交请求。有成千上万种不同的用户代理字符串,但根据一般经验,我们希望瞄准最常见的可用字符串,通常是使用 Chrome 网络浏览器的 Windows 计算机:

# Windows 10 + Chrome
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36

# Mac Os + Chrome
Mozilla/5.0 (Macintosh; Intel Mac OS X 12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36

# ios + Chrome
Mozilla/5.0 (iPhone; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/99.0.4844.59 Mobile/15E148 Safari/604.1

# Android + Chrome
Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.78 Mobile Safari/537.36

请注意,配置的 User-Agent 字符串应与其余标准标头匹配,例如AcceptAccept-Encoding

由于用户代理指示各种软件版本,我们希望让我们的网络抓取器与最流行的版本保持同步,甚至在我们的抓取器池​​中使用许多不同的用户代理字符串来分发我们的网络。有几个公共的在线用户代理字符串数据库,例如whatismybrowser.com 提供的数据库——当大规模抓取时,选择一个健康的用户代理很重要!

Sec-Fetch 标头

Sec -Fetch-标头系列(又名获取元数据请求标头)指示可用于网络抓取指纹识别的一些安全细节:

  • Sec-Fetch-Site表示请求的来源。当网络抓取时,我们希望用于none直接请求和same-site动态数据请求(XHR 类型的请求)。
  • Sec-Fetch-Mode指示请求的导航来源。在网络抓取中,我们将用于navigate直接请求和same-origincorsno-cors(取决于网站的功能)用于动态数据请求。
  • Sec-Fetch-User指示请求是由用户还是 javascript 发出的。此标头的值始终为?1or 被省略。
  • Sec-Fetch-Dest表示请求的文档类型。在网络抓取中,这通常document用于直接 HTML 请求和empty动态数据请求。

这些是 Chrome 浏览器在处理 HTTPS 网站时使用的默认值,但是在动态 javascript 支持的网站中功能可能有所不同,因此始终值得关注每个网络抓取工具的这些标头。

Sec-CH(客户端提示)标头

Sec-CH-标头系列是对较新浏览器版本实现的用户代理字符串的一种新的实验性尝试。这些标头基本上包含 User-Agent 标头中可用的相同数据,因此匹配这两个标头的值很重要,以免被识别为网络抓取工具。

从 User-Agent 字符串中提取客户端提示详细信息的 Python 脚本

可选的标准标题

Web 浏览器也倾向于发送许多可选的标准标头,这些标头指示某些安全、呈现或内容功能。让我们来看看一些常见的,它们是什么意思,以及在网络抓取时我们应该如何处理它们。

参考标头

指示浏览历史记录,哪个页面将客户端引向当前页面。
这是一个很好的标题来掩盖抓取模式。换句话说,将此标头设置为自然的内容是一种很好的做法,例如,如果我们正在抓取 website.com/category/product.html,我们会将 referer 设置为website.comorwebsite.com/category以在我们将网站导航为普通用户会。有时我们甚至可能想将其设置为常见的搜索引擎,如google.com,bing.comduckduckgo.com

另一件需要注意的事情是noreferrer 链接不发送这个标题。当使用网络抓取工具跟踪这些链接时,我们也不应发送Referer标头,因为它很容易泄露。

Cookie 在网络抓取中起着重要作用,这里没有什么值得关注的。

首先,许多网页依靠 cookie 来跟踪会话历史,这意味着网络抓取工具需要复制或“破解”这种行为。常见的抓取模式称为“会话预走或预热” ——当网络抓取器在连接到产品页面之前,通过抓取其他页面(如主页和产品类别)来收集会话 cookie 并显示与网络的自然连接模式来预热 HTTP 会话服务器。

换句话说,在我们抓取之前,webstore.com/product1我们会访问webstore.comwebstore.com/products确保我们的抓取器看起来像“人”。

此外,确保 cookie 字符串与浏览器中的字符串相似也有助于避免识别。通常,HTTP 客户端库在生成 cookie 字符串时会有所不同,从而使网络抓取工具易于识别。

最后,有时可能值得编辑连接 cookie 而不是发送一些用于分析或跟踪的 cookie。许多网站承认一些用户会在他们的浏览器中运行反跟踪插件,因此通过在我们的抓取工具中删除这些 cookie,我们可以避免额外的连接监控,这可能会暴露我们的网络抓取工具。

授权标头

授权标头是向 Web 服务器提供秘密授权令牌的标准方式。在网络抓取中,我们可能会看到它用于动态的、javascript 驱动的网站中的 XHR 数据资源。授权令牌很少是动态的,所以我们经常可以将它们复制到网络爬虫代码中,或者它们可以在 HTML 正文或 javascript 资源中找到。

X 标题

许多现代网站利用 javascript 前端,可以为动态功能、分析或身份验证实现自定义标头。这些标头通常带有前缀并且X-是非标准的,但是我们在网络抓取中经常看到的很少,所以让我们来看看一些:

x-api-key

此标头的变体表示微服务安全密钥。通常,可以在 HTML 源代码(留意<script>标签)或前面的请求中找到此键。

x-csrf-令牌

Csrf 代表跨站点伪造,此令牌用于确保请求来自同一来源。当涉及到网络抓取时,通常此令牌嵌入在 HTML 源中,因此抓取器需要请求 HTML 页面以在连接到数据资源之前提取此令牌。

概    括

在这篇介绍性博客中,我们了解了网络抓取中最常见的标头,以及它们如何泄露我们的网络抓取程序身份,从而导致阻塞或限制。我们还介绍了如何使用标头顺序来识别网络抓取工具,以及如何对网络浏览器行为进行逆向工程,以便在我们的抓取工具中复制它。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。