MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Web Scraping et Crawling avec Scrapy et MongoDB

La dernière fois, nous avons implémenté un grattoir Web de base qui téléchargeait les dernières questions de StackOverflow et stockait les résultats dans MongoDB. Dans cet article, nous allons étendre notre grattoir afin qu'il parcoure les liens de pagination au bas de chaque page et récupère les questions (titre de la question et URL) de chaque page.

Bonus gratuit : Cliquez ici pour télécharger un squelette de projet Python + MongoDB avec le code source complet qui vous montre comment accéder à MongoDB à partir de Python.

Mises à jour :

  1. 09/06/2015 - Mise à jour vers la dernière version de Scrapy (v1.0.3) et PyMongo (v3.0.3) - bravo !

Avant de commencer tout travail de grattage, passez en revue les conditions d'utilisation du site et respectez le fichier robots.txt. En outre, respectez les pratiques de grattage éthiques en évitant d'inonder un site de nombreuses demandes sur une courte période. Traitez tout site que vous grattez comme s'il s'agissait du vôtre.

Il s'agit d'une collaboration entre les gens de Real Python et György - un passionné de Python et développeur de logiciels, travaillant actuellement dans une entreprise de big data et cherchant un nouvel emploi en même temps. Vous pouvez lui poser des questions sur Twitter - @kissgyorgy.


Mise en route

Il y a deux façons possibles de continuer là où nous nous sommes arrêtés.

La première consiste à étendre notre Spider existant en extrayant chaque lien de page suivante de la réponse dans le parse_item méthode avec une expression xpath et juste yield une Request objet avec un rappel au même parse_item méthode. De cette façon, scrapy fera automatiquement une nouvelle demande au lien que nous spécifions. Vous pouvez trouver plus d'informations sur cette méthode dans la documentation de Scrapy.

L'autre option, beaucoup plus simple, consiste à utiliser un autre type d'araignée - le CrawlSpider (lien). C'est une version étendue de la base Spider , conçu exactement pour notre cas d'utilisation.



L'araignée rampante

Nous utiliserons le même projet Scrapy du dernier tutoriel, alors récupérez le code du dépôt si vous en avez besoin.


Créer le passe-partout

Dans le répertoire "stack", commencez par générer le spider boilerplate à partir du crawl modèle :

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

Le projet Scrapy devrait maintenant ressembler à ceci :

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

Et le stack_crawler.py le fichier devrait ressembler à ceci :

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

Nous avons juste besoin de faire quelques mises à jour de ce passe-partout…



Mettre à jour les start_urls liste

Tout d'abord, ajoutez la première page de questions aux start_urls liste :

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


Mettre à jour les rules liste

Ensuite, nous devons dire à l'araignée où il peut trouver les liens de la page suivante en ajoutant une expression régulière aux rules attribut :

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

Scrapy demandera désormais automatiquement de nouvelles pages basées sur ces liens et transmettra la réponse au parse_item méthode pour extraire les questions et les titres.

Si vous êtes très attentif, cette regex limite le crawling aux 9 premières pages puisque pour cette démo nous ne voulons pas tout scraper. 176 234 pages !



Mettre à jour le parse_item méthode

Maintenant, nous avons juste besoin d'écrire comment analyser les pages avec xpath, ce que nous avons déjà fait dans le dernier tutoriel - alors copiez-le simplement :

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

C'est tout pour l'araignée, mais pas démarrez-le tout de suite.



Ajouter un délai de téléchargement

Nous devons être gentils avec StackOverflow (et n'importe quel site, d'ailleurs) en définissant un délai de téléchargement dans settings.py :

DOWNLOAD_DELAY = 5

Cela indique à Scrapy d'attendre au moins 5 secondes entre chaque nouvelle demande qu'il effectue. Vous êtes essentiellement en train de vous limiter. Si vous ne le faites pas, StackOverflow vous limitera en débit; et si vous continuez à gratter le site sans imposer de limite de débit, votre adresse IP pourrait être bannie. Alors, soyez gentil :Traitez tout site que vous grattez comme si c'était le vôtre.

Il ne reste plus qu'une chose à faire :stocker les données.




MongoDB

La dernière fois, nous n'avons téléchargé que 50 questions, mais comme nous récupérons beaucoup plus de données cette fois-ci, nous voulons éviter d'ajouter des questions en double à la base de données. Nous pouvons le faire en utilisant un upsert MongoDB, ce qui signifie que nous mettons à jour le titre de la question s'il est déjà dans la base de données et l'insérons autrement.

Modifier le MongoDBPipeline nous avons défini précédemment :

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

Pour plus de simplicité, nous n'avons pas optimisé la requête et n'avons pas traité les index puisqu'il ne s'agit pas d'un environnement de production.



Tester

Démarrez l'araignée !

$ scrapy crawl stack_crawler

Maintenant, asseyez-vous et regardez votre base de données se remplir de données !

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


Conclusion

Vous pouvez télécharger l'intégralité du code source à partir du référentiel Github. Commentez ci-dessous avec des questions. Santé !

Bonus gratuit : Cliquez ici pour télécharger un squelette de projet Python + MongoDB avec le code source complet qui vous montre comment accéder à MongoDB à partir de Python.

Vous cherchez plus de scraping Web ? Assurez-vous de consulter les cours Real Python. Vous cherchez à embaucher un web scraper professionnel ? Découvrez GoScrape.