共计 2786 个字符,预计需要花费 7 分钟才能阅读完成。
动态网页爬取的三大核心痛点
在抓取现代网页时,传统爬虫经常会遇到三个典型问题:

- JS 渲染缺失:许多网站内容依赖 JavaScript 动态加载,单纯 HTTP 请求无法获取完整 DOM 树
- 反爬机制突破:验证码、行为检测、指纹识别等技术让简单爬虫寸步难行
- 会话状态维护:登录状态、cookies 管理需要复杂的手动处理
为什么选择 Playwright?
对比同类工具,Playwright 具有显著优势:
- 多浏览器支持:Chromium/Firefox/WebKit 统一 API
- 自动等待机制:内置智能等待减少手动 sleep 调用
- 多语言 SDK:TypeScript/Java/Python/.NET 全支持
- 移动端模拟:设备型号、GPS、触摸事件模拟
与 Selenium 相比,Playwright 的启动速度快 3 - 5 倍,内存占用减少 40%。Puppeteer 虽性能接近,但缺少跨浏览器支持。
基础爬虫框架搭建(TypeScript 版)
import {chromium, Browser, Page} from 'playwright';
class BasicCrawler {
private browser: Browser | null = null;
async init() {
this.browser = await chromium.launch({
headless: true,
timeout: 15000
});
}
async crawl(url: string) {if (!this.browser) throw new Error('Browser not initialized');
const page = await this.browser.newPage();
try {
// 关键配置项
await page.setExtraHTTPHeaders({'Accept-Language': 'en-US,en;q=0.9'});
// 智能等待导航完成
await page.goto(url, {
waitUntil: 'networkidle',
timeout: 30000
});
// 示例:提取页面标题
const title = await page.title();
console.log(`Page title: ${title}`);
// 返回清理后的页面内容
return await page.content();} finally {await page.close();
}
}
async close() {this.browser?.close();
}
}
// 使用示例
(async () => {const crawler = new BasicCrawler();
await crawler.init();
await crawler.crawl('https://example.com');
await crawler.close();})();
关键代码解析
- 选择器最佳实践:
- 优先使用
data-testid等语义化属性 - 避免脆弱的 XPath 路径
-
示例:
await page.click('button[data-role="submit"]') -
请求节流控制:
// 限制并发请求数量 const MAX_CONCURRENT = 5; const semaphore = new Semaphore(MAX_CONCURRENT); async function throttledRequest(url: string) {await semaphore.acquire(); try {return await crawler.crawl(url); } finally {semaphore.release(); } } -
异常处理机制:
async function safeCrawl(url: string, retry = 3) {for (let i = 0; i < retry; i++) { try {return await crawler.crawl(url); } catch (err) {console.error(`Attempt ${i+1} failed:`, err); if (i === retry - 1) throw err; await new Promise(r => setTimeout(r, 2000 * (i + 1))); } } }
性能优化实战
并发模式测试数据
| 并发数 | 内存占用(MB) | 成功率 | 平均耗时(ms) |
|---|---|---|---|
| 5 | 320 | 98% | 1200 |
| 10 | 580 | 95% | 1500 |
| 20 | 1100 | 88% | 2100 |
分布式架构方案
graph TD
A[调度中心] -->| 分发任务 | B(Worker 1)
A -->| 分发任务 | C(Worker 2)
A -->| 分发任务 | D(Worker N)
B --> E[(Redis 任务队列)]
C --> E
D --> E
反反爬策略
-
UserAgent 轮换:
const userAgents = ['Mozilla/5.0 (Windows NT 10.0)...', 'Mozilla/5.0 (Macintosh; Intel Mac OS X...']; await page.setUserAgent(userAgents[Math.floor(Math.random() * userAgents.length)] ); -
指纹伪装:
await chromium.launch({ args: [ '--disable-blink-features=AutomationControlled', `--window-size=${randomInt(1200,1400)},${randomInt(800,1000)}` ] }); -
代理 IP 集成:
const proxy = pickFromProxyPool(); // 从 IP 池获取 const browser = await chromium.launch({ proxy: {server: `http://${proxy.ip}:${proxy.port}`, username: proxy.user, password: proxy.pass } });
生产环境检查清单
- 内存泄漏检测:
- 使用
--inspect参数启动 Node.js -
通过 Chrome DevTools Memory 面板检查
-
断点续爬实现:
interface TaskState { url: string; lastCursor?: string; retryCount: number; } // 使用 LevelDB 持久化状态 const db = new Level('./state-db'); -
日志监控方案:
- ELK 收集浏览器 console 日志
- Prometheus 监控请求成功率
- 关键指标告警阈值:
- 失败率 > 5%
- 平均延迟 > 3s
总结建议
经过实际项目验证,Playwright 在动态内容采集场景下表现优异。建议从简单项目入手,逐步添加:
- 先实现基础爬取功能
- 加入异常处理和重试机制
- 最后优化性能和安全策略
遇到疑难问题时,多利用 Playwright 的调试工具:
# 启动带 UI 的调试模式
DEBUG=pw:api npx playwright test --headed
正文完
发表至: 技术分享
近一天内
