同步 CasperJS 操作期间的异步调用

Asynchronous call during a synchronous CasperJS operation

在遇到麻烦后(第一个计时器 nodejs 和 casperjs/phantomjs)它开始工作了。我用 curl(php).

完成了这项工作

这是我试图完成的:

  1. 登录
  2. 获取所有单位
  3. 解析他们的详细信息
  4. (我的问题)ajax 调用
  5. 提供了 2 个单元的详细信息
casper.start(url, function() {
      this.evaluate(function() {
          document.querySelector("input[name='username']").value = "username";
          document.querySelector("input[name='password']").value = "passwrd";
          document.querySelector("#login").click();
     });
     console.log("Logged in..");
});

var processPage = function() {
    console.log("Get all units..");
    var units = this.evaluate(getUnits);
    allUnits.push(units);

    if (!this.evaluate(isLastPage)) {
        this.thenClick('.paging li:last-child a').then(function() {
            currentPage++;
            console.log("Stepping to page.. " + currentPage);
            this.waitFor(function() {
                return currentPage === this.evaluate(getSelectedPage);
            }, processPage, terminate);
        });
    } else{
        require('utils').dump(allUnits);
        casper.then(function() {
             this.capture('test.png');
        });
        console.log("Total unit count: " + allUnits.length);
    }
};



 casper.waitForSelector('.units', processPage, terminate);
 casper.run();

在下面的函数中,我解析了行,我也想添加 ajax 获取的 2 个详细信息,但我不知道该怎么做。 (异步)

function getUnits() {
    var rows = document.querySelectorAll('.units');
    var units = [];

    for (var i = 0, row; row = rows[i]; i++) {
        var aID = row.querySelector('a').getAttribute('href').split('/');
        unit['id'] = aID[2];
        //add other details for the unit

        **//Do a async call to the 2 external links with the ID and add the details to the unit**

        units.push(unit);
    } 

    return units;

};

需要注意的重要一点是,最后我想 运行 单元上的另一个功能,但在 运行 之前必须已经获取所有功能...

编辑

登录后页面显示 table 我得到了 table 就像这样

我试图用 casper 正常获取最后 2 个字段,但有时它得到了值,有时却没有(请求有时太慢)

我想知道的是如何在不等待每一行(单位)直到它获得值的情况下获得这些字段。 (所以每个单元都应该为他们自己获取值并将它们填充到他们的对象中。所以可能需要回调?

或者我可以自己完成请求我只需要 ID 和 cookie 来执行 post(link 将 ID 和 Cookie 作为参数)并获取详细信息并填写它在但我不知道该怎么做,或者第一个解决方案是否效果更好,或者即使这是可能的...

最重要的是,在所有单元都有其详细信息之后,它应该继续应用程序的逻辑...

由于 PhantomJS(和 CasperJS)有两个上下文,很容易脱离执行流程。

我看到有两种方法可以解决您的问题。

1。自己发送请求

需要在页面上下文内部(evaluate()内部)触发请求,让外部上下文等待结果。我假设你可以在页面上下文中有一个成功的回调。

您必须将外部请求的结果放在全局某个地方,以便外部上下文可以访问它。例如,像这样修改您的 getUnits() 函数:

function getUnits() {
    var rows = document.querySelectorAll('.units');
    var units = [];
    window.__externalRequestResults = [[], []];

    for (var i = 0, row; row = rows[i]; i++) {
        var aID = row.querySelector('a').getAttribute('href').split('/');
        unit['id'] = aID[2];
        //add other details for the unit

        //Do a async call to the 2 external links with the ID and add the details to the unit
        (function(i){
            var xhr = new XMLHttpRequest();
            xhr.open("GET", someURLwithParameters, true);
            xhr.onreadystatechange = function(){
                if (xhr.readyState === 4) { // DONE
                    __externalRequestResults[0][i] = xhr.responseText;
                }
            };

            xhr = new XMLHttpRequest();
            xhr.open("GET", someOtherURLwithParameters, true);
            xhr.onreadystatechange = function(){
                if (xhr.readyState === 4) { // DONE
                    __externalRequestResults[1][i] = xhr.responseText;
                }
            };
            xhr.send();
        })(i);

        units.push(unit);
    } 

    return units;
};

现在您可以检索即时结果,然后等待其他结果:

var processPage = function() {
    console.log("Get all units..");
    var units = this.evaluate(getUnits);
    var numberOfRows = this.getElementsInfo("table tr").length; // TODO: fix selector
    var externalUnits;
    this.waitFor(function test(){
        externalUnits = this.getGlobal("__externalRequestResults");
        for(var i = 0; i < numberOfRows; i++) {
            if (externalUnits[0][i] == null || externalUnits[1][i] == null) {
                return false
            }
        }
        return true;
    }, function _then(){
        allUnits.push(units);
        allUnits.push(externalUnits); // TODO: maybe a little differently

        if (!this.evaluate(isLastPage)) {
            //... as before
        } else{
            //... as before
        }
    }, terminate);
};

当两个附加数据列表的数量与 table 的行数相同且所有行都已填充时,触发等待期结束。这会创建一个稀疏数组,并且 Array#push() 无法使用,因为不同的 Ajax 请求的顺序可能与发送它们的顺序不同。

2。让浏览器处理请求

在第二种情况下,您让 CasperJS 等待所有数据进入。挑战在于编写执行此操作的检查函数。

var processPage = function() {
    console.log("Get all units..");
    var numberOfRows = this.getElementsInfo("table tr").length; // TODO: fix selector
    this.waitFor(function test(){
        var data1, data2;
        for(var i = 1; i <= numberOfRows; i++) {
            data1 = this.fetchText("table tr:nth-child("+i+") td:nth-child(4)") || "";
            data2 = this.fetchText("table tr:nth-child("+i+") td:nth-child(5)") || "";
            if (data1.trim() === "" || data2.trim() === "") {
                return false
            }
        }
        return true;
    }, function _then(){
        var units = this.evaluate(getUnits);
        allUnits.push(units);

        if (!this.evaluate(isLastPage)) {
            //... as before
        } else{
            //... as before
        }
    }, terminate);
};

现在,您甚至不需要在 getUnits() 中发出 Ajax 请求,只需收集所有静态信息即可。

不要忘记将失败的等待超时设置得足够大,以便所有 Ajax 请求都能及时完成。例如,比所有 ajax 请求加载的正常时间大 3 或 4 倍。您可以为此使用全局 casper.options.waitTimeout