#javascript #web-scraping #optimization #puppeteer #puppeteer-cluster
#javascript #очистка веб-страниц #оптимизация #кукловод #кукловод-кластер
Вопрос:
Я очень новичок в puppeteer. Я начал вчера и пытаюсь создать программу, которая просматривает URL-адрес, который постепенно сохраняет идентификаторы игроков один за другим и сохраняет статистику игрока с помощью NeDB. Нужно просмотреть тысячи ссылок, и я обнаружил, что если я использую цикл for, мой компьютер в основном выходит из строя, потому что 1000 хромий пытаются открыть все одновременно. Есть ли лучший способ или правильный способ сделать это? Любые советы будут оценены.
const puppeteer = require('puppeteer');
const Datastore = require('nedb');
const database = new Datastore('database.db');
database.loadDatabase();
async function scrapeProduct(url){
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
let attributes = [];
//Getting player's name
const [name] = await page.$x('//*[@id="ctl00_ctl00_ctl00_Main_Main_name"]');
const txt = await name.getProperty('innerText');
const playerName = await txt.jsonValue();
attributes.push(playerName);
//Getting all 12 individual stats of the player
for(let i = 1; i < 13; i ){
let vLink = '//*[@id="ctl00_ctl00_ctl00_Main_Main_SectionTabBox"]/div/div/div/div[1]/table/tbody/tr[' i ']/td[2]';
const [e1] = await page.$x(vLink);
const val = await e1.getProperty('innerText');
const skillVal = await val.jsonValue();
attributes.push(skillVal);
}
//creating a player object to store the data how i want (i know this is probably ugly code and could be done in a much better way)
let player = {
Name: attributes[0],
Athleticism: attributes[1],
Speed: attributes[2],
Durability: attributes[3],
Work_Ethic: attributes[4],
Stamina: attributes[5],
Strength: attributes[6],
Blocking: attributes[7],
Tackling: attributes[8],
Hands: attributes[9],
Game_Instinct: attributes[10],
Elusiveness: attributes[11],
Technique: attributes[12],
};
database.insert(player);
await browser.close();
}
//For loop to loop through 1000 player links... Url.com is swapped in here because the actual url is ridiculously long and not important.
for(let i = 0; i <= 1000; i ){
let link = 'https://url.com/?id=' i 'amp;section=Ratings';
scrapeProduct(link);
console.log("Player #" i " scrapped");
}
Комментарии:
1. Для меня наличие нового экземпляра chromium для каждой страницы, которую вы очищаете, звучит ужасно неэффективно и не нужно для очистки данных. Но, по крайней мере, вы должны обрабатывать каждую страницу перед загрузкой следующей в любом случае, как уже было предложено.
2. Я не хочу закрываться из браузера каждый раз, когда заканчиваю очистку, но я не уверен, как заставить его работать, не делая этого. Я попытался инициализировать и объявить браузер и страницу вне моей функции, а затем просто изменить «await page.goto (url)», но я получаю ошибки, сообщающие мне, что браузер и страница не определены.
Ответ №1:
Проще всего было бы дождаться завершения каждой ссылки, прежде чем начинать следующую:
(async () => {
for(let i = 0; i <= 1000; i ){
let link = 'https://url.com/?id=' i 'amp;section=Ratings';
await scrapeProduct(link);
console.log("Player #" i " scrapped");
}
})();
Вы также можете разрешить открывать только столько, сколько может обработать ваш компьютер. Это потребует больше ресурсов, но позволит быстрее завершить процесс. Определите желаемый предел, затем сделайте что-то вроде:
let i = 0;
const getNextLink = () => {
if (i > 1000) return;
let link = 'https://url.com/?id=' i 'amp;section=Ratings';
i ;
return scrapeProduct(link)
.then(getNextLink)
.catch(handleErrors);
};
Promise.all(Array.from(
{ length: 4 }, // allow 4 to run concurrently
getNextLink
))
.then(() => {
// all done
});
Вышеизложенное позволяет scrapeProduct
одновременно активировать 4 вызова — измените номер по мере необходимости.
Комментарии:
1. Большое вам спасибо за ваш ответ. Мне понравилось ваше решение. Я думаю, что это работает довольно медленно, потому что на каждой веб-странице, которую я очищаю, я полностью закрываю браузер и страницу, а не просто меняю URL страницы. Но, когда я пытаюсь объявить браузер вне функции очистки, он действует так, как будто браузер и страница еще не объявлены. Я не уверен, почему это так, я получаю такие ошибки, как «browser.getPage()» не является функцией.
Ответ №2:
Если вы считаете, что проблема со скоростью заключается в повторном открытии / закрытии браузера при каждом запуске, переместите браузер в глобальную область видимости и инициализируйте его значением null. Затем создайте функцию инициализации с чем-то вроде:
async function init(){
if(!browser)
browser = await puppeteer.launch()
}
Разрешить передачу страниц в вашу функцию scrapeProduct . async function scrapeProduct(url)
становится async function scrapeProduct(url,page)
. Заменить await browser.close()
на await page.close()
. Теперь ваш цикл будет выглядеть так:
//For loop to loop through 1000 player links... Url.com is swapped in here because the actual url is ridiculously long and not important.
await init();
for(let i = 0; i <= 1000; i ){
let link = 'https://url.com/?id=' i 'amp;section=Ratings';
let page = await browser.newPage()
scrapeProduct(link,page);
console.log("Player #" i " scrapped");
}
await browser.close()
Если вы хотите ограничить количество страниц, которые браузер будет запускать одновременно, вы можете создать функцию для этого:
async function getTotalPages(){
const allPages = await browser.pages()
return allPages.length
}
async function newPage(){
const MAX_PAGES = 5
await new Promise(resolve=>{
// check once a second to check on pages open
const interval = setInterval(async ()=>{
let totalPages = await getTotalPages()
if(totalPages< MAX_PAGES){
clearInterval(interval)
resolve()
}
},1000)
})
return await browser.newPage()
}
Если бы вы сделали это, в вашем цикле вы бы заменили let page = await browser.newPage
на let page = await newPage()