MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Web Scraping und Crawling mit Scrapy und MongoDB

Letztes Mal haben wir einen einfachen Web Scraper implementiert, der die neuesten Fragen von StackOverflow heruntergeladen und die Ergebnisse in MongoDB gespeichert hat. In diesem Artikel erweitern wir unseren Scraper so, dass er die Paginierungslinks unten auf jeder Seite durchsucht und die Fragen (Fragetitel und URL) von jeder Seite löscht.

Kostenloser Bonus: Klicken Sie hier, um ein Python + MongoDB-Projektskelett mit vollständigem Quellcode herunterzuladen, der Ihnen zeigt, wie Sie von Python aus auf MongoDB zugreifen.

Aktualisierungen:

  1. 06.09.2015 – Aktualisiert auf die neueste Version von Scrapy (v1.0.3) und PyMongo (v3.0.3) – Prost!

Bevor Sie mit einem Scraping-Job beginnen, lesen Sie die Nutzungsbedingungen der Website und respektieren Sie die robots.txt-Datei. Halten Sie sich auch an ethische Scraping-Praktiken, indem Sie eine Website nicht über einen kurzen Zeitraum mit zahlreichen Anfragen überfluten. Behandle jede Website, die du kratzt, als wäre es deine eigene.

Dies ist eine Zusammenarbeit zwischen den Leuten von Real Python und György – einem Python-Enthusiasten und Softwareentwickler, der derzeit bei einem Big-Data-Unternehmen arbeitet und gleichzeitig einen neuen Job sucht. Sie können ihm Fragen auf Twitter stellen - @kissgyorgy.


Erste Schritte

Es gibt zwei Möglichkeiten, dort fortzufahren, wo wir aufgehört haben.

Die erste besteht darin, unseren vorhandenen Spider zu erweitern, indem jeder nächste Seitenlink aus der Antwort im parse_item extrahiert wird Methode mit einem XPath-Ausdruck und nur yield eine Request Objekt mit einem Rückruf an dasselbe parse_item Methode. Auf diese Weise stellt scrapy automatisch eine neue Anfrage an den von uns angegebenen Link. Weitere Informationen zu dieser Methode finden Sie in der Scrapy-Dokumentation.

Die andere, viel einfachere Möglichkeit besteht darin, einen anderen Spinnentyp zu verwenden - den CrawlSpider (Verknüpfung). Es ist eine erweiterte Version des grundlegenden Spider , genau für unseren Anwendungsfall konzipiert.



Die CrawlSpider

Wir verwenden dasselbe Scrapy-Projekt aus dem letzten Tutorial, also holen Sie sich den Code aus dem Repo, wenn Sie ihn brauchen.


Boilerplate erstellen

Beginnen Sie im „stack“-Verzeichnis damit, die Spider-Boilerplate aus dem crawl zu generieren Vorlage:

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

Das Scrapy-Projekt sollte nun so aussehen:

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

Und die stack_crawler.py Datei sollte so aussehen:

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

Wir müssen nur ein paar Aktualisierungen an dieser Standardvorlage vornehmen …



Aktualisieren Sie die start_urls Liste

Fügen Sie zuerst die erste Seite mit Fragen zu den start_urls hinzu Liste:

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


Aktualisiere die rules Liste

Als nächstes müssen wir der Spinne mitteilen, wo sie die Links der nächsten Seite finden kann, indem wir den rules einen regulären Ausdruck hinzufügen Attribut:

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

Scrapy fordert nun basierend auf diesen Links automatisch neue Seiten an und leitet die Antwort an parse_item weiter Methode zum Extrahieren der Fragen und Titel.

Wenn Sie genau aufpassen, beschränkt diese Regex das Crawling auf die ersten 9 Seiten, da wir für diese Demo nicht alle schaben wollen 176.234 Seiten!



Aktualisieren Sie das parse_item Methode

Jetzt müssen wir nur noch schreiben, wie wir die Seiten mit xpath parsen, was wir bereits im letzten Tutorial getan haben - also kopieren Sie es einfach rüber:

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

Das war's für die Spinne, aber nicht fang doch einfach damit an.



Download-Verzögerung hinzufügen

Wir müssen nett zu StackOverflow (und jeder anderen Website) sein, indem wir eine Download-Verzögerung in settings.py festlegen :

DOWNLOAD_DELAY = 5

Dies weist Scrapy an, zwischen jeder neuen Anfrage mindestens 5 Sekunden zu warten. Sie begrenzen sich im Wesentlichen selbst. Wenn Sie dies nicht tun, wird StackOverflow Ihre Rate begrenzen; und wenn Sie die Website weiterhin ohne eine Ratenbegrenzung durchsuchen, könnte Ihre IP-Adresse gesperrt werden. Seien Sie also nett - Behandeln Sie jede Website, die Sie kratzen, als wäre es Ihre eigene.

Jetzt bleibt nur noch eines zu tun - die Daten speichern.




MongoDB

Letztes Mal haben wir nur 50 Fragen heruntergeladen, aber da wir dieses Mal viel mehr Daten erfassen, möchten wir vermeiden, doppelte Fragen zur Datenbank hinzuzufügen. Wir können dies tun, indem wir ein MongoDB-Upsert verwenden, was bedeutet, dass wir den Fragentitel aktualisieren, wenn er bereits in der Datenbank vorhanden ist, und andernfalls einfügen.

Ändern Sie die MongoDBPipeline wir haben früher definiert:

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

Der Einfachheit halber haben wir die Abfrage nicht optimiert und uns nicht mit Indizes befasst, da dies keine Produktionsumgebung ist.



Test

Starten Sie die Spinne!

$ scrapy crawl stack_crawler

Lehnen Sie sich jetzt zurück und sehen Sie zu, wie sich Ihre Datenbank mit Daten füllt!

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


Schlussfolgerung

Sie können den gesamten Quellcode aus dem Github-Repository herunterladen. Kommentieren Sie unten mit Fragen. Prost!

Kostenloser Bonus: Klicken Sie hier, um ein Python + MongoDB-Projektskelett mit vollständigem Quellcode herunterzuladen, der Ihnen zeigt, wie Sie von Python aus auf MongoDB zugreifen.

Auf der Suche nach mehr Web Scraping? Schauen Sie sich unbedingt die Real Python-Kurse an. Möchten Sie einen professionellen Web Scraper mieten? Sehen Sie sich GoScrape an.