从网页到结构化数据,只需要一个 Prompt:LLM 解析器实战

80 阅读5分钟

做过新闻类分析的同学,大多都经历过这种循环:

“帮我把这个页面的标题、时间、正文抓出来。”
“又改版了,再修一下 XPath 吧。”
“嗯?这个栏目怎么又换模板了?”

于是问题就来了:
有没有可能,不用我们自己盯着 DOM 结构写解析器,而是让 LLM 来生成解析规则?

这篇文章,就是把“Prompt → Parser 自动构建”这件事整理成一份速查指南。
随用随查,复制后基本就能跑。

本文以 第一财经(yicai.com)头条财经新闻页面 为例,演示完整流程。

1. 能用它做什么?

1)把自然语言变成解析规则

你说一句“帮我提取标题、作者、时间、正文”,
LLM 能反推出对应的 XPath / CSS 选择器。

2)生成可运行的 Python 解析器

它能自动拼装一个 parse(html),字段错了还会给 fallback。

3)自动识别网页特征

例如第一财经的新闻正文结构不太规整,有广告、有空段落,但 LLM 能识别出来并避开。

4)解析失败可自动修复

字段提不出来?丢回去再问一次就行。

5)能把 Prompt 转成一个长期可复用的 Parser 文件

适合新闻、资讯类站点这种常年改模板的网站。

2. Prompt 模板

下面是实践中效果最稳定的 Prompt 结构。

生成字段解析规则

请根据下面的财经新闻 HTML,生成一份字段解析规则。
字段:标题(title)、作者(author)、发布时间(pub_time)、正文内容(content)
输出 JSON,格式为 {"title": "...", "author": "...", "pub_time": "...", "content": "..."}
HTML:
{HTML_CONTENT}

生成可执行 parse(html) 的 Python 函数

根据上面的字段解析规则,生成一个 Python 函数 parse(html),返回 dict。
要求:
- 使用 XPath
- 解析失败返回 None,不报错
- 正文允许合并多个段落

解析失败自动修复

以下字段解析失败,请根据页面结构重写相应 XPath:{字段列表}
HTML:
{HTML_CONTENT}

3. 完整示例:抓第一财经头条 + LLM 自动生成解析器

示例内容包括:

  • 用 Playwright 抓第一财经最新头条
  • 通过爬虫代理访问
  • 调用 LLM 自动生成解析规则
  • 用生成的规则提取标题、作者、时间、正文

下面的代码可以直接运行。

import asyncio
import json
from playwright.async_api import async_playwright
from openai import AsyncOpenAI
from lxml import etree

# ============ 亿牛云代理配置(示例 www.16yun.cn)===========
PROXY_HOST = "proxy.16yun.cn"   # 代理域名
PROXY_PORT = "3100"            # 代理端口
PROXY_USER = "your_username"    # 代理用户名
PROXY_PASS = "your_password"    # 代理密码
# ============================================

# ============ LLM 调用:自动生成解析规则 ============
client = AsyncOpenAI(api_key="YOUR_API_KEY")

async def generate_parser(html):
    """让 LLM 读懂第一财经新闻页面,并自动生成解析规则"""
    prompt = f"""
    下面是一篇来自第一财经(yicai.com)的财经新闻 HTML,请为它生成解析规则。
    字段:标题(title)、作者(author)、发布时间(pub_time)、正文内容(content)
    输出 JSON 格式,不要写解释文字。
    HTML:
    {html}
    """
    resp = await client.chat.completions.create(
        model="gpt-4.1",
        messages=[{"role": "user", "content": prompt}]
    )
    return json.loads(resp.choices[0].message.content)


# ============ 解析执行逻辑 ============
def run_parser(html, rules):
    """根据 LLM 提供的规则执行字段提取"""
    tree = etree.HTML(html)
    data = {}

    for field, xpath in rules.items():
        try:
            res = tree.xpath(xpath)

            # 正文经常是多段落,特殊处理
            if field == "content" and isinstance(res, list):
                cleaned = [x.strip() for x in res if isinstance(x, str)]
                data[field] = "\n".join(cleaned) or None
            else:
                data[field] = res[0].strip() if res else None

        except:
            data[field] = None

    return data


# ============ Playwright 抓取第一财经头条 ============
async def fetch_page(url):
    """使用 Playwright 抓取第一财经页面内容(走代理)"""
    proxy_config = {
        "server": f"http://{PROXY_HOST}:{PROXY_PORT}",
        "username": PROXY_USER,
        "password": PROXY_PASS
    }

    async with async_playwright() as pw:
        browser = await pw.chromium.launch(
            headless=True,
            proxy=proxy_config
        )
        page = await browser.new_page()
        await page.goto(url, timeout=30000)
        html = await page.content()
        await browser.close()
        return html


# ============ 主流程:抓 → 解析器生成 → 解析 ============
async def main():
    url = "https://www.yicai.com/news/latest.html"  # 第一财经最新头条

    print("正在抓取第一财经头条网页……")
    html = await fetch_page(url)

    print("正在让 LLM 分析 HTML 并自动生成解析器……")
    rules = await generate_parser(html)
    print("生成的解析规则:", rules)

    print("正在执行解析……")
    result = run_parser(html, rules)
    print("结果:\n", result)


if __name__ == "__main__":
    asyncio.run(main())

4. 一些配置经验

LLM 的 prompt 一定要清晰

越结构化越准确,尤其是:

  • 指定字段名
  • 指定输出 JSON
  • 指定 XPath
  • 明确“不报错、返回 None”

模糊的指令经常导致格式混乱、字段缺失。

第一财经的页面结构特点

做过第一财经的都知道,页面结构有几个小坑:

  • 正文 <p> 有时混入广告段落
  • 作者标签不固定,有些文章没有
  • 发布时间格式不统一
  • 有时正文被包装在嵌套 div 中

LLM 对这些结构异常其实识别得挺准,用它来生成 XPath 会省不少人工调试时间。

XPath 比 CSS 更稳

新闻站点结构复杂,XPath 的容错性更好。

代理选长连接更稳定

Playwright 会保持长连接,代理稳定性极其关键。
普通短连接代理很容易导致超时或中断。

5. 最快 5 分钟验证是否可用

如果你只想验证“LLM 生成解析器是否靠谱”,按这四步即可:

第一步:抓 HTML

html = await fetch_page("https://www.yicai.com/news/latest.html")

第二步:自动生成解析规则

rules = await generate_parser(html)

第三步:执行解析

result = run_parser(html, rules)

第四步:打印

print(result)

通常第一次就能提到标题、时间和部分正文。
不准?把失败字段扔回 LLM,它会自己修。

最后的一点想法

网站解析这件事,本质是“页面一变 → 解析器就得跟着改”。
但现在我们有了一个新的思路:

  • 以前是我们去看 DOM,再写 XPath
  • 现在是让 LLM 去看 DOM,再写 XPath

这不仅减少工作量,还能做出更“适配多模板”的解析器。