97

我正在使用 CasperJS 通过网站自动执行一系列点击、完成表单、解析数据等。

Casper 似乎以语句的形式组织成一个预设步骤列表then(参见他们的示例:http: //casperjs.org/quickstart.html),但不清楚是什么触发了下一个语句实际运行。

例如,是否then等待所有待处理的请求完成?是否injectJS算作待处理的请求?如果我有一个then嵌套的语句 - 链接到open语句的末尾会发生什么?

casper.thenOpen('http://example.com/list', function(){
    casper.page.injectJs('/libs/jquery.js');
    casper.evaluate(function(){
        var id = jQuery("span:contains('"+itemName+"')").closest("tr").find("input:first").val();
        casper.open("http://example.com/show/"+id); //what if 'then' was added here?
    });
});

casper.then(function(){
    //parse the 'show' page
});

我正在寻找有关 CasperJS 中流程如何工作的技术解释。我的具体问题是我的最后一个then语句(上面)在我的casper.open语句之前运行,我不知道为什么。

4

3 回答 3

93

then()基本上在堆栈中添加了一个新的导航步骤。一个步骤是一个 javascript 函数,它可以做两种不同的事情:

  1. 等待上一步 - 如果有的话 - 正在执行
  2. 等待请求的 url 和相关页面加载

我们来看一个简单的导航场景:

var casper = require('casper').create();

casper.start();

casper.then(function step1() {
    this.echo('this is step one');
});

casper.then(function step2() {
    this.echo('this is step two');
});

casper.thenOpen('http://google.com/', function step3() {
    this.echo('this is step 3 (google.com is loaded)');
});

您可以打印出堆栈中所有创建的步骤,如下所示:

require('utils').dump(casper.steps.map(function(step) {
    return step.toString();
}));

这给出了:

$ casperjs test-steps.js
[
    "function step1() { this.echo('this is step one'); }",
    "function step2() { this.echo('this is step two'); }",
    "function _step() { this.open(location, settings); }",
    "function step3() { this.echo('this is step 3 (google.com is loaded)'); }"
]

注意_step()CasperJS 自动添加的为我们加载 url 的功能;当 url 被加载时,堆栈中可用的下一步——即step3()——被调用。

定义导航步骤后,run()按顺序一一执行:

casper.run();

脚注:回调/监听器的东西是Promise 模式的实现。

于 2012-08-14T17:51:25.980 回答
33

then()仅注册一系列步骤。

run()及其一系列运行器函数、回调和侦听器,都是执行每个步骤的实际工作。

每当一个步骤完成时,CasperJS 将检查 3 个标志:pendingWaitloadInProgressnavigationRequested. 如果这些标志中的任何一个为真,则什么也不做,闲置直到稍后时间(setInterval样式)。如果这些标志都不是真的,那么下一步将被执行。

从 CasperJS 1.0.0-RC4 开始,存在一个缺陷,在某些基于时间的情况下,“尝试执行下一步”方法将在 CasperJS 有时间引发loadInProgressornavigationRequested标志之一之前被触发。解决方案是在离开预期会引发这些标志的任何步骤之前提高其中一个标志(例如:在要求 a 之前或之后提高标志casper.click()),可能像这样:

(注意:这只是说明性的,更像是伪代码而不是正确的 CasperJS 形式......)

step_one = function(){
    casper.click(/* something */);
    do_whatever_you_want()
    casper.click(/* something else */); // Click something else, why not?
    more_magic_that_you_like()
    here_be_dragons()
    // Raise a flag before exiting this "step"
    profit()
}

为了将该解决方案封装到一行代码中,我blockStep()在这个 github拉取请求中介绍了扩展click()clickLabel()作为帮助保证我们在使用then(). 查看请求以获取更多信息、使用模式和最小测试文件。

于 2012-11-01T21:43:30.510 回答
0

根据CasperJS 文档

then()

签名: then(Function then)

通过提供一个简单的函数,此方法是向堆栈添加新导航步骤的标准方法:

casper.start('http://google.fr/');

casper.then(function() {
  this.echo('I\'m in your google.');
});

casper.then(function() {
  this.echo('Now, let me write something');
});

casper.then(function() {
  this.echo('Oh well.');
});

casper.run();

您可以根据需要添加任意数量的步骤。请注意,当前Casper实例this会在步骤函数中自动为您绑定关键字。

要运行您定义的所有步骤,请调用该run()方法,然后瞧。

注意:您必须start()使用 casper 实例才能使用该then()方法。

警告:添加到的阶跃函数then()在两种不同的情况下进行处理:

  1. 当上一个 step 函数已经执行时,
  2. 当先前的主 HTTP 请求已执行并加载页面时;

请注意,没有单一的页面加载定义;是在触发 DOMReady 事件的时候吗?是“所有请求都已完成”吗?是“正在执行所有应用程序逻辑”吗?还是“正在渲染的所有元素”?答案总是取决于上下文。因此,为什么鼓励您始终使用waitFor()家庭方法来明确控制您的实际期望。

一个常见的技巧是使用waitForSelector()

casper.start('http://my.website.com/');

casper.waitForSelector('#plop', function() {
  this.echo('I\'m sure #plop is available in the DOM');
});

casper.run();

在幕后,源代码Casper.prototype.then如下所示:

/**
 * Schedules the next step in the navigation process.
 *
 * @param  function  step  A function to be called as a step
 * @return Casper
 */
Casper.prototype.then = function then(step) {
    "use strict";
    this.checkStarted();
    if (!utils.isFunction(step)) {
        throw new CasperError("You can only define a step as a function");
    }
    // check if casper is running
    if (this.checker === null) {
        // append step to the end of the queue
        step.level = 0;
        this.steps.push(step);
    } else {
        // insert substep a level deeper
        try {
            step.level = this.steps[this.step - 1].level + 1;
        } catch (e) {
            step.level = 0;
        }
        var insertIndex = this.step;
        while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) {
            insertIndex++;
        }
        this.steps.splice(insertIndex, 0, step);
    }
    this.emit('step.added', step);
    return this;
};

解释:

换句话说,then()安排导航过程中的下一步。

then()被调用时,它被传递一个函数作为参数,该参数将作为一个步骤调用。

它检查一个实例是否已经启动,如果没有,它会显示以下错误:

CasperError: Casper is not started, can't execute `then()`.

接下来,它检查page对象是否为null.

如果条件为真,Casper 将创建一个新page对象。

之后,then()验证step参数以检查它是否不是函数。

如果参数不是函数,则会显示以下错误:

CasperError: You can only define a step as a function

然后,该函数检查 Casper 是否正在运行。

如果 Casper 未运行,then()则将该步骤附加到队列的末尾。

否则,如果 Casper 正在运行,它会插入一个比上一步更深的子步骤。

最后,该then()函数通过发出一个step.added事件结束,并返回 Casper 对象。

于 2018-07-06T21:33:16.223 回答