Web Crawler

基于 Node.js 的生产级批量网页爬取工具,支持代理轮换与自动重试。

已扫描
适合谁
数据采集工程师、自动化运维人员
不适合谁
单页快速抓取用户、需要 JavaScript 渲染的页面爬取者
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @mike442144/node-crawler

Skill 说明

命令、参数、文件名以原文为准

Node 爬虫 (crawler 包)

crawler 是一个基于 Node.js 的网页爬虫库:内置队列 + 可配置的连接池 + 按域名限速 + 自动重试 + 代理轮换 + 字符集检测 + 服务端 Cheerio(类似 jQuery)HTML 解析。基于 got 构建,支持 HTTP/2。

前置条件

  • Node.js >= 22
  • 纯 ESM:import Crawler from "crawler"
  • 若代码库必须使用 CommonJS,请安装 crawler@beta
npm install crawler

适用场景

此技能适用于生产环境、大规模爬取任务。当任务规模较大时应考虑使用:

  • 爬取大量页面(数十到数百万),并提取结构化数据
  • 批量下载文件——图片、PDF、压缩包等,支持重试与断点续传

(续传逻辑需开发者通过 userParams 和文件存在性检查自行实现)

  • 长时间运行的爬虫,需要限速、重试和连接池管理
  • 多步骤工作流——分页、链接追踪、级联爬取
  • 代理轮换、字符集检测、HTTP/2 —— 真正生产级爬虫所依赖的基础功能

不适用场景

  • 单个页面或一次性请求 → 使用 curl 更轻量。为 1-2 个页面启动一个 Crawler 实例属于过度设计。
  • 页面需要JavaScript 渲染 → 应使用 agent-browser 技能(Playwright/Puppeteer)
  • 简单的API 数据获取 → 使用 fetch / got 配合 JSON 解析即可

API

核心决策:队列模式 vs 直接发送

方式适用场景
**crawler.add() + 'drain' 事件**大多数情况。经过队列、连接池、限速、重试、代理轮换等流程
**crawler.send()**一次性请求。跳过队列、限速、preRequest'request' 事件等机制

基础用法:队列模式

import Crawler from "crawler";

const c = new Crawler({
  maxConnections: 10,
  callback: (error, res, done) => {
    if (error) {
      console.error(error);
    } else {
      const $ = res.$;  // Cheerio 实例(默认启用)
      console.log($("title").text());
    }
    done();  // 必须调用:释放连接槽位,否则爬虫会死锁
  },
});

c.on("drain", () => console.log("全部完成"));

c.add("https://example.com");
c.add(["https://a.com", "https://b.com"]);
c.add({ url: "https://c.com", jQuery: false,
        callback: (e, res, done) => { /* 自定义回调 */ done(); } });

两个最关键规则:

  1. 每个队列回调中的每个分支都必须调用 done(),包括 if (error) 分支
  2. 爬虫完成的标志是 'drain' 事件触发,而非 add() 返回。add() 仅用于入队任务

限速与重试

const c = new Crawler({
  rateLimit: 1000,    // 请求之间的最小间隔 ≥ 1000ms(强制 maxConnections=1)
  retries: 2,         // 默认值:2
  retryInterval: 3000,// 重试前等待的时间(毫秒)
  timeout: 20000,     // 请求超时时间(毫秒)
  callback: (e, res, done) => { /* ... */ done(); },
});

使用 Cheerio 提取数据

Cheerio 默认启用。可使用 jQuery 选择器提取数据:

callback: (e, res, done) => {
  const $ = res.$;
  const titles = $("h2.title").map((i, el) => $(el).text().trim()).get();
  const links  = $("a").map((i, el) => $(el).attr("href")).get();
  done();
}

二进制文件下载

import fs from "fs";
const c = new Crawler({
  encoding: null,     // 保持 body 为 Buffer
  jQuery: false,      // 跳过 Cheerio 解析
  callback: (err, res, done) => {
    if (!err) fs.writeFileSync(res.options.userParams.filename, res.body);
    done();
  },
});
c.add({ url: "https://host/file.png", userParams: { filename: "file.png" } });

代理轮换

推荐使用 'schedule' 事件进行动态分配(优于直接使用 proxies 数组):

c.on("schedule", options => { options.proxy = "http://proxy:port"; });
c.on("request",  options => { options.searchParams = { t: Date.now() }; });

不同代理可设置不同的限速器:

c.add({ url: "...", rateLimiterId: 1, proxy: "http://p1:port" });
c.add({ url: "...", rateLimiterId: 2, proxy: "http://p2:port" });

HTTP/2 支持

c.add({ url: "https://...", http2: true, callback: (e, res, done) => { done(); } });

使用 Charles 或自签名证书时,需添加 rejectUnauthorized: false

传递上下文数据

使用 userParams 附加数据;在回调中通过 res.options.userParams 读取。

不要直接在 options 对象上附加自定义字段。

常见陷阱

  • ❌ 在 if (error) 分支中忘记调用 done() → 爬虫死锁
  • ❌ 在 add() 后立即写 console.log("done") → 应监听 'drain' 事件
  • ❌ 设置 maxConnections > 1rateLimit > 0maxConnections 会被强制设为 1
  • ❌ 期望 send() 触发 preRequest'request' 事件 → send() 跳过所有队列机制
  • ❌ 通过 body 传递 POST 表单数据 → v2 版本要求使用 form
  • ❌ 二进制下载未设置 encoding: null → 输出文件损坏

选项参考

完整选项列表请参见 [references/options.md](references/options.md)。

代码示例

所有场景的完整可运行示例请见 [references/examples.md](references/examples.md):

  • 基础队列爬取
  • 限速配置
  • Cheerio 数据提取
  • 二进制文件下载
  • 直接请求
  • HTTP/2 支持
  • 代理轮换
  • preRequest 钩子
  • 完整爬虫(分页 + 数据提取 + 链接追踪)
M
@mike442144

已收录 1 个 Skill

相关推荐