【问题标题】:Getting around Node's Asynchronous nature绕过 Node 的异步特性
【发布时间】:2016-11-28 05:58:22
【问题描述】:

我正在编写一个内容抓取工具,用于抓取特定网站上有关衬衫的信息。我在 Node 中使用 NPM 包设置了所有内容,以抓取并创建 CSV 文件。我遇到的问题是,众所周知,Node 本质上是异步的。我尝试编写的 CSV 文件是在我创建的 JSON 对象完成创建之前编写的(使用 each 循环迭代来构建它),因此它传递了我的 json2csv(npm 包)的“字段”参数。但它将我的数据作为空对象传递。谁能告诉我如何告诉节点等到我的 json 对象构建完成后再尝试使用 fs.writefile 创建 CSV 文件?谢谢

'use strict';

//require NPM packages

var request = require('request');
var cheerio = require('cheerio');
var fs = require('fs');
var json2csv = require('json2csv');

//Array for shirts JSON object for json2csv to write.
var ShirtProps = [];

var homeURL = "http://www.shirts4mike.com/";

//start the scraper
scraper(); 

//Initial scrape of the shirts link from the home page
function scraper () {
  //use the datafolderexists function to check if data is a directory
  if (!DataFolderExists('data')) {
    fs.mkdir('data');
  }
  //initial request of the home url + the shirts.php link
  request(homeURL + "shirts.php", function (error, response, html) {
    if (!error && response.statusCode == 200) {
      var $ = cheerio.load(html);

      //scrape each of the links for its html data
      $('ul.products li').each(function(i, element){
        var ShirtURL = $(this).find('a').attr('href');
        console.log(ShirtURL);
        //pass in each shirtURL data to be scraped to add it to an object
        ShirtHTMLScraper(ShirtURL);
      }); 
      FileWrite();
      // end first request
    } else {
      console.error(error);
    }
  });
}

//create function to write the CSV file.
function FileWrite() {
  var fields = ['Title', 'Price', 'ImageURL', 'URL', 'Time'];
  var csv = json2csv({data: ShirtProps, fields: fields}); 
  console.log(csv);
  var d = new Date();
  var month = d.getMonth()+1;
  var day = d.getDate();
  var output = d.getFullYear() + '-' +
  ((''+month).length<2 ? '0' : '') + month + '-' +
  ((''+day).length<2 ? '0' : '') + day;

  fs.writeFile('./data/' + output + '.csv', csv, function (error) {
    if (error) throw error;      
  });    
}

//function to scrape each of the shirt links and create a shirtdata object for each.
function ShirtHTMLScraper(ShirtURL) {
  request(homeURL + ShirtURL, function (error, response, html) {
    if (!error && response.statusCode == 200) {
      var $ = cheerio.load(html);
      var time = new Date().toJSON().substring(0,19).replace('T',' ');
      //json array for json2csv
      var ShirtData = {
        title: $('title').html(),
        price: $(".price").html(),
        imgURL: $('img').attr('src'),
        url: homeURL + ShirtURL,
        time: time.toString() 
      };
      //push the shirt data scraped into the shirtprops array
      ShirtProps.push(ShirtData);
      console.log(ShirtProps);

      // //set the feilds in order for the CSV file
      // var fields = ['Title', 'Price', 'ImageURL', 'URL', 'Time'];

      // //use json2csv to write the file -

      // var csv = json2csv({data: ShirtProps, fields: fields}); 
      // console.log(csv);

      // //date for the filesystem to save the scrape with today's date.
      // var d = new Date();
      // var month = d.getMonth()+1;
      // var day = d.getDate();
      // var output = d.getFullYear() + '-' +
      // ((''+month).length<2 ? '0' : '') + month + '-' +
      // ((''+day).length<2 ? '0' : '') + day;

      //   //use filesystem to write the file, or overrite if it exists.
      //     fs.writeFile('./data/' + output + '.csv', csv, function (error) {
      //       if (error) throw error;

      //     }); //end writeFile
    } else {
      console.error(error);
    }
  });
}

//Check if data folder exists, source: http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js
function DataFolderExists(folder) {
  try {
    // Query the entry
    var DataFolder = fs.lstatSync(folder);

    // Is it a directory?
    if (DataFolder.isDirectory()) {
      return true;
    } else {
      return false;
    }
  } //end try
  catch (error) {
    console.error(error);
  }
}

【问题讨论】:

  • 拥抱正确的 javascript 技术的异步特性,而不是与之抗衡

标签: javascript jquery node.js asynchronous npm


【解决方案1】:

与其说节点本质上是异步的,不如说是某些函数是异步的。在这种情况下,使用请求的调用是异步的。您在第二个请求调用(在 ShirtHTMLScraper 中的那个)开始后直接调用 FileWrite。在填充 ShirtProps 之后,将对 FileWrite 的调用放在 ShirtHTMLScraper 的回调中。

edit:仔细观察后,这也行不通。问题是您在同步循环中调用异步函数。您可以通过创建一个计数器来实现它,该计数器在每个异步回调上递增,并检查您是否达到了您正在迭代的项目的长度。如果您正在进行最后一次迭代,请运行 FileWrite。

更好的方法可能是查看 Async 库。您可以使用 .each() 提供两个回调,一个在每次迭代时运行,一个在它们全部完成时运行。

【讨论】:

  • Matt,我试过了,还是不行,每次调用filewrite都会显示空白数据,在我设置的正确字段下。此外,如果我将它放在 ShirtHTMLScraper 的回调函数中,它会在每次迭代每个衬衫链接时写入文件,我希望它在填充包含 ShirtProps 对象的键值对的对象后写入文件.
  • ShirtProps 推送后的console.log 是否为空?因为如果它是正确的,并且在它之后调用 FileWrite 时失败,那么 FileWrite 就有问题。我明白你对重复写入的意思,我也会看看
  • 不, ShirtProps 的 console.log 很好,实际上显示它在尝试写入文件后构建对象,因为 console.log 每次遍历衬衫链接并获取数据时都会被调用,我这样做是为了让我可以看到对象实际上是一次构建 1 件衬衫,但在尝试写入文件后对象再次完成构建:(
  • 我的意思是,如果你在 ShirtProps 的控制台日志之后直接调用 FileWrite,你就知道在调用 FileWrite 之前数据在 ShirtProps 中(console.log 肯定是毕竟是同步的!)。您将遇到写入过多的问题,我更改了答案以反映。由于 fs.writeFile 也是异步的,我想知道调用多个写入是否会影响其写入能力。不过,这似乎是一个远大的目标,因为它在第一次尝试写入时应该至少有一条数据,并且您应该至少看到那一条。
  • 我明白了为什么我没有得到任何要写入的数据。我的键值对与我尝试用于 json2csv 节点包 DOH 的字段不完全相同!现在我试图弄清楚如何将第一个刮板中刮取的产品链接数量传递给第二个刮板,这样我就可以在上面使用计数器,并且只有在计数器与相同数量的产品链接匹配时才运行文件写入页。当我全局定义它并在第一个刮板中传递 ul 产品 li 的值时,它显示为未定义。
猜你喜欢
  • 2013-08-05
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 2016-01-28
  • 2020-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多