← 返回网络爬虫目录
← 返回学习笔记首页
请求头伪装与指纹规避
网络爬虫专题 · 掌握请求头伪装与反检测
专题: 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,一个完整的浏览器请求还包含多个关键请求头,缺少任何一个都可能导致被识别为爬虫:
User-Agent :标识客户端身份,最基础的伪装字段
Accept :声明可接收的内容类型,如 text/html,application/xhtml+xml
Accept-Language :语言偏好,通常为 zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding :支持的压缩格式,如 gzip, deflate, br
Referer :来源页面URL,模拟从其他页面跳转过来的访问行为
Connection :连接管理方式,通常为 keep-alive
Upgrade-Insecure-Requests :声明支持HTTPS,值为1
Sec-Fetch-* :一系列安全相关的请求头,现代浏览器会自动添加
从浏览器复制完整的请求头
最直接的获取完整请求头的方法是从真实浏览器中复制。打开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中常用的指纹规避策略:
隐藏自动化标识 :删除navigator.webdriver属性,移除Chrome中的"Chrome正受到自动测试软件控制"提示条
覆盖navigator属性 :修改navigator.plugins、navigator.languages等属性使其与真实浏览器一致
使用undetected-chromedriver :这是一个专为规避反爬检测而设计的Selenium封装库
设置窗口尺寸 :使用常见的屏幕分辨率,避免无头模式下的默认尺寸
模拟鼠标移动 :在点击前模拟人类鼠标移动轨迹,而不是直接跳转到目标元素
# 使用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指纹识别出爬虫使用的不是真实浏览器。
Python requests库的TLS指纹与浏览器存在明显差异
使用curl_cffi库可以模拟浏览器的TLS指纹
pyhttpx库支持修改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()
浏览器环境完整性检测
最先进的反爬系统会全面检测浏览器环境的完整性,包括但不限于:
navigator.webdriver :Selenium和Playwright会设置此属性为true,需要覆盖
navigator.plugins :检测浏览器安装的插件数量和名称,真实浏览器通常有插件
navigator.languages :检测语言偏好列表,爬虫通常设置得过于简单
Chrome.runtime :检测是否存在chrome.runtime对象,无头浏览器可能缺失某些属性
WebDriver特征 :检测window.navigator中是否存在与WebDriver相关的属性
屏幕尺寸与色彩深度 :无头浏览器的默认值与真实设备不同
# 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协议、突破身份认证系统、窃取非公开数据等行为可能涉及法律风险。请始终遵守目标网站的使用条款和当地法律法规。
参考资料:
Python requests官方文档 | Scrapy框架文档 | Playwright官方指南 | fake_useragent库文档 | curl_cffi项目文档 | undetected-chromedriver项目
本学习笔记为本人学习资料,不得转载
免责声明:本文仅供学习研究使用,请勿用于非法用途。