本文介绍了使用 Firecrawl API 监控 Web 内容变更,以弹壳回声游戏 Wiki 为例,构建自动化内容更新系统。
摘要
本文详细介绍了如何利用 Firecrawl API 构建一个自动化的 Web 内容变更检测系统,以监控弹壳回声游戏 Wiki 的更新。该系统利用 Firecrawl 的变更追踪功能(changeTracking),定期抓取 Wiki 页面,自动检测内容的新增、更新或删除。文章分步骤讲解了项目配置,包括 uv、.env 和 git 等工具的使用;数据模型的定义(使用了 Pydantic 库);核心代码的实现(涉及 changeTracking 和 batch_scrape_urls 等关键功能);以及如何使用 GitHub Actions 实现自动化部署,保证检索增强生成 (RAG) 系统知识库的实时更新。
变化检测简介
今天,许多 AI 应用依赖于最新的信息。例如,检索增强系统需要当前的网页数据以提供准确的回答,而知识库必须反映最新的产品信息。然而,面对成千上万下载的页面,识别原始来源何时发生变化是困难的。
这个问题影响到实际系统。如果客服聊天机器人使用过时的文档,可能会给出错误的答案。如果金融分析工具没有捕捉到最新的市场报告,可能会错过重要的变化。在医疗保健领域,治疗建议必须基于最新的临床指南和研究。
为了解决这些挑战,我们已经将变化检测集成到我们的网络抓取 API 中。这是如何工作的:
from firecrawl import FirecrawlApp
# Initialize the app
app = FirecrawlApp()
# Check if a page has changed
base_url = "https://bullet-echo.fandom.com/wiki/Special:AllPages"
result = app.scrape_url(
base_url,
formats=["changeTracking", "markdown"],
)
tracking_data = result.changeTracking
print(tracking_data)
输出结果:
ChangeTrackingData(previousScrapeAt='2025-05-03T15:47:57.21162+00:00', changeStatus='changed', visibility='visible', diff=None, json=None)
代码片段首先初始化了 FirecrawlApp
,然后使用格式 "changeTracking"
和 "markdown"
抓取指定的 URL。在格式列表中包含 "changeTracking"
可以使 Firecrawl 将当前抓取结果与之前存在的页面版本进行比较。
结果中的 tracking_data
对象提供了几个关键字段:
-
previousScrapeAt
:本次比较所依据的上次抓取的时间戳(如果这是第一次抓取,则为None
)。 -
changeStatus
: 表示比较的结果。可能的值包括:"new"
: 该页面之前未被抓取。"same"
: 该页面内容自上次抓取以来未发生变化。"changed"
: 该页面内容自上次抓取以来已发生变化。"已删除"
: 该页面之前存在但现在已不存在。
-
可见性
: 显示页面是否当前可发现("可见"
)或不可发现("隐藏"
)。
这允许您通过程序检测页面是否已更改、是否为新页面或已被删除,并相应地采取行动。
注意:如果您完全不了解 Firecrawl,请参阅我们的文档 。
在本教程中,我们将构建一个 Wiki 监控系统,跟踪托管在 Fandom 上的游戏 Wiki 页面的变化。我们的系统将每周和每月进行抓取,智能地识别哪些页面发生了变化,并仅下载更新的内容。
关于 Web 数据集
Bullet Echo 是我最喜欢的移动游戏,游戏玩法出色。它包含一个俯视 2D 多人吃鸡模式,快速反应和潜行至关重要。游戏 trivia 和文档由社区在 Bullet Echo 维基 维护,其中包含超过 180 篇文章:
我们的目的是下载所有这些页面为 markdown 文件,并构建一个系统,定期更新这些页面,因为经常添加新的游戏功能。
逐步构建变化检测系统
现在,让我们从增量步骤开始构建我们的项目。
项目设置和配置
在本节中,我们将设置用于变更检测系统的项目环境。我们将使用 uv
,这是一个快速的 Python 包安装器和解析器,来创建和管理我们的 Python 项目。我们还将配置使用 Firecrawl API 所需的环境。
-
安装 uv:
pip install uv
-
创建项目目录结构 :
mkdir -p change-detection cd change-detection
-
使用 uv 初始化项目 :
uv init --python=3.12
这会创建一个基本的
pyproject.toml
文件,并将 Python 3.12 设置为项目的 Python 版本。 -
使用 uv 添加项目依赖:
uv add firecrawl-py pydantic python-dotenv
-
创建数据和代码目录:
mkdir -p data src
-
设置环境变量: 在项目根目录创建一个
.env
文件,用于存储您的 Firecrawl API 密钥:echo "FIRECRAWL_API_KEY=your_api_key_here" > .env
-
创建一个.gitignore 文件:
echo ".env" > .gitignore echo ".venv" > .gitignore
-
获取 Firecrawl API 密钥 :
- 访问 Firecrawl 的网站
- 注册账户或登录
- 导航到您的账户设置
- 生成一个新的 API 密钥
- 将此密钥复制到你的
.env
文件中
-
设置 Git 仓库 :
git init git add . git commit -m "Initial project setup"
-
在 GitHub 上初始化仓库 :
-
创建一个新的 GitHub 仓库
-
将你的本地仓库推送到 GitHub:
git remote add origin https://github.com/yourusername/change-detection.git git push -u origin main
-
在你的 GitHub 仓库中,前往设置 > 秘密和变量 > 行动
-
添加一个新的仓库秘密,命名为
FIRECRAWL_API_KEY
,并输入你的 Firecrawl API 密钥
-
我们的项目环境设置完成后,我们现在可以创建数据模型并在接下来的步骤中实现变更检测系统的核心功能。
定义数据模型和工具函数
首先,我们需要从位于 https://bullet-echo.fandom.com/wiki/Special:AllPages
处的“所有页面”URL 抓取每个单独页面的链接。
让我们定义数据模型和实用函数,这些组件将为我们的变更检测系统提供动力。这些组件将帮助我们结构化数据并在我们的抓取工作流中处理常见操作。
结构化数据提取的数据模型
我们将从创建 models.py
开始,该文件定义了 Pydantic 模型,用于结构化抓取的数据:
from pydantic import BaseModel
from typing import List
class Article(BaseModel):
url: str
title: str
class ArticleList(BaseModel):
articles: List[Article]
这段代码创建了两个关键模型:
Article
:表示单个维基文章及其 URL 和标题- ArticleList: Article 对象的集合
这些 Pydantic 模型起着至关重要的作用:它们定义了 Firecrawl 结构化数据提取的模式。当我们使用批量抓取和提取时,Firecrawl 可以直接填充我们的数据模型。这种方法比手动解析 HTML 更稳健。
例如,我们将在这个脚本的提取中使用这个模型作为模式:
# Example usage in our scraping code
result = app.batch_scrape_urls(
[BASE_URL],
formats=["extract"],
extract={"schema": ArticleList.model_json_schema()},
)
# Now we can access the structured data
all_articles = [a["url"] for a in result.data[0].extract["articles"]]
变化检测的辅助函数
接下来,我们将创建 utils.py,其中包含一些基本的辅助函数:
from pathlib import Path
def is_changed(firecrawl_app, url):
result = firecrawl_app.scrape_url(url, formats=["changeTracking", "markdown"])
return result.changeTracking.changeStatus == "changed"
这个 is_changed()
函数是我们的变更检测系统的中心。它:
-
使用
changeTracking
格式抓取 URL -
检查返回的
changeStatus
,它可以是:"new"
:第一次看到这个页面"same"
: 未检测到更改"changed"
: 内容已更新"removed"
: 该页面不再存在
-
仅在内容更改时返回
True
def save_markdown(markdown, path):
with open(path, "w") as f:
f.write(markdown)
def save_status_data(status_data, path):
# Create the directory if it doesn't exist
Path(path).mkdir(parents=True, exist_ok=True)
for s in status_data.data:
url = s.metadata.get("url")
title = s.metadata.get("og:title")
if url and title:
# Get a clean title
title = title.replace(" ", "-").replace("/", "-").replace("|", "-")
filename = Path(path) / f"{title}.md"
# Check if the file already exists
if not filename.exists():
save_markdown(s.markdown, filename)
这些附加功能处理:
save_markdown()
: 将 markdown 内容保存到磁盘save_status_data()
: 处理批量抓取结果,提取元数据,并仅保存新内容到适当命名的文件中
这些模型和实用函数共同为我们构建变更检测系统提供了强大的基础。Pydantic 模型为提取的数据提供了一个干净的结构,而实用函数则处理变更检测和文件管理的机制。
在下一节中,我们将使用这些组件来实现核心抓取逻辑,以监控子弹回声维基的更改。
第三步:初始基础 URL 抓取
现在让我们实现每月抓取逻辑,该逻辑将获取维基文章的完整列表并跟踪更改。此脚本将每月运行一次,以确保我们捕获所有新内容和更新内容。
让我们创建 src/monthly_scrape.py
:
import time
from pathlib import Path
from firecrawl import FirecrawlApp
from dotenv import load_dotenv
from models import ArticleList
from utils import is_changed, save_status_data
# Load environment variables
load_dotenv()
# Configuration variables
BASE_URL = "https://bullet-echo.fandom.com/wiki/Special:AllPages"
# Data directory
DATA_DIR = Path("data")
# Files and Paths
ARTICLES_LIST_FILE = DATA_DIR / "all_articles.txt"
OUTPUT_DIRECTORY = DATA_DIR / "bullet-echo-wiki"
OUTPUT_DIRECTORY.mkdir(exist_ok=True, parents=True)
# Job Parameters
TIMEOUT_SECONDS = 180 # 3 minutes timeout
POLLING_INTERVAL = 30 # Seconds between status checks
# Initialize Firecrawl app
app = FirecrawlApp()
这初始化了 Firecrawl 客户端,需要一个 API 密钥,该密钥将从我们的环境变量中加载。我们还为文件路径和作业参数设置了常量。
现在让我们检查我们在函数中使用的 Firecrawl 核心功能:
def get_article_list():
"""Get the list of articles from the wiki or from the cached file."""
if is_changed(app, BASE_URL) or not ARTICLES_LIST_FILE.exists():
print("The wiki pages list has changed. Scraping the wiki pages list...")
# Scrape the wiki pages list
result = app.batch_scrape_urls(
[BASE_URL],
formats=["extract"],
extract={"schema": ArticleList.model_json_schema()},
)
# Extract all article URLs
all_articles = [a["url"] for a in result.data[0].extract["articles"]]
print(f"Found {len(all_articles)} articles")
# Write the links to a text file
with open(ARTICLES_LIST_FILE, "w") as f:
for article in all_articles:
f.write(article + "\n")
return all_articles
else:
print("The wiki pages list has not changed. Scraping from existing list...")
with open(ARTICLES_LIST_FILE, "r") as f:
return [line.strip() for line in f.readlines()]
这个函数使用了两个关键的 Firecrawl 方法:
-
该
is_changed()
实用函数(我们定义的)调用了app.scrape_url()
,使用了["changeTracking", "markdown"]
格式。changeTracking
格式返回了 URL 上次抓取以来是否发生变化的元数据。 -
该
app.batch_scrape_urls()
方法接受:- 要爬取的 URL 列表(这里只有一个 URL)
- 一个
formats
参数指定输出格式,这里使用extract
- 一个
extract
参数包含从我们的 Pydantic 模型中推导出的结构化数据提取模式
批量爬取函数会智能地处理页面内容,并根据我们的模式提取结构化数据,从而消除手动解析 HTML 的需要。
def scrape_and_monitor_articles(article_urls):
"""Scrape articles and monitor the process until completion or timeout."""
print(f"Scraping {len(article_urls)} articles...")
# Start the batch scrape job
job = app.async_batch_scrape_urls(article_urls)
start_time = time.time()
# Monitor the job status and save results
while True:
status = app.check_batch_scrape_status(job.id)
if status.status == "completed":
print("Batch scrape completed successfully!")
break
# Check if timeout has been reached
if time.time() - start_time > TIMEOUT_SECONDS:
print(f"Timeout of {TIMEOUT_SECONDS} seconds reached. Exiting.")
break
# Save the partial results
save_status_data(status, OUTPUT_DIRECTORY)
print("Waiting for batch scrape to complete...")
time.sleep(POLLING_INTERVAL)
这个函数展示了 Firecrawl 的异步批量处理功能:
app.async_batch_scrape_urls()
启动一个异步任务并返回一个带有 ID 的工作对象app.check_batch_scrape_status(job.id)
监控任务状态直到完成- 返回的状态对象包含可以逐增量处理的部分结果
这种方法对于大型 URL 批次非常高效,因为它允许在作业完全完成之前监控进度并处理部分结果。
结合变更检测和结构化数据提取功能,提供了一种有效的方法来监控和更新我们的维基数据,而无需重新下载未更改的内容。
在下一步中,我们将实现每周抓取脚本,该脚本更频繁地关注检查单个页面的变更。
第4步:个体页面抓取和调度
现在我们已经建立了每月抓取系统,让我们实现一个更频繁的每周检查,专注于检测单个页面的变更,而无需重新抓取所有内容。
让我们创建 src/weekly_scrape.py
:
from pathlib import Path
from firecrawl import FirecrawlApp
from dotenv import load_dotenv
from utils import save_markdown
# Load environment variables
load_dotenv()
# Configuration variables
# URLs
BASE_URL = "https://bullet-echo.fandom.com/wiki/Special:AllPages"
# Data directory
DATA_DIR = Path("data")
# Files and Paths
ARTICLES_LIST_FILE = DATA_DIR / "all_articles.txt"
OUTPUT_DIRECTORY = DATA_DIR / "bullet-echo-wiki"
OUTPUT_DIRECTORY.mkdir(exist_ok=True, parents=True)
# Job Parameters
TIMEOUT_SECONDS = 180 # 3 minutes timeout
POLLING_INTERVAL = 30 # Seconds between status checks
# Initialize Firecrawl app
app = FirecrawlApp()
这个设置类似于我们的月度脚本,但请注意,我们只需要导入 save_markdown
工具,因为我们将在本脚本中以不同的方式处理文件。
现在让我们实现读取文章列表的函数:
def read_article_list():
"""Read the article list from the file."""
if ARTICLES_LIST_FILE.exists():
with open(ARTICLES_LIST_FILE, "r") as f:
return [line.strip() for line in f.readlines()]
else:
return []
与月度脚本不同,我们不需要检查文章列表是否已更改或重新生成它。我们只需读取由月度任务创建的现有列表。如果文件不存在,我们返回一个空列表。
现在来看看检查每篇文章是否有更改的核心功能:
def scrape_and_monitor_articles(article_urls):
"""Scrape and monitor the articles."""
for url in article_urls:
print(f"Checking {url} for changes...")
try:
scrape_result = app.scrape_url(url, formats=["markdown", "changeTracking"])
except Exception as e:
print(f"Error scraping {url}: {e}")
continue
if scrape_result.changeTracking.changeStatus == "changed":
print(f"The article {url} has changed. Saving the new version...")
title = (
url.split("/")[-1]
.replace("%27s", "'")
.replace("_", "-")
.replace(" ", "-")
)
save_markdown(scrape_result.markdown, OUTPUT_DIRECTORY / f"{title}.md")
else:
print(f"The article {url} has not changed.")
print("All articles have been checked for changes.")
这个函数逐个处理每篇文章,而不是批量处理:
- 它会遍历我们文章列表中的每个 URL
- 对于每个 URL,它会调用
app.scrape_url()
方法 - 它会检查
changeStatus
以确定页面自上次抓取以来是否发生变化 - 如果页面已更改,它将从 URL 中提取干净的标题并保存更新的 Markdown 内容
这种方法与我们每月的批量处理方式有很大不同:
- 它按顺序处理 URL,而不是批量处理
- 只保存已更改的页面,跳过未更改的内容
- 它直接从 URL 中提取标题,而不是从元数据中提取
最后,让我们实现主函数:
def main():
article_urls = read_article_list()
scrape_and_monitor_articles(article_urls)
if __name__ == "__main__":
main()
周度脚本比月度脚本更专注且轻量级。虽然月度脚本建立了所有内容的全面基线,但周度脚本可以高效地检查单个页面的变化,并仅下载已更新的内容。
这种两层方法优化了我们的变更检测系统:
- 每月扫描会重建文章列表并提供全面的内容更新
- 每周扫描专注于检测并更新仅更改的内容
通过将这两个过程结合起来,我们在维基监控系统中实现了全面性和高效性。
在下一步中,我们将通过增强错误处理和监控功能来提升我们的系统。
第 5 步:设置 GitHub Actions 以调度脚本
现在我们已经有了抓取脚本,我们需要一种方法来定期自动运行它们。GitHub Actions 为我们直接从仓库自动化这些任务提供了很好的方式。
让我们设置 GitHub Actions 工作流来自动运行我们的脚本:
-
首先,创建必要的目录:
mkdir -p .github/workflows
-
在
.github/workflows/monthly-scrape.yml
创建每月抓取工作流文件:
name: Monthly Scraping Bullet Echo Base URL
on:
schedule:
# Run at midnight (00:00) on the first day of every month
- cron: "0 0 1 * *"
workflow_dispatch: # Allow manual triggering
permissions:
contents: write
jobs:
scrape:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
cache: "pip"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
- name: Run scraping script
run: python src/monthly_scrape.py
env:
# Include any environment variables needed by the script
# These should be set as secrets in your repository settings
FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
- name: Commit and push changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git add data/
git commit -m "Update data from monthly scrape" || echo "No changes to commit"
git push
3. 您还必须在 .github/workflows/weekly-scrape.yml
创建每周抓取工作流文件:
name: Weekly Scraping Bullet Echo Base URL
on:
schedule:
# Run at midnight (00:00) on the first day of every week
- cron: "0 0 * * 1"
workflow_dispatch: # Allow manual triggering
permissions:
contents: write
jobs:
scrape:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
- name: Run scraping script
run: python src/weekly_scrape.py
env:
FIRECRAWL_API_KEY: ${{ secrets.FIRECRAWL_API_KEY }}
- name: Commit and push changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git add data/
git commit -m "Update data from weekly scrape" || echo "No changes to commit"
git push
让我们分解一下工作流文件:
使用 Cron 调度
on:
schedule:
# Run at midnight (00:00) on the first day of every month
- cron: "0 0 1 * *"
workflow_dispatch: # Allow manual triggering
代码 cron
的语法结构为 minute hour day-of-month month day-of-week
:
- 代码
0 0
:在午夜(00:00) - 代码
1
:在每月的第一天 * *
: 每个月,每周的每一天
对于每周的工作流,cron 表达式 0 0 * * 1
每周一午夜运行。
手动运行工作流
两个工作流都包含 workflow_dispatch
触发器,这允许您通过 GitHub UI 手动运行它们。要手动运行工作流:
- 导航到你的 GitHub 仓库
- 在仓库页面顶部点击“Actions”选项卡
- 在左侧边栏中选择要运行的工作流(“Monthly Scraping Bullet Echo Base URL” 或 “Weekly Scraping Bullet Echo Base URL”)
- 在页面右侧点击“运行工作流”按钮
- 选择要运行工作流的分支(通常是“main”)
- 点击绿色的“运行工作流”按钮以启动此过程
这特别适用于测试您的工作流或在计划时间之外运行它们。
权限和工作流步骤
两种工作流的权限和步骤相同,唯一的区别在于执行哪个脚本。
通过设置这两种工作流,您将创建一个完全自动化的系统:
- 每月的第一天,GitHub Actions 运行月度抓取,重建文章列表并执行全面内容更新
- 每周一,GitHub Actions 会运行每周抓取任务,高效地检查并仅更新已更改的内容
结果会自动提交回仓库,创建一个受版本控制的数据变更历史记录。
在下一步中,我们将讨论存储我们抓取数据的策略以及如何使其更易于下游应用程序访问。
第六步:生产环境的存储策略
当前的实现将抓取的维基内容存储为本地目录结构中的 markdown 文件。虽然这种方法适用于演示,但在生产环境中使用时有几个限制:
- 有限的可扩展性 : 本地文件系统存储对于包含数千页的大数据集扩展性不佳。
- 有限的可访问性 : 本地存储的文件不容易被其他系统或应用程序访问。
- 没有索引 : 简单的文件存储使得高效地搜索或查询内容变得困难。
- 有限的元数据 : 我们当前的方法仅存储关于更改和内容的少量元数据。
- 没有版本控制 : 内容变化时我们会覆盖文件,导致历史版本丢失。
对于生产环境,我们应该考虑更 robust 的存储解决方案:
数据库存储
实施数据库解决方案将有助于更好地组织和检索:
# Example using SQLAlchemy with SQLite (for simplicity)
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import datetime
Base = declarative_base()
class WikiArticle(Base):
__tablename__ = 'wiki_articles'
id = Column(Integer, primary_key=True)
url = Column(String, unique=True, index=True)
title = Column(String)
content = Column(Text)
last_updated = Column(DateTime)
last_checked = Column(DateTime)
change_status = Column(String) # new, same, changed, removed
def __repr__(self):
return f"<WikiArticle(title='{self.title}')>"
# Initialize database
engine = create_engine('sqlite:///data/wiki_articles.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
这将使我们能够:
- 存储文章内容及其全面的元数据
- 跟踪最后更新和检查的时间戳
- 通过各种标准查询文章
- 构建 API 层以供下游应用使用
云存储选项
对于生产环境,请考虑使用云存储解决方案:
-
Amazon S3 或类似对象存储 :
import boto3 def save_to_s3(content, title, bucket_name="wiki-content"): s3 = boto3.client('s3') s3.put_object( Body=content, Bucket=bucket_name, Key=f"articles/{title}.md", ContentType="text/markdown" )
-
向量数据库对于语义搜索:
from pinecone import Pinecone def store_with_embeddings(content, title, url): # Generate embeddings using a model like OpenAI's embedding = get_embedding(content) # Store in vector database pc = Pinecone(api_key="your-api-key") index = pc.Index("wiki-articles") index.upsert( vectors=[{ "id": url, "values": embedding, "metadata": {"title": title, "last_updated": datetime.now().isoformat()} }] )
下游应用的内容 API
创建一个简单的 REST API 来提供内容:
from fastapi import FastAPI, HTTPException
from sqlalchemy.orm import Session
app = FastAPI()
@app.get("/articles/")
def list_articles(session: Session = Depends(get_session)):
articles = session.query(WikiArticle).all()
return articles
@app.get("/articles/{title}")
def get_article(title: str, session: Session = Depends(get_session)):
article = session.query(WikiArticle).filter(WikiArticle.title == title).first()
if not article:
raise HTTPException(status_code=404, detail="Article not found")
return article
@app.get("/search/")
def search_articles(query: str, session: Session = Depends(get_session)):
# Implement full-text search
articles = session.query(WikiArticle).filter(
WikiArticle.content.like(f"%{query}%")
).all()
return articles
变更历史和版本控制
实施版本控制以保留历史内容:
class WikiArticleVersion(Base):
__tablename__ = 'wiki_article_versions'
id = Column(Integer, primary_key=True)
article_id = Column(Integer, ForeignKey('wiki_articles.id'))
content = Column(Text)
version_date = Column(DateTime, default=datetime.datetime.utcnow)
change_description = Column(String)
通过实现这些更 robust 的存储解决方案,我们的变更检测系统对于生产应用变得更加有用:
- 知识获取系统可以直接查询 API 获取最新的内容
- 分析工具可以监控和可视化变更模式
- 通知系统可以提醒内容所有者关于内容更新
- 内容管道可以自动处理和转换更新的内容
这些改进将我们的基于文件的简单演示转变为一个可以可靠地将数据传递给下游应用程序并保持内容更改完整历史的生产就绪系统。
结论
这就是我们如何使用 Firecrawl 构建变更检测系统的逐步指南。我们创建了一个强大的解决方案,可以高效地监控网页内容的变化,并仅下载更新的页面。该系统使用每月的全面扫描来建立基线,并使用每周的增量检查来捕捉最近的变化。通过利用 Firecrawl 的变更跟踪和结构化数据提取功能,我们已经消除了复杂的 HTML 解析和手动变更检测逻辑的需要。