使用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
相关产品推荐
相关产品推荐

