使用Phantom刮取表单提交的信息[英] Scrape information with form submit using Phantom

问题描述

我想对 进行网页抓取这个网站.我已经看到这些 API 是可用的,但是正如 duraid 在我的 上一个问题,不建议使用.

所以我尝试使用 Node.js 和 Phantom.jshref="https://www.npmjs.com/package/phantom" rel="nofollow noreferrer">Phantom.

这是我的代码:

var phantom = require('phantom');

// object of methods
var methods = {};
var loadInProgress = false;
var url = 'http://data.un.org/Data.aspx?q=population&d=PopDiv&f=variableID%3A12';

methods.download = async function(req, res) {
    const instance = await phantom.create();
    const page = await instance.createPage();

    await page.on('onResourceRequested', function(requestData) {
        console.info('Requesting', requestData.url);
    });
    await page.on('onConsoleMessage', function(msg) {
        console.info(msg);
    });
    await page.on('onLoadStarted', function() {
        loadInProgress = true;
        console.log('Load started...');
    });
    await page.on('onLoadFinished', function() {
        loadInProgress = false;
        console.log('Load end');
    });

    const status = await page.open(url);
    console.log('STATUS:', status);

    const content = await page.property('content');
    console.log('CONTENT:', content);

    // submit
    await page.evaluate(function() {
        document.getElementById('crID%3a250').value = 'crID%3a250'; // France
        document.getElementById('timeID%3a79').value = 'timeID%3a79'; // 2015
        document.getElementById('varID%3a2').value = 'varID%3a2'; // Medium
        document.getElementById('ctl00_main_filters_anchorApplyBottom').submit(); // submit button
    });

    var result = await page.evaluate(function() {
        return document.querySelectorAll('html')[0].outerHTML; 
    });
    console.log('RESULT:', result);

    await instance.exit();
};

module.exports = methods;

(他们如何选择更多的国家和更多的年份?)

我尝试选择 France 作为 Country or Area,2015 作为 Year 和 medium 作为变体.

所以crID%3a250是元素的id:

<input type="checkbox" id="crID%3a250" value="crID%3a250" name="France" />
<label for="crID%3a250">France</label><br />

timeID%3a79 是元素的 id:

<input type="checkbox" id="timeID%3a79" value="timeID%3a79" name="2015" />
<label for="timeID%3a79">2015</label><br />

varID%3a2 是元素的 id:

<input type="checkbox" id="varID%3a2" value="varID%3a2" name="Medium" />
<label for="varID%3a2">Medium</label><br />

然后ctl00_main_filters_anchorApplyBottom是按钮元素的id:

<div class="All">
    <img src="_Images/IconUpdateResults.png" alt="Update" width="11px" height="11px" title="Apply filters" />&nbsp;<a href="javascript:;" id="ctl00_main_filters_anchorApplyBottom" title="Apply filters" onclick="ApplyFilters(SendFilterRequest);">Apply Filters</a>
</div>

但我得到的是网页本身(HTML 格式),而不是我感兴趣的数据.所以就好像我没有选择任何参数一样.为什么?

<小时>

编辑 1

在@Vaviloff 的建议下,我尝试更改代码但没有成功.我的服务器端语言是 Node.js.

使用 Phantom 我修改了这样的代码:

methods.download = async function(req, res) {
    const instance = await phantom.create();
    const page = await instance.createPage();

    await page.on('onResourceRequested', function(requestData) {
        console.log('Requesting', requestData.url);
    });
    await page.on('onConsoleMessage', function(msg) {
        console.log(msg);
    });

    const status = await page.open(url);
    console.log('\n\nSTATUS:', status);

    // submit
    await page.evaluate(function() {
        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']; 

        // select countries
        for(var c in countries) {
            document.getElementById(countries[c]).setAttribute('checked', true);
        }
        // select years
        for(var y in years) {
            document.getElementById(years[y]).setAttribute('checked', true);
        }
        // select variants
        document.getElementById('varID%3a2').setAttribute('checked', true); // medium
        // click button
        document.getElementById('ctl00_main_filters_anchorApplyBottom').click(); 
    });

    console.log('\nWaiting 1.5 seconds...');    
   await timeout(1500);

   // get only the table contents
    var result = await page.evaluate(function() {
        return document.querySelectorAll('.DataContainer table')[0].outerHTML; 
    });
    console.log('\n\nRESULT:', result);

    elaborateResult(result);

    await instance.exit();
};

function elaborateResult(res) {
    var el = document.createElement('html'); // ** ERROR HERE **
    el.innerHTML = result;
    console.log('\n\nTD ELEMENTS:', el.getElementsByTagName('td'));
    //var obj = utilFunc.createJsonObjectPop(year, country, population);
    //console.log(obj);
}

有两个错误:

  1. result 仅包含结果第一页上的值,但是通过所做的选择,您将获得 22 页的结果,我不明白如何获得我感兴趣的所有值并将它们链接起来在变量 result.
  2. 假设已经解决了第 (1) 点的问题,现在我应该详细说明获得的结果并创建一个像这样的对象:

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

这是elaborateResult(res)函数应该做的(当然,函数不完整,我必须完成它但我在第一行得到错误),但我得到错误:

<块引用>

ReferenceError: 文档未定义

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

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. 我不知道如何选择年份或如何选择变体或如何更改页面.

我觉得有点愚蠢,但我无法得到我想要的……我被困住了.非常欢迎帮助!

推荐答案

您的脚本有几个问题会阻止成功抓取.

要检查一个复选框,你不需要再次设置它的值(它已经在 HTML 中设置了!),你将它的 checked 属性设置为 true:

document.getElementById('crID%3a250').setAttribute("checked", true); // France

提交表单的按钮是一个超链接<a>,它没有submit方法,应该被点击(代码中甚至有onClick功能)

 document.getElementById('ctl00_main_filters_anchorApplyBottom').click(); // submit the form

**搜索请求**是通过 ajax 发送的,需要时间才能完成,因此您的脚本应该至少等待一秒钟才能尝试获取数据.我将在下面的完整工作代码中展示如何等待.

下一步,你可能只得到表格数据,不需要浏览所有的HTML:

var result = await page.evaluate(function() {
    return document.querySelectorAll('.DataContainer table')[0].outerHTML; 
});

以下是您脚本的精简版,已修正问题:

var phantom = require('phantom');

var url = 'http://data.un.org/Data.aspx?q=population&d=PopDiv&f=variableID%3A12';

// A promise to wait for n of milliseconds
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

(async function(req, res) {
    const instance = await phantom.create();
    const page = await instance.createPage();

    await page.on('onResourceRequested', function(requestData) {
        console.info('Requesting', requestData.url);
    });
    await page.on('onConsoleMessage', function(msg) {
        console.info(msg);
    });

    const status = await page.open(url);
    await console.log('STATUS:', status);

    // submit
    await page.evaluate(function() {
        document.getElementById('crID%3a250').setAttribute("checked", true); // France
        document.getElementById('timeID%3a79').setAttribute("checked", true); // 2015
        document.getElementById('varID%3a2').setAttribute("checked", true); // Medium
        document.getElementById('ctl00_main_filters_anchorApplyBottom').click(); // click submit button
    });

    console.log('Waiting 1.5 seconds..');    
    await timeout(1500);

    // Get only the table contents
    var result = await page.evaluate(function() {
        return document.querySelectorAll('.DataContainer table')[0].outerHTML; 
    });
    await console.log('RESULT:', result);

    await instance.exit();
})();
<小时>

最后但并非最不重要的观察是,您可以简单地尝试重播表单发出的 ajax 请求并找出 搜索请求的 URL 在另一个选项卡中打开时效果很好:

搜索结果为 HTML

您甚至不需要无头浏览器来获取它,只需 cUrl/requests 和 process.这种情况经常发生在网站上,因此在抓取之前检查浏览器开发工具中的网络选项卡很有用.

更新

如果结果太多以至于分散在几个页面上,那么在请求中还要使用一个参数:Page:

<块引用>

data.un.org/Handlers/DataHandler.ashx?Service=page&Page=3&DataFilter=variableID:12&DataMartId=PopDiv&UserQuery=population&c=2,4,6,7&s=_crEngNameOrderBy:asc,_timeEngNameOrderBy:desc,_varEngNameOrderBy:asc&RequestId=461

本文地址:https://www.itbaoku.cn/post/1740103.html