【问题标题】:How can I catch and process the data from the XHR responses using casperjs?如何使用 casperjs 捕获和处理来自 XHR 响应的数据?
【发布时间】:2014-08-24 16:13:15
【问题描述】:

网页上的数据是动态显示的,似乎检查html中的每一个变化并提取数据是一项非常艰巨的任务,还需要我使用非常不可靠的XPaths。所以我希望能够从XHR 数据包中提取数据。

我希望能够从XHR 数据包中提取信息,并生成“XHR”数据包发送到服务器。 提取信息部分对我来说更重要,因为可以通过使用 casperjs 自动触发 html 元素来轻松处理信息的发送。

我附上我的意思的截图。

响应选项卡中的文本是我之后需要处理的数据。 (此 XHR 响应已从服务器收到。)

【问题讨论】:

  • @ArtjomB。现在我只是从 html 元素中提取信息并部署脚本。不过,获取响应的价值会更优雅。几天后我会考虑使用它。真的很抱歉,我还不能合并这个。

标签: ajax google-chrome web-scraping phantomjs casperjs


【解决方案1】:

这并不容易,因为resource.received 事件处理程序只提供像urlheadersstatus 这样的元数据,而不是实际数据。底层 phantomjs 事件处理程序的行为方式相同。


无状态 AJAX 请求

如果ajax调用是无状态的,你可以重复请求

casper.on("resource.received", function(resource){
    // somehow identify this request, here: if it contains ".json"
    // it also also only does something when the stage is "end" otherwise this would be executed two times
    if (resource.url.indexOf(".json") != -1 && resource.stage == "end") {
        var data = casper.evaluate(function(url){
            // synchronous GET request
            return __utils__.sendAJAX(url, "GET");
        }, resource.url);
        // do something with data, you might need to JSON.parse(data)
    }
});
casper.start(url); // your script

您可能希望将事件侦听器添加到resource.requested。这样你就不需要为调用完成。

您也可以像这样在控制流内部执行此操作(来源:A: CasperJS waitForResource: how to get the resource i've waited for):

casper.start(url);

var res, resData;
casper.waitForResource(function check(resource){
    res = resource;
    return resource.url.indexOf(".json") != -1;
}, function then(){
    resData = casper.evaluate(function(url){
        // synchronous GET request
        return __utils__.sendAJAX(url, "GET");
    }, res.url);
    // do something with the data here or in a later step
});

casper.run();

有状态的 AJAX 请求

如果它不是无状态的,则需要替换 XMLHttpRequest 的实现。您将需要注入您自己的 onreadystatechange 处理程序实现,在页面 window 对象中收集信息,然后在另一个 evaluate 调用中收集它。

您可能想查看XHR faker in sinon.js 或为XMLHttpRequest 使用以下完整代理(我根据How can I create a XMLHttpRequest wrapper/proxy? 中的方法3 对其进行建模):

function replaceXHR(){
    (function(window, debug){
        function args(a){
            var s = "";
            for(var i = 0; i < a.length; i++) {
                s += "\t\n[" + i + "] => " + a[i];
            }
            return s;
        }
        var _XMLHttpRequest = window.XMLHttpRequest;

        window.XMLHttpRequest = function() {
            this.xhr = new _XMLHttpRequest();
        }

        // proxy ALL methods/properties
        var methods = [ 
            "open", 
            "abort", 
            "setRequestHeader", 
            "send", 
            "addEventListener", 
            "removeEventListener", 
            "getResponseHeader", 
            "getAllResponseHeaders", 
            "dispatchEvent", 
            "overrideMimeType"
        ];
        methods.forEach(function(method){
            window.XMLHttpRequest.prototype[method] = function() {
                if (debug) console.log("ARGUMENTS", method, args(arguments));
                if (method == "open") {
                    this._url = arguments[1];
                }
                return this.xhr[method].apply(this.xhr, arguments);
            }
        });

        // proxy change event handler
        Object.defineProperty(window.XMLHttpRequest.prototype, "onreadystatechange", {
            get: function(){
                // this will probably never called
                return this.xhr.onreadystatechange;
            },
            set: function(onreadystatechange){
                var that = this.xhr;
                var realThis = this;
                that.onreadystatechange = function(){
                    // request is fully loaded
                    if (that.readyState == 4) {
                        if (debug) console.log("RESPONSE RECEIVED:", typeof that.responseText == "string" ? that.responseText.length : "none");
                        // there is a response and filter execution based on url
                        if (that.responseText && realThis._url.indexOf("whatever") != -1) {
                            window.myAwesomeResponse = that.responseText;
                        }
                    }
                    onreadystatechange.call(that);
                };
            }
        });

        var otherscalars = [
            "onabort",
            "onerror",
            "onload",
            "onloadstart",
            "onloadend",
            "onprogress",
            "readyState",
            "responseText",
            "responseType",
            "responseXML",
            "status",
            "statusText",
            "upload",
            "withCredentials",
            "DONE",
            "UNSENT",
            "HEADERS_RECEIVED",
            "LOADING",
            "OPENED"
        ];
        otherscalars.forEach(function(scalar){
            Object.defineProperty(window.XMLHttpRequest.prototype, scalar, {
                get: function(){
                    return this.xhr[scalar];
                },
                set: function(obj){
                    this.xhr[scalar] = obj;
                }
            });
        });
    })(window, false);
}

如果您想从一开始就捕获 AJAX 调用,则需要将其添加到第一个事件处理程序中

casper.on("page.initialized", function(resource){
    this.evaluate(replaceXHR);
});

evaluate(replaceXHR),当您需要时。

控制流如下所示:

function replaceXHR(){ /* from above*/ }

casper.start(yourUrl, function(){
    this.evaluate(replaceXHR);
});

function getAwesomeResponse(){
    return this.evaluate(function(){
        return window.myAwesomeResponse;
    });
}

// stops waiting if window.myAwesomeResponse is something that evaluates to true
casper.waitFor(getAwesomeResponse, function then(){
    var data = JSON.parse(getAwesomeResponse());
    // Do something with data
});

casper.run();

如上所述,我为 XMLHttpRequest 创建了一个代理,以便每次在页面上使用它时,我都可以用它做一些事情。您抓取的页面使用xhr.onreadystatechange 回调来接收数据。代理是通过定义一个特定的 setter 函数来完成的,该函数将接收到的数据写入页面上下文中的window.myAwesomeResponse。您唯一需要做的就是检索此文本。


JSONP 请求

如果您知道前缀(使用加载的 JSON 调用的函数,例如 insert({"data":["Some", "JSON", "here"],"id":"asdasda")),则为 JSONP 编写代理会更容易。您可以在页面上下文中覆盖insert

  1. 页面加载后

    casper.start(url).then(function(){
        this.evaluate(function(){
            var oldInsert = insert;
            insert = function(json){
                window.myAwesomeResponse = json;
                oldInsert.apply(window, arguments);
            };
        });
    }).waitFor(getAwesomeResponse, function then(){
        var data = JSON.parse(getAwesomeResponse());
        // Do something with data
    }).run();
    
  2. 或在收到请求之前(如果函数在请求被调用之前注册)

    casper.on("resource.requested", function(resource){
        // filter on the correct call
        if (resource.url.indexOf(".jsonp") != -1) {
            this.evaluate(function(){
                var oldInsert = insert;
                insert = function(json){
                    window.myAwesomeResponse = json;
                    oldInsert.apply(window, arguments);
                };
            });
        }
    }).run();
    
    casper.start(url).waitFor(getAwesomeResponse, function then(){
        var data = JSON.parse(getAwesomeResponse());
        // Do something with data
    }).run();
    

【讨论】:

  • 如果我想从使用ajax加载它的网站获取图像src,我尝试了“scrollTo”和“viewport”,但我最终失败了,我该怎么办?
  • @qianjiahao 您应该提出一个新问题并正确描述您的问题。我不知道你在说什么。
  • 在 2019 年末对我不起作用,我根据这个答案撰写了另一个答案。 stackoverflow.com/a/58168312/1265306
【解决方案2】:

我可能迟到了,但答案可能会帮助像我这样以后会遇到这个问题的人。

我必须从 PhantomJS 开始,然后转到 CasperJS,但最终还是选择了 SlimerJS。 Slimer 基于 Phantom,与 Casper 兼容,可以在“response.body”部分使用相同的 onResponseReceived 方法向您发送响应正文。

参考:https://docs.slimerjs.org/current/api/webpage.html#webpage-onresourcereceived

【讨论】:

  • 但不幸的是,SlimerJS 依赖于安装的 Firefox。
  • @nchaud - 在我看来,这是一个加号而不是减号。 FF是最强浏览器,对javascript/DOM的支持最好
  • @nchaud FF 或任何其他 XUL Runner,我想。
  • 但它需要安装,如果您正在开发需要部署在客户端站点上的东西,这就是问题所在。 (可以理解,这主要用于内部测试,在这种情况下,很容易在 CI 服务器上安装 FF/XULRunner)。
  • @nchaud 我实际上无法想象可以安装 SlimerJS 但不能安装 FF 或 XULRunner 的情况。我认为您需要在某处安装 SlimerJS 二进制文件的版本,为什么您不能在那里也安装 FF?
【解决方案3】:

@Artjom's answer 在最近的 Chrome 和 CasperJS 版本中对我不起作用。

基于@Artjom's answergilly3's answer on how to replace XMLHttpRequest,我编写了一个新的解决方案,它应该适用于不同浏览器的大多数/所有版本。对我有用。

SlimerJS 无法在较新版本的 FireFox 上运行,因此对我没有好处。

下面是添加监听器以加载 XHR(不依赖于 CasperJS)的通用代码:

var addXHRListener = function (XHROnStateChange) {

    var XHROnLoad = function () {
        if (this.readyState == 4) {
            XHROnStateChange(this)
        }
    }

    var open_original = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function (method, url, async, unk1, unk2) {
        this.requestUrl = url
        open_original.apply(this, arguments);
    };

    var xhrSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function () {

        var xhr = this;
        if (xhr.addEventListener) {
            xhr.removeEventListener("readystatechange", XHROnLoad);
            xhr.addEventListener("readystatechange", XHROnLoad, false);
        } else {
            function readyStateChange() {
                if (handler) {
                    if (handler.handleEvent) {
                        handler.handleEvent.apply(xhr, arguments);
                    } else {
                        handler.apply(xhr, arguments);
                    }
                }
                XHROnLoad.apply(xhr, arguments);
                setReadyStateChange();
            }

            function setReadyStateChange() {
                setTimeout(function () {
                    if (xhr.onreadystatechange != readyStateChange) {
                        handler = xhr.onreadystatechange;
                        xhr.onreadystatechange = readyStateChange;
                    }
                }, 1);
            }

            var handler;
            setReadyStateChange();
        }
        xhrSend.apply(xhr, arguments);
    };

}

这是在加载 XHR 时发出自定义事件的 CasperJS 代码:

casper.on("page.initialized", function (resource) {
    var emitXHRLoad = function (xhr) {
        window.callPhantom({eventName: 'xhr.load', eventData: xhr})
    }
    this.evaluate(addXHRListener, emitXHRLoad);
});

casper.on('remote.callback', function (data) {
    casper.emit(data.eventName, data.eventData)
});

以下是监听“xhr.load”事件并获取 XHR 响应正文的代码:

casper.on('xhr.load', function (xhr) {
    console.log('xhr load', xhr.requestUrl)
    console.log('xhr load', xhr.responseText)
});

【讨论】:

    【解决方案4】:

    此外,您还可以直接下载内容并在以后进行操作。 这是我用来检索 JSON 并将其保存在本地的脚本示例:

    var casper = require('casper').create({
        pageSettings: {
            webSecurityEnabled: false
        }
    });
    
    var url = 'https://twitter.com/users/username_available?username=whatever';
    
    casper.start('about:blank', function() {
       this.download(url, "hop.json");
    });
    
    casper.run(function() {
        this.echo('Done.').exit();
    });

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-26
      • 2018-09-11
      • 1970-01-01
      • 2014-06-29
      • 2023-03-25
      • 2016-12-12
      • 1970-01-01
      • 2021-03-26
      相关资源
      最近更新 更多