Da última vez, implementamos um web scraper básico que baixava as perguntas mais recentes do StackOverflow e armazenava os resultados no MongoDB. Neste artigo, estenderemos nosso raspador para que ele rastreie os links de paginação na parte inferior de cada página e raspe as perguntas (título da pergunta e URL) de cada página.
Bônus grátis: Clique aqui para baixar um esqueleto de projeto Python + MongoDB com código-fonte completo que mostra como acessar o MongoDB a partir do Python.
Atualizações:
- 09/06/2015 - Atualizado para a versão mais recente do Scrapy (v1.0.3) e PyMongo (v3.0.3) - parabéns!
Antes de iniciar qualquer trabalho de raspagem, revise a política de termos de uso do site e respeite o arquivo robots.txt. Além disso, siga as práticas de raspagem ética, não inundando um site com inúmeras solicitações em um curto período de tempo. Trate qualquer site copiado como se fosse seu.
Esta é uma peça de colaboração entre o pessoal da Real Python e György - um entusiasta de Python e desenvolvedor de software, atualmente trabalhando em uma empresa de big data e procurando um novo emprego ao mesmo tempo. Você pode fazer perguntas a ele no twitter - @kissgyorgy.
Primeiros passos
Existem duas maneiras possíveis de continuar de onde paramos.
A primeira é estender nosso Spider existente extraindo cada link da próxima página da resposta no
parse_item
método com uma expressão xpath e apenas yield
uma Request
objeto com um retorno de chamada para o mesmo parse_item
método. Dessa forma, o scrapy fará automaticamente uma nova solicitação para o link que especificamos. Você pode encontrar mais informações sobre esse método na documentação do Scrapy. A outra opção muito mais simples é utilizar um tipo diferente de aranha - o
CrawlSpider
(link). É uma versão estendida do básico Spider
, projetado exatamente para nosso caso de uso. O CrawlSpider
Usaremos o mesmo projeto Scrapy do último tutorial, então pegue o código do repositório se precisar.
Criar o Caldeirão
Dentro do diretório “stack”, comece gerando o clichê do spider a partir do
crawl
modelo:$ scrapy genspider stack_crawler stackoverflow.com -t crawl
Created spider 'stack_crawler' using template 'crawl' in module:
stack.spiders.stack_crawler
O projeto Scrapy agora deve ficar assim:
├── scrapy.cfg
└── stack
├── __init__.py
├── items.py
├── pipelines.py
├── settings.py
└── spiders
├── __init__.py
├── stack_crawler.py
└── stack_spider.py
E o stack_crawler.py arquivo deve ficar assim:
# -*- 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
Só precisamos fazer algumas atualizações neste clichê…
Atualize os start_urls
lista
Primeiro, adicione a primeira página de perguntas ao
start_urls
Lista:start_urls = [
'http://stackoverflow.com/questions?pagesize=50&sort=newest'
]
Atualize as rules
lista
Em seguida, precisamos informar ao spider onde ele pode encontrar os links da próxima página adicionando uma expressão regular às
rules
atributo:rules = [
Rule(LinkExtractor(allow=r'questions\?page=[0-9]&sort=newest'),
callback='parse_item', follow=True)
]
O Scrapy agora solicitará automaticamente novas páginas com base nesses links e passará a resposta para o
parse_item
método para extrair as perguntas e títulos.
Se você estiver prestando atenção, este regex limita o rastreamento às primeiras 9 páginas, pois para esta demonstração não queremos raspar todos 176.234 páginas!
Atualize o parse_item
método
Agora só precisamos escrever como analisar as páginas com xpath, o que já fizemos no último tutorial - basta copiá-lo:
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
Isso é tudo para a aranha, mas não iniciá-lo ainda.
Adicionar um atraso de download
Precisamos ser legais com o StackOverflow (e qualquer site, aliás) definindo um atraso de download em settings.py :
DOWNLOAD_DELAY = 5
Isso diz ao Scrapy para esperar pelo menos 5 segundos entre cada nova solicitação que ele faz. Você está essencialmente limitando a sua taxa. Se você não fizer isso, o StackOverflow limitará sua taxa; e se você continuar a raspar o site sem impor um limite de taxa, seu endereço IP poderá ser banido. Portanto, seja gentil - Trate qualquer site que você criar como se fosse seu.
Agora só resta uma coisa a fazer - armazenar os dados.
MongoDB
Da última vez, baixamos apenas 50 perguntas, mas como estamos coletando muito mais dados desta vez, queremos evitar adicionar perguntas duplicadas ao banco de dados. Podemos fazer isso usando um upsert do MongoDB, o que significa que atualizamos o título da pergunta se já estiver no banco de dados e inserimos caso contrário.
Modifique o
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, não otimizamos a consulta e não lidamos com índices, pois este não é um ambiente de produção.
Teste
Comece a aranha!
$ scrapy crawl stack_crawler
Agora sente-se e observe seu banco de dados se encher de dados!
$ mongo
MongoDB shell version: 3.0.4
> use stackoverflow
switched to db stackoverflow
> db.questions.count()
447
>
Conclusão
Você pode baixar todo o código-fonte do repositório do Github. Comente abaixo com perguntas. Saúde!
Bônus grátis: Clique aqui para baixar um esqueleto de projeto Python + MongoDB com código-fonte completo que mostra como acessar o MongoDB a partir do Python.
Procurando mais raspagem da web? Não deixe de conferir os cursos Real Python. Quer contratar um web scraper profissional? Confira GoScrape.