Webページのデータは動的に表示され、htmlのすべての変更をチェックしてデータを抽出することは非常に困難な作業であり、非常に信頼性の低いXPathを使用する必要があるようです。したがって、XHR
パケットからデータを抽出できるようにしたいと思います。
XHR
パケットから情報を抽出し、サーバーに送信する「XHR」パケットを生成できるようにしたいと考えています。 casperjsを使用してhtml要素を自動的にトリガーすることで情報の送信を簡単に処理できるため、情報の抽出部分は私にとってより重要です。
私が言っていることのスクリーンショットを添付しています。
[応答]タブのテキストは、後で処理する必要のあるデータです。 (このXHR応答はサーバーから受信されました。)
_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:待機していたリソースを取得する方法 ):
_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
呼び出しで収集する必要があります。
sinon.jsのXHRフェイカー を確認するか、次のXMLHttpRequest
の完全なプロキシを使用することをお勧めします(私は どのようにXMLHttpRequestラッパー/プロキシ? ):
_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呼び出しを最初からキャプチャする場合は、これを最初のイベントハンドラーの1つに追加する必要があります
_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
_コールバックを使用してデータを受信します。プロキシは、受信したデータをページコンテキストの_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();
_
私はパーティーに遅れるかもしれませんが、答えは将来この問題に陥る私のような誰かを助けるかもしれません。
PhantomJSから始めて、CasperJSに移行しましたが、最終的にSlimerJSに落ち着きました。 SlimerはPhantomに基づいており、Casperと互換性があり、「response.body」部分で同じonResponseReceivedメソッドを使用して応答本文を送り返すことができます。
参照: https://docs.slimerjs.org/current/api/webpage.html#webpage-onresourcereceived
@ Artjomの回答 最近のChromeおよびCasperJSバージョンでは、私には機能しません。
@ Artjomの回答 と XMLHttpRequestの置き換え方法に関するgilly3の回答 に基づいて、さまざまなブラウザーのほとんど/すべてのバージョンで機能する新しいソリューションを作成しました。私のために働きます。
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)
});