用Playwright打造AI网页抓取Agent:从爬虫到智能数据提取(2026)

📘 AI教程 💬 🔥 Trending

🩺 摘要

传统爬虫死了?没有,但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记忆系统——向量数据库原理解析