You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Cheerio多页面爬虫时,如何正确链式调用两个抓取函数?

问题描述

我正在学习使用Cheerio从网页抓取数据,已掌握单页面数据抓取方法,现尝试实现多页面抓取。我编写了两个分别对应不同URL的抓取函数,在index.js中按如下方式使用:

const express = require('express');
const scraper = require('./scraper');
const fs = require('fs');
const app = express();

app.get('/search/:title', (req, res) => {
  scraper.func1(req.params.title).then(cars => {
    res.json(cars);
    fs.writeFile(
      './json/cars.json',
      JSON.stringify(cars, null, 2), // optional params to format it nicely
      err => err ? console.error('Data not written!', err) : console.log('Data written!')
    );
  });
  scraper.func2(req.params.title).then(cars => {
    res.json(cars);
    fs.writeFile(
      './json/cars2.json',
      JSON.stringify(cars, null, 2), // optional params to format it nicely
      err => err ? console.error('Data2 not written!', err) : console.log('Data2 written!')
    );
  });
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Listening on ${port}`);
});

显然这样的调用方式无法正常工作,但单独调用每个函数都能正常运行。请问应该如何正确链式调用这两个函数?


解决方案

先帮你理清问题根源:

  • 你在同一个路由里调用了两次res.json(),但Express的响应对象只能发送一次响应,第二次调用时会直接报错,因为响应流已经关闭了。
  • 两个异步抓取函数是并行执行的,但你没有统一处理它们的结果和响应逻辑,导致整个流程混乱。

下面给你两种常见的正确处理方式,根据你的需求选择:

方式一:并行执行两个抓取函数(推荐,效率更高)

如果两个抓取任务没有依赖关系,想同时执行以节省时间,可以用Promise.all()等待所有任务完成,再统一处理结果和响应:

const express = require('express');
const scraper = require('./scraper');
const fs = require('fs').promises; // 用Promise版本的fs,避免回调嵌套
const app = express();

app.get('/search/:title', async (req, res) => {
  try {
    // 并行发起两个抓取请求,等待两者都完成
    const [carsFromFunc1, carsFromFunc2] = await Promise.all([
      scraper.func1(req.params.title),
      scraper.func2(req.params.title)
    ]);

    // 写入第一个文件(用await确保写入完成,也能捕获写入错误)
    await fs.writeFile('./json/cars.json', JSON.stringify(carsFromFunc1, null, 2));
    console.log('Data written!');

    // 写入第二个文件
    await fs.writeFile('./json/cars2.json', JSON.stringify(carsFromFunc2, null, 2));
    console.log('Data2 written!');

    // 只发送一次响应,返回合并后的结果
    res.json({
      source1: carsFromFunc1,
      source2: carsFromFunc2
    });
  } catch (err) {
    // 统一捕获所有错误,返回500状态码和错误信息
    console.error('Processing failed:', err);
    res.status(500).json({ error: err.message });
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Listening on ${port}`);
});

方式二:链式顺序执行两个函数

如果第二个抓取任务依赖第一个的结果,或者你需要严格按顺序执行,可以用async/await按顺序调用:

const express = require('express');
const scraper = require('./scraper');
const fs = require('fs').promises;
const app = express();

app.get('/search/:title', async (req, res) => {
  try {
    // 先执行第一个抓取函数
    const carsFromFunc1 = await scraper.func1(req.params.title);
    await fs.writeFile('./json/cars.json', JSON.stringify(carsFromFunc1, null, 2));
    console.log('Data written!');

    // 再执行第二个抓取函数(如果需要,可以使用第一个函数的结果)
    const carsFromFunc2 = await scraper.func2(req.params.title);
    await fs.writeFile('./json/cars2.json', JSON.stringify(carsFromFunc2, null, 2));
    console.log('Data2 written!');

    // 发送合并后的响应
    res.json({
      source1: carsFromFunc1,
      source2: carsFromFunc2
    });
  } catch (err) {
    console.error('Processing failed:', err);
    res.status(500).json({ error: err.message });
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Listening on ${port}`);
});

额外提示

  • 用fs.promises替代传统的fs.writeFile回调,配合async/await可以让代码更简洁,还能捕获写入文件时的错误。
  • 一定要统一处理错误,避免因为某个环节出错导致请求挂起或者无响应。

内容的提问来源于stack exchange,提问作者lr_optim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:56:47