Screen Scraping con Node.js

Es posible que haya usado NodeJS como un servidor web, pero ¿sabía que también puede usarlo para el rastreo web? En este tutorial, revisaremos cómo eliminar las páginas web estáticas, y aquellas molestas con contenido dinámico, con la ayuda de NodeJS y algunos módulos útiles de NPM..



Un poco sobre raspado web

El raspado web siempre ha tenido una connotación negativa en el mundo del desarrollo web, y por una buena razón. En el desarrollo moderno, las API están presentes en la mayoría de los servicios populares y deben usarse para recuperar datos en lugar de raspar. El problema inherente al raspado es que se basa en la estructura visual de la página que se raspa. Siempre que ese HTML cambie, no importa cuán pequeño sea el cambio, puede romper completamente su código.

A pesar de estos defectos, es importante aprender un poco sobre el raspado web y algunas de las herramientas disponibles para ayudar con esta tarea. Cuando un sitio no revela una API o cualquier fuente de sindicación (RSS / Atom, etc.), la única opción que nos queda para obtener ese contenido ... es raspar.

Nota: Si no puede obtener la información que necesita a través de una API o un feed, es una buena señal de que el propietario no quiere que esa información sea accesible. Sin embargo, hay excepciones..


Por qué usar NodeJS?

Los raspadores se pueden escribir en cualquier idioma, realmente. La razón por la que disfruto usando Node es debido a su naturaleza asíncrona, lo que significa que mi código no está bloqueado en ningún momento del proceso. Estoy bastante familiarizado con JavaScript, así que eso es una ventaja adicional. Finalmente, hay algunos módulos nuevos que se han escrito para NodeJS que facilitan el rastreo de sitios web de una manera confiable (bueno, ¡tan confiable como puede ser el raspado!). Empecemos!


Raspado simple con YQL

Comencemos con el caso de uso simple: páginas web estáticas. Estas son sus páginas web estándar de run-of-the-mill. Para estos, Yahoo! Query Language (YQL) debe hacer el trabajo muy bien. Para aquellos que no están familiarizados con YQL, es una sintaxis similar a SQL que se puede usar para trabajar con diferentes API de una manera consistente.

YQL tiene algunas tablas geniales para ayudar a los desarrolladores a sacar HTML de una página. Los que quiero destacar son:

  • html
  • data.html.cssselect
  • htmlstring

Revisemos cada uno de ellos y revisemos cómo implementarlos en NodeJS.

html mesa

los html La tabla es la forma más básica de eliminar HTML de una URL. Una consulta regular usando esta tabla se ve así:

seleccione * desde html donde url = "http://finance.yahoo.com/q?s=yhoo" y xpath = "// div [@ id =" yfi_headlines "] / div [2] / ul / li / a "

Esta consulta consta de dos parámetros: la "url" y la "xpath". La url es autoexplicativa. El XPath consiste en una cadena XPath que indica a YQL qué sección del HTML debe devolverse. Prueba esta consulta aquí.

Los parámetros adicionales que puede utilizar incluyen navegador (booleano), juego de caracteres (cadena), y compat (cuerda). No he tenido que usar estos parámetros, pero consulte la documentación si tiene necesidades específicas.

No se siente cómodo con XPath?

Desafortunadamente, XPath no es una forma muy popular de atravesar la estructura de árbol HTML. Puede ser complicado leer y escribir para principiantes..

Veamos la siguiente tabla, que hace lo mismo pero le permite usar CSS en su lugar.

data.html.cssselect mesa

los data.html.cssselect La tabla es mi forma preferida de eliminar HTML de una página. Funciona de la misma manera que la html tabla pero le permite a CSS en lugar de XPath. En la práctica, esta tabla convierte el CSS a XPath bajo el capó y luego llama al html Mesa, por lo que es un poco más lento. La diferencia debe ser despreciable para las necesidades de raspado..

Una consulta regular utilizando esta tabla se ve así:

seleccione * de data.html.cssselect donde url = "www.yahoo.com" y css = "# news a"

Como puedes ver, es mucho más limpio. Te recomiendo que pruebes este método primero cuando intentas raspar HTML usando YQL. Prueba esta consulta aquí.

htmlstring mesa

los htmlstring La tabla es útil para los casos en los que está intentando raspar una gran parte del texto con formato de una página web.

El uso de esta tabla le permite recuperar todo el contenido HTML de esa página en una sola cadena, en lugar de JSON que se divide según la estructura DOM.

Por ejemplo, una respuesta JSON regular que raspa un etiqueta se ve así:

"results": "a": "href": "…", "target": "_blank", "content": "Cook Chief de Apple para subir a un nuevo escenario"

¿Ves cómo se definen los atributos como propiedades? En cambio, la respuesta de la htmlstring mesa se vería así:

"resultados": "resultado": "El jefe ejecutivo de Apple, Cook, para subir a un nuevo escenario

Entonces, ¿por qué usarías esto? Bueno, por mi experiencia, esto es de gran utilidad cuando intentas raspar una gran cantidad de texto con formato. Por ejemplo, considere el siguiente fragmento de código:

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Proin nec diam magna. Sed non lorem a nisi porttitor pharetra et non arcu.

Utilizando el htmlstring En la tabla, puede obtener este HTML como una cadena y usar expresiones regulares para eliminar las etiquetas HTML, lo que le deja solo el texto. Esta es una tarea más fácil que la iteración a través de JSON que se ha dividido en propiedades y objetos secundarios según la estructura DOM de la página..


Usando YQL con NodeJS

Ahora que sabemos un poco sobre algunas de las tablas disponibles para nosotros en YQL, implementemos un raspador web utilizando YQL y NodeJS. Afortunadamente, esto es muy simple, gracias a la nodo-yql módulo de Derek Gathright.

Podemos instalar el módulo utilizando npm:

npm instalar yql

El módulo es extremadamente simple, consiste en un solo método: el YQL.exec () método. Se define como lo siguiente:

exec de función (consulta de cadena [, devolución de llamada de función] [, parámetros de objeto] [, httpOptions de objeto])

Podemos usarlo exigiéndolo y llamando. YQL.exec (). Por ejemplo, digamos que queremos eliminar los titulares de todas las publicaciones en la página principal de Nettuts:

var YQL = require ("yql"); nuevo YQL.exec ('select * from data.html.cssselect donde url = "http://net.tutsplus.com/" y css = ". post_title a"', function (response) // response consiste en JSON que puedes analizar);

Lo bueno de YQL es su capacidad para probar sus consultas y determinar qué JSON está recuperando en tiempo real. Vaya a la consola para probar esta consulta, o haga clic aquí para ver el JSON en bruto.

los params y httpOptions Los objetos son opcionales. Los parámetros pueden contener propiedades tales como env (si está utilizando un entorno específico para las tablas) y formato (xml o json). Todas las propiedades pasaron a params están codificados en URI y se adjuntan a la cadena de consulta. los httpOptions objeto se pasa en el encabezado de la solicitud. Aquí, puede especificar si desea habilitar SSL, por ejemplo.

El archivo JavaScript, llamado yqlServer.js, contiene el código mínimo requerido para raspar usando YQL. Puede ejecutarlo emitiendo el siguiente comando en su terminal:

nodo yqlServer.js

Excepciones y otras herramientas notables.

YQL es mi opción preferida para eliminar contenido de páginas web estáticas, porque es fácil de leer y de usar. Sin embargo, YQL fallará si la página web en cuestión tiene un robots.txt Archivo que niega una respuesta a él. En este caso, puede ver algunas de las utilidades que se mencionan a continuación, o usar PhantomJS, que veremos en la siguiente sección..

Node.io es una utilidad útil de Node que está diseñada específicamente para el rastreo de datos. Puede crear trabajos que toman entrada, la procesan y devuelven algo de salida. Node.io está bien visto en Github, y tiene algunos ejemplos útiles para comenzar.

JSDOM es un proyecto muy popular que implementa el DOM W3C en JavaScript. Cuando se suministra HTML, puede construir un DOM con el que pueda interactuar. Consulte la documentación para ver cómo puede usar JSDOM y cualquier biblioteca JS (como jQuery) juntos para recopilar datos de páginas web.


Páginas de raspado con contenido dinámico

Hasta ahora, hemos analizado algunas herramientas que pueden ayudarnos a rastrear páginas web con contenido estático. Con YQL, es relativamente fácil. Desafortunadamente, a menudo se nos presentan páginas con contenido que se carga dinámicamente con JavaScript. En estos casos, la página suele estar vacía inicialmente, y luego el contenido se agrega después. ¿Cómo podemos tratar este problema??

Un ejemplo

Déjame dar un ejemplo de lo que quiero decir; He cargado un archivo HTML simple a mi propio sitio web, que agrega algunos contenidos, a través de JavaScript, dos segundos después de la document.ready () se llama funcion Puedes consultar la página aquí. Así es como se ve la fuente:

   Página de prueba con contenido añadido después de cargar la página   El contenido de esta página se adjunta al DOM una vez que se carga la página. 

Ahora, vamos a tratar de raspar el texto dentro de la

usando YQL.

var YQL = require ("yql"); nuevo YQL.exec ('select * from data.html.cssselect where url = "http://tilomitra.com/repository/screenscrape/ajax.html" y css = "# content"', function (response) // ¡Esto volverá indefinido! El raspado no tuvo éxito! Console.log (response.results););

Notarás que YQL vuelve. indefinido porque, cuando se carga la página, la

esta vacio. El contenido no ha sido añadido todavía. Puedes probar la consulta por ti mismo aquí.

Veamos cómo podemos solucionar este problema.!

Entrar en phantomjs

PhantomJS puede cargar páginas web e imitar un navegador basado en Webkit sin la GUI.

Mi método preferido para obtener información de estos sitios es usar PhantomJS. PhantomJS se describe a sí mismo como un "Webkit sin cabeza con una API de JavaScript. En términos simplistas, esto significa que PhantomJS puede cargar páginas web e imitar un navegador basado en Webkit sin la GUI. Como desarrollador, podemos recurrir a métodos específicos que PhantomJS proporciona. Ejecute el código en la página. Dado que se comporta como un navegador, los scripts en la página web se ejecutan como lo harían en un navegador normal..

Para obtener datos de nuestra página, vamos a utilizar PhantomJS-Node, un pequeño gran proyecto de código abierto que une PhantomJS con NodeJS. Bajo el capó, este módulo ejecuta PhantomJS como un proceso secundario..

Instalando PhantomJS

Antes de que pueda instalar el módulo NPM PhantomJS-Node, debe instalar PhantomJS. Sin embargo, instalar y construir PhantomJS puede ser un poco complicado..

Primero, diríjase a PhantomJS.org y descargue la versión adecuada para su sistema operativo. En mi caso, fue Mac OSX..

Después de descargarlo, descomprímelo en algún lugar como / Aplicaciones /. A continuación, desea agregarlo a su CAMINO:

sudo ln -s /Aplicaciones/phantomjs-1.5.0/bin/phantomjs / usr / local / bin /

Reemplazar 1.5.0 con su versión descargada de PhantomJS. Tenga en cuenta que no todos los sistemas tendrán / usr / local / bin /. Algunos sistemas tendrán: / usr / bin /, /compartimiento/, o usr / X11 / bin en lugar.

Para usuarios de Windows, consulte el breve tutorial aquí. Sabrá que está todo configurado cuando abre su Terminal y escribe Phantomjs, y no recibes ningun error.

Si te sientes incómodo editando tu CAMINO, tome nota de dónde descomprimió PhantomJS y le mostraré otra forma de configurarlo en la siguiente sección, aunque le recomiendo que edite su CAMINO.

Instalación de PhantomJS-Node

Configurar PhantomJS-Node es mucho más fácil. Siempre que tenga instalado NodeJS, puede instalarlo a través de npm:

npm instalar phantom

Si no editaste tu CAMINO en el paso anterior al instalar PhantomJS, puede ir al fantasma/ directorio derribado por npm y editar esta línea en phantom.js.

ps = child.spawn ('phantomjs', args.concat ([__ dirname + '/shim.js', puerto]));

Cambia el camino a:

ps = child.spawn ('/ path / to / phantomjs-1.5.0 / bin / phantomjs', args.concat ([__ dirname + '/shim.js', puerto]));

Una vez hecho esto, puedes probarlo ejecutando este código:

 var phantom = require ('phantom'); phantom.create (function (ph) return ph.createPage (function (page) return page.open ("http://www.google.com", function (status) console.log ("open google?" , estado); return page.evaluate ((function () return document.title;), function (result) console.log ('El título de la página es' + resultado); return ph.exit ();); );););

Ejecutar esto en la línea de comandos debería mostrar lo siguiente:

abrió google? el título de la página de éxito es Google

Si tienes esto, estás listo y listo para empezar. Si no es así, publica un comentario y trataré de ayudarte!

Usando PhantomJS-Node

Para que sea más fácil para usted, he incluido un archivo JS, llamado phantomServer.js en la descarga que utiliza parte de la API de PhantomJS para cargar una página web. Espera 5 segundos antes de ejecutar JavaScript que raspa la página. Puede ejecutarlo navegando al directorio y emitiendo el siguiente comando en su terminal:

 nodo phantomServer.js

Daré una visión general de cómo funciona aquí. Primero, requerimos PhantomJS:

 var phantom = require ('phantom');

A continuación, implementamos algunos métodos de la API. A saber, creamos una instancia de página y luego llamamos al abierto() método:

 phantom.create (function (ph) return ph.createPage (function (page) // De aquí en adelante, podemos usar los métodos de API de PhantomJS devolver page.open ("http://tilomitra.com/repository/screenscrape /ajax.html ", function (status) // La página ahora está abierta console.log (" ¿sitio abierto? ", status););););

Una vez que la página está abierta, podemos inyectar un poco de JavaScript en la página. Inyectemos jQuery a través de page.injectJs () método:

 phantom.create (function (ph) return ph.createPage (function (page) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", function (status) console.log ("¿Sitio abierto?", estado); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function () // jQuery Loaded // Podemos usar cosas como $ ("body"). Html () aquí.););););

jQuery ahora está cargado, pero no sabemos si el contenido dinámico en la página se ha cargado todavía. Para dar cuenta de esto, generalmente pongo mi código de raspado dentro de un setTimeout () Función que se ejecuta después de un cierto intervalo de tiempo. Si desea una solución más dinámica, la API de PhantomJS le permite escuchar y emular ciertos eventos. Vayamos con el caso simple:

 setTimeout (function () return page.evaluate (function () // Obtenga lo que desea de la página usando jQuery. // Una buena manera es rellenar un objeto con todos los comandos jQuery que necesita y luego devolver el objeto . var h2Arr = [], // array que contiene todo el html para los elementos h2 pArr = []; // array que contiene todo el html para los elementos p // Rellene los dos arreglos $ ('h2'). each (function () h2Arr.push ($ (this) .html ());); $ ('p'). each (function () pArr.push ($ (this) .html ());); // Devuelva este retorno de datos h2: h2Arr, p: pArr, function (result) console.log (result); // Cierre la sesión de los datos. Ph.exit (););, 5000);

Poniendo todo junto, nuestra phantomServer.js archivo se ve así:

 var phantom = require ('phantom'); phantom.create (function (ph) return ph.createPage (function (page) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", function (status) console.log ("¿Sitio abierto?", estado); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function () // jQuery Loaded . // Espere un poco para que se cargue el contenido de AJAX en la página. Aquí, estamos esperando 5 segundos. SetTimeout (function () return page.evaluate (function () // Obtenga lo que desea de la página usando jQuery Una buena manera es rellenar un objeto con todos los comandos jQuery que necesita y luego devolver el objeto. Var h2Arr = [], pArr = []; $ ('h2'). Each (function () h2Arr.push ($ (this) .html ());); $ ('p'). each (function () pArr.push ($ (this) .html ());); return h2: h2Arr, p: pArr;, function (result) console.log (result); ph.exit (););, 5000);););););

Esta implementación es un poco tosca y desorganizada, pero hace el punto. Con PhantomJS, podemos raspar una página que tiene contenido dinámico. Su consola debe mostrar lo siguiente:

 → nodo phantomServer.js sitio abierto? success h2: ['Article 1', 'Article 2', 'Article 3'], p: ['Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Ut sed nulla turpis, in faucibus ante. Vivamus ut malesuada est. Curabitur vel enim eus purus pharetra tempor id en tellus. ',' Curabitur euismod hendrerit quam ut euismod. 'Ut leo sem, viverra nec gravida nec, tristique nec arcu'. ]

Conclusión

En este tutorial, revisamos dos formas diferentes de realizar raspado web. Si se raspa desde una página web estática, podemos aprovechar YQL, que es fácil de configurar y usar. Por otro lado, para los sitios dinámicos, podemos aprovechar PhantomJS. Es un poco más difícil de configurar, pero proporciona más capacidades. Recuerde: también puede usar PhantomJS para sitios estáticos!

Si tiene alguna pregunta sobre este tema, no dude en preguntar a continuación y haré todo lo posible para ayudarlo.