¿Cómo puedo capturar y procesar los datos de las respuestas XHR usando casperjs?

Los datos en la página web se muestran dinámicamente y parece que verificar cada cambio en el html y extraer los datos es una tarea muy desalentadora y también me exige utilizar XPaths poco confiables. Así que me gustaría poder extraer los datos de los paquetes XHR .

Espero poder extraer información de los paquetes XHR y generar paquetes ‘XHR’ para enviar al servidor. La parte de extracción de información es más importante para mí porque el envío de información se puede manejar fácilmente activando automáticamente elementos html usando casperjs.

Adjunto una captura de pantalla de lo que quiero decir. enter image description here

El texto en la pestaña de respuesta es la información que necesito procesar luego. (Esta respuesta XHR ha sido recibida del servidor).

Esto no es posible, ya que el controlador de eventos resource.received solo proporciona metadatos como url , headers o status , pero no los datos reales. El controlador de eventos phantomjs subyacente actúa de la misma manera.


Solicitud AJAX sin estado

Si la llamada ajax no tiene estado , puede repetir la solicitud

 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 

Es posible que desee agregar el detector de eventos a resource.requested . De esa forma no es necesario que la llamada se complete.

También puede hacer esto directamente dentro del flujo de control como este (fuente: A: CasperJS waitForResource: cómo obtener el recurso que he esperado ):

 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(); 

Solicitud de AJAX con estado

Si no es apátrida , deberá reemplazar la implementación de XMLHttpRequest. Necesitará inyectar su propia implementación del controlador onreadystatechange , recostackr la información en el objeto de la window la página y luego recostackrla en otra llamada de evaluate .

Es posible que desee ver el faker XHR en sinon.js o utilizar el siguiente proxy completo para XMLHttpRequest (lo modelé después del método 3 de ¿Cómo puedo crear un envoltorio / proxy 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); } 

Si desea capturar las llamadas AJAX desde el principio, debe agregar esto a uno de los primeros controladores de eventos.

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

o evaluate(replaceXHR) cuando lo necesite.

El flujo de control se vería así:

 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(); 

Como se describió anteriormente, creo un proxy para XMLHttpRequest para que cada vez que se use en la página, pueda hacer algo con él. La página que xhr.onreadystatechange utiliza la callback xhr.onreadystatechange para recibir datos. El proxying se realiza definiendo una función setter específica que escribe los datos recibidos en window.myAwesomeResponse en el contexto de la página. Lo único que debes hacer es recuperar este texto.


Solicitud JSONP

Escribir un proxy para JSONP es aún más fácil, si conoce el prefijo (la función para llamar con el JSON cargado, por ejemplo, insert({"data":["Some", "JSON", "here"],"id":"asdasda") ). Puede sobrescribir la insert en el contexto de la página

  1. después de que se carga la página

     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. o antes de que se reciba la solicitud (si la función se registra justo antes de invocar la solicitud)

     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(); 

Puede que llegue tarde a la fiesta, pero la respuesta puede ayudar a alguien como yo, que podría caer en este problema más adelante en el futuro.

Tuve que comenzar con PhantomJS, luego me mudé a CasperJS pero finalmente me conformé con SlimerJS. Slimer se basa en Phantom, es compatible con Casper y puede devolverte el cuerpo de respuesta utilizando el mismo método onResponseReceived, en la parte “response.body”.

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

Además, también puedes descargar directamente el contenido y manipularlo más tarde. Aquí está el ejemplo del script que estoy usando para recuperar un JSON y guardarlo localmente:

 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(); });