如何解析多个页面?

时间:2017-11-11 15:52:10

标签: javascript json node.js cheerio

我一直在尝试将站点表数据解析为json文件,如果我逐个执行每个页面,我可以这样做,但是看到有415页需要一段时间。

我已经看过并阅读了很多有关此主题的StackOverflow问题,但我似乎无法修改我的脚本以便它;

  1. 每页抓取并每页提取项目IDS的50个项目
  2. 以限速方式这样做,所以我不会对服务器产生负面影响
  3. 脚本等待所有请求完成,以便我可以将每个项目+项目ID写入JSON文件。
  4. 我相信你应该能够使用request-promise和promise.all做到这一点,但我无法理解。

    实际抓取数据很好我只是无法制作代码,刮一页,然后转到下一个URL,延迟或暂停请求之间。 下面的代码是我得到的最接近的代码,但我多次得到相同的结果,我无法减慢请求率。

    页面URLS的示例:

    1. http://test.com/itemlist/1
    2. http://test.com/itemlist/2
    3. http://test.com/itemlist/3等(最多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);
      });
      
    4. 对于可能误解node.js及其模块的道歉,我并不真正使用该语言,但我需要抓取一些数据而且我真的不喜欢python。

2 个答案:

答案 0 :(得分:2)

因为你需要一个一个地运行请求Promise.all()不会有帮助。 递归承诺(我不确定它是否正确命名)会。

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! 
        });
}

此代码仍缺少错误处理。 另外我相信async / await会变得更加清晰:

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

但您需要查找/编写自己fetch()setTimeout()的{​​{1}}

实现

答案 1 :(得分:1)

从@skyboyer输入建议使用递归承诺后,我引出了一个名为Sequential execution of Promises using reduce()的GitHub Gist

首先我创建了我的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);
}

然后

       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;
}

然后最后调用.then对promises序列,我唯一的问题就是在结果中返回多个数组,每个数组中都有相同的数据,所以因为每个数组中的所有数据都是相同的所以我只需要第一个我的所有已解析的项目都包含ID,然后我将其写入JSON文件。

  sequencePromise.then(function(results){
  var lastResult = results.length;
  console.log(results[0]);
  writeToFile(results[0]);
});