为什么要做爬虫

运营技术博客需要内容灵感。每天手动刷 GitHub Trending、Hacker News、V2EX、掘金——太累了。而且漏掉好内容是常态。

解决方案:写一个自动爬虫,每 30 分钟轮换采集 7 个数据源,去重分类后生成每日技术日报。

系统架构

调度器 (30分钟循环)
  ├── GitHub API  →  15条仓库
  ├── Hacker News  →  20条热门
  ├── Reddit API   →  20条编程帖
  ├── V2EX (HTML)  →  cheerio 解析
  ├── 掘金 (HTML)  →  cheerio 解析
  ├── 知乎热榜     →  HTML 抓取
  └── ProductHunt  →  HTML 抓取
         ↓
  data/raw/YYYY-MM-DD-HHmm-源名.json
         ↓
  规则过滤 + 分类
         ↓
  日报 HTML → 部署

API 源 vs HTML 源

两类数据源的处理方式完全不同:

API 源(GitHub、HN、Reddit)直接 fetch()JSON.parse(),稳定高效:

async function github() {
  const data = await fetch('https://api.github.com/search/repositories?...');
  return data.items.map(r => ({
    title: r.full_name,
    url: r.html_url,
    stars: r.stargazers_count
  }));
}

HTML 源(V2EX、掘金、知乎)需要 cheerio 解析 DOM:

async function v2ex() {
  const html = await fetch('https://www.v2ex.com/?tab=creative');
  const $ = cheerio.load(html);
  const items = [];
  $('.cell.item').each((i, el) => {
    items.push({
      title: $(el).find('.topic-link').text().trim(),
      url: 'https://www.v2ex.com' + $(el).find('.topic-link').attr('href')
    });
  });
  return items;
}

轮换调度策略

不能同时请求所有源——会被限流甚至封 IP。采用轮换调度

const sources = [
  { id: 'github', interval: 15 },      // 每15分钟
  { id: 'hackernews', interval: 15 },
  { id: 'v2ex', interval: 8 },         // 每8分钟
  { id: 'zhihu', interval: 10 },       // 每10分钟
  // ...
];

function shouldCrawl(source, state) {
  const elapsed = (Date.now() - state.lastAttempt) / 60000;
  return elapsed >= source.interval;
}

30 分钟主循环内,各源按自己的间隔触发。单一源不会被连续请求。

错误处理

网络请求不可能百分百成功。需要三次重试 + 降级

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    const result = await crawlSource(source);
    break;
  } catch (e) {
    if (attempt === 2) {
      // 三次全失败 → 记录错误,下轮重试
      state.consecutiveErrors++;
      notifyIfNeeded(source, e);
    }
    await sleep(2000 * (attempt + 1)); // 指数退避
  }
}

规则分类

爬回来的数据需要分类。不用 AI,用关键词规则足够:

function classify(item) {
  const title = item.title.toLowerCase();
  if (['ai', 'llm', 'gpt'].some(k => title.includes(k))) return 'AI/ML';
  if (['react', 'vue', 'css'].some(k => title.includes(k))) return '前端';
  if (['rust', 'go', 'python'].some(k => title.includes(k))) return '编程语言';
  return '综合';
}

75% 以上的条目能被正确分类——剩下的归入「综合」也不影响阅读体验。

自动化部署

爬虫注册为 Windows 计划任务,每 30 分钟自动运行。日报每天 08:00 生成并部署到服务器。人不需要参与——打开浏览器就能看到今天的技术精选。

完整源码在 GitHub 开源,含 7 个爬取器 + 调度器 + 日报生成器。