「AI Agent 全栈开发 50 讲」——从本地模型部署到多智能体系统,一年省 87 万 第 49 课 | Playwright 进阶:反爬对抗与浏览器指纹
·
第 49 课 | Playwright 进阶:反爬对抗与浏览器指纹
使用 Playwright 替代 Selenium,配合 stealth 插件、代理 IP 池、人类行为模拟,应对更复杂的网站反爬机制。
一、Playwright vs Selenium:为什么迁移
| 维度 | Selenium | Playwright |
|---|---|---|
| 速度 | 较慢(需显式等待) | 快(自动等待元素就绪) |
| 反检测 | 容易被识别 | 内置多种反检测特性 |
| API 设计 | 传统 WebDriver 模式 | 现代 async/await,简洁直观 |
| 移动端 | 不支持 | 原生支持移动端模拟(iPhone/Android) |
| 网络拦截 | 需要额外配置 | 内置 route 拦截,可修改请求/响应 |
| 多页面 | 窗口切换复杂 | Context 隔离,天然支持多标签页 |
| 安装 | 需要 WebDriver 匹配浏览器版本 | 一条命令安装浏览器 |
迁移成本低:Playwright 的 API 设计更现代,从 Selenium 迁移通常只需 1-2 小时。
二、反爬策略全景
三、浏览器指纹伪装
3.1 什么是浏览器指纹
网站通过以下信息构建你的「指纹」:
- User-Agent(浏览器类型和版本)
- 屏幕分辨率
- 时区
- 语言偏好
- 字体列表
- WebGL 渲染器
- Canvas 指纹
如果指纹与正常浏览器不一致,就会被识别为爬虫。
3.2 完整指纹配置
from playwright.sync_api import sync_playwright
import random
# 真实浏览器 User-Agent 池
USER_AGENTS = [
# Chrome Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
# Chrome Mac
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
# Edge Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
# Firefox Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
]
# 常见屏幕分辨率
VIEWPORTS = [
{"width": 1920, "height": 1080},
{"width": 2560, "height": 1440},
{"width": 1366, "height": 768},
{"width": 1440, "height": 900},
]
# 中文语言与地区
LOCALES = ["zh-CN", "zh-TW", "zh-HK"]
TIMEZONES = ["Asia/Shanghai", "Asia/Hong_Kong", "Asia/Taipei"]
def create_stealth_context(browser):
"""创建带随机指纹的浏览器上下文"""
context = browser.new_context(
viewport=random.choice(VIEWPORTS),
user_agent=random.choice(USER_AGENTS),
locale=random.choice(LOCALES),
timezone_id=random.choice(TIMEZONES),
# 额外指纹配置
geolocation={"latitude": 31.2304, "longitude": 121.4737}, # 上海
permissions=["geolocation"],
color_scheme="light",
device_scale_factor=1,
is_mobile=False,
has_touch=False,
)
return context
四、Stealth 插件:隐藏自动化特征
4.1 默认情况下 Playwright 仍然可被检测
// 网站在浏览器中运行以下检测代码:
navigator.webdriver // Playwright 默认返回 true
4.2 注入反检测脚本
def inject_stealth_scripts(page):
"""注入反检测脚本,隐藏自动化特征"""
# 1. 隐藏 navigator.webdriver
page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
""")
# 2. 伪造 Chrome 运行时对象
page.add_init_script("""
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {},
};
""")
# 3. 伪造权限查询
page.add_init_script("""
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
""")
# 4. 伪造插件列表
page.add_init_script("""
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
""")
# 5. 伪造语言列表
page.add_init_script("""
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh', 'en'],
});
""")
五、代理 IP 池
import random
import time
class ProxyPool:
"""简易代理 IP 池"""
def __init__(self, proxies: list[str]):
self.proxies = proxies
self.failed_proxies: dict[str, float] = {} # 失败的代理及其冷却时间
self.cooldown_seconds = 300 # 失败后冷却 5 分钟
def get_proxy(self) -> str:
"""获取一个可用代理"""
now = time.time()
available = [
p for p in self.proxies
if p not in self.failed_proxies or now > self.failed_proxies[p]
]
if not available:
# 所有代理都失败了,重置冷却
self.failed_proxies.clear()
available = self.proxies
return random.choice(available)
def mark_failed(self, proxy: str):
"""标记代理失败"""
self.failed_proxies[proxy] = time.time() + self.cooldown_seconds
def create_browser_with_proxy(playwright, proxy_config: dict):
"""使用代理创建浏览器"""
# 代理格式: {"server": "http://proxy_ip:port", "username": "user", "password": "pass"}
return playwright.chromium.launch(
headless=True,
proxy=proxy_config if proxy_config else None,
)
六、人类行为模拟
import random
import time
import asyncio
from playwright.async_api import Page
class HumanBehavior:
"""模拟人类浏览行为"""
@staticmethod
async def random_delay(min_ms=500, max_ms=2000):
"""随机延迟,模拟人类阅读和思考时间"""
delay = random.uniform(min_ms, max_ms) / 1000
await asyncio.sleep(delay)
@staticmethod
async def human_type(page: Page, selector: str, text: str):
"""模拟人类打字(逐字输入,带随机间隔)"""
element = await page.query_selector(selector)
await element.click()
for char in text:
await page.keyboard.type(char, delay=random.randint(50, 200))
# 偶尔停顿(模拟思考)
if random.random() < 0.1:
await asyncio.sleep(random.uniform(0.3, 1.0))
@staticmethod
async def human_scroll(page: Page):
"""模拟人类滚动(不规则的滚动距离和速度)"""
for _ in range(random.randint(3, 8)):
scroll_distance = random.randint(200, 800)
await page.evaluate(f"window.scrollBy(0, {scroll_distance})")
await asyncio.sleep(random.uniform(1.0, 3.0))
@staticmethod
async def human_mouse_move(page: Page):
"""模拟鼠标移动轨迹"""
# 从当前位置移动到随机目标
x = random.randint(100, 800)
y = random.randint(100, 600)
await page.mouse.move(x, y, steps=random.randint(5, 15))
七、完整爬取示例
from playwright.sync_api import sync_playwright
def crawl_36kr_articles():
"""爬取 36 氪文章(教学示例)"""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = create_stealth_context(browser)
page = context.new_page()
# 注入反检测脚本
inject_stealth_scripts(page)
# 访问目标页面
page.goto("https://36kr.com/", wait_until="networkidle")
# 等待内容加载
page.wait_for_selector(".article-item", timeout=10000)
# 提取文章列表
articles = page.evaluate("""
() => {
const items = document.querySelectorAll('.article-item');
return Array.from(items).slice(0, 10).map(item => ({
title: item.querySelector('.article-title')?.innerText || '',
url: item.querySelector('a')?.href || '',
summary: item.querySelector('.article-description')?.innerText || '',
}));
}
""")
context.close()
browser.close()
return articles
八、运行
cd code/lesson-49-playwright
uv sync
uv run playwright_advanced.py
九、本课小结
- Playwright 是 Selenium 的现代替代品,API 更简洁、反检测更强
- 四层反爬策略:指纹伪装 → Stealth 注入 → 代理轮换 → 行为模拟
- 浏览器指纹包含 UA、分辨率、时区、语言等十余个维度
- 人类行为模拟的关键是「随机性」——固定的延迟反而容易被识别
- 反爬是攻防博弈,没有一劳永逸的方案,需要持续更新策略
配套代码:code/lesson-49-playwright/playwright_advanced.py
更多推荐


所有评论(0)