使用Node.js进行Web抓取并请求

时间:2018-04-09 07:26:12

标签: node.js web-scraping

我想对this site进行网页抓取。 我已经看到API可用,但正如previous question中的 duraid 所示,建议不要使用它们。

所以我尝试将Node.jsPhantom.jsPhantom一起使用。 但是用户Vaviloff pointed out to me不需要无头浏览器,因为它足以使用搜索请求的URL。

所以我改变了策略,我尝试不使用Phantom,而是使用普通request

var cheerio = require('cheerio');
var request = require('request-promise');

var options = {
    uri: 'http://data.un.org/Handlers/DataHandler.ashx?Service=query&Anchor=variableID%3a12&Applied=crID%3a8&crID%3a40;timeID%3a79&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=302',
    transform: function(body) {
        return cheerio.load(body);
    }
};

methods.download = async function(req, res) {
    request(options)
    .then(function($) {
        console.log('\n\nTHEN: ', $);
    })
    .catch(function(err) {
        console.log('Error', err.stack());
    });
}

如果我运行此代码,我会得到:

THEN:  function (selector, context, r, opts) {
    if (!(this instanceof initialize)) {
      return new initialize(selector, context, r, opts);
    }
    opts = _.defaults(opts || {}, options);
    return Cheerio.call(this, selector, context, r || root, opts);
  }

在这种情况下,我还有其他问题。

  1. 我不知道如何构建网址。 在上面的示例中,我选择了阿尔巴尼亚(crID% 3a8)和奥地利(crID% 3a40)和2015年作为年份(timeID% 3a79)。 然而,如果我转到刚建成的链接,我会得到2100年至2095年阿尔巴尼亚的数据。
  2. 我不知道如何选择年份或如何选择变体或如何更改页面。
  3. 我对以下信息感兴趣:

    var countries = {
        'Albania': 'crID%3a8',
        'Austria': 'crID%3a40',
        'Belgium': 'crID%3a56',
        'Bulgaria': 'crID%3a100',
        'Croatia': 'crID%3a191',
        'Cyprus': 'crID%3a196',
        'Denmark': 'crID%3a208',
        'Estonia': 'crID%3a233',
        'Finland': 'crID%3a246',
        'France': 'crID%3a250',
        'Germany': 'crID%3a276',
        'Greece': 'crID%3a300',
        'Iceland': 'crID%3a352',
        'Ireland': 'crID%3a372',
        'Italy': 'crID%3a380',
        'Latvia': 'crID%3a428',
        'Netherlands': 'crID%3a528',
        'Norway': 'crID%3a578',
        'Poland': 'crID%3a616',
        'Portugal': 'crID%3a620',
        'Romania': 'crID%3a642',
        'Slovakia': 'crID%3a703',
        'Slovenia': 'crID%3a705',
        'Spain': 'crID%3a724',
        'Sweden': 'crID%3a752',
        'Switzerland': 'crID%3a756',
        'United Kingdom': 'crID%3a826'
    };
    // 2018 - 1980
    var years = ['timeID%3a83', 'timeID%3a82', 'timeID%3a81', 'timeID%3a79', 'timeID%3a78', 'timeID%3a77', 'timeID%3a76', 'timeID%3a75', 'timeID%3a73', 'timeID%3a72', 'timeID%3a71', 'timeID%3a70', 'timeID%3a69', 'timeID%3a67', 'timeID%3a66', 'timeID%3a65', 'timeID%3a64', 'timeID%3a63', 'timeID%3a61', 'timeID%3a60', 'timeID%3a59', 'timeID%3a58', 'timeID%3a57', 'timeID%3a55', 'timeID%3a54', 'timeID%3a53', 'timeID%3a52', 'timeID%3a51', 'timeID%3a49', 'timeID%3a48', 'timeID%3a47', 'timeID%3a46', 'timeID%3a45', 'timeID%3a43', 'timeID%3a42', 'timeID%3a41', 'timeID%3a40', 'timeID%3a39', 'timeID%3a37']; 
    // medium
    var variants = 'varID%3a2';
    

    仅为了完整性:一旦选择了数据,我需要创建一个这样的对象:

    var date = [{year: 2018, country: 'Albania', population: 2934.363}, {year: 2017, country: 'Albania', population: 2930.187}, ..., {year: 1980, country: 'United Kingdom ', population: 56265.475}]

    所以我创建了一个类似的函数:

    methods.createJsonObjectPop = function(year, country, population) {
        return {
            year: year, 
            country: country, 
            population: population
        };
    }
    

    任何建议对我都有很大的帮助!

    编辑1

    内容分为几页。我们如何获得所有数据?通过打开所有页面并合并数据? 这很明显。 如果 X 是页数,我想我必须做不同的 X 请求。

    该网站如何知道请求了哪个网页? 我想是感谢网址,但我不确定(如http://...Page=3...)。

    我想象这个伪代码:

    var basicUrl = 'http://data.un.org/Handlers/DataHandler.ashx?Service=query&Anchor=variableID%3a12&Applied=crID%3a8;crID%3a40;crID%3a56;crID%3a100;crID%3a191;crID%3a196;crID%3a208;crID%3a233;crID%3a246;crID%3a250;crID%3a276;crID%3a300;crID%3a352;crID%3a372;crID%3a380;crID%3a428;crID%3a528;crID%3a578;crID%3a616;crID%3a620;crID%3a642;crID%3a703;crID%3a705;crID%3a724;crID%3a752;crID%3a756;crID%3a826;timeID%3a83;timeID%3a82;timeID%3a81;timeID%3a79;timeID%3a78;timeID%3a77;timeID%3a76;timeID%3a75;timeID%3a73;timeID%3a72;timeID%3a71;timeID%3a70;timeID%3a69;timeID%3a67;timeID%3a66;timeID%3a65;timeID%3a64;timeID%3a63;timeID%3a61;timeID%3a60;timeID%3a59;timeID%3a58;timeID%3a57;timeID%3a55;timeID%3a54;timeID%3a53;timeID%3a52;timeID%3a51;timeID%3a49;timeID%3a48;timeID%3a47;timeID%3a46;timeID%3a45;timeID%3a43;timeID%3a42;timeID%3a41;timeID%3a40;timeID%3a39;timeID%3a37;varID%3a2&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=531';
    let promises = [];
    let allData = [];
    
    var options = {
        uri: url,
        transform: function(body) {
            return cheerio.load(body);
        }
    };
    
    methods.download = async function(req, res) {
        for(var i = 0; i < X; i++) {
            var url = basicUrl + '&Page=' + i;
            let res = await request(options, url);
            let data = elaborateData(res);
            allData.push(data);
        }
        return Promise.all(promises);
    }
    
    function elaborateData(res) {
        var el = document.createElement('html');
        // use javascript or jQuery to get data like:
        // var year = getElementByTag(...);
        // var country = getElementByTag(...);
        // var population = getElementByTag(...);
        return createJsonObjectPop(year, country, population);
    }
    

    编辑2

    var basicUrl = 'http://data.un.org/Handlers/DataHandler.ashx?Service=query&Anchor=variableID%3a12&Applied=crID%3a8;crID%3a40;crID%3a56;crID%3a100;crID%3a191;crID%3a196;crID%3a208;crID%3a233;crID%3a246;crID%3a250;crID%3a276;crID%3a300;crID%3a352;crID%3a372;crID%3a380;crID%3a428;crID%3a528;crID%3a578;crID%3a616;crID%3a620;crID%3a642;crID%3a703;crID%3a705;crID%3a724;crID%3a752;crID%3a756;crID%3a826;timeID%3a83;timeID%3a82;timeID%3a81;timeID%3a79;timeID%3a78;timeID%3a77;timeID%3a76;timeID%3a75;timeID%3a73;timeID%3a72;timeID%3a71;timeID%3a70;timeID%3a69;timeID%3a67;timeID%3a66;timeID%3a65;timeID%3a64;timeID%3a63;timeID%3a61;timeID%3a60;timeID%3a59;timeID%3a58;timeID%3a57;timeID%3a55;timeID%3a54;timeID%3a53;timeID%3a52;timeID%3a51;timeID%3a49;timeID%3a48;timeID%3a47;timeID%3a46;timeID%3a45;timeID%3a43;timeID%3a42;timeID%3a41;timeID%3a40;timeID%3a39;timeID%3a37;varID%3a2&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=531';
    let promises = [];
    let allData = [];
    var pages = 22; // data are splitting in 22 pages
    
    methods.download = async function(req, res) {
        for(var i = 0; i < pages; i++) {
            var url = basicUrl + '&Page=' + i;
    
            var options = {
                uri: url,
                transform: function(html) {
                    return cheerio.load(html);
                }
            };
    
            let res = await request(options)
            .then(function($) {
                return $;
            })
            .catch(function(err) {
                console.log('Error', err.stack());
            });
    
            console.log('\n\nRES:', res);
            let data = elaborateData(res);
            allData.push(data);
        }
        return Promise.all(promises);
    }
    
    function elaborateData($) {
        $('.td').each(function() {
            console.log($(this).text());
        });
        // use javascript or jQuery to get data like:
        // var year = getElementByTag(...);
        // var country = getElementByTag(...);
        // var population = getElementByTag(...);
        //return createJsonObjectPop(year, country, population);
    }
    

    如果我运行此代码,我会得到:

    RES: function (selector, context, r, opts) {
        if (!(this instanceof initialize)) {
          return new initialize(selector, context, r, opts);
        }
        opts = _.defaults(opts || {}, options);
        return Cheerio.call(this, selector, context, r || root, opts);
      }
    

    编辑3

    var cheerioTableparser = require('cheerio-tableparser');
    
    methods.download = async function(req, res) {
        for(var i = 0; i < 22; i++) {
            var url = basicUrl + '&Page=' + i; // DOESN'T WORK
    
            var options = {
                uri: url,
                transform: function(html) {
                    return cheerio.load(html);
                }
            };
    
            let res = await request(options)
            .then(function($) {
                return $;
            })
            .catch(function(err) {
                console.log('Error', err.stack());
            });
    
            //console.log('\n\nRES:', res);
            let data = elaborateData(res);
            allData.push(data);
        }
        return Promise.all(promises);
    }
    
    function elaborateData($) {
        cheerioTableparser($);
        var data = $('table').parsetable(true, true, true);
    
        var countries = data[0];
        var years = data[1];
        var variants = data[2];
        var values = data[3];
        console.log('\ncountries:', countries);
        console.log('\nyears:', years);
        console.log('\nvariants:', variants);
        console.log('\nvalues:', values);
    
        // use javascript or jQuery to get data like:
        // var year = getElementByTag(...);
        // var country = getElementByTag(...);
        // var population = getElementByTag(...);
        //return createJsonObjectPop(year, country, population);
    }
    

    我明白了:

    countries: [ 'Country or Area',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Albania',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria',
      'Austria' ]
    
    years: [ 'Year(s)',
      '2018',
      '2017',
      '2016',
      '2015',
      '2014',
      '2013',
      '2012',
      '2011',
      '2010',
      '2009',
      '2008',
      '2007',
      '2006',
      '2005',
      '2004',
      '2003',
      '2002',
      '2001',
      '2000',
      '1999',
      '1998',
      '1997',
      '1996',
      '1995',
      '1994',
      '1993',
      '1992',
      '1991',
      '1990',
      '1989',
      '1988',
      '1987',
      '1986',
      '1985',
      '1984',
      '1983',
      '1982',
      '1981',
      '1980',
      '2018',
      '2017',
      '2016',
      '2015',
      '2014',
      '2013',
      '2012',
      '2011',
      '2010',
      '2009',
      '2008' ]
    
    variants: [ 'Variant',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium',
      'Medium' ]
    
    values: [ 'Value',
      '2934.363',
      '2930.187',
      '2926.348',
      '2923.352',
      '2920.775',
      '2918.978',
      '2920.039',
      '2926.659',
      '2940.525',
      '2962.635',
      '2991.651',
      '3023.907',
      '3054.331',
      '3079.179',
      '3097.747',
      '3111.005',
      '3119.029',
      '3122.408',
      '3121.970',
      '3115.576',
      '3103.759',
      '3093.041',
      '3092.228',
      '3106.736',
      '3140.595',
      '3189.583',
      '3240.587',
      '3275.431',
      '3281.454',
      '3253.656',
      '3197.067',
      '3121.336',
      '3041.007',
      '2966.798',
      '2901.592',
      '2842.624',
      '2788.314',
      '2735.329',
      '2681.239',
      '8751.820',
      '8735.453',
      '8712.137',
      '8678.657',
      '8633.220',
      '8577.782',
      '8517.548',
      '8459.864',
      '8409.949',
      '8370.038',
      '8338.453' ]
    

    它有效,只有我才能从第一页获取数据。

1 个答案:

答案 0 :(得分:0)

密切关注抓取网站的所有请求。

如果您的目标网站是针对1以外的网页的ajax请求,您会看到初始网址

  

http://data.un.org/Handlers/DataHandler.ashx?Service=query&Anchor=variableID%3a12&Applied=crID%3a8&crID%3a40;timeID%3a79&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=302

被另一个替换:

  

http://data.un.org/Handlers/DataHandler.ashx?Service=page&Page=2&DataFilter=variableID%3a12%3btimeID%3a178%2c179&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=361

即。 Service=query已替换为Service=pagePage=N也接受页码var post_options = { host: 'actualUrlAddress', protocol: 'https:' port: '443', path: '/api/SyncPersonnelViaAwsApi/SapEaiCall', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': post_data.length } };

您可以在所有现代浏览器中监控此类请求,但目前我预先准备好谷歌浏览器,因为它的开发工具是我体验中最快的。

因此,在谷歌浏览器中按F12或CTRL + I然后单击“网络”选项卡,然后单击“XHR”过滤器。这将显示该网站发出的所有新的ajax请求。

Turn on AJAX requests monitor