Los programas que recopilan información de una página web en busca de
información se llaman crawler, spider, o arañas en castellano.
Es una tarea tan común, que existen multitud de bibliotecas y frameworks con
este propósito, en algunos casos simplificando la programación hasta el punto
en el que se puede programar un crawler sencillo con menos de 100 lineas de
código. Y eso es justamente lo que voy a hacer en este post.
Imaginemos que quiero crear una pequeña base de datos para organizar que
libros he leído, y cuales me faltan por leer, pero no quiero tener que
introducir manualmente la información de cada libro, así que voy a obtener
toda la informacón de una página web, por ejemplo
epublibre.
Uso
El primer paso es iniciar un nuevo proyecto:
$ scrapy startproject epublibre_crawler
Esto crea un directorio de proyecto con la siguiente estructura:
epublibre_crawler/
├── epublibre_crawler
│ ├── __init__.py
│ ├── items.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ └── __init__.py
└── scrapy.cfg
Una vez creado el proyecto, tenemos que definir los items que queremos extraer,
o mejor dicho la clase donde se almacenaran los datos extraídos por
scrapy. En mi caso el titulo del libro, y su autor es suficiente, así que
añadimos al archivo items.py la subclase de Item:
from scrapy.item import Item, Field
class BookItem(Item):
title = Field()
author = Field()
El siguiente paso es describir usando expresiones XPath, como se puede extraer
la información del titulo y autor, de manera que Scrapy pueda diferenciarla
del resto de código html de la página de cada libro.
Si miramos el código html de la página de un libro, el título está contenido
en un div de la forma:
<div class="det_titulo" id="titulo_libro" style="display:inline-block;">
Omega
</div>
Dado que el titulo tiene un identificador único "titulo_libro", es fácil de
extraer:
xpath("//div[@id='titulo_libro']/text()[normalize-space()]")
para el nombre del autor, tenemos:
<div class="negrita aut_sec" style="display:inline-block;">
<a href="http://www.epublibre.org/autor/index/425">Jack McDevitt</a>
</div>
este es el único div de la página que usa la clase aut_sec, así que podemos
extraer el nombre con:
xpath("//div[@class='negrita aut_sec']/a/text()")
Una vez tenemos las reglas de extracción, hay que describir como son las
direcciones de las páginas de libro a las que queremos aplicarlas. El formato
es http://www.epublibre.org/libro/detalle/6467, con el numero final variando
para cada libro:
Por último hay que crear la araña que usara scrapy en el directorio spiders:
#epublibre_spider.py
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import Selector
# Importar la clase donde almacenar los resultados
from epublibre_crawler.items import BookItem
class BookSpider(CrawlSpider):
# Nombre de la araña.
name = 'epublibre'
# Dominios en los que el crawler tiene permiso a acceder
allowed_domains = ['epublibre.org']
# La direccion de inicio para el crawler
start_urls = ['http://www.epublibre.org']
# Regla para diferenciar los enlaces de libros y función que se les aplica
rules = [Rule(SgmlLinkExtractor(allow=['/libro/detalle/\d+']), 'parse_book')]
def parse_book(self, response):
""" Parser para las pagina de detalle de los libros"""
sel = Selector(response)
# Creamos un nuevo libro y asignamos los valores extraidos a
# los campos correspondientes.
book = BookItem()
author = sel.xpath("//div[@class='negrita aut_sec']/a/text()").extract()
title = sel.xpath("//div[@id='titulo_libro']/text()[normalize-space()]").extract()
# Con Strip eliminamos tabulaciones y linea nueva.
book['title'] = title[0].strip("\t\n\r")
book['author'] = author[0].strip("\t\n\r")
return book
Antes de iniciar scrapy hay que modificar las configuración y limitar la
velocidad a la que los datos son accedidos, para no crear un ataque DOS:
# Scrapy settings for epublibre_scrapy project
#
# For simplicity, this file contains only the most important settings by
# default. All the other settings are documented here:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
#
BOT_NAME = 'epublibre_crawler'
SPIDER_MODULES = ['epublibre_scrapy.spiders']
NEWSPIDER_MODULE = 'epublibre_scrapy.spiders'
# Maximo una pagina cada minuto
DOWNLOAD_DELAY = 60
# Identificate
# USER_AGENT = 'epublibre_crawler (+http://www.tudominio.com)'
Finalmente solo queda ejecutar scrapy para que recolecte la información y la
guarde en un archivo en alguno de los formatos soportados (XML, JSON, CSV),
o hasta directamente en una base de datos usando una pipeline. En mi
caso JSON es el formato elegido:
$ scrapy crawl epublibre -o libros.json -t json
el nombre epublibre es el que asigné a la araña BookSpider en la
variable name.
El formato del archivo de salida es el siguiente:
[{"author": ["Ray Bradbury"], "title": ["Fahrenheit 451"]},
{"author": ["John Scalzi"], "title": ["Redshirts"]}
]
Nota: Este programa es sólo un ejemplo para explicar el funcionamiento de
scrapy, si realmente deseas extraer la información de epublibre.org, la propia
página distribuye un archivo CSV con todos los datos de los libros
disponibles.