Javascript NodeJS Express web抓取标题问题
我正在抓取一个粉丝网站,以便在我的web应用程序中显示角色信息,但在发送标题后,我遇到了无法设置标题的问题。我试图在我的请求中使用承诺,但我想我可能对我的代码实际上在做什么有一个基本的误解 最终的目标是通过循环通过一个boss名称数组来获取100个页面的数据,将该数据存储在一个数组中,然后最终将其导出以供以后使用。目前我可以将数据存储在数组中,但即使代码执行并刮取数据,仍然会出现错误 server.jsJavascript NodeJS Express web抓取标题问题,javascript,node.js,express,Javascript,Node.js,Express,我正在抓取一个粉丝网站,以便在我的web应用程序中显示角色信息,但在发送标题后,我遇到了无法设置标题的问题。我试图在我的请求中使用承诺,但我想我可能对我的代码实际上在做什么有一个基本的误解 最终的目标是通过循环通过一个boss名称数组来获取100个页面的数据,将该数据存储在一个数组中,然后最终将其导出以供以后使用。目前我可以将数据存储在数组中,但即使代码执行并刮取数据,仍然会出现错误 server.js var express = require('express'); var cheerio
var express = require('express');
var cheerio = require('cheerio');
var app = express();
var rp = require('request-promise');
var fsp = require('fs-promise');
app.get('/', function(req, res){
urls = [
'fansite/boss1', 'fansite/boss2'
];
var bosses = [];
function parse(html) {
var $ = cheerio.load(html);
$('.page-header__title').filter(function () {
var data = $(this);
name = data.text();
bosses.push(name);
})
console.log(bosses);
return bosses;
}
urls.forEach(function (url) {
rp(url)
.then(parse)
.then(res.send('Bosses Updated.'))
.catch(err => console.log('Error:', err));
});
})
app.listen('8081')
console.log('Running on port 8081');
exports = module.exports = app;
输出:
node server.js start
Running on port 8081
[ 'Obor' ]
[ 'Obor', 'Zulrah' ]
Error: Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
at ServerResponse.header (/Users/aaron/Personal Projects/node-scraper/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/Users/aaron/Personal Projects/node-scraper/node_modules/express/lib/response.js:170:12)
at rp.then.then (/Users/aaron/Personal Projects/node-scraper/server.js:31:21)
at tryCatcher (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0 (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/promise.js:693:18)
at Async._drainQueue (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/async.js:133:16)
at Async._drainQueues (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues (/Users/aaron/Personal Projects/node-scraper/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:672:20)
at tryOnImmediate (timers.js:645:5)
at processImmediate [as _immediateCallback] (timers.js:617:5)
res.send向客户端发送整个HTTP响应,包括头和内容,这就是为什么您无法多次调用它的原因 res.send将整个HTTP响应发送到客户端,包括头和内容,这就是您无法多次调用它的原因 如果要在发送响应之前等待处理所有URL
Promise.all(urls.map(function (url) {
return rp(url).then(parse);
}))
.then(() => res.send('Bosses Updated.'))
.catch(err => console.log('Error:', err));
或
如果要在发送响应之前等待处理所有URL
Promise.all(urls.map(function (url) {
return rp(url).then(parse);
}))
.then(() => res.send('Bosses Updated.'))
.catch(err => console.log('Error:', err));
或
您可以使用Promise.all并捕获个人请求,这样您就不会失去成功的请求。承诺后在响应中报告。所有请求均以响应完成:
const Fail = function(reason){this.reason=reason;};
const isFail = x=>(x&&x.constructor)===Fail;
const isNotFail = x=>!isFail(x);
Promise.all(
urls.map(
url=>
rp(url)
.then(parse)
.catch(err => new Fail([url,err]))
)
)
.then(
results=>
res.json(results)
);
如果您向一个站点发出许多请求,则可能需要限制请求。根据您正在发出的打开请求的数量或您希望在特定时间段内发出的请求的数量。您可以使用进行此操作,但如果您的express应用程序是一个公共站点,其中可能有许多用户可以开始刮取,则最好不要让目标站点将您的刮取视为攻击
const max = throttle(8)//maximum 8 open connections
//const max = throttlePeriod(8,1000);//maximum 8 requests per second
Promise.all(
urls.map(
url=>
max(rp)(url)//throttle requests made
.then(parse)
.catch(err => new Fail([url,err]))
)
)
.then(
results=>
res.send(JSON.parse)
)
您可以使用Promise.all并捕获个人请求,这样您就不会失去成功的请求。承诺后在响应中报告。所有请求均以响应完成:
const Fail = function(reason){this.reason=reason;};
const isFail = x=>(x&&x.constructor)===Fail;
const isNotFail = x=>!isFail(x);
Promise.all(
urls.map(
url=>
rp(url)
.then(parse)
.catch(err => new Fail([url,err]))
)
)
.then(
results=>
res.json(results)
);
如果您向一个站点发出许多请求,则可能需要限制请求。根据您正在发出的打开请求的数量或您希望在特定时间段内发出的请求的数量。您可以使用进行此操作,但如果您的express应用程序是一个公共站点,其中可能有许多用户可以开始刮取,则最好不要让目标站点将您的刮取视为攻击
const max = throttle(8)//maximum 8 open connections
//const max = throttlePeriod(8,1000);//maximum 8 requests per second
Promise.all(
urls.map(
url=>
max(rp)(url)//throttle requests made
.then(parse)
.catch(err => new Fail([url,err]))
)
)
.then(
results=>
res.send(JSON.parse)
)
。然后需要一个函数作为参数,您提供调用res.send'boss Updated'的结果。作为参数-尽管如此,我看不出这会导致您描述的错误-即,您的代码将res.send'boss Updated..两次,紧接着,甚至在调用parse之前-quick fix。然后=>res.send'boss Updated.-但是,再一次,我看不出这对解决您的问题有什么帮助honest@JaromandaX这也让我很困惑,谢谢你给我的关于老板更新路线的提示,这很有意义。我将用一些输出更新我的答案。然后期望一个函数作为参数,您将提供调用res.send'boss Updated'的结果。作为参数-虽然,我看不出这会导致您描述的错误-即,您的代码将res.send'boss Updated'。立即执行两次,甚至在调用解析之前-快速修复。然后=>res.send'boss Updated.-但是,再一次,我看不出这对解决您的问题有什么帮助honest@JaromandaX这也让我很困惑,谢谢你给我的关于老板更新路线的提示,这很有意义。我将用一些输出更新我的答案。我正在抓取一个粉丝网站,最终目标是抓取100多页。这表明最好执行Promise.allp.catchFail.thenresult=>Fail,notFail,这样你就不会在一次失败中失去整个结果好吧,是的,当然-错误处理语义是不相关的:我正在抓取一个粉丝网站,最终目标是抓取100多个页面。这表明最好执行Promise.allp.catchFail.thenresult=>Fail,notFail,这样您就不会在一次失败中丢失整个结果好的,是的,当然-错误处理语义是不相关的:pI没有考虑可能需要限制我的请求,因为我将在接近100的时候发出请求。谢谢我想我可能会实现您的解决方案,但目前,在承诺之后,我会将json发送到一个输出文件,但我使用的是重复数据。然后results=>fsp.writeFile'output.json',json.stringifyresults,null,4有什么想法吗?@Aaron Throttling取决于您正在抓取的站点。有些人不允许这样做,并且在发出太多请求时会暂时阻止您的ip。如果您正在使用它,它应该替换现有的文件,这意味着您的刮削产生了重复的记录。也许有些页面有不同的url,但数据相同?我没有想到可能需要限制我的请求,因为我将接近100。谢谢我想我可能会实现您的解决方案,但目前,在承诺之后,我将把json发送到一个输出文件,但我使用.then results=>fsp.writeFile'output.json',json.stringifyr获取重复数据
esults,null,4有什么想法吗?@Aaron Throttling取决于你正在抓取的站点。有些人不允许这样做,并且在发出太多请求时会暂时阻止您的ip。如果您正在使用它,它应该替换现有的文件,这意味着您的刮削产生了重复的记录。也许有些页面有不同的url,但数据相同?