MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Web Scraping e Scansione con Scrapy e MongoDB

L'ultima volta abbiamo implementato un web scraper di base che scaricava le ultime domande da StackOverflow e memorizzava i risultati in MongoDB. In questo articolo estenderemo il nostro scraper in modo che esegua la scansione dei link di impaginazione nella parte inferiore di ogni pagina e estragga le domande (titolo e URL della domanda) da ogni pagina.

Bonus gratuito: Fare clic qui per scaricare uno scheletro di progetto Python + MongoDB con codice sorgente completo che mostra come accedere a MongoDB da Python.

Aggiornamenti:

  1. 09/06/2015 - Aggiornato all'ultima versione di Scrapy (v1.0.3) e PyMongo (v3.0.3) - evviva!

Prima di iniziare qualsiasi lavoro di scraping, rivedi i termini di utilizzo del sito e rispetta il file robots.txt. Inoltre, attenersi alle pratiche di scraping etico evitando di inondare un sito con numerose richieste in un breve lasso di tempo. Tratta qualsiasi sito che raschia come se fosse il tuo.

Questo è un pezzo di collaborazione tra le persone di Real Python e György, un appassionato di Python e sviluppatore di software, che attualmente lavora in una società di big data e allo stesso tempo cerca un nuovo lavoro. Puoi fargli domande su Twitter - @kissgyorgy.


Per iniziare

Ci sono due modi possibili per continuare da dove ci eravamo interrotti.

Il primo è estendere il nostro Spider esistente estraendo ogni collegamento alla pagina successiva dalla risposta nel parse_item metodo con un'espressione xpath e solo yield una Request oggetto con una richiamata allo stesso parse_item metodo. In questo modo scrapy effettuerà automaticamente una nuova richiesta al link da noi specificato. Puoi trovare maggiori informazioni su questo metodo nella documentazione di Scrapy.

L'altra opzione, molto più semplice, è utilizzare un diverso tipo di ragno:il CrawlSpider (collegamento). È una versione estesa di Spider di base , progettato esattamente per il nostro caso d'uso.



Il CrawlSpider

Utilizzeremo lo stesso progetto Scrapy dell'ultimo tutorial, quindi prendi il codice dal repository se ne hai bisogno.


Crea il boilerplate

All'interno della directory "stack", inizia generando lo spider boilerplate dal crawl modello:

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

Il progetto Scrapy ora dovrebbe assomigliare a questo:

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

E lo stack_crawler.py il file dovrebbe assomigliare a questo:

# -*- 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

Abbiamo solo bisogno di apportare alcuni aggiornamenti a questo boilerplate...



Aggiorna start_urls elenco

Innanzitutto, aggiungi la prima pagina di domande a start_urls elenco:

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


Aggiorna le rules elenco

Successivamente, dobbiamo dire allo spider dove può trovare i collegamenti alla pagina successiva aggiungendo un'espressione regolare alle rules attributo:

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

Scrapy ora richiederà automaticamente nuove pagine in base a quei collegamenti e passerà la risposta a parse_item metodo per estrarre le domande ei titoli.

Se stai prestando molta attenzione, questa espressione regolare limita la scansione alle prime 9 pagine poiché per questa demo non vogliamo raschiare tutto 176.234 pagine!



Aggiorna parse_item metodo

Ora dobbiamo solo scrivere come analizzare le pagine con xpath, cosa che abbiamo già fatto nell'ultimo tutorial, quindi basta copiarlo:

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

Questo è tutto per il ragno, ma non avvialo ancora.



Aggiungi un ritardo di download

Dobbiamo essere gentili con StackOverflow (e qualsiasi sito, se è per questo) impostando un ritardo di download in settings.py :

DOWNLOAD_DELAY = 5

Questo dice a Scrapy di attendere almeno 5 secondi tra ogni nuova richiesta che fa. Stai essenzialmente limitandoti il ​​tasso. Se non lo fai, StackOverflow ti limiterà; e se continui a raschiare il sito senza imporre un limite di velocità, il tuo indirizzo IP potrebbe essere bannato. Quindi, sii gentile:Tratta qualsiasi sito che raschia come se fosse il tuo.

Ora resta solo una cosa da fare:archiviare i dati.




MongoDB

L'ultima volta abbiamo scaricato solo 50 domande, ma poiché questa volta stiamo raccogliendo molti più dati, vogliamo evitare di aggiungere domande duplicate al database. Possiamo farlo usando un MongoDB upsert, il che significa che aggiorniamo il titolo della domanda se è già nel database e inseriamo altrimenti.

Modifica la MongoDBPipeline abbiamo definito in precedenza:

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

Per semplicità, non abbiamo ottimizzato la query e non abbiamo trattato gli indici poiché questo non è un ambiente di produzione.



Test

Avvia il ragno!

$ scrapy crawl stack_crawler

Ora siediti e guarda il tuo database riempirsi di dati!

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


Conclusione

Puoi scaricare l'intero codice sorgente dal repository Github. Commenta di seguito con domande. Saluti!

Bonus gratuito: Fare clic qui per scaricare uno scheletro di progetto Python + MongoDB con codice sorgente completo che mostra come accedere a MongoDB da Python.

Cerchi più web scraping? Assicurati di controllare i corsi di Real Python. Stai cercando di assumere un web scraper professionale? Dai un'occhiata a GoScrape.