lesson-09
💡 进群学习加 wx: agentupdate
(申请发送: agentupdate)
(申请发送: agentupdate)
9.1 架构回顾:谁在代理谁?
在进入代码实战前,我们需要理清流量走向,这非常容易混淆。许多开发者会问:"我的 Node.js 爬虫代码需要配置代理吗?"
答案是:不需要!
在这个架构中,你的核心应用(Node.js 后端)与目标网站之间,隔着我们搭建的 Firecrawl 代理黑盒:
graph LR
Node[Node.js 业务后台
无代理, 真实IP] -->|HTTP POST| FCAPI[本地 Firecrawl:3002]
subgraph 代理黑盒区域
FCAPI -->|传递抓取任务| Playwright[Playwright 容器]
Playwright -->|通过环境变量 PROXY_SERVER| WARP[宿主机 WARP SOCKS5:40000]
end
WARP -->|CF 边缘 IP 出海| Target[目标反爬网站]
Target -.-> WARP -.-> Playwright -.-> FCAPI -.->|Markdown 内容| Node核心优势:
- 业务解耦:你的后台 Node.js 代码完全不需要处理代理逻辑、维护代理池,甚至不需要知道 WARP 的存在。
- 安全隔离:如果代理挂了,只影响发往 Firecrawl 的请求,你的 Node.js 连接数据库、调用大模型 API(如 Gemini)依然走本地极速网络。
9.2 在业务代码中调用 Firecrawl
当我们将 Firecrawl 作为基础设施部署好后,在 Node.js 中调用它就变成了一次简单的 HTTP/REST 请求。
我们不建议使用复杂的 SDK,直接使用原生的 fetch 即可获得最灵活的控制权:
// crawler/src/utils/firecrawl-client.ts
export async function scrapeWithFirecrawl(url: string) {
const FIRECRAWL_API_URL = "http://localhost:3002";
console.log(`🛡️ 正在通过本地代理集群抓取: ${url}`);
try {
const response = await fetch(`${FIRECRAWL_API_URL}/v1/scrape`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 本地自建无需真实 API Key
'Authorization': 'Bearer dummy-key'
},
body: JSON.stringify({
url: url,
formats: ['markdown', 'html'],
// 关键参数:给底层的 Playwright 留出渲染时间
waitFor: 2000,
// 绕过常见的无头浏览器检测
mobile: false,
}),
});
if (!response.ok) {
throw new Error(`Firecrawl 响应错误: ${response.status}`);
}
const data = await response.json();
if (data.success && data.data) {
return {
success: true,
content: data.data.markdown, // 返回清洗后的纯正文
html: data.data.html
};
}
return { success: false, error: "未获取到数据" };
} catch (error) {
console.error(`❌ Firecrawl 抓取失败:`, error);
return { success: false, error: String(error) };
}
}
9.3 应对极端反爬:高级配置项
针对一些特殊的网站,我们可以通过修改发送给 Firecrawl 的 payload 来加强穿透力:
1. 处理强制弹窗与验证码 (Interact)
如果目标网站打开就是一个 "Accept Cookies" 遮罩层,阻碍了正文提取:
{
"url": "https://example.com",
"formats": ["markdown"],
"actions": [
{ "type": "click", "selector": "#accept-cookies-btn" },
{ "type": "wait", "milliseconds": 1000 }
]
}
2. 只抓取特定区域
如果整个网页很庞大,我们只想要文章主体的 Markdown,以节省后续丢给大模型的 Token 成本:
{
"url": "https://news.ycombinator.com",
"formats": ["markdown"],
"includeTags": ["article", "main", ".story-content"],
"excludeTags": ["nav", "footer", ".ads-banner"]
}
9.4 课后问题
- 为什么在 Node.js 中调用大模型 API(如 Gemini)时,不应该让 Node.js 走 WARP 代理?
- 如果你的 Firecrawl 容器和 Node.js 应用部署在同一个 Docker Compose 网络中,
FIRECRAWL_API_URL应该写成什么? waitFor: 2000这个参数在突破反爬时起到了什么关键作用?