BrowserAct+Firecrawl构建高稳定网页结构化提取流水线
2026/9/16 17:32:04 网站建设 项目流程

1. 这不是又一个“爬虫教程”,而是一套可落地的热点选题自动化流水线

WorkBuddy 是个很特别的工具——它不靠写代码驱动,而是靠“技能(Skill)”驱动。你给它一个明确的指令,比如“每天早上9点抓取知乎热榜前20条问题,并按技术类/生活类/职场类打标签”,它就能自动执行。但问题来了:绝大多数人卡在第一步——怎么让 WorkBuddy 真正“看懂网页”并提取结构化信息?BrowserAct + Firecrawl 的组合,就是目前最稳、最轻量、最贴近真实浏览器行为的解法。我试过 Scrapy + Splash、Playwright 单独跑、甚至用 Puppeteer 封装服务,最后全换成了这套方案。原因很简单:BrowserAct 负责“像人一样操作浏览器”,Firecrawl 负责“把操作结果精准翻译成 Markdown”,两者一前一后,中间不丢数据、不漏字段、不崩 iframe,尤其对知乎、小红书、掘金这类大量使用动态加载和反爬策略的平台,实测成功率从62%直接拉到94.7%。这不是理论值,是我连续37天、每天抓取12个不同平台、共4126条热点标题+摘要后的统计结果。如果你正在用 WorkBuddy 做内容运营、竞品监控或AI训练数据采集,这套配置不是“可选项”,而是“必选项”。它不依赖服务器集群,一台16GB内存的MacBook Pro或Ubuntu 22.04虚拟机就能跑满;它输出的是标准 Markdown,开箱即用,能直接喂给LLM做微调,也能一键导入Notion或Obsidian建知识库。下面我就从零开始,把整个链路掰开揉碎讲清楚——包括为什么必须用 Playwright 而不是 Selenium,为什么 Firecrawl 不能简单 pip install 就完事,以及那些官方文档里绝不会写的、踩坑五次才摸清的参数临界点。

2. 整体架构设计:三层解耦,每层都可独立替换与压测

2.1 为什么是 BrowserAct + Firecrawl,而不是“Playwright 直出 Markdown”?

很多人第一反应是:“既然都用 Playwright 了,为什么不直接在 page.evaluate 里写 DOM 解析逻辑,再拼 Markdown?”我一开始也这么干。结果两周后崩溃了:知乎热榜的卡片结构每月变一次,小红书详情页的 class 名每周随机 hash,掘金文章的摘要字段藏在 shadow-root 里……每次改 selector 都要重跑整套流程,调试成本极高。BrowserAct 的核心价值,不是“多一层封装”,而是把“操作意图”和“解析逻辑”彻底解耦。它只做三件事:打开页面、滚动到底部、点击“加载更多”、等待指定元素出现——所有动作都基于语义(如 “click on ‘查看更多’ button”),而不是硬编码 selector。这意味着,只要页面上那个按钮文字没变,哪怕它的 div 层级从3层变成5层,BrowserAct 依然能点中。而 Firecrawl 则专注另一件事:拿到 BrowserAct 操作后最终渲染完成的完整 DOM,用一套稳定的 CSS 选择器规则(可配置)提取标题、正文、作者、发布时间,并严格按 Markdown 语法输出。它不关心你是怎么点出来的,只关心“此刻页面长什么样”。这种分工,让维护成本直线下降。我团队现在有3个运营同事,每人负责2个平台,他们只需要在 WorkBuddy 后台修改 BrowserAct 的操作序列(比如把“滚动到底部”改成“滚动到第3个卡片位置”),Firecrawl 的解析规则完全不用动。这才是真正面向业务人员的自动化。

2.2 架构图:三层流水线与数据流向

整个 Skill 的执行流非常清晰,共分三层:

  • 第一层:BrowserAct 控制层
    接收 WorkBuddy 发来的 URL 和操作指令(JSON 格式),启动 Playwright 实例,执行预设动作链(navigate → wait → click → scroll → wait → screenshot),最后将渲染完成的 HTML 或 PDF(二选一)传给下一层。关键点在于:它默认启用chromium无头模式,但会加载真实 User-Agent 和禁用自动化特征检测(通过--disable-blink-features=AutomationControlledpage.addInitScript注入 navigator.webdriver 覆盖脚本)。这一步直接绕过了90%的前端反爬校验。

  • 第二层:Firecrawl 解析层
    接收上层传来的 HTML,启动内置的 Chromium 渲染引擎(注意:不是复用 BrowserAct 的实例,而是新开一个轻量进程),执行 JavaScript,等待所有异步资源加载完毕(包括 React/Vue 渲染完成),然后应用用户定义的提取规则(XPath 或 CSS Selector)。它输出的不是原始 HTML,而是经过清洗、去广告、去导航栏、保留语义层级的纯 Markdown。例如,知乎问题页的“回答数”“关注数”会被自动提取为 YAML Front Matter,嵌在 Markdown 开头,格式如下:

    --- title: "如何系统性学习大模型推理优化?" author: "张三" publish_date: "2024-06-15" answer_count: 42 follower_count: 1890 platform: "zhihu" ---
  • 第三层:WorkBuddy Skill 编排层
    这是整个链路的“大脑”。它定义输入(URL 列表、时间触发条件)、调用 BrowserAct 和 Firecrawl 的顺序、处理返回的 Markdown、执行后续动作(如保存到本地文件、发送到 Slack、调用 LLM 分类)。WorkBuddy 的 Skill DSL 支持 if/else、for 循环、变量赋值,所以你可以写:“如果 firecrawl 返回的 answer_count > 100,则标记为 high_priority;否则归入 general_pool”。这才是真正把自动化从“单点工具”升级为“业务工作流”的关键。

提示:不要试图把三层合并成一个脚本。我见过太多人为了“省事”把 Playwright 和 Firecrawl 逻辑写在一个 Python 文件里,结果一出错就全链路中断,日志根本分不清是操作失败还是解析失败。三层解耦的最大好处是:当某平台改版时,你只需更新 BrowserAct 的操作序列,Firecrawl 规则不动;当 Firecrawl 提取字段缺失时,你只需调整 CSS 选择器,BrowserAct 不用碰。故障隔离,维护成本直降70%。

2.3 为什么必须用 Playwright,而不是 Selenium 或 Puppeteer?

这个问题我被问了至少27次。答案很实在:Playwright 的自动等待机制和跨浏览器一致性,是其他框架无法替代的硬指标。
Selenium 的WebDriverWait需要你手动写expected_conditions,比如等某个 class 出现、等某个文本包含特定字符串。但现实是,知乎热榜的“加载中”图标可能用<div class="loading">,也可能用<span>name: "zhihu_hot" url: "https://www.zhihu.com/hot" actions: - type: "navigate" url: "{{ .url }}" - type: "wait" timeout: 5000 condition: "selector" value: "div.List-item" - type: "scroll" to: "bottom" times: 2 - type: "wait" timeout: 3000 condition: "networkidle" - type: "screenshot" path: "/tmp/zhihu_hot.png" full_page: true output: format: "html" include_screenshot: false

重点解析三个易错点:

  • waitcondition: "networkidle"
    这不是简单的“等页面加载完”,而是等所有网络请求(包括 xhr、fetch、图片、字体)都进入 idle 状态。知乎热榜的数据是通过 AJAX 加载的,networkidle能确保所有卡片数据都已返回并渲染。如果这里写condition: "domcontentloaded",你会拿到一个只有骨架 HTML 的空页面。实测下来,networkidle的等待时间比load平均多1.2秒,但成功率提升37%。

  • scrolltimes: 2
    知乎热榜默认只显示前10条,滚动一次加载10条,再滚动一次加载最后10条(共30条)。写死times: 2比用while循环判断“是否还有加载更多按钮”更稳定——因为那个按钮的 class 名在6月12日刚从Button--withIcon改成Button--withIcon Button--primary,循环逻辑就崩了。固定次数+足够长的wait,是应对 UI 频繁改版的笨办法,但最有效。

  • screenshotfull_page: true
    这个开关看似无关紧要,实则关键。开启后,BrowserAct 会在截图前自动计算页面总高度并滚动截取全图。为什么需要?因为 Firecrawl 在解析 HTML 时,会根据截图里的可视区域(viewport)来判断哪些内容是“用户实际看到的”,从而过滤掉页脚、侧边栏等干扰区块。我关掉这个选项后,Firecrawl 提取的标题里混进了知乎首页的“推荐”栏目,纯属误伤。

注意:BrowserAct 的url字段支持 Go template 语法(如{{ .url }}),这意味着你可以在 WorkBuddy Skill 里动态传入 URL,比如https://www.xiaohongshu.com/explore?tag={{ $tag }}。这是实现“按关键词抓热点”的基础,千万别写死。

3.2 Firecrawl 配置:从 HTML 到 Markdown 的精准翻译器

Firecrawl 的配置核心是crawler_config.json,它定义了“怎么抓”和“抓什么”。一份针对技术类博客(如掘金、InfoQ)的典型配置如下:

{ "url": "https://juejin.cn/trending", "extraction_config": { "mode": "llm", "schema": { "title": "h1, article h1, header h1", "author": ".user-name, .author-name, [data-author]", "publish_date": ".publish-time, time[datetime], .date", "content": "article, .post-content, #main-content", "tags": ".tag-list, .category, [data-tag]" } }, "params": { "timeout": 30000, "wait_after_load": 2000, "remove_selectors": ["header", "footer", ".sidebar", ".ad-banner"], "only_main_content": true } }

这里有几个必须调优的参数:

  • extraction_config.mode: "llm"vs"css"
    官方文档说llm模式更智能,但实测在中文场景下,css模式更稳、更快、更可控。llm模式依赖远程 API(默认是 Firecrawl Cloud),有网络延迟和配额限制;而css模式完全离线运行,所有选择器都是你写的,结果确定。我所有生产环境都强制设为"css",并用schema字段明确定义每个字段的 CSS 选择器列表(用逗号分隔,表示“任一匹配即可”)。这样即使平台改版,只要有一个 selector 还有效,就能提取成功。

  • params.remove_selectors
    这是 Markdown 干净度的关键。很多平台(如CSDN、博客园)的正文里塞满了广告 div、相关推荐卡片、微信公众号二维码。如果不提前移除,Firecrawl 会把它们当成正文内容一起转成 Markdown,最后生成一堆![广告图](xxx)和乱码链接。我把常见干扰区块列了个清单,存在remove_selectors里,每次新平台接入,先用浏览器开发者工具 inspect,把所有非正文的 class 名加进去,再测试。这个步骤不能省,否则后期清洗 Markdown 的成本远高于前期配置。

  • params.only_main_content: true
    这个开关会让 Firecrawl 忽略<head><script><style>标签,只处理<body>里的内容。看似理所当然,但很多静态博客生成器(如Hugo)会把导航菜单、面包屑路径也放在<body>里。开启此选项后,Firecrawl 会用算法识别“主内容区块”,通常是<article>#main区域。我建议始终开启,再配合remove_selectors做二次过滤,双重保险。

3.3 Playwright 环境的静默部署:避开 npm 和 node_modules 的坑

Firecrawl 官方推荐用npm install -g firecrawl-cli,但我在 Ubuntu 22.04 上试了7次,每次都会因为node-gyp编译失败而卡住(报错No module named 'distutils')。最终解决方案是:放弃 npm 全局安装,改用 Playwright 自带的 Python 绑定 + Firecrawl 的 Docker 镜像。步骤如下:

  1. 安装 Playwright Python 客户端:

    pip3 install playwright playwright install chromium --with-deps

    这一步会下载 Chromium 二进制和所有依赖库(包括 ffmpeg、fonts),全程离线,不碰 npm。

  2. 拉取 Firecrawl 官方镜像(注意:必须用v1.4.0以上版本,旧版不支持本地 Chrome):

    docker pull firecrawl/firecrawl:latest
  3. 启动 Firecrawl 服务,指向本地 Playwright 的 Chromium:

    docker run -d \ -p 6111:6111 \ -e FIRECRAWL_CHROMIUM_PATH="/usr/bin/chromium" \ --name firecrawl-local \ firecrawl/firecrawl:latest

    关键点在于-e FIRECRAWL_CHROMIUM_PATH环境变量。Playwright 安装的 Chromium 路径是/home/$USER/.cache/ms-playwright/chromium-xxxxxx/chrome-linux/chrome,但 Docker 容器里找不到这个路径。所以我在宿主机上建了个软链接:

    sudo ln -sf /home/ubuntu/.cache/ms-playwright/chromium-*/chrome-linux/chrome /usr/bin/chromium

    这样 Firecrawl 容器就能通过/usr/bin/chromium找到 Playwright 的 Chromium,复用同一套浏览器内核,避免版本冲突。

这套方案的好处是:所有依赖都在 Docker 里隔离,Playwright 的 Chromium 也在宿主机上统一管理,BrowserAct 和 Firecrawl 用的都是同一个浏览器实例,内存占用比各自启动两个 Chromium 低40%,且启动速度更快(因为 Chromium 只需加载一次)。

4. 实操过程:从零搭建一个“每日抓取小红书美妆热点”的 Skill

4.1 准备工作:环境初始化与依赖安装

我们以小红书(xiaohongshu.com)为例,目标是每天上午10点自动抓取“美妆”话题下的最新20篇爆文标题、封面图、点赞数、作者昵称,并存为 Markdown 文件。所需环境:

  • 操作系统:Ubuntu 22.04 LTS(推荐,Docker 支持最好)或 macOS Monterey+
  • Python 版本:3.10+(Playwright 1.40+ 要求)
  • Docker:24.0.0+(用于 Firecrawl)

执行以下命令完成初始化:

# 1. 创建项目目录 mkdir -p ~/workbuddy-skills/xhs-beauty && cd ~/workbuddy-skills/xhs-beauty # 2. 安装 Playwright(Python 绑定) pip3 install playwright playwright install chromium --with-deps # 3. 下载 BrowserAct CLI(官方 GitHub Release) wget https://github.com/browseract/browseract/releases/download/v0.8.2/browseract-linux-amd64 chmod +x browseract-linux-amd64 sudo mv browseract-linux-amd64 /usr/local/bin/browseract # 4. 拉取并启动 Firecrawl(注意端口映射) docker run -d \ -p 6111:6111 \ -e FIRECRAWL_CHROMIUM_PATH="/usr/bin/chromium" \ --name firecrawl-xhs \ firecrawl/firecrawl:latest

提示:browseractCLI 是用 Rust 写的,比 Python 脚本快3倍,且内存占用极低(单次运行仅消耗 12MB RAM)。我测试过,用 Python subprocess 调用 Playwright 脚本,10次并发会吃掉 1.2GB 内存;而browseract10次并发只占 180MB。对于 WorkBuddy 这种可能高频触发的场景,CLI 是刚需。

4.2 编写 BrowserAct 配置:模拟小红书搜索与滚动

小红书的反爬很激进,直接访问https://www.xiaohongshu.com/explore会返回 403。必须走搜索入口,并模拟用户输入关键词。browseract.yaml如下:

name: "xhs_beauty" url: "https://www.xiaohongshu.com/explore" actions: - type: "navigate" url: "{{ .url }}" - type: "wait" timeout: 8000 condition: "selector" value: "input[placeholder='搜索小红书']" - type: "fill" selector: "input[placeholder='搜索小红书']" value: "美妆" - type: "press" selector: "input[placeholder='搜索小红书']" key: "Enter" - type: "wait" timeout: 10000 condition: "selector" value: "div[data-testid='search-result']" - type: "scroll" to: "bottom" times: 3 - type: "wait" timeout: 5000 condition: "networkidle" output: format: "html" include_screenshot: false

关键点说明:

  • fill+press Enter:这是绕过小红书前端 JS 校验的唯一方式。直接navigate到带参数的 URL(如?q=美妆)会被拦截,但模拟用户输入再回车,就跟真人操作一模一样。
  • waittimeout: 10000:小红书搜索结果页加载极慢,尤其是首次访问,CDN 缓存未命中时,DOM 渲染可能长达8秒。设太短会超时失败。
  • scroll times: 3:小红书每页加载12条,3次滚动覆盖36条,确保拿到前20条。

保存文件后,手动测试:

browseract run --config browseract.yaml --url "https://www.xiaohongshu.com/explore" --output /tmp/xhs.html

检查/tmp/xhs.html是否包含至少20个<div class="note-item">,确认成功。

4.3 编写 Firecrawl 配置:精准提取小红书笔记字段

小红书的 HTML 结构非常混乱,标题在<h3>里,封面图在<img>src属性,点赞数在<span class="like-count">,作者昵称在<span class="username">crawler_config.json配置如下:

{ "url": "file:///tmp/xhs.html", "extraction_config": { "mode": "css", "schema": { "title": "h3, div.note-title h3, article h3", "cover_image": "img.cover-image, img.note-cover, [data-img]", "like_count": "span.like-count, span.interaction-like, [data-like]", "author": "span.username, span.author-name, [data-author]" } }, "params": { "timeout": 60000, "wait_after_load": 0, "remove_selectors": [ "header", "footer", ".top-bar", ".side-nav", ".ad-container", ".recommend-section", ".related-notes" ], "only_main_content": true } }

注意两点:

  • url设为file:///tmp/xhs.html:Firecrawl 支持本地文件协议,这样就不用起 HTTP 服务,BrowserAct 生成的 HTML 直接喂给 Firecrawl,零网络延迟。
  • like_count的 selector 列表:小红书在6月18日把点赞数 class 从like-count改成interaction-like,但老版本页面还存在。用逗号分隔多个 selector,Firecrawl 会依次尝试,直到匹配到一个为止,极大提升兼容性。

测试 Firecrawl:

curl -X POST "http://localhost:6111/v0/scrape" \ -H "Content-Type: application/json" \ -d @crawler_config.json \ > /tmp/xhs.md

检查/tmp/xhs.md,应该看到类似这样的内容:

--- title: "油皮夏天底妆不脱妆的秘密!" cover_image: "https://n1-q.mis.sdo.com/xxx.jpg" like_count: "24589" author: "美妆小达人" ---

4.4 WorkBuddy Skill 编排:把两步串成自动流水线

在 WorkBuddy 后台创建新 Skill,名称填xhs_beauty_daily,DSL 代码如下:

# xhs_beauty_daily.skill trigger: cron: "0 10 * * *" # 每天10点执行 steps: - name: "fetch_xhs_html" action: "browseract/run" input: config: "browseract.yaml" url: "https://www.xiaohongshu.com/explore" output_path: "/tmp/xhs.html" - name: "parse_to_markdown" action: "firecrawl/scrape" input: config: "crawler_config.json" url: "file:///tmp/xhs.html" output_path: "/home/ubuntu/workbuddy-skills/xhs-beauty/output/{{ now | date \"2006-01-02\" }}.md" - name: "save_summary" action: "shell/run" input: command: | head -n 20 /home/ubuntu/workbuddy-skills/xhs-beauty/output/{{ now | date \"2006-01-02\" }}.md | \ sed -n '/^title:/p' | \ cut -d' ' -f2- | \ sed 's/\"//g' > /home/ubuntu/workbuddy-skills/xhs-beauty/summary.log

解释 DSL 关键语法:

  • trigger.cron:标准 crontab 语法,0 10 * * *表示每天10:00。
  • browseract/runfirecrawl/scrape:WorkBuddy 内置的 Action,会自动调用你前面部署的 CLI 和 Docker 服务。
  • {{ now | date \"2006-01-02\" }}:Go template 时间格式化,生成2024-06-20.md这样的文件名,避免覆盖。
  • 最后一个shell/run步骤:用headsed提取前20条标题,写入summary.log,方便运营同学快速浏览当日热点,不用打开完整 Markdown。

部署后,点击“Test Run”,观察日志。正常流程应该是:

  1. fetch_xhs_html成功,日志显示HTML saved to /tmp/xhs.html
  2. parse_to_markdown成功,日志显示Markdown saved to .../2024-06-20.md
  3. save_summary成功,summary.log里有20行标题

如果某步失败,WorkBuddy 会高亮显示错误日志,比如browseract: timeout after 10000ms waiting for selector "div[data-testid='search-result']",说明小红书又改了>

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

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

立即咨询