用Playwright打造AI网页抓取Agent:从爬虫到智能数据提取(2026)
🩺 摘要
传统爬虫死了?没有,但AI爬虫确实在抢它们的工作。Playwright(GitHub 93K stars,截至2026年...
📝 详情
传统爬虫死了?没有,但AI爬虫确实在抢它们的工作。Playwright(GitHub 93K stars,截至2026年7月)打开了一个真正「看得懂」网页的抓取方式。
传统爬虫 vs AI爬虫
传统爬虫(Requests + BeautifulSoup)有三大死穴: 1. JavaScript渲染 — 大部分现代网站内容由JS生成,requests拿不到 2. 反爬机制 — Cloudflare、reCAPTCHA、指纹检测,Requests栈扛不住 3. 结构变化 — 页面改个class名,你的爬虫就废了
Playwright解决了1和2(因为它是一个真浏览器)。而加上LLM,第3点也不攻自破。
核心思路: 让Playwright渲染页面 → 提取原始文本/DOM → 让LLM理解内容并提取需要的信息。不再写死CSS选择器。
第一步:基础智能爬虫
最简单的版本——告诉Agent「去这个网址,找到XX信息」:
# smart_scraper.py
import asyncio
from playwright.async_api import async_playwright
from openai import AsyncOpenAI
llm = AsyncOpenAI()
async def smart_scrape(url: str, instruction: str) -> dict:
"""用自然语言指令爬取网页"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 假装是人类浏览器
await page.set_extra_http_headers({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
await page.goto(url, wait_until="networkidle")
await page.wait_for_timeout(2000) # 等JS渲染
# 提取页面文本
content = await page.evaluate("""
() => {
// 移除无用元素
const remove = ['script', 'style', 'nav', 'footer', 'header'];
remove.forEach(tag => {
document.querySelectorAll(tag).forEach(el => el.remove());
});
return document.body.innerText;
}
""")
await browser.close()
# 让LLM提取信息
response = await llm.chat.completions.create(
model="gpt-4o-mini", # 提取信息用小模型就够了
messages=[
{"role": "system", "content": f"你是一个数据提取专家。从以下网页内容中提取用户需要的信息。只返回纯JSON,不要任何解释。"},
{"role": "user", "content": f"网页内容:\n{content[:8000]}\n\n请提取:{instruction}"}
],
response_format={"type": "json_object"}
)
return response.choices[0].message.content
# 使用示例
result = await smart_scrape(
"https://news.ycombinator.com/",
"提取前10条新闻的标题、链接和分数"
)
print(result)
这就是AI爬虫的核心模式: 浏览器渲染获取内容 → LLM理解提取。不需要写一行CSS选择器。
第二步:结构化数据提取Agent
上面的例子是「一次性」的。真正的Agent需要能交互——点击"加载更多"、翻页、填表单。
# agent_scraper.py
from playwright.async_api import async_playwright, Page
from openai import AsyncOpenAI
class ScrapingAgent:
def __init__(self):
self.llm = AsyncOpenAI()
async def think_what_to_do(self, page: Page, goal: str) -> dict:
"""让Agent观察页面并决定下一步"""
# 获取页面信息
url = page.url
title = await page.title()
visible_text = await page.evaluate("""
() => document.body.innerText.slice(0, 3000)
""")
links = await page.evaluate("""
() => Array.from(document.querySelectorAll('a[href]'))
.slice(0, 20)
.map(a => ({ text: a.innerText.slice(0, 50), href: a.href }))
""")
prompt = f"""你是一个网页抓取Agent。你的目标是:{goal}
当前页面:{url}
标题:{title}
可见内容概要:{visible_text[:500]}
可用链接(前20个):
{chr(10).join([f'- {l["text"]}: {l["href"]}' for l in links])}
请决定下一步操作。返回JSON格式:
- 如果要提取数据:{{"action": "extract", "fields": ["字段名1", "字段名2"]}}
- 如果要点击链接:{{"action": "click", "selector": "链接文本或href"}}
- 如果已经完成:{{"action": "done", "data": {{...}}}}"""
response = await self.llm.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"}
)
return eval(response.choices[0].message.content)
async def extract_data(self, page: Page, fields: list) -> dict:
"""提取指定字段"""
content = await page.evaluate("""
() => {
['script','style','nav','footer'].forEach(
t => document.querySelectorAll(t).forEach(e => e.remove())
);
return document.body.innerText;
}
""")
response = await self.llm.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content":
f"从以下内容中提取{fields},返回JSON:\n{content[:10000]}"}],
response_format={"type": "json_object"}
)
return eval(response.choices[0].message.content)
async def run(self, start_url: str, goal: str, max_steps: int = 10):
"""运行Agent"""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
page.set_default_timeout(15000)
await page.goto(start_url, wait_until="networkidle")
await page.wait_for_timeout(2000)
for step in range(max_steps):
print(f"Step {step + 1}: Observing page...")
decision = await self.think_what_to_do(page, goal)
if decision["action"] == "extract":
data = await self.extract_data(page, decision["fields"])
print(f"Extracted: {data}")
elif decision["action"] == "click":
# 尝试多种方式点击
link_text = decision.get("selector", "")
try:
await page.click(f'a:has-text("{link_text}")')
await page.wait_for_load_state("networkidle")
print(f"Clicked: {link_text}")
except:
# 用LLM再试一次
print(f"Failed to click: {link_text}")
elif decision["action"] == "done":
await browser.close()
return decision.get("data", {})
await page.wait_for_timeout(1000)
await browser.close()
return {"error": "Max steps reached"}
这个Agent的思路是:观察→决定→行动→再观察的循环。类似自主Agent的ReAct模式,只是行动从「调用工具」变成了「在浏览器里操作」。
第三步:反爬对抗实战技巧
Playwright解决了浏览器层面的问题,但真实世界的反爬虫比你想象的狡猾。
1. Headless检测绕过
很多网站检测 navigator.webdriver。Playwright默认是true,改掉:
await page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
""")
2. 指纹随机化
# 随机化浏览器特征
await page.evaluate("""
// 假装有显卡
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
// 假装有摄像头和麦克风
Object.defineProperty(navigator.mediaDevices, 'enumerateDevices', {
get: () => async () => [{kind: 'videoinput'}, {kind: 'audioinput'}]
});
""")
3. 真人行为模拟
# 随机鼠标移动
import random
for _ in range(random.randint(3, 8)):
x = random.randint(100, 800)
y = random.randint(100, 600)
await page.mouse.move(x, y, steps=random.randint(5, 15))
await page.wait_for_timeout(random.randint(100, 500))
# 随机滚动
await page.evaluate(f"window.scrollTo(0, {random.randint(200, 1200)})")
await page.wait_for_timeout(random.randint(500, 2000))
4. 代理轮换
# 在browser.new_context里设置代理
context = await browser.new_context(
proxy={"server": "http://your-proxy:8080"}
)
重要提醒: 遵守robots.txt和各网站的条款。这些技巧是为了处理合理的技术障碍(如防止被封),不是为了突破法律边界。
常见问题排查
| 问题 | 原因 | 解决 |
|---|---|---|
| 页面空白 | JS未渲染 | 用 wait_until="networkidle" + 额外等2-3秒 |
| 被识别为机器人 | Headless检测 | 注入反检测脚本 + 伪装UA |
| 页面元素找不到 | 动态加载 | 用 page.wait_for_selector() 代替直接click |
| LLM提取不准 | 内容太多超出上下文 | 分段提取,每次限制5000字 |
| 内存爆了 | 浏览器进程残留 | 用完保证调用 browser.close() |
总结
AI + Playwright的组合,让网页抓取从「写死选择器」进化到「告诉它要什么」:
- 第一步: 简单版本——Playwright渲染 + LLM提取,5行代码实现智能抓取
- 第二步: Agent版本——自动循环观察→决策→行动,处理翻页/填表/多页采集
- 第三步: 反爬技巧——Headless检测绕过、指纹随机化、真人行为模拟
这套方案最实用的地方:网站改版不需要改代码。 以前爬虫维护最怕的就是网站改版。AI爬虫只关心内容,不关心class名。就算UI变了,只要内容还在,LLM就能提取出来。
💡 建议收藏本文。 这篇文章的三步走框架,足以搭建能满足90%需求的数据采集Agent。
📤 转发给做数据采集的朋友。 用Playwright + AI代替传统爬虫,维护成本降低80%。
上篇:AI Agent记忆系统——向量数据库原理解析
💬 评论 (0)