MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Web Scraping e Crawling com Scrapy e MongoDB


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