我正在使用puppeteer-cluster来爬行网页。
如果我每个网站一次打开多个页面(8-10页),则连接速度变慢,并且出现许多超时错误,如下所示:
TimeoutError:超出导航超时:超过30000ms
我只需要访问每个页面的HTML代码。我不需要等待domcontentloaded等。
是否可以告诉 page.goto()仅等待来自网络服务器的第一个响应?还是我需要使用其他技术而不是伪造者?
答案 0 :(得分:2)
domcontentloaded 是第一个html内容的事件。
在完全加载并解析了初始HTML文档之后,无需等待样式表,图像和子帧完成加载,就会触发DOMContentLoaded事件。
仅在加载初始HTML文档时,以下内容便会完成加载。
await page.goto(url, {waitUntil: 'domcontentloaded'})
但是,如果您一次加载10页,则可以阻止图像或样式表以节省带宽并更快地加载。
将下面的代码放在正确的位置(使用page.goto
进行导航之前),它将停止加载图像,样式表,字体和脚本。
await page.setRequestInterception(true);
page.on('request', (request) => {
if (['image', 'stylesheet', 'font', 'script'].indexOf(request.resourceType()) !== -1) {
request.abort();
} else {
request.continue();
}
});
答案 1 :(得分:2)
@ user3817605,我为您提供了完美的代码。 :)
/**
* The methods `page.waitForNavigation` and `frame.waitForNavigation` wait for the page
* event `domcontentloaded` at minimum. This function returns a promise that resolves as
* soon as the specified page `event` happens.
*
* @param {puppeteer.Page} page
* @param {string} event Can be any event accepted by the method `page.on()`. E.g.: "requestfinished" or "framenavigated".
* @param {number} [timeout] optional time to wait. If not specified, waits forever.
*/
function waitForEvent(page, event, timeout) {
page.once(event, done);
let fulfill, timeoutId = (typeof timeout === 'number' && timeout >= 0) ? setTimeout(done, timeout) : -1;
return new Promise(resolve => fulfill = resolve);
function done() {
clearTimeout(timeoutId);
fulfill();
}
}
您要求一个函数仅等待第一个响应,因此您可以像下面这样使用该函数:
page.goto(<URL>); // use .catch(() => {}) if you kill the page too soon, to avoid throw errors on console
await waitForEvent(page, 'response'); // after this line here you alread have the html response received
这正是您所要的。但是请注意,“收到的响应”与“收到的完整html响应”不同。第一个是响应的开始,最后一个是响应的结束。因此,也许您想使用由“响应”指示的事件“ requestfinished”。实际上,您可以使用伪造者页面所接受的任何事件。他们是: 关闭,控制台,对话框,domcontentloaded,错误,frameattached,framedetached,framenavigated,负载,指标,pageerror,弹出窗口,请求,requestfailed,requestfinish,响应,workercreated,workerdestroyed。
尝试使用以下方法:请求完成或框架导航。也许它们会适合您。
为帮助您确定最适合您的代码,可以设置如下测试代码:
const puppeteer = require('puppeteer');
/**
* The methods `page.waitForNavigation` and `frame.waitForNavigation` wait for the page
* event `domcontentloaded` at minimum. This function returns a promise that resolves as
* soon as the specified page `event` happens.
*
* @param {puppeteer.Page} page
* @param {string} event Can be any event accepted by the method `page.on()`. E.g.: "requestfinished" or "framenavigated".
* @param {number} [timeout] optional time to wait. If not specified, waits forever.
*/
function waitForEvent(page, event, timeout) {
page.once(event, done);
let fulfill, timeoutId = (typeof timeout === 'number' && timeout >= 0) ? setTimeout(done, timeout) : -1;
return new Promise(resolve => fulfill = resolve);
function done() {
clearTimeout(timeoutId);
fulfill();
}
}
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const cdp = await page.target().createCDPSession();
await cdp.send('Network.enable');
await cdp.send('Page.enable');
const t0 = Date.now();
page.on('request', req => console.log(`> ${Date.now() - t0} request start: ${req.url()}`));
page.on('response', req => console.log(`< ${Date.now() - t0} response: ${req.url()}`));
page.on('requestfinished', req => console.log(`. ${Date.now() - t0} request finished: ${req.url()}`));
page.on('requestfailed', req => console.log(`E ${Date.now() - t0} request failed: ${req.url()}`));
page.goto('https://www.google.com').catch(() => { });
await waitForEvent(page, 'requestfinished');
console.log(`\nThe page was released after ${Date.now() - t0}ms\n`);
await page.close();
await browser.close();
})();
/* The output should be something like this:
> 2 request start: https://www.google.com/
< 355 response: https://www.google.com/
> 387 request start: https://www.google.com/tia/tia.png
> 387 request start: https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png
. 389 request finished: https://www.google.com/
The page was released after 389ms
*/
答案 2 :(得分:0)
我可以看到另外两种实现您想要的方法:使用page.waitForResponse
和page.waitForFunction
。让我们看看。
使用page.waitForResponse,您可以执行以下操作:
page.goto('https://www.google.com/').catch(() => {});
await page.waitForResponse('https://www.google.com/'); // don't forget to put the final slash
很简单,恩?如果您不喜欢它,请尝试page.waitForFunction
并等待,直到创建de document
:
page.goto('https://www.google.com/').catch(() => {});
await page.waitForFunction(() => document); // you can use `window` too. It is almost the same
此代码将等待直到document
存在。当html的第一位到达并且浏览器开始创建文档的de DOM树表示时,就会发生这种情况。
但是请注意,尽管这两种解决方案都很简单,但是它们都没有等到下载整个html页面/文档。如果需要的话,您应该修改其他答案的waitForEvent
函数,以接受要完整下载的特定URL。示例:
/**
* The methods `page.waitForNavigation` and `frame.waitForNavigation` wait for the page
* event `domcontentloaded` at minimum. This function returns a promise that resolves as
* soon as the specified `requestUrl` resource has finished downloading, or `timeout` elapses.
*
* @param {puppeteer.Page} page
* @param {string} requestUrl pass the exact url of the resource you want to wait for. Paths must be ended with slash "/". Don't forget that.
* @param {number} [timeout] optional time to wait. If not specified, waits forever.
*/
function waitForRequestToFinish(page, requestUrl, timeout) {
page.on('requestfinished', onRequestFinished);
let fulfill, timeoutId = (typeof timeout === 'number' && timeout >= 0) ? setTimeout(done, timeout) : -1;
return new Promise(resolve => fulfill = resolve);
function done() {
page.removeListener('requestfinished', onRequestFinished);
clearTimeout(timeoutId);
fulfill();
}
function onRequestFinished(req) {
if (req.url() === requestUrl) done();
}
}
如何使用它:
page.goto('https://www.amazon.com/').catch(() => {});
await waitForRequestToFinish(page, 'https://www.amazon.com/', 3000);
显示完整的console.logs的完整示例:
const puppeteer = require('puppeteer');
/**
* The methods `page.waitForNavigation` and `frame.waitForNavigation` wait for the page
* event `domcontentloaded` at minimum. This function returns a promise that resolves as
* soon as the specified `requestUrl` resource has finished downloading, or `timeout` elapses.
*
* @param {puppeteer.Page} page
* @param {string} requestUrl pass the exact url of the resource you want to wait for. Paths must be ended with slash "/". Don't forget that.
* @param {number} [timeout] optional time to wait. If not specified, waits forever.
*/
function waitForRequestToFinish(page, requestUrl, timeout) {
page.on('requestfinished', onRequestFinished);
let fulfill, timeoutId = (typeof timeout === 'number' && timeout >= 0) ? setTimeout(done, timeout) : -1;
return new Promise(resolve => fulfill = resolve);
function done() {
page.removeListener('requestfinished', onRequestFinished);
clearTimeout(timeoutId);
fulfill();
}
function onRequestFinished(req) {
if (req.url() === requestUrl) done();
}
}
(async () => {
const netMap = new Map();
const browser = await puppeteer.launch();
const page = await browser.newPage();
const cdp = await page.target().createCDPSession();
await cdp.send('Network.enable');
await cdp.send('Page.enable');
const t0 = Date.now();
cdp.on('Network.requestWillBeSent', ({ requestId, request: { url: requestUrl } }) => {
netMap.set(requestId, requestUrl);
console.log(`> ${Date.now() - t0}ms\t requestWillBeSent:\t${requestUrl}`);
});
cdp.on('Network.responseReceived', ({ requestId }) => console.log(`< ${Date.now() - t0}ms\t responseReceived:\t${netMap.get(requestId)}`));
cdp.on('Network.dataReceived', ({ requestId, dataLength }) => console.log(`< ${Date.now() - t0}ms\t dataReceived:\t\t${netMap.get(requestId)} ${dataLength} bytes`));
cdp.on('Network.loadingFinished', ({ requestId }) => console.log(`. ${Date.now() - t0}ms\t loadingFinished:\t${netMap.get(requestId)}`));
cdp.on('Network.loadingFailed', ({ requestId }) => console.log(`E ${Date.now() - t0}ms\t loadingFailed:\t${netMap.get(requestId)}`));
// The magic happens here
page.goto('https://www.amazon.com').catch(() => { });
await waitForRequestToFinish(page, 'https://www.amazon.com/', 3000);
console.log(`\nThe page was released after ${Date.now() - t0}ms\n`);
await page.close();
await browser.close();
})();
/* OUTPUT EXAMPLE
[... lots of logs removed ...]
> 574ms requestWillBeSent: https://images-na.ssl-images-amazon.com/images/I/71vvXGmdKWL._AC_SY200_.jpg
< 574ms dataReceived: https://www.amazon.com/ 65536 bytes
< 624ms responseReceived: https://images-na.ssl-images-amazon.com/images/G/01/AmazonExports/Fuji/2019/February/Dashboard/computer120x._CB468850970_SY85_.jpg
> 628ms requestWillBeSent: https://images-na.ssl-images-amazon.com/images/I/81Hhc9zh37L._AC_SY200_.jpg
> 629ms requestWillBeSent: https://images-na.ssl-images-amazon.com/images/G/01/personalization/ybh/loading-4x-gray._CB317976265_.gif
< 631ms dataReceived: https://www.amazon.com/ 58150 bytes
. 631ms loadingFinished: https://www.amazon.com/
*/
此代码显示了很多请求和响应,但是一旦完全下载了“ https://www.amazon.com/”,该代码就会停止。