He estado intentando analizar los datos de una tabla de sitios en un archivo json, lo que puedo hacer si hago cada página una por una, pero viendo que hay 415 páginas que tomarían un tiempo.

He visto y leído muchas preguntas de StackOverflow sobre este tema, pero parece que no puedo modificar mi secuencia de comandos para que;

  1. Raspa cada página y extrae los 50 elementos con ID de elemento por página
  2. Hágalo con una tarifa limitada para que no afecte negativamente al servidor
  3. El script espera hasta que se realicen todas las solicitudes para que pueda escribir cada elemento + id de elemento en un archivo JSON.

Creo que debería poder hacer esto usando request-promise y promise.all, pero no puedo resolverlo.

El raspado real de los datos está bien. Simplemente no puedo crear el código, raspar una página y luego ir a la siguiente URL con un retraso o pausar entre las solicitudes. El siguiente código es lo más cercano que tengo, pero obtengo los mismos resultados varias veces y no puedo reducir la tasa de solicitudes.

Ejemplo de URL de la página:

  1. http://test.com/itemlist/1
  2. http://test.com/itemlist/2
  3. http://test.com/itemlist/3 etc (hasta 415)

    for (var i = 1; i <= noPages; i++) {
    urls.push({url: itemURL + i});
    console.log(itemURL + i);
    }
    
     Promise.map(urls, function(obj) {
     return rp(obj).then(function(body) {
    var $ = cheerio.load(body);
    //Some calculations again...
    rows = $('table tbody tr');
    $(rows).each(function(index, row) {
      var children = $(row).children();
      var itemName = children.eq(1).text().trim();
      var itemID = children.eq(2).text().trim();
    
      var itemObj = {
        "id" : itemID,
        "name" : itemName
      };
    
      itemArray.push(itemObj);
    });
    return itemArray;
      });
     },{concurrency : 1}).then(function(results) {
       console.log(results);
      for (var i = 0; i < results.length; i++) {
       // access the result's body via results[i]
        //console.log(results[i]);
      }
     }, function(err) {
     // handle all your errors here
      console.log(err);
    });
    

Disculpas por quizás malinterpretar node.js y sus módulos, realmente no uso el lenguaje, pero necesitaba extraer algunos datos y realmente no me gusta Python.

1
Pheonix2105 11 nov. 2017 a las 18:52

2 respuestas

La mejor respuesta

Ya que necesita que las solicitudes se ejecuten solo una por una, Promise.all () no ayudaría. La promesa recursiva (no estoy seguro de que sea la denominación correcta) lo haría.

function fetchAllPages(list) {
    if (!list || !list.length) return Promise. resolve(); // trivial exit
    var urlToFetch = list.pop();
    return fetchPage(urlToFetch).
        then(<wrapper that returns Promise will be resolved after delay >).
        then(function() {
            return fetchAllPages(list); // recursion! 
        });
}

Este código aún carece de manejo de errores. También creo que puede volverse mucho más claro con async / await:

for(let url of urls) {
    await fetchAndProcess(url);
    await <wrapper around setTimeout>;
}

Pero necesitas encontrar / escribir tu propia implementación de fetch() y setTimeout() que son async

2
skyboyer 11 nov. 2017 a las 22:05

Después de la entrada de @skyboyer sugiriendo el uso de promesas recursivas, fui dirigido a un Gist de GitHub llamado Ejecución secuencial de promesas usando reduce ( )

Primero creé mi matriz de URLS

for (var i = 1; i <= noPages; i++) {
    //example urls[0] = "http://test.com/1"
    //example urls[1] = "http://test.com/2"
    urls.push(itemURL + i);
    console.log(itemURL + i);
}

Entonces

       var sequencePromise = urls.reduce(function(promise, url) {
         return promise.then(function(results) {
        //fetchIDsFromURL async function (it returns a promise in this case) 
         //when the promise resolves I have my page data
         return fetchIDsFromURL(url)
        .then(promiseWithDelay(9000))
        .then(itemArr => {
          results.push(itemArr);
          //calling return inside the .then method will make sure the data you want is passed onto the next
          return results;
        });
    });
}, Promise.resolve([]));



// async
function fetchIDsFromURL(url)
{
  return new Promise(function(resolve, reject){
    request(url, function(err,res, body){
      //console.log(body);
      var $ = cheerio.load(body);
      rows = $('table tbody tr');
      $(rows).each(function(index, row) {
        var children = $(row).children();
        var itemName = children.eq(1).text().trim();
        var itemID = children.eq(2).text().trim();
        var itemObj = {
          "id" : itemID,
          "name" : itemName
        };
        //push the 50 per page scraped items into an array and resolve with 
        //the array to send the data back from the promise
        itemArray.push(itemObj);
      });
      resolve(itemArray);
    });
 });
}

//returns a promise that resolves after the timeout
function promiseWithDelay(ms)
{
  let timeout =  new Promise(function(resolve, reject){
    setTimeout(function()
    {
      clearTimeout(timeout);
      resolve();
    }, ms);
  });

  return timeout;
}

Luego, finalmente llame a. Then en la secuencia de promesas, el único problema que tuve con esto fue devolver múltiples matrices dentro de los resultados con los mismos datos en cada una, así que dado que todos los datos son iguales en cada matriz, solo tomo la primera que tiene todo mis elementos analizados con ID, luego los escribí en un archivo JSON.

  sequencePromise.then(function(results){
  var lastResult = results.length;
  console.log(results[0]);
  writeToFile(results[0]);
});
1
Pheonix2105 12 nov. 2017 a las 17:30