我在梦魇之间移动页面和刮擦

时间:2016-08-04 15:52:02

标签: javascript html web-scraping promise nightmare

有一个网站包含一个包含25个条目列表的页面,其中每个条目都是指向包含我需要的一些信息的页面的链接。我想进入列表页面,然后: 1)点击第一个条目的链接 2)检索所有的html 3)点击返回列表页面(有一个按钮) 4)重复每个其他列表

我也希望尽可能高效地做到这一点,我已经被告知这意味着利用承诺。这是我的代码草图,它不起作用:

var Nightmare = require('nightmare');
var nightmare = Nightmare({ openDevTools: true, show: true })
var Xray = require('x-ray');
var x = Xray();
var resultArr = [];

nightmare
.goto(hidTestURL)
.wait(2500)
.click('input[name="propertySearchOptions:advanced"]') //start navigating to listing page
.wait(2500)
.type('input[name="propertySearchOptions:streetName"]', 'Main')
.wait(2500)
.select('select[name="propertySearchOptions:recordsPerPage"]', '25')
.wait(2500)
.click('input[name="propertySearchOptions:search"]') //at listing page
.wait(2500)
.then(function(){
  nightmare
  .click('a[href^="Property.aspx?prop_id=228645"]') //first entry
  .evaluate(function(){ //retrieve info
    var resultArr = [];
    resultArr.push(document.querySelector('html').innerHTML);
  })
})

nightmare
.click('a[id="propertyHeading_searchResults"]') //return to listing page
.evaluate(function(){
  return resultArr.push(document.querySelector('html').innerHTML); retrieve listing page info to show that it returned.
})
.then(function (resultArr) {
  console.log('resultArr', resultArr);
  x(resultArr[1], 'body@html') //output listing page html
    .write('results.json');
})

这可以到列表页面,然后不再继续。我也尝试了相同的代码,但return nightmare除了第一个代码之外每次使用nightmare。我看过一些使用return的示例,但是当我这样做时,代码引发了错误。

我也试过不包括第三个nightmare(空格后的那个),而是试图通过直接转到.click()来继续旧的梦魇实例,但这也引发了一个错误

我显然需要一些关于噩梦的语法和语义的帮助,但除了API列表之外,没有太多的在线文档。有谁知道我怎么能做这个工作?

1 个答案:

答案 0 :(得分:6)

首先,调用梦魇就像你拥有它 - 分成两个链 - 是可能不会做你想要的。 (This comment thread是一个很好的 - 尽管很长 - 引物。)内存服务,来自第二个链的动作将在第一个链后立即排队,导致(可能)不良行为。你说你的写法略有不同 - 我很想看到它,听起来它可能有点接近。

其次,您试图在resultArr中举起.evaluate(),这是不可能的。传递给.evaluate()的函数在Electron内部进行了字符串化和重构 - 这意味着您将丢失函数周围的环境上下文。如果你很好奇的话,This example中的nightmare-examples会更加深入。

第三,也许这是一个错字或我的误解意图:你的href选择器使用starts-with(^=)运算符,这是故意的吗?这应该是一个结尾 - ($=)?

第四,looping over asynchronous operations is tricky。我的印象可能也是一个绊脚石?

考虑到所有这些,让我们来看看修改原始脚本。不可否认,因为我无法访问您的测试网址,所以这有点嘻哈:

var Nightmare = require('nightmare');
var nightmare = Nightmare({ openDevTools: true, show: true })
var Xray = require('x-ray');
var x = Xray();

nightmare
.goto(hidTestURL)
.wait(2500)
.click('input[name="propertySearchOptions:advanced"]') //start navigating to listing page
.wait(2500)
.type('input[name="propertySearchOptions:streetName"]', 'Main')
.wait(2500)
.select('select[name="propertySearchOptions:recordsPerPage"]', '25')
.wait(2500)
.click('input[name="propertySearchOptions:search"]') //at listing page
.wait(2500)
.evaluate(function(){
  //using `Array.from` as the DOMList is not an array, but an array-like, sort of like `arguments`
  //planning on using `Array.map()` in a moment
  return Array.from(
    //give me all of the elements where the href contains 'Property.aspx'
    document.querySelectorAll('a[href*="Property.aspx"]'))
    //pull the target hrefs for those anchors
    .map(a => a.href);
})
.then(function(hrefs){
  //here, there are two options:
  //  1. you could navigate to each link, get the information you need, then navigate back, or
  //  2. you could navigate straight to each link and get the information you need.
  //I'm going to go with #1 as that's how it was in your original script.

  //here, we're going to use the vanilla JS way of executing a series of promises in a sequence.
  //for every href in hrefs,
  return hrefs.reduce(function(accumulator, href){
    //return the accumulated promise results, followed by...
    return accumulator.then(function(results){
      return nightmare
        //click on the href
        .click('a[href="'+href+'"]')
        //get the html
        .evaluate(function(){
          return document.querySelector('html').innerHTML;
        })
        //add the result to the results
        .then(function(html){
          results.push(html);
          return results;
        })
        .then(function(results){
          //click on the search result link to go back to the search result page
          return nightmare
            .click('a[id="propertyHeading_searchResults"]')
            .then(function() {
              //make sure the results are returned
              return results;
            });
        })
    });
  }, Promise.resolve([])) //kick off the reduce with a promise that resolves an empty array
})
.then(function (resultArr) {
  //if I haven't made a mistake above with the `Array.reduce`, `resultArr` should now contain all of your links' results
  console.log('resultArr', resultArr);
  x(resultArr[1], 'body@html') //output listing page html
    .write('results.json');
});

希望这足以让你开始。