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:
- 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.