我正在尝试使用一定数量的标签来打开和操作这些标签,同时等待网络IO加速我对域的抓取过程。
我在Go中通过工作池监听通道解决了同样的问题,但我不确定如何在Node.js和Puppeteer中解决同样的问题。
我的猜测是循环过程
async function nextPage() {
try {
for (var link of uncrawledLinks.keys()) {
if (runningThreads < maxThreads) {
var page = await browser.newPage();
console.log("nextPage() # runningThreads: " + runningThreads + " # uncrawledLinks.size: " + uncrawledLinks.size);
//debugger;
crawlPage(page, link);
}
}
当我将“maxthreads”增加到1以上时可能会产生问题,但直到现在我都无法解决问题。
当我将其设置为高于1时发生的问题是链接被抓取两次(但不是每个链接,只有约90%的链接),因此我得到重复项,这使得爬虫不可用。
我想过使用像Redis或SQLite这样的数据库,但我想先解决问题而不用它来更好地理解问题(直到现在我没有性能/内存问题所以在内存中这样做是没有的问题)。
可运行的示例代码:
'use strict';
const puppeteer = require('puppeteer');
const url = require('url');
// start URL
const startUrlObj = url.parse("http://example.de/");
const startUrlDomain = startUrlObj.protocol + "//" + startUrlObj.hostname;
const startUrl = url.format(startUrlObj);
let browser;
let pages = [];
let uncrawledLinks = new Map();
let crawledLinks = [];
let runningThreads = 0;
const maxThreads = 1;
start();
async function start() {
console.log("Starting Crawler");
browser = await puppeteer.launch();
console.log("Finished initializing browser object");
uncrawledLinks.set(startUrl, "");
nextPage();
};
async function crawlPage(page, link) {
try {
console.log("starting crawl for: " + link);
runningThreads++;
const response = await page.goto(link, {
waitUntil: 'networkidle2',
timeout: 30000
});
// find all links in the form <a href="xxx">
const hrefs = await page.$$eval('a', as => as.map(a => a.href));
hrefs.forEach(function(foundLink, key) {
if (foundLink.startsWith(startUrlDomain)) {
var tempUrl = url.parse(foundLink);
// remove #asd and ?param1=y values from URL
tempUrl.hash = null;
tempUrl.search = null;
var tempLink = url.format(tempUrl);
//console.log(url.format(tempLink));
if (crawledLinks.includes(tempLink) === false) {
if (tempLink.endsWith(".html") === true) {
uncrawledLinks.set(tempLink, "false");
//pages.push(tempLink);
}
}
}}, hrefs)
//console.log("Found new links: " + i + " # " + link);
// crawling queues
uncrawledLinks.delete(link);
crawledLinks.push(link);
} catch (error) {
// Log errors
console.error(error);
} finally {
runningThreads--;
await page.close();
await nextPage();
}
}
async function nextPage() {
try {
for (var link of uncrawledLinks.keys()) {
if (runningThreads < maxThreads) {
var page = await browser.newPage();
//console.log("nextPage() # runningThreads: " + runningThreads + " # uncrawledLinks.size: " + uncrawledLinks.size);
//debugger;
crawlPage(page, link);
}
}
} catch (error) {
console.error(error);
} finally {
if (uncrawledLinks.size === 0 && runningThreads === 0) {
console.log("Finished crawling");
console.log(crawledLinks);
await browser.close();
}
}
}
答案 0 :(得分:0)
首先,不幸的是(或者幸运的是,取决于你的观点)你没有Node.js中的线程(技术上你有,但它们不能从JavaScript代码中获得 - 仅在C ++级别)。这只是为了说清楚,因为它可能是一个无辜的命名错误或误解,可能会给你不合理的期望。
其次,请注意您正在捕捉erorr
但正在打印error
,因此您可能会因为变量拼写错误而导致某些错误无法显示。
最后,您正在尝试做的事情(异步操作与任何给定时间内有多少未完成的操作并行限制)可以使用Bluebird的eachLimit
方法轻松解决:
使用async
关键字的简单循环不容易做到这一点,因为通过在循环中使用async
,您将停止迭代,直到解析了promise,而不是并行执行其他迭代,并且您必须手动跟踪计数器,但是awaiting
对计数器的状态进行更改而不是给定的函数调用。使用async
模块更容易,而不是async
关键字。
答案 1 :(得分:0)
我发现必须用Promise包装IO内容,因为只有Promise才能提供异步IO。
async function crawlPage(page, link) {
browser.newPage().then(
async page => {
try {
await page.setRequestInterception(true);
// catch all requests
page.on('request', request => {
})
await page.goto(uncrawledUrl, {
waitUntil: 'networkidle2',
timeout: 10000
}).then(
async response => {