各位观众老爷们,大家好!我是你们的老朋友,代码界的段子手,今天咱们来聊聊一个让爬虫工程师和自动化测试工程师头疼,但又不得不面对的问题:Headless Browser 的反检测。
想象一下,你辛辛苦苦写了一个爬虫,准备大干一场,结果一启动就被网站无情地屏蔽了,是不是感觉一口老血差点喷出来?或者你写的自动化测试脚本,好不容易跑起来了,结果因为太像机器人,导致测试结果不准确,甚至误判了重要的业务逻辑?
别慌!今天我就来教大家如何把你的 Headless Browser 打扮得像个真人一样,让那些反爬虫机制哭着喊着放你过去!
第一幕:了解你的敌人(反爬虫机制)
想要战胜敌人,首先要了解敌人。反爬虫机制五花八门,但万变不离其宗,它们的核心目标是区分真人和机器。那么,它们通常会从哪些方面入手呢?
-
User-Agent: 这是最常见的反爬虫手段之一。网站会检查你的 User-Agent,如果发现是 Headless Browser 的默认 User-Agent (例如 "HeadlessChrome"),那肯定会被毫不留情地拒绝。
-
JavaScript 指纹: Headless Browser 的 JavaScript 环境与真实浏览器存在差异,这些差异可以被网站用来生成指纹,从而识别你的 Headless Browser。常见的指纹包括:
navigator
对象中的属性,例如navigator.webdriver
、navigator.plugins
、navigator.languages
等。- Canvas 指纹:通过绘制一些图形,然后获取 Canvas 元素的
toDataURL()
值,不同的浏览器渲染出来的结果会有细微的差异。 - WebGL 指纹:类似 Canvas 指纹,利用 WebGL 渲染一些图形,然后获取指纹。
-
行为模式: 机器的行为模式通常比较规律,例如快速访问大量页面、没有鼠标移动、没有键盘输入等。网站会分析你的行为模式,如果发现异常,就会认为你是机器人。
-
IP 地址: 如果你的 IP 地址被大量用于爬虫或者恶意攻击,那么很可能会被网站加入黑名单。
-
HTTP Headers: Headless Browser 默认发送的 HTTP Headers 可能会缺少一些真实浏览器应该有的 Header,或者 Header 的顺序不正确。
第二幕:乔装打扮(模拟真实用户环境)
了解了反爬虫机制,接下来就是乔装打扮,让你的 Headless Browser 看上去更像一个真人。
1. 修改 User-Agent
这是最基本的操作,你需要将 Headless Browser 的 User-Agent 修改成一个真实的 User-Agent。你可以从以下几个途径获取真实的 User-Agent:
- 在线 User-Agent 列表: 网上有很多 User-Agent 列表,你可以随机选择一个。
- 自己的浏览器: 打开你的浏览器,在控制台中输入
navigator.userAgent
,就可以获取你的 User-Agent。
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
print(driver.execute_script("return navigator.userAgent;")) #验证是否修改成功
driver.quit()
2. 隐藏 navigator.webdriver
属性
navigator.webdriver
属性是 Headless Browser 的一个明显的特征,我们需要将其隐藏。
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
"""
})
driver.get("https://www.example.com")
print(driver.execute_script("return navigator.webdriver;")) # 验证是否隐藏成功
driver.quit()
3. 修改其他 JavaScript 指纹
除了 navigator.webdriver
属性,还有很多其他的 JavaScript 指纹可以被用来识别 Headless Browser。我们需要尽可能地模拟真实浏览器的 JavaScript 环境。
navigator.plugins
和navigator.languages
: 这两个属性可以用来模拟浏览器安装的插件和支持的语言。
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh', 'en-US', 'en'],
});
"""
})
driver.get("https://www.example.com")
print(driver.execute_script("return navigator.plugins;"))
print(driver.execute_script("return navigator.languages;"))
driver.quit()
- Canvas 和 WebGL 指纹: 这两个指纹比较复杂,需要一些图像处理的知识。你可以随机生成一些图像,然后用 Canvas 或 WebGL 渲染出来,再获取指纹。或者使用一些现成的库来生成随机的指纹。
4. 模拟用户行为
仅仅修改 JavaScript 指纹还不够,我们需要模拟真实用户的行为,让网站觉得你不是一个机器人。
- 随机延迟: 在访问页面、点击按钮、输入文本等操作之间加入随机延迟,模拟用户的思考时间。
import time
import random
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
time.sleep(random.uniform(1, 3)) # 随机延迟 1-3 秒
# 点击按钮
button = driver.find_element("xpath", "//button[@id='myButton']")
button.click()
time.sleep(random.uniform(0.5, 1.5))
# 输入文本
input_field = driver.find_element("xpath", "//input[@id='myInput']")
input_field.send_keys("Hello, world!")
time.sleep(random.uniform(0.3, 0.8))
driver.quit()
- 鼠标移动: 模拟鼠标移动的轨迹,可以使用一些库来实现,例如
pynput
。
from pynput.mouse import Controller
import time
import random
mouse = Controller()
def move_mouse(x, y):
current_x, current_y = mouse.position
steps = 10
for i in range(steps):
mouse.position = (current_x + (x - current_x) * (i + 1) / steps,
current_y + (y - current_y) * (i + 1) / steps)
time.sleep(random.uniform(0.01, 0.05))
# 获取元素的位置
element = driver.find_element("xpath", "//button[@id='myButton']")
location = element.location
size = element.size
x = location['x'] + size['width'] / 2
y = location['y'] + size['height'] / 2
move_mouse(x, y) # 模拟鼠标移动到元素中心
- 键盘输入: 模拟键盘输入的频率和速度,可以随机地插入一些错误,然后再删除。
import time
import random
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
input_field = driver.find_element("xpath", "//input[@id='myInput']")
text = "Hello, world!"
for char in text:
input_field.send_keys(char)
time.sleep(random.uniform(0.05, 0.2))
# 随机插入错误
if random.random() < 0.1:
input_field.send_keys(random.choice("abcdefghijklmnopqrstuvwxyz"))
time.sleep(random.uniform(0.05, 0.1))
input_field.send_keys(Keys.BACKSPACE) # 删除错误
time.sleep(random.uniform(0.05, 0.1))
driver.quit()
5. 使用代理 IP
如果你的 IP 地址被网站加入了黑名单,那么你需要使用代理 IP 来隐藏你的真实 IP 地址。
- 免费代理: 网上有很多免费的代理 IP,但是质量参差不齐,很多都不可用。
- 付费代理: 付费代理 IP 的质量通常比较好,但是需要一定的成本。
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
# 配置代理 IP
proxy = "your_proxy_ip:port"
options.add_argument('--proxy-server=%s' % proxy)
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
driver.quit()
6. 使用 Chrome 扩展
可以使用 Chrome 扩展来辅助反检测,例如:
- Anti Detect: 可以自动修改 User-Agent、隐藏
navigator.webdriver
属性等。 - Proxy SwitchyOmega: 可以方便地切换代理 IP。
第三幕:实战演练(代码示例)
下面是一个完整的代码示例,演示了如何使用 Selenium 和 ChromeOptions 来模拟真实用户环境:
import time
import random
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
def create_webdriver():
options = webdriver.ChromeOptions()
# 隐藏 webdriver 属性
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
# 设置 user-agent
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15"
]
options.add_argument(f"user-agent={random.choice(user_agents)}")
# 设置代理 (可选)
# proxy = "your_proxy_ip:port"
# options.add_argument('--proxy-server=%s' % proxy)
driver = webdriver.Chrome(options=options)
# 修改 navigator 对象
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
Object.defineProperty(navigator, 'webdriver', {
get: () => false
});
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh', 'en-US', 'en'],
});
"""
})
return driver
def simulate_human_behavior(driver):
# 随机延迟
time.sleep(random.uniform(1, 3))
# 模拟鼠标移动 (简化的示例)
# 获取元素的位置
# element = driver.find_element("xpath", "//button[@id='myButton']")
# location = element.location
# size = element.size
# x = location['x'] + size['width'] / 2
# y = location['y'] + size['height'] / 2
# print(f"Moving mouse to x:{x}, y:{y}") # For demonstration - REMOVE when running headlessly
# # move_mouse(x, y) # 假设你已经定义了 move_mouse 函数
# 模拟键盘输入 (简化的示例)
input_field = None # 替换成你的输入框的 xpath
if input_field:
text = "Hello, world!"
for char in text:
input_field.send_keys(char)
time.sleep(random.uniform(0.05, 0.2))
# 随机插入错误
if random.random() < 0.1:
input_field.send_keys(random.choice("abcdefghijklmnopqrstuvwxyz"))
time.sleep(random.uniform(0.05, 0.1))
input_field.send_keys(Keys.BACKSPACE) # 删除错误
time.sleep(random.uniform(0.05, 0.1))
if __name__ == '__main__':
driver = create_webdriver()
driver.get("https://www.httpbin.org/headers") # 方便测试网站,查看请求头
simulate_human_behavior(driver) # 模拟用户行为
time.sleep(5) # 保持浏览器打开一段时间方便观察
driver.quit()
第四幕:进阶技巧(更高级的反检测手段)
以上只是一些基本的反检测手段,如果你的目标网站的反爬虫机制比较高级,那么你需要使用更高级的技巧。
- 使用真实的浏览器: 与其费尽心思地模拟 Headless Browser,不如直接使用真实的浏览器。你可以使用 Selenium Grid 来管理多个真实的浏览器,然后让你的爬虫或者自动化测试脚本在这些浏览器上运行。
- 验证码识别: 如果网站使用了验证码,那么你需要使用验证码识别技术来自动识别验证码。可以使用一些第三方的验证码识别服务,例如 2Captcha。
- 机器学习: 可以使用机器学习算法来识别网站的反爬虫机制,然后根据不同的反爬虫机制采取不同的反检测策略。
第五幕:总结与建议
Headless Browser 的反检测是一个持续对抗的过程,网站的反爬虫机制会不断升级,你需要不断地学习新的反检测技巧。
- 不要过度爬取: 如果你的爬虫访问网站的频率过高,那么很容易被网站识别为机器人。请合理控制爬取频率,遵守网站的 robots.txt 协议。
- 尊重网站: 不要利用爬虫来获取网站的敏感信息,或者对网站进行恶意攻击。
一些建议:
- 更新你的webdriver: 不断更新你的webdriver,以获得最新的补丁和功能,这有助于更好地模拟真实浏览器的行为。
- 监控和适应: 持续监控你的爬虫或自动化测试脚本,并根据网站的反爬虫策略的变化进行调整。
最后,希望今天的讲座能够帮助大家更好地应对 Headless Browser 的反检测。记住,技术是把双刃剑,请合理使用,不要用于非法用途。
祝大家编码愉快,爬虫顺利!