网页上的数据是动态显示的,似乎检查html中的每个更改并提取数据是一项非常艰巨的任务,还需要我使用非常不可靠的XPath。所以我希望能够从XHR
数据包中提取数据。
我希望能够从XHR
数据包中提取信息以及生成' XHR'要发送到服务器的数据包。
提取信息部分对我来说更重要,因为可以通过使用casperjs自动触发html元素来轻松处理信息的发送。
我附上了我的意思截图。
响应选项卡中的文本是我之后需要处理的数据。 (已从服务器收到此XHR响应。)
答案 0 :(得分:21)
这不容易实现,因为resource.received
事件处理程序仅提供url
,headers
或status
等元数据,但不提供实际数据。底层的phantomjs事件处理程序的行为方式相同。
如果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();
如果不是无状态,则需要替换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
。您唯一需要做的就是检索此文本。
如果您知道前缀(使用加载的JSON调用的函数,例如insert({"data":["Some", "JSON", "here"],"id":"asdasda")
),则为JSONP编写代理会更容易。您可以在页面上下文中覆盖insert
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();
或收到请求之前(如果函数是在调用请求之前注册的)
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();
答案 1 :(得分:7)
我可能会迟到这个派对,但答案可能会帮助像我这样的人,以后会发生这个问题。
我不得不从PhantomJS开始,然后转移到CasperJS但最终以SlimerJS结算。 Slimer基于Phantom,与Casper兼容,并且可以使用相同的onResponseReceived方法将响应主体发送回“response.body”部分。
参考:https://docs.slimerjs.org/current/api/webpage.html#webpage-onresourcereceived
答案 2 :(得分:1)
@Artjom's answer在我最近的Chrome和CasperJS版本中不起作用。
基于@Artjom's answer和gilly3'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);
};
}
这是CasperJS代码,用于在XHR加载时发出自定义事件:
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)
});
答案 3 :(得分:-1)
此外,您还可以直接下载内容并在以后进行操作。 以下是我用来检索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();
});