🎯 一款可定制、具备反检测功能的云浏览器,由自主研发的 Chromium驱动,专为网页爬虫AI 代理设计。👉立即试用
返回博客

如何构建自我修复的网页抓取器

Emily Chen
Emily Chen

Advanced Data Extraction Specialist

05-Aug-2026

TL;DR:

  • 一个今天有效的CSS选择器在网站重命名类时失效,普通的抓取器悄无声息地返回空数据。
  • 一个自我修复的抓取器在运行时修复选择器:它获取页面,尝试保存的选择器,当未匹配时,向语言模型请求从实时DOM读取的新选择器。
  • 该管道通过Scrapeless Universal Scraping API获取渲染后的页面,因此模型始终对真实的、执行脚本的标记进行修复,而非空壳。
  • 实时运行显示保存的选择器.author-name匹配零个元素,然后deepseek-v4-flash建议small.author,提取了所有十位作者。
  • 修复后的选择器在被信任之前会对DOM进行验证——错误的猜测不会匹配任何元素并被丢弃,因此错误的建议不会悄悄地损坏你的数据。
  • 在免费计划上获取Scrapeless API密钥,并端到端运行整个循环。

管道概览

抓取器之所以失效并不是因为代码错误,而是因为页面发生了变动。解决的办法是将选择器视为可以重新生成的数据,而不是嵌入源代码中的常量。该管道分为五个阶段来实现:

抓取渲染页面 (Scrapeless) → 尝试保存的选择器 → 检测未匹配 → 用语言模型修复 → 验证并提取 → 持久化有效选择器

下面的每个阶段都是针对实时页面进行的。抓取和修复是离开你机器的两个调用;其他所有都可以进行本地解析以供检查。

阶段 1:使用Scrapeless抓取渲染后的页面

修复的效果取决于你向模型展示的HTML,因此抓取必须返回浏览器构建的页面,而不是客户端渲染的站点首次发送的空容器。该渲染抓取的工作是由Scrapeless Universal Scraping API完成的。Universal Scraping API使用js_render服务器端渲染页面,并在其data字段中返回完成的HTML。安装管道使用的两个库:

bash Copy
pip install requests beautifulsoup4

将两个密钥放入环境中——用于抓取的Scrapeless密钥和用于修复的模型密钥:

bash Copy
export SCRAPELESS_API_KEY="your_scrapeless_api_key"
export OPENROUTER_API_KEY="your_openrouter_api_key"

阶段 2:尝试保存的选择器并检测故障

在今天的页面上运行上个月的抓取器,失败是安静的:选择器不匹配任何内容,提取返回空列表,因此一个简单的作业写入零行并报告成功。下面的演示页面使用JavaScript构建其引用;保存的选择器.author-name是标记不再使用的一个类,代表了任何被重新命名的选择器:

python Copy
import os, requests
from bs4 import BeautifulSoup

def fetch(url):
    r = requests.post(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": True}},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()["data"]

soup = BeautifulSoup(fetch("https://quotes.toscrape.com/js/"), "html.parser")

SAVED_SELECTOR = ".author-name"          # 上个月有效;该站点重命名了类
authors = [e.get_text(strip=True) for e in soup.select(SAVED_SELECTOR)]
print(f"保存的选择器 {SAVED_SELECTOR!r} 匹配:{len(authors)}",
      "-> 已损坏,提取无数据" if not authors else "-> 正常")

运行使静默失败变得显眼:

text Copy
保存的选择器 '.author-name' 匹配:0 -> 已损坏,提取无数据

阶段 3:用语言模型修复

当保存的选择器不匹配任何内容时,将实时DOM交给模型并请求替代方案。模型读取实际结构——由CSS选择器规范定义的标签、类和嵌套——并返回对其面前页面量身定制的选择器,而不是它在训练时看到的页面。将回复限制为JSON对象保持回答为单一选择器字符串,而不是解释:

python Copy
import os, json, requests

def propose_selector(html_fragment, target):
    prompt = (f'一个网页抓取CSS选择器损坏了。从这个HTML片段返回JSON '
              f'{{"selector": "<css>"}}以获取包含{target}的元素。只返回JSON。\n\n'
              f"HTML:\n{html_fragment}")
    r = requests.post(
        "https://openrouter.ai/api/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"},
        json={"model": "deepseek/deepseek-v4-flash",
              "messages": [{"role": "user", "content": prompt}],
              "response_format": {"type": "json_object"}, "temperature": 0},
        timeout=120,
    )
    r.raise_for_status()
    return json.loads(r.json()["choices"][0]["message"]["content"])["selector"]

发送一个渲染的 .quote 块就足够提供上下文,并保持令牌计数以及成本较低。

获取您的免费计划 API 密钥:app.scrapeless.com

阶段 4:验证和提取

一个提议的选择器在 DOM 确认之前仅仅是建议。在相同的 soup 上运行修复后的选择器并计算匹配次数:真正的修复返回行,错误的猜测返回零,并在它能够写入空数据之前被丢弃。完整的循环将这三个调用联系在一起:

python Copy
import os, json, requests
from bs4 import BeautifulSoup

def fetch(url):
    r = requests.post(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"], "Content-Type": "application/json"},
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": True}},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()["data"]

def propose_selector(html_fragment, target):
    prompt = (f'一个网页抓取 CSS 选择器损坏了。从这个 HTML 片段中,返回 JSON '
              f'{{"selector": "<css>"}} 以获取持有 {target} 的元素。只返回 JSON。\n\n'
              f"HTML:\n{html_fragment}")
    r = requests.post(
        "https://openrouter.ai/api/v1/chat/completions",
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}", "Content-Type": "application/json"},
        json={"model": "deepseek/deepseek-v4-flash",
              "messages": [{"role": "user", "content": prompt}],
              "response_format": {"type": "json_object"}, "temperature": 0},
        timeout=120,
    )
    r.raise_for_status()
    return json.loads(r.json()["choices"][0]["message"]["content"])["selector"]

def extract(soup, selector):
    return [e.get_text(strip=True) for e in soup.select(selector)]

soup = BeautifulSoup(fetch("https://quotes.toscrape.com/js/"), "html.parser")
authors = extract(soup, ".author-name")

if not authors:
    print("保存的选择器 '.author-name' 匹配:0 -> 修复")
    sample = str(soup.select_one(".quote") or soup.body)[:1500]
    healed = propose_selector(sample, "quote author name")
    print("模型提议的选择器:", repr(healed))
    authors = extract(soup, healed)
    print("修复后的选择器匹配:", len(authors))

print("作者:", ", ".join(authors[:3]), "...")

损坏的选择器进行修复,提取又填充:

text Copy
保存的选择器 '.author-name' 匹配:0 -> 修复
模型提议的选择器: 'small.author'
修复后的选择器匹配: 10
作者: 阿尔伯特·爱因斯坦, J.K. 罗琳, 简·奥斯汀 ...

因为修复后的选择器是针对您解析器已经使用的 CSS 选择器引擎 进行检查的,因此验证是准确的:要么匹配元素,要么不匹配,并且只有匹配才被接受。

阶段 5:持久化有效选择器

修复一次是维护;每次运行都修复是浪费。在选择器验证后,将其写回到一个小缓存中,按字段索引,以便下次运行直接从磁盘读取有效选择器,只有在该选择器也损坏时才调用模型:

python Copy
import json
from pathlib import Path

CACHE = Path("selectors.json")

def remember(field, selector):
    cache = json.loads(CACHE.read_text()) if CACHE.exists() else {}
    cache[field] = selector
    CACHE.write_text(json.dumps(cache, indent=2))

remember("quote_author", "small.author")   # 下一次运行从修复后的选择器开始

抓取程序现在优雅地降级:它在选择器有效时依赖于缓存选择器,并在页面发生变化时立即修复它们,而不是等待有人注意并编辑代码。

结论

一个自愈的抓取器将停机事件中的破损选择器转变为运行时事件。无抓取的通用抓取API提供模型所需的渲染HTML,模型读取实时DOM并提出一个选择器,而与解析器的匹配计数决定修复是否有效,然后再写入任何数据。上面的运行—.author-name匹配零,small.author匹配十—是微型循环:获取、检测、修复、验证、持久化。在您扩展之前,请根据定价页面对您预计的请求量进行规划,并阅读关于浏览器如何将HTML解析成DOM的入门指南,如果您想理解渲染抓取的重要性。有关提取中语言模型的更广泛视角,可以查看什么是LLM抓取器的解释。

加入我们的社区,声Claim一个免费计划,并与其他建立弹性抓取器的开发者交流经验:Discord · Telegram

常见问题解答

问:当抓取器“破损”时到底发生了什么?

通常是选择器,而不是逻辑。一个网站发布了重新设计,重命名了某个类或重构了其标记,保存的CSS选择器停止匹配,因此提取返回一个空列表。请求仍然成功,这就是为什么很容易忽视这种失败。

问:为什么通过无抓取的通用抓取API进行抓取,而不是简单的请求?

因为模型只能根据所显示的HTML进行自愈。客户端渲染的页面会对裸HTTP客户端返回一个空容器,因此模型将推断没有数据的标记。通用抓取API在服务器端渲染页面,并返回完成的DOM,给模型提供真实的结构进行处理。

问:如何防止模型创造出一个匹配不到任何内容的选择器?

您在信任之前要进行验证。修复的选择器在解析的DOM上运行,并计数匹配;返回零个元素的选择器会被丢弃,而不是被使用。模型提出选择器,但匹配计数决定结果。

问:这会在每次运行时调用模型吗?

不会。一旦选择器验证成功,它会根据字段缓存到磁盘,后续运行直接读取有效选择器。仅当缓存选择器停止匹配时才会调用模型,这样能确保延迟和成本与实际失败挂钩,而不是与每次抓取挂钩。

问:以这种方式抓取是否合法?

修复循环并不改变您被允许收集的内容。抓取公共页面,遵循网站条款及其爬虫指令,避免处理您没有依据的个人数据,并保持适度的请求频率。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录