sql >> Database >  >> NoSQL >> MongoDB

Webscraping en crawlen met Scrapy en MongoDB

De vorige keer hebben we een eenvoudige webschraper geïmplementeerd die de nieuwste vragen van StackOverflow heeft gedownload en de resultaten heeft opgeslagen in MongoDB. In dit artikel breiden we onze scraper uit zodat deze door de paginalinks onder aan elke pagina kruipt en de vragen (vraagtitel en URL) van elke pagina schrapt.

Gratis bonus: Klik hier om een ​​Python + MongoDB-projectskelet met volledige broncode te downloaden die u laat zien hoe u toegang krijgt tot MongoDB vanuit Python.

Updates:

  1. 09/06/2015 - Bijgewerkt naar de nieuwste versie van Scrapy (v1.0.3) en PyMongo (v3.0.3) - proost!

Lees voordat u aan een scrapopdracht begint, het gebruiksvoorwaardenbeleid van de site en respecteer het robots.txt-bestand. Houd u ook aan ethische scraping-praktijken door een site niet in korte tijd met talloze verzoeken te overspoelen. Behandel elke site die u schrapt alsof het uw eigen site is.

Dit is een samenwerking tussen de mensen van Real Python en György - een Python-enthousiasteling en softwareontwikkelaar, die momenteel bij een big data-bedrijf werkt en tegelijkertijd op zoek is naar een nieuwe baan. Je kunt hem vragen stellen op Twitter - @kissgyorgy.


Aan de slag

Er zijn twee mogelijke manieren om verder te gaan waar we gebleven waren.

De eerste is om onze bestaande Spider uit te breiden door elke volgende paginalink te extraheren uit het antwoord in de parse_item methode met een xpath-expressie en gewoon yield een Request object met een callback naar hetzelfde parse_item methode. Op deze manier zal scrapy automatisch een nieuw verzoek doen naar de link die we specificeren. U kunt meer informatie over deze methode vinden in de Scrapy-documentatie.

De andere, veel eenvoudigere optie is om een ​​ander type spider te gebruiken - de CrawlSpider (koppeling). Het is een uitgebreide versie van de basis Spider , precies ontworpen voor onze use case.



De CrawlSpider

We gebruiken hetzelfde Scrapy-project uit de laatste tutorial, dus pak de code uit de repo als je die nodig hebt.


Maak de Boilerplate

Begin in de "stack"-directory met het genereren van de spider-boilerplate uit de crawl sjabloon:

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

Het Scrapy-project zou er nu als volgt uit moeten zien:

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

En de stack_crawler.py bestand zou er als volgt uit moeten zien:

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

We hoeven alleen een paar updates aan deze standaardtekst door te voeren...



Update de start_urls lijst

Voeg eerst de eerste pagina met vragen toe aan de start_urls lijst:

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


Update de rules lijst

Vervolgens moeten we de spider vertellen waar hij de links naar de volgende pagina kan vinden door een reguliere expressie toe te voegen aan de rules kenmerk:

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

Scrapy zal nu automatisch nieuwe pagina's opvragen op basis van die links en het antwoord doorgeven aan de parse_item methode om de vragen en titels te extraheren.

Als je goed oplet, beperkt deze regex het crawlen tot de eerste 9 pagina's, omdat we voor deze demo niet alle willen schrapen 176.234 pagina's!



Update het parse_item methode

Nu hoeven we alleen nog te schrijven hoe we de pagina's kunnen ontleden met xpath, wat we al deden in de vorige tutorial - dus kopieer het gewoon over:

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

Dat is het voor de spin, maar doe niet begin er nog maar mee.



Een downloadvertraging toevoegen

We moeten aardig zijn voor StackOverflow (en welke site dan ook) door een downloadvertraging in te stellen in settings.py :

DOWNLOAD_DELAY = 5

Dit vertelt Scrapy om ten minste 5 seconden te wachten tussen elk nieuw verzoek dat het doet. Je bent in wezen jezelf aan het beperken. Als u dit niet doet, zal StackOverflow u beperken; en als u doorgaat met het schrapen van de site zonder een snelheidslimiet op te leggen, kan uw IP-adres worden verbannen. Dus wees aardig - Behandel elke site die je schrapt alsof het de jouwe is.

Nu rest er nog maar één ding:de gegevens opslaan.




MongoDB

De vorige keer hebben we slechts 50 vragen gedownload, maar omdat we deze keer veel meer gegevens verzamelen, willen we voorkomen dat er dubbele vragen aan de database worden toegevoegd. We kunnen dat doen door een MongoDB upsert te gebruiken, wat betekent dat we de vraagtitel bijwerken als deze al in de database staat en anders invoegen.

Wijzig de MongoDBPipeline we eerder hebben gedefinieerd:

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

Voor de eenvoud hebben we de zoekopdracht niet geoptimaliseerd en hebben we geen indexen behandeld, omdat dit geen productieomgeving is.



Test

Start de spin!

$ scrapy crawl stack_crawler

Leun nu achterover en kijk hoe uw database zich vult met gegevens!

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


Conclusie

U kunt de volledige broncode downloaden van de Github-repository. Reageer hieronder met vragen. Proost!

Gratis bonus: Klik hier om een ​​Python + MongoDB-projectskelet met volledige broncode te downloaden die u laat zien hoe u toegang krijgt tot MongoDB vanuit Python.

Op zoek naar meer webscraping? Zorg ervoor dat u de Real Python-cursussen bekijkt. Op zoek naar een professionele webschraper? Bekijk GoScrape.



  1. Wat is de beste MongoDB GUI? — Update 2019

  2. Verbinding maken met uw MongoDB-implementaties met behulp van Robo 3T GUI

  3. Dynamische toetsen na $groeperen op

  4. Hoe kan ik redis-server stoppen?