请求头伪装与指纹规避

网络爬虫专题 · 掌握请求头伪装与反检测

专题:Python网络爬虫系统学习

关键词:Python, 网络爬虫, 请求头伪装, User-Agent, 浏览器指纹, JA3指纹, 反爬检测, 指纹规避

一、请求头检测原理

当爬虫向目标服务器发起HTTP请求时,服务器首先检查的就是请求头(Request Headers)。请求头本质上是一种元数据,包含客户端环境、偏好设置和连接信息。服务器通过这些信息判断请求是否来自真实的浏览器。

被检测的关键请求头

服务器会重点检测以下几类请求头:User-Agent标识浏览器类型和版本;Accept告诉服务器客户端能处理的内容类型;Accept-Language声明客户端语言偏好;Accept-Encoding指定支持的压缩算法;Referer指示请求来源页面;Connection管理连接策略。任何一个请求头不符合浏览器规范,都可能触发反爬机制。

异常请求头组合的特征

真实的浏览器请求头具有特定的组合规律。例如,Chrome浏览器会发送"Upgrade-Insecure-Requests: 1",而大部分爬虫框架不会自动添加此字段。Python的requests库默认发送的User-Agent是"python-requests/x.x.x",这是一个极其明显的爬虫标识。此外,请求头的顺序、值的格式(如Accept-Language的权重q值语法)也都是服务器检测的维度。

二、User-Agent伪装

User-Agent的结构

一个完整的User-Agent字符串包含多个组成部分,以Chrome浏览器为例:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

其中"Mozilla/5.0"是历史遗留的兼容性标识;括号内"Windows NT 10.0; Win64; x64"表示操作系统和架构;"AppleWebKit/537.36"是渲染引擎标识;"Chrome/120.0.0.0"表示浏览器名称和版本号。每个部分都有其特定的格式要求,随意拼接容易被识别。

桌面端与移动端UA区别

移动端User-Agent通常包含"Mobile"字样,操作系统标识也不同。iOS设备的UA中包含类似"iPhone; CPU iPhone OS 17_0 like Mac OS X"的片段,Android设备则包含"Android 14; Mobile"等标识。如果爬虫的目标网站有移动端版本,使用移动端UA可以降低被检测概率。

随机UA切换(fake_useragent库)

使用Python的fake_useragent库可以方便地生成随机的User-Agent字符串:

from fake_useragent import UserAgent ua = UserAgent() # 生成随机UA random_ua = ua.random # Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... # 生成指定浏览器UA chrome_ua = ua.chrome firefox_ua = ua.firefox safari_ua = ua.safari # 在requests中使用 headers = {'User-Agent': ua.random} response = requests.get(url, headers=headers)

需要注意的是,fake_useragent库依赖在线数据库更新,在网络受限环境中可能初始化较慢。可以预先下载并缓存UA列表,或者手动维护一个UA池。

常见浏览器UA列表

以下是一个手动维护的常用UA池示例,涵盖不同浏览器和操作系统:

USER_AGENTS = [ # Chrome on Windows "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", # Chrome on macOS "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", # Firefox on Windows "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0", # Safari on macOS "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15", # Edge on Windows "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", # Mobile Chrome on Android "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36", # Mobile Safari on iPhone "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1", ]

三、请求头完整性模拟

必须伪装的请求头

除了User-Agent,一个完整的浏览器请求还包含多个关键请求头,缺少任何一个都可能导致被识别为爬虫:

从浏览器复制完整的请求头

最直接的获取完整请求头的方法是从真实浏览器中复制。打开Chrome开发者工具(F12),切换到Network标签,刷新页面,点击任意请求查看Request Headers部分。复制后可以直接在Python中使用:

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://www.google.com/", "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", } response = requests.get(url, headers=headers)

requests中设置请求头

在Python requests库中,有两种方式设置请求头:每次请求单独设置和使用Session统一管理。单独设置适合单次请求,Session方式更适合需要保持登录状态或连续访问的场景:

# 方式一:每次请求单独设置 response = requests.get(url, headers=headers) # 方式二:使用Session统一管理 session = requests.Session() session.headers.update({ "User-Agent": ua.random, "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }) response = session.get(url)

Session默认请求头

需要注意的是,requests.Session对象本身会设置一些默认请求头。可以通过 session.headers 查看当前Session携带的所有请求头,确保没有遗漏或异常的默认值。特别注意"python-requests"相关的标识必须被覆盖掉。

四、浏览器指纹规避

仅伪装请求头已经不足以应对现代反爬系统,网站还会通过JavaScript采集浏览器的各种特征来生成唯一的浏览器指纹。以下是最常见的指纹检测技术及对应的规避方法。

Canvas指纹检测原理

Canvas指纹利用HTML5 Canvas API在不同设备上的渲染差异。浏览器会绘制一段包含文字和图形的图像,然后提取图像的base64编码。由于不同设备的显卡驱动、抗锯齿算法、字体渲染等差异,同一段Canvas绘制代码在不同设备上会产生略微不同的结果,从而生成唯一指纹。

// 网站检测Canvas指纹的典型代码 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillText('Hello, World!', 2, 2); const fingerprint = canvas.toDataURL();

在Selenium或Playwright中规避Canvas指纹检测,可以通过注入JavaScript修改Canvas API的行为:

// Playwright中规避Canvas指纹 await page.addInitScript(() => { const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function(type) { const canvas = this; const result = originalToDataURL.call(canvas, type); // 在返回值中引入微小噪声 return result.replace(/[a-f0-9]{10,}/g, (match) => { return match + Math.random().toString(16).slice(2, 4); }); }; });

WebGL指纹

WebGL指纹通过获取GPU渲染器的信息来生成唯一标识。网站通过 canvas.getContext('webgl') 获取WebGL上下文,然后读取渲染器信息(Renderer)和供应商信息(Vendor)。这些信息通常包含具体的GPU型号和驱动程序版本,具有很高的区分度。

字体指纹

字体指纹检测通过检测系统已安装的字体列表来识别用户。不同操作系统和用户安装的字体集合各不相同,能够提供较高的辨识度。规避方法是在自动化浏览器中预先安装常用字体集合,或通过JavaScript拦截字体检测API的返回值。

AudioContext指纹

AudioContext指纹利用Web Audio API在不同设备上处理音频信号时产生的微小差异。网站创建AudioContext对象,生成一段音频信号,提取处理后的音频数据的哈希值。芯片组和音频驱动的差异会导致不同的输出结果。

时区与语言设置

浏览器的时区和语言设置也是指纹的重要组成部分。自动化工具默认的时区通常是UTC,而真实用户的时区会与所在地区匹配。在Playwright中可以这样设置:

# Playwright中设置时区和语言 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context( locale='zh-CN', timezone_id='Asia/Shanghai', permissions=['geolocation'], geolocation={'longitude': 121.473701, 'latitude': 31.230416}, ) page = context.new_page() page.goto('https://example.com')

Selenium/Playwright中的指纹规避

自动化浏览器比requests库更容易被检测,因为除了请求头外,还有大量浏览器层面的特征。以下是Selenium和Playwright中常用的指纹规避策略:

# 使用undetected-chromedriver规避检测 import undetected_chromedriver as uc driver = uc.Chrome() driver.get('https://example.com')

五、常见反爬识别特征

了解服务器如何识别爬虫,是做好伪装的第一步。以下是服务器检测爬虫时最常用的判断依据:

缺少特定请求头

缺少某些浏览器特有的请求头是最容易被识别的特征。例如,Python requests库发起的请求默认不含Accept-Language、Accept-Encoding等字段,更不会包含Sec-Fetch-*系列安全请求头。现代反爬系统会检查这些字段的存在性,缺少任何一个都会增加爬虫识别概率。

请求头顺序异常

虽然HTTP规范不要求请求头的顺序,但一些反爬系统会检测请求头的排列顺序是否与真实浏览器一致。不同浏览器对请求头有固定的排序规则,如果请求头的排列顺序与任何已知浏览器都不匹配,就会被判定为异常请求。

请求头值不符合规范

请求头的值有特定的格式要求。例如,Accept-Language中的q值(权重)必须在0到1之间,且格式为"q=0.9";Accept字段中各种类型之间用逗号分隔,且按优先级排序。一个常见的错误是使用错误的q值格式或遗漏了某些必要参数。

Accept-Encoding缺失

现代浏览器几乎都会发送Accept-Encoding请求头,声明支持的压缩算法(gzip、deflate、br)。如果请求中缺少此字段,或者声明的算法组合不合常理(例如同时包含了过于老旧的compress算法),服务器会将该请求标记为可疑。

核心要点:请求头伪装的关键在于"完整性"和"一致性"。仅修改User-Agent而忽略其他请求头,往往会在反爬检测的第一道防线就被识别。最佳的实践是从真实浏览器中完整复制请求头,并通过Session统一管理。

六、Scrapy请求头配置

Scrapy框架提供了多种配置请求头的方式,从全局配置到中间件层级的灵活控制。

DEFAULT_REQUEST_HEADERS设置

在Scrapy项目的settings.py中,可以通过DEFAULT_REQUEST_HEADERS设置全局默认请求头:

# settings.py DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://www.google.com/', }

随机UA中间件

在Scrapy中实现随机User-Agent切换,需要编写自定义下载中间件:

# middlewares.py import random from fake_useragent import UserAgent class RandomUserAgentMiddleware: def __init__(self): self.ua = UserAgent() @classmethod def from_crawler(cls, crawler): return cls() def process_request(self, request, spider): request.headers['User-Agent'] = self.ua.random

然后在settings.py中启用该中间件:

# settings.py DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 400, }

请求头中间件实现

除了User-Agent,还可以构建一个更加全面的请求头中间件,在每次请求时随机选择一套完整的请求头配置:

# middlewares.py - 完整请求头中间件 class RandomHeadersMiddleware: def __init__(self): self.headers_pool = [ { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Accept-Encoding': 'gzip, deflate, br', 'Referer': 'https://www.baidu.com/', }, { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Firefox/121.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate', 'Referer': 'https://www.google.com/', }, ] def process_request(self, request, spider): headers = random.choice(self.headers_pool) for key, value in headers.items(): request.headers[key] = value

七、高级伪装技术

对于反爬能力强的目标网站,仅靠请求头伪装和浏览器配置已经不够,需要采用更高级的指纹规避技术。

TLS指纹(JA3指纹)规避

JA3指纹是一种通过分析TLS握手过程中的参数(如密码套件、椭圆曲线、扩展列表等)来识别客户端类型的机制。不同编程语言和库的TLS实现具有独特的特征,即使请求头伪装得再完美,服务器仍然可以通过TLS指纹识别出爬虫使用的不是真实浏览器。

# 使用curl_cffi模拟浏览器TLS指纹 from curl_cffi import requests # 指定模拟的浏览器类型 response = requests.get( 'https://example.com', impersonate='chrome120' # 模拟Chrome 120的TLS指纹 )

HTTP/2指纹模拟

随着HTTP/2的普及,服务器开始通过HTTP/2的帧格式、设置参数、流控制等特征来识别客户端。不同浏览器和库在HTTP/2协议层面的实现细节各不相同。curl_cffi库同样支持模拟浏览器的HTTP/2指纹。

请求间隔随机化

固定间隔的请求是爬虫的典型特征。真实用户的访问间隔是不规则的,受思考时间、页面加载时间等多种因素影响。实现随机化间隔时,应该使用正态分布或泊松分布等数学模型,而不是简单的均匀随机数:

import time import random # 不推荐:固定间隔 time.sleep(3) # 不推荐:均匀随机间隔 time.sleep(random.uniform(2, 4)) # 推荐:正态分布模拟人类思考时间 delay = random.gauss(mu=3.5, sigma=1.0) delay = max(0.5, min(10.0, delay)) # 限制范围 time.sleep(delay)

鼠标轨迹模拟

许多反爬系统会记录鼠标移动轨迹来判断访问者是否为真人。真实的鼠标移动轨迹具有贝塞尔曲线特征,而非从一个点直接跳到另一个点。使用Selenium时,可以通过ActionChains模拟自然的鼠标移动:

from selenium.webdriver.common.action_chains import ActionChains import numpy as np def human_move(driver, element): """模拟人类鼠标移动到目标元素""" action = ActionChains(driver) # 先移动到目标附近的随机偏移位置 action.move_to_element_with_offset( element, np.random.randint(-50, 50), np.random.randint(-50, 50) ) action.perform() time.sleep(random.uniform(0.1, 0.3)) # 再移动到目标元素 action.move_to_element(element) action.perform()

浏览器环境完整性检测

最先进的反爬系统会全面检测浏览器环境的完整性,包括但不限于:

# Playwright中覆盖浏览器环境检测 await page.addInitScript(() => { // 覆盖webdriver属性 Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); // 覆盖plugins Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5], }); // 覆盖languages Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'], }); });

重要提示:请求头伪装和指纹规避技术应当仅用于合法的数据采集和学习研究。违反网站robots.txt协议、突破身份认证系统、窃取非公开数据等行为可能涉及法律风险。请始终遵守目标网站的使用条款和当地法律法规。