☰
AI Agent 网页数据自动化:LLM+Playwright+FastAPI
2026/10/13 3:35:35 网站建设 项目流程

先说结论:网上对“Agent 通杀网站逆向”的各种说法,很多都是营销话术。Agent 智能体真正能解决的是网页信息提取、流程自动化和结构化数据整理,而不是绕过网站的安全机制。本文会把“AI Agent 做网站数据自动化”这件事讲清楚:架构怎么搭、环境怎么准备、接口怎么调、批量任务怎么跑,以及哪些事情绝对不能做。

文章会给出通用可落地的工程方案:LLM 负责规划与语义理解,Playwright 负责浏览器控制,FastAPI 提供接口服务,任务队列负责批量处理。所有操作都以自有站点、授权测试环境或公开合法数据源为前提。如果你关心的是合法范围内的网页数据采集与流程自动化,这篇可以直接收藏。

1. 核心能力速览

在动手之前,先把能力边界列成一张表。这里说的“Agent 智能体”不是一个单一开源项目,而是一套基于 LLM + 浏览器自动化 + 结构化输出的通用流程框架,底层全部使用公开生态里的成熟组件。

能力项说明
项目类型通用 Agent 流程框架,由 LLM 规划器 + 浏览器自动化工具 + 结构化输出层组成
核心功能网页文本提取、表格结构化、表单填写、分页遍历、登录后页面解析、批量信息收集
运行依赖Python 3.10+、Node.js(可选)、Chromium 浏览器、LLM API 或本地模型服务
模型接入OpenAI 兼容接口,支持调用云端 API,也支持 Ollama / vLLM 等本地模型服务
推荐硬件CPU 即可运行;若使用本地 LLM,显存需按模型实际规格评估
启动方式命令行单次运行 + FastAPI 启动 Web 服务
API 能力提供 HTTP 接口,可接收任务并返回结构化 JSON
批量任务支持目录批量处理、队列顺序执行、失败重试
输出格式JSON、Markdown、本地文件归档
适合场景自有系统数据核对、公开数据整理、文档站抓取解析、自动化流程验证
不适合场景绕过登录态、破解验证码、对抗反爬风控、抓取受限数据

这里必须强调一个原则:Agent 只能访问你有权访问的数据。凡是需要绕过网站安全机制才能拿到的内容,都不是这套框架应该解决的问题。

2. 适用场景与使用边界

2.1 合法且高频的落地场景

首先是自有站点或内网系统的数据核对。很多运营人员需要定期从后台导出报表再整理成固定格式,Agent 可以自动登录、读取页面数据、按模板汇总,省掉重复点击。

其次是公开文档站的结构化整理。比如技术文档、产品说明、行业报告页面,Agent 可以抽取正文、表格、关键字段,导出为 Markdown 或 JSON。这里要注意遵守站点本身的使用条款和 robots.txt 限制。

第三是授权测试环境下的流程自动化。在本地搭建演示系统,或者在公司测试环境执行表单提交、按钮点击、分页浏览,Agent 可以反复尝试,替代手工回归测试。

2.2 明确不能做的场景

电商平台是这类话题的高发区。很多电商站点有严格的反爬风控,页面结构也会频繁变化,且用户协议通常禁止未经授权的数据采集。不要尝试用 Agent 对抗验证码、模拟登录态绕过风控,或者高频请求公开商品页。正确的替代路径是:使用平台开放 API,或者通过数据授权合作获取结构化数据。

另一个不能做的是绕过登录限制读取付费内容。这类行为可能违反账号使用规则和相关法律法规,风险远高于收益。

2.3 隐私与版权红线

如果目标页面涉及个人信息(手机号、地址、订单记录),采集前必须确认是否有合法处理依据,并做好脱敏和权限控制。输出结果如果包含第三方版权内容,只能用于个人学习或已获授权的用途,不能直接商用发布。

3. 环境准备与前置条件

整套框架的核心是“LLM 做计划,浏览器执行,代码做校验”。环境准备分成三层:LLM 服务、浏览器自动化环境、项目依赖。

3.1 LLM 服务选择

Agent 的规划能力由 LLM 决定。你有两种接法:

  • 云端 API:直接用 OpenAI 兼容接口,优点是无需 GPU,缺点是每次调用产生费用,且数据会发送到第三方服务。敏感页面不适合走这条路。
  • 本地模型:用 Ollama 或 vLLM 起一个本地 API 服务,数据不出本机,但需要按模型规格准备内存和显存。7B 级别模型在小规模任务上表现尚可,复杂长页面需要更强模型。

本地模型启动示例(Ollama):

# 安装 ollama 后拉取模型,这里以 qwen2.5 为例 ollama pull qwen2.5:7b ollama serve

启动后本地 API 地址为http://127.0.0.1:11434/v1,兼容 OpenAI 的/v1/chat/completions接口。

3.2 检查清单

检查项要求说明
操作系统Windows / Linux / macOS 均可
Python3.10 及以上
Chromium通过 Playwright 安装,用于页面渲染
磁盘空间浏览器约 300MB,虚拟环境 2GB 左右
内存本机跑 7B 模型建议 16GB 以上
显存本机跑 7B 模型建议至少 8GB,具体以模型为准
端口11434(本地模型)、8000(Agent API 服务)
网络能访问目标测试站点和模型服务

启动前先确认端口没有被占用:

# Linux / macOS lsof -i :8000 lsof -i :11434 # Windows PowerShell netstat -ano | findstr :8000

如果端口被占用,要么关掉旧进程,要么换端口启动。

4. 智能体整体架构与关键设计

4.1 三层架构

整个 Agent 框架拆成三层:

  • 规划层:接收用户任务,调用 LLM 将任务拆成可执行步骤,例如“打开页面 -> 定位正文 -> 抽取标题和表格 -> 输出 JSON”。
  • 执行层:通过 Playwright 控制浏览器执行规划层给出的动作,包括页面访问、点击、输入、滚动、翻页。
  • 校验层:执行结束后检查结果完整性,如果页面结构变化导致抽取失败,则把错误信息回传给规划层,重新生成修正步骤。

这样的结构与单独写死爬虫脚本的区别在于:脚本依赖固定的 CSS 选择器,而 Agent 可以用语义描述定位页面元素,例如“找到商品列表区域”“把表格第一列作为 key”,不绑定具体页面结构。

4.2 Agent 工作循环

核心循环如下:

  1. 用户提交任务描述和目标 URL。
  2. 规划层生成操作步骤清单。
  3. 执行层逐条执行浏览器动作。
  4. 校验层判断当前步骤是否成功。
  5. 若失败,将页面快照和错误文本回传给 LLM,重新规划。
  6. 全部步骤完成后,统一输出结构化结果。

这个循环相当于一个简化版 ReAct 模式:Thought -> Action -> Observation,直到完成用户目标。

4.3 工具函数设计

执行层建议把浏览器能力封装成独立工具函数,便于 Agent 调用,也方便人工单测。常见工具函数如下:

工具名入参输出说明
open_pageurl页面摘要打开页面并截取可见文本
extract_textselector文本内容按语义描述或选择器提取文本
extract_tableselectorJSON 数组将页面表格转换为结构化数组
fill_formselector, value成功/失败填写表单字段
click_buttonselector成功/失败点击按钮
paginatedirection成功/失败翻页操作
snapshot无截图路径保存当前页面截图,用于失败回溯

工具封装得越干净,Agent 的规划成功率越高。不要把页面操作散落在业务流程里。

5. 搭建部署与启动方式

5.1 安装项目依赖

先创建虚拟环境并安装 Python 依赖:

python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install playwright fastapi uvicorn openai pydantic

安装 Playwright 浏览器内核:

playwright install chromium

如果系统缺少浏览器运行库,Linux 环境还要安装依赖:

# Ubuntu / Debian 示例 playwright install-deps

这里不需要 GPU,CPU 环境可以正常完成页面渲染和文本提取。

5.2 项目目录结构

建议按以下结构组织:

agent-project/ ├── agent/ │ ├── planner.py # LLM 规划器 │ ├── executor.py # 浏览器执行器 │ ├── validator.py # 结果校验器 │ └── tools.py # 工具函数集 ├── app.py # FastAPI 入口 ├── tasks.json # 批量任务配置 ├── inputs/ # 输入素材 ├── outputs/ # 输出结果 └── logs/ # 运行日志

5.3 核心代码:Agent 主循环

下面给出一段可以跑通的简化版 Agent 主循环,实际项目可以在此之上扩展:

# agent/core.py from playwright.sync_api import sync_playwright from openai import OpenAI class WebAgent: def __init__(self, base_url: str, api_key: str, model: str = "qwen2.5:7b"): self.client = OpenAI(base_url=base_url, api_key=api_key) self.model = model self.tool_names = ["open_page", "extract_text", "extract_table", "fill_form", "click_button", "paginate"] def plan(self, task: str, page_info: str) -> list[dict]: prompt = f""" 当前可调用工具:{self.tool_names} 目标任务:{task} 当前页面信息:{page_info} 请返回 JSON 格式的操作步骤列表,字段为 step, tool, params。 """ resp = self.client.chat.completions.create( model=self.model, messages=[{"role": "user", "content": prompt}], temperature=0.2 ) return eval(resp.choices[0].message.content) # 仅作示例,生产环境建议用 json.loads 解析 def run(self, task: str, start_url: str): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(start_url, wait_until="domcontentloaded") steps = self.plan(task, page.inner_text("body")[:2000]) results = [] for step in steps: try: result = self._execute_tool(page, step["tool"], step["params"]) results.append({"step": step["step"], "status": "ok", "result": result}) except Exception as exc: results.append({"step": step["step"], "status": "error", "error": str(exc)}) break browser.close() return results def _execute_tool(self, page, tool: str, params: dict): # 实际项目在这里把工具名映射到 tools.py 里的函数 if tool == "open_page": page.goto(params["url"], wait_until="domcontentloaded") return page.title() if tool == "extract_text": return page.inner_text(params["selector"]) raise NotImplementedError(f"工具 {tool} 未实现")

需要注意:示例中的eval仅用于演示可读性,生产环境必须使用json.loads并要求 LLM 严格输出 JSON,避免表达式注入风险。

5.4 启动 Web API 服务

用 FastAPI 把 Agent 包成 HTTP 服务:

# app.py from fastapi import FastAPI from pydantic import BaseModel from agent.core import WebAgent app = FastAPI() agent = WebAgent( base_url="http://127.0.0.1:11434/v1", api_key="local", model="qwen2.5:7b" ) class TaskRequest(BaseModel): task: str start_url: str @app.post("/run") def run_task(req: TaskRequest): return agent.run(req.task, req.start_url)

启动服务:

uvicorn app:app --host 0.0.0.0 --port 8000

启动后可以通过http://127.0.0.1:8000/docs查看 Swagger 文档,也可以直接用 POST 请求调用接口。如果端口冲突,换一个端口即可。

6. 功能测试与效果验证

框架搭好后,建议按以下顺序跑功能测试。为保证合规,所有测试目标都使用本地演示页面或公开合法数据源。

6.1 静态信息提取测试

测试目的:验证 Agent 是否能把网页正文和标题抽取为结构化 JSON。

操作步骤:使用一个公开技术文档页面或本地静态 HTML 文件,提交任务“提取页面标题、第一段正文和所有二级标题”。

请求示例:

curl -X POST http://127.0.0.1:8000/run \ -H "Content-Type: application/json" \ -d '{"task": "提取页面标题、第一段正文和所有二级标题", "start_url": "http://127.0.0.1:8080/test.html"}'

预期结果:返回 JSON 中包含完整字段,标题正确、正文无乱码、二级标题数量与实际页面一致。

判断标准:字段级校验通过,缺失字段为 0。

失败排查:如果提取结果为空,先检查目标页面是否被正常加载,再确认页面正文是否通过 JavaScript 动态渲染。

6.2 表格转结构化数据测试

测试目的:验证表格抽取能力,这是 Agent 在数据处理场景中最常被用到的功能。

操作步骤:准备一个包含多行多列的本地 HTML 表格页面,提交任务“把页面上第一个表格转成 JSON”。

预期结果:表格每一行变成一个 JSON 对象,表头成为字段名,空单元格保持为空字符串而不是丢弃。

常见问题:多层表头、合并单元格会显著增加解析难度。如果表格结构复杂,应该让 Agent 先输出 Markdown 格式,再二次转换。

6.3 表单填写与点击流程测试

测试目的:验证 Agent 能否根据语义描述完成“输入关键词 -> 点击搜索 -> 读取结果”的整段流程。

操作步骤:在本地测试系统准备一个带搜索框的页面,提交任务“在搜索框输入 Agent,点击搜索按钮,读取结果区域文本”。

预期结果:搜索框正确填入内容,按钮点击成功,返回结果区域包含目标关键词。

判断标准:观察 Playwright 日志,确认每个动作都有明确成功标记;如果点击后页面跳转,需要等待新页面加载完成再执行下一步。

失败排查:如果定位失败,将页面截图回传给 LLM,用图片描述替代纯文本描述,成功率会明显提升。

6.4 分页与动态加载测试

测试目的:验证翻页和滚动加载。

操作步骤:在本地测试页模拟分页列表,提交任务“遍历前 3 页,收集所有列表项标题”。

预期结果:返回结果包含 3 页的数据总和,且没有重复项。

注意点:分页遍历必须加入页数上限,否则动态加载页面可能无限制滚动下去。批量任务中建议统一加 max_pages 参数。

6.5 输出质量校验

每次任务结束后都应该跑一遍字段完整性检查。可以写一个独立校验函数:

def validate_result(data: dict, required_keys: list[str]) -> bool: missing = [key for key in required_keys if not data.get(key)] if missing: print(f"缺少字段: {missing}") return False return True

校验不通过时,把缺失字段名和页面摘要重新交给 LLM 做第二次规划,而不是直接丢弃结果。这比提高 prompt 温度参数更有效。

7. 接口 API 与批量任务

7.1 API 设计原则

接口层需要区分两类能力:单次任务接口和批量任务接口。单次任务用于联调和测试,批量任务用于生产环境。批量任务不建议同步等待,而应采用“提交任务 -> 后台执行 -> 轮询状态”的异步模式。

扩展 FastAPI 接口:

# app.py 追加 import uuid from pydantic import BaseModel BATCH_TASKS = {} class BatchRequest(BaseModel): tasks: list[dict] @app.post("/batch") def create_batch(req: BatchRequest): batch_id = str(uuid.uuid4()) BATCH_TASKS[batch_id] = {"status": "running", "results": [], "total": len(req.tasks)} # 实际项目这里应丢给后台任务队列处理,而不是直接阻塞 return {"batch_id": batch_id, "status": "accepted"} @app.get("/batch/{batch_id}") def get_batch(batch_id: str): return BATCH_TASKS.get(batch_id, {"status": "not_found"})

7.2 Python 客户端调用示例

import requests import time BASE_URL = "http://127.0.0.1:8000" def submit_batch(tasks): resp = requests.post(f"{BASE_URL}/batch", json={"tasks": tasks}, timeout=30) batch_id = resp.json()["batch_id"] print("批量任务ID:", batch_id) return batch_id def wait_batch(batch_id, interval=5, timeout=300): for _ in range(timeout // interval): status = requests.get(f"{BASE_URL}/batch/{batch_id}", timeout=30).json() if status["status"] != "running": return status time.sleep(interval) return {"status": "timeout"}

7.3 批量任务配置模板

批量任务建议使用 JSON 文件维护,方便审计和复用:

{ "max_retry": 3, "rate_limit_seconds": 2, "tasks": [ { "id": "task-001", "task": "提取页面标题和第一段", "start_url": "http://127.0.0.1:8080/doc1.html", "required_keys": ["title", "summary"] }, { "id": "task-002", "task": "提取表格为JSON", "start_url": "http://127.0.0.1:8080/doc2.html", "required_keys": ["table"] } ] }

rate_limit_seconds字段用来控制相邻请求间隔,这对任何网站都是必要的。不要删除这个字段,也不要把间隔设为 0。

失败重试策略:单个任务失败时先记录错误快照,间隔指数退避重试(如 3 秒、9 秒、27 秒),超过最大重试次数后将该任务标记为 failed,并继续执行后续任务。不要让一个失败任务卡死整个批次。

8. 资源占用与性能观察

8.1 LLM Token 开销

Agent 每轮规划都需要把任务描述、页面文本、当前状态发送给 LLM。一个普通页面正文约几千字,换算成 token 大约是 1000 到 3000。如果循环中每步都携带完整页面文本,token 消耗会快速增长。

优化方式:

  • 截断页面文本:默认只取 body 前 2000 到 4000 字符。
  • 缓存页面摘要:同一 URL 在短时间内的页面摘要直接复用。
  • 使用小模型做规划:规划任务是简单 JSON 输出,7B 级别模型足够,不必每次都调用大模型。

8.2 浏览器内存与并发

从常见部署形态看,单个 Chromium context 的内存开销在数百 MB 量级。如果同时开多个并发任务,内存压力会成倍上升。建议控制 Chrome 实例数量,优先用同一个浏览器实例内开多个 tab,而不是多个独立浏览器进程。

使用headless=True能省掉渲染开销,常规文本提取任务不需要窗口可见。只有在需要截图排障时才临时开启有头模式。

8.3 性能观察指标

建议记录以下指标:

  • 单任务总耗时
  • LLM 调用次数
  • 单次调用的 token 数
  • 浏览器内存峰值
  • 失败重试次数
  • 某个固定步骤的成功率

通过观察这些指标,可以判断瓶颈在 LLM 规划、页面渲染还是目标结构变化。页面结构变化是 Agent 方案最值得关注的风险点:目标站点改版后,旧规则全部失效。缓解策略是让 Agent 输出更依赖语义描述而不是固定选择器,并在每次任务前先做一次页面结构摘要。

9. 常见问题与排查方法

问题现象可能原因排查方式解决方案
依赖安装失败Python 版本过低或网络源不可用查看 pip 日志升级 Python,换国内镜像源
Chromium 启动失败缺少系统运行库或浏览器内核未安装运行 playwright install-deps安装系统依赖后重启
LLM 接口超时模型服务未启动或网络不通curl 测试接口确认 ollama serve 正常
Agent 返回空结果页面为动态渲染,正文未加载完成截图检查页面状态增加 wait 策略
数据提取缺字段页面结构与 prompt 描述不符打印页面摘要用语义描述替换选择器
批量任务卡住单任务异常未捕获查看任务日志加超时控制,失败跳转
被目标站点限制访问请求频率过高查看 HTTP 状态码拉大间隔,改用官方 API
端口冲突旧进程残留netstat 检查端口kill 旧进程或换端口

额外提醒:如果目标站点返回验证码或风控拦截,正确的做法是立即停止采集并调整方案,而不是尝试绕过。一旦触发 IP 限制,解决成本远高于采集收益。

10. 合规与工程化最佳实践

10.1 授权边界检查清单

每次新增目标站点前,先做五步检查:

  1. 确认自己是否有合法访问权限,例如自有站点、团队内部系统或已获得授权。
  2. 阅读目标站点的 robots.txt 和使用条款,确认是否允许自动化访问。
  3. 评估数据性质:是否涉及个人信息、付费内容或商业机密。
  4. 控制访问频率,避免对目标站点造成负载压力。
  5. 保留任务日志,记录采集时间、目标地址和用途。
# 检查目标站点的抓取约定 curl https://example.com/robots.txt

10.2 数据与日志管理

输出结果按来源和用途分目录存放,敏感字段在落盘前做脱敏。任务日志保留至少 30 天。Agent 的每个决策步骤都尽量记录:使用哪个模型、输入了什么任务、调用了什么工具、返回了什么结果。这样一旦出现数据问题,可以回溯到具体环节。

10.3 模型与接口安全

如果 Agent 服务对外提供接口,必须限制访问范围。生产环境不要直接暴露到公网,可以绑定内网 IP,或增加 Token 鉴权:

uvicorn app:app --host 127.0.0.1 --port 8000

对外提供服务时,把 API Key 放在环境变量中,不要写进代码仓库:

export LLM_BASE_URL=http://127.0.0.1:11434/v1 export LLM_API_KEY=your_key

10.4 上线前效果复核

批量采集结果上线前,人工抽样核对 5% 到 10% 的数据。Agent 的抽取质量受页面结构影响较大,特别是表格和复杂排版。宁可保留一份经过人工复核的小数据集,也不要直接发布未校验的全量结果。

11. 总结与下一步

AI Agent 做网站信息提取的完整链路是:LLM 负责语义理解,浏览器自动化负责操作,代码校验负责兜底。这套架构能够覆盖公开文档整理、自有系统数据核对、表单流程自动化等场景,门槛不高,CPU 机器可以跑,部署核心就是 Python 环境加浏览器。第一批建议做的测试很简单:找一个本地 HTML 页面,让 Agent 把标题和表格提取成 JSON,跑通后再加上批量任务和失败重试。

最容易踩的坑有三个:第一,没有控制请求频率被目标站点限制;第二,用固定选择器依赖页面结构,页面一改就失效;第三,批量任务没有超时和重试机制,单任务失败卡死整个队列。这些坑都能通过日志和校验流程提前发现。

更需要注意的是边界问题。Agent 能自动化流程,但不能自动化“授权”。目标站点允不允许自动化访问、数据能不能采集、用在哪,这些判断不能交给模型决定,只能由使用者负责。先在授权环境下验证能力,再逐步放大任务规模,这套框架会非常顺手。下一步可以尝试的方向是:多 Agent 协作做拆分任务、把页面截图纳入模型输入以提升定位准确率、以及将抽取结果接入数据看板做定期核对。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询