进阶
用 AI 智能体做文档结构化抽取:PDF / 网页 → 表格
合同、发票、研报、竞品官网——里面全是关键信息,但都是「人读得懂、机器读不动」的非结构化文本。你每天花大量时间把 PDF 里的字段一个一个敲进表格。本教程教你用 AI 智能体把任意文档抽成整齐的结构化表格:定义字段、调用抽取工具、批量跑、校验落库。这一步也是上一节 Firecrawl 抓取之后最自然的下游。
📑 本教程适合:需要把大量文档/网页转成表格的同学——财务对账、合同归档、竞品参数对比、研报摘要。需要一点点 Python 基础(不会也能看思路)。
先搞懂:什么是「结构化抽取」
大白话:你给模型一张「表格模板」(叫 schema),它负责把文档里对应内容填进格子里。和「让模型随便总结」不同,抽取要求固定字段、可校验、可入库。
| 方式 | 输出 | 能不能直接进数据库 |
|---|---|---|
| 自由总结 | 一段文字 | 不能,还得人再整理 |
| 结构化抽取 | 固定字段的 JSON | 能,直接转 DataFrame/表 |
抽取得稳,关键在 schema 写得够具体。「价格」这种字段要写明单位、币种、是否为区间,否则模型会自由发挥,下游表格就乱了。
Step 1:定义你的字段 schema
1 先画「表格模板」
以「从发票 PDF 抽字段」为例,schema 越细越稳:
schema = {
"发票号码": "字符串,形如 INV-2026-001",
"开票日期": "YYYY-MM-DD 格式日期",
"销售方": "公司全称",
"金额_含税": "数字,单位人民币元,不含『元』字",
"税率": "数字,如 0.13 表示 13%",
"明细行数": "整数"
}
💡 给每个字段写「格式约束」比写「描述」更有用。模型对格式敏感,对语义容易自由发挥。
Step 2:把 PDF / 网页读成文本
2 先拿到干净的原文
PDF 用解析库,网页可复用上一节的 Firecrawl:
# PDF → 文本
from pypdf import PdfReader
text = "\n".join(p.extract_text() for p in PdfReader("发票.pdf").pages)
# 网页 → 文本(用 Firecrawl 拿干净 Markdown)
# 见本中心《Firecrawl 实战》教程
markdown = app.scrape_url(url, params={"formats":["markdown"]})["markdown"]
扫描件/图片型 PDF 抽不出文字,需要先做 OCR(如 PaddleOCR / 云 OCR)。纯文本 PDF 才能直接抽。
Step 3:调用模型的「结构化输出」能力
3 让模型按 schema 吐 JSON
主流模型都支持「JSON / 函数调用」模式,强制输出符合 schema:
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role":"system","content":"你负责从文档抽取字段,只返回 JSON。"},
{"role":"user","content": f"字段要求:{schema}\n文档:{text}"}
],
response_format={"type":"json_object"} # 强制 JSON
)
import json
record = json.loads(resp.choices[0].message.content)
🔑 关键技巧:
response_format=json_object 是「防跑题」的开关。没有它,模型可能回你一段解释+JSON 混在一起,下游解析就崩。Step 4:批量处理一堆文档
4 一个文件夹批量变表格
import pandas as pd, glob, os
rows = []
for f in glob.glob("invoices/*.pdf"):
text = read_pdf(f)
rec = extract(text, schema) # Step 3 的函数
rec["_来源文件"] = os.path.basename(f)
rows.append(rec)
df = pd.DataFrame(rows)
df.to_excel("发票汇总.xlsx", index=False)
批量一定要加「来源文件名」列——哪条记录出问题,你才追得回去。别只存结果,丢了溯源。
Step 5:校验与纠错,别全信模型
5 抽完必须过一道关
模型偶尔会幻觉出不存在的字段值。加一层规则校验:
def validate(rec):
errs = []
if not rec["发票号码"].startswith("INV"):
errs.append("发票号码格式异常")
if not (0 <= rec["税率"] <= 1):
errs.append("税率超出 0~1 范围")
if rec["金额_含税"] <= 0:
errs.append("金额为非正数")
return errs
# 有问题的单独导出人工复核
bad = [r for r in rows if validate(r)]
if bad:
pd.DataFrame(bad).to_excel("待复核.xlsx", index=False)
🛡️ 重要:结构化抽取是「自动化第一道」,金额、法律条款这类高风险字段务必留人工复核通道。全自动 ≠ 零校验。
Step 6:把它封装成 Agent 的「抽取工具」
6 让 Agent 自己决定抽什么
和 Firecrawl 一样,把抽取包成 Tool,Agent 接到文档就能自动结构化:
@tool
def extract_document(text: str, schema: str) -> str:
"""把一段文档文本按给定 schema 抽成 JSON 字段。"""
return json.dumps(extract(text, eval(schema)))
# 组合拳:Firecrawl 抓取 → extract_document 抽取 → 写库
# 一个「竞品参数对比 Agent」就这么成型了
小结
结构化抽取是 Agent 从「会聊天」走向「会干活」的关键一步:把散落的文档变成可查询、可分析、可入库的表格。记住三件事——schema 写细、输出锁 JSON、结果必校验。配合《Firecrawl 实战》抓取、《Agent 反馈闭环》持续优化,你的文档处理流水线就闭环了。