Правильное перебирание нескольких ссылок

#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()