sql >> Base de Datos >  >> NoSQL >> MongoDB

Web Scraping y Crawling con Scrapy y MongoDB

La última vez implementamos un web scraper básico que descargaba las últimas preguntas de StackOverflow y almacenaba los resultados en MongoDB. En este artículo ampliaremos nuestro raspador para que rastree los enlaces de paginación en la parte inferior de cada página y extraiga las preguntas (título de la pregunta y URL) de cada página.

Bono gratis: Haga clic aquí para descargar un esqueleto de proyecto de Python + MongoDB con el código fuente completo que le muestra cómo acceder a MongoDB desde Python.

Actualizaciones:

  1. 09/06/2015 - Actualizado a la última versión de Scrapy (v1.0.3) y PyMongo (v3.0.3) - ¡salud!

Antes de comenzar cualquier trabajo de raspado, revise la política de términos de uso del sitio y respete el archivo robots.txt. Además, cumpla con las prácticas éticas de raspado al no inundar un sitio con numerosas solicitudes en un corto período de tiempo. Trate cualquier sitio que extraiga como si fuera suyo.

Esta es una pieza de colaboración entre la gente de Real Python y György, un desarrollador de software y entusiasta de Python, que actualmente trabaja en una empresa de big data y busca un nuevo trabajo al mismo tiempo. Puedes hacerle preguntas en twitter - @kissgyorgy.


Cómo empezar

Hay dos formas posibles de continuar desde donde lo dejamos.

El primero es extender nuestro Spider existente extrayendo cada enlace de la página siguiente de la respuesta en el parse_item método con una expresión xpath y solo yield una Request objeto con una devolución de llamada al mismo parse_item método. De esta manera, scrapy automáticamente hará una nueva solicitud al enlace que especifiquemos. Puede encontrar más información sobre este método en la documentación de Scrapy.

La otra opción, mucho más simple, es utilizar un tipo diferente de araña:la CrawlSpider (Enlace). Es una versión extendida del Spider básico , diseñado exactamente para nuestro caso de uso.



La Araña Reptadora

Usaremos el mismo proyecto Scrapy del último tutorial, así que tome el código del repositorio si lo necesita.


Crear el modelo estándar

Dentro del directorio "stack", comience generando el modelo de araña a partir del crawl plantilla:

$ scrapy genspider stack_crawler stackoverflow.com -t crawl
Created spider 'stack_crawler' using template 'crawl' in module:
  stack.spiders.stack_crawler

El proyecto Scrapy ahora debería verse así:

├── scrapy.cfg
└── stack
    ├── __init__.py
    ├── items.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py
        ├── stack_crawler.py
        └── stack_spider.py

Y el stack_crawler.py el archivo debería verse así:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule

from stack.items import StackItem


class StackCrawlerSpider(CrawlSpider):
    name = 'stack_crawler'
    allowed_domains = ['stackoverflow.com']
    start_urls = ['http://www.stackoverflow.com/']

    rules = (
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        i = StackItem()
        #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
        #i['name'] = response.xpath('//div[@id="name"]').extract()
        #i['description'] = response.xpath('//div[@id="description"]').extract()
        return i

Solo tenemos que hacer algunas actualizaciones a este modelo...



Actualice las start_urls lista

Primero, agregue la primera página de preguntas a start_urls lista:

start_urls = [
    'http://stackoverflow.com/questions?pagesize=50&sort=newest'
]


Actualizar las rules lista

A continuación, debemos decirle a la araña dónde puede encontrar los enlaces de la página siguiente agregando una expresión regular a las rules atributo:

rules = [
    Rule(LinkExtractor(allow=r'questions\?page=[0-9]&sort=newest'),
         callback='parse_item', follow=True)
]

Scrapy ahora solicitará automáticamente nuevas páginas basadas en esos enlaces y pasará la respuesta al parse_item método para extraer las preguntas y los títulos.

Si está prestando mucha atención, esta expresión regular limita el rastreo a las primeras 9 páginas, ya que para esta demostración no queremos raspar todas. ¡176.234 páginas!



Actualice el parse_item método

Ahora solo tenemos que escribir cómo analizar las páginas con xpath, lo que ya hicimos en el último tutorial, así que simplemente cópielo:

def parse_item(self, response):
    questions = response.xpath('//div[@class="summary"]/h3')

    for question in questions:
        item = StackItem()
        item['url'] = question.xpath(
            'a[@class="question-hyperlink"]/@href').extract()[0]
        item['title'] = question.xpath(
            'a[@class="question-hyperlink"]/text()').extract()[0]
        yield item

Eso es todo para la araña, pero no empezarlo todavía.



Agregar un retraso de descarga

Necesitamos ser amables con StackOverflow (y con cualquier sitio, en realidad) configurando un retraso de descarga en settings.py :

DOWNLOAD_DELAY = 5

Esto le dice a Scrapy que espere al menos 5 segundos entre cada nueva solicitud que realiza. Esencialmente te estás limitando a ti mismo. Si no hace esto, StackOverflow lo limitará; y si continúa raspando el sitio sin imponer un límite de velocidad, su dirección IP podría ser prohibida. Por lo tanto, sé amable:trata cualquier sitio que extraigas como si fuera tuyo.

Ahora solo queda una cosa por hacer:almacenar los datos.




MongoDB

La última vez solo descargamos 50 preguntas, pero como esta vez estamos recopilando muchos más datos, queremos evitar agregar preguntas duplicadas a la base de datos. Podemos hacer eso usando un upsert de MongoDB, lo que significa que actualizamos el título de la pregunta si ya está en la base de datos e insertamos de lo contrario.

Modificar el MongoDBPipeline definimos anteriormente:

class MongoDBPipeline(object):

    def __init__(self):
        connection = pymongo.MongoClient(
            settings['MONGODB_SERVER'],
            settings['MONGODB_PORT']
        )
        db = connection[settings['MONGODB_DB']]
        self.collection = db[settings['MONGODB_COLLECTION']]

    def process_item(self, item, spider):
        for data in item:
            if not data:
                raise DropItem("Missing data!")
        self.collection.update({'url': item['url']}, dict(item), upsert=True)
        log.msg("Question added to MongoDB database!",
                level=log.DEBUG, spider=spider)
        return item

Para simplificar, no optimizamos la consulta y no manejamos índices ya que este no es un entorno de producción.



Prueba

¡Empieza la araña!

$ scrapy crawl stack_crawler

¡Ahora siéntese y observe cómo su base de datos se llena de datos!

$ mongo
MongoDB shell version: 3.0.4
> use stackoverflow
switched to db stackoverflow
> db.questions.count()
447
>


Conclusión

Puede descargar el código fuente completo desde el repositorio de Github. Comenta abajo con preguntas. ¡Salud!

Bono gratis: Haga clic aquí para descargar un esqueleto de proyecto de Python + MongoDB con el código fuente completo que le muestra cómo acceder a MongoDB desde Python.

¿Buscas más web scraping? Asegúrese de consultar los cursos de Real Python. ¿Está buscando contratar un web scraper profesional? Echa un vistazo a GoScrape.