MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Skrobanie i indeksowanie stron internetowych za pomocą Scrapy i MongoDB

Ostatnim razem zaimplementowaliśmy podstawowy web scraper, który pobierał najnowsze pytania ze StackOverflow i zapisywał wyniki w MongoDB. W tym artykule rozszerzymy nasz skrobak, aby przeszukiwał linki stronicowania u dołu każdej strony i usuwał pytania (tytuł pytania i adres URL) z każdej strony.

Bezpłatny bonus: Kliknij tutaj, aby pobrać szkielet projektu Python + MongoDB z pełnym kodem źródłowym, który pokazuje, jak uzyskać dostęp do MongoDB z Pythona.

Aktualizacje:

  1. 06.09.2015 – Zaktualizowano do najnowszej wersji Scrapy (v1.0.3) i PyMongo (v3.0.3) – pozdrawiamy!

Przed rozpoczęciem jakiejkolwiek pracy związanej ze skrobaniem zapoznaj się z warunkami korzystania z witryny i przestrzegaj pliku robots.txt. Przestrzegaj również etycznych praktyk skrobania, nie zalewając witryny licznymi żądaniami w krótkim czasie. Traktuj każdą witrynę, którą zeskrobujesz, jak własną.

Jest to element współpracy między ludźmi z Real Python i György – entuzjastą Pythona i programistą, obecnie pracującym w firmie zajmującej się big data i jednocześnie poszukującym nowej pracy. Możesz zadawać mu pytania na Twitterze - @kissgyorgy.


Pierwsze kroki

Istnieją dwa możliwe sposoby kontynuowania od miejsca, w którym skończyliśmy.

Pierwszym z nich jest rozszerzenie naszego istniejącego Spidera poprzez wyodrębnienie każdego następnego linku do strony z odpowiedzi w parse_item metoda z wyrażeniem xpath i po prostu yield Request obiekt z wywołaniem zwrotnym do tego samego parse_item metoda. W ten sposób scrapy automatycznie wyśle ​​nowe żądanie do wskazanego przez nas linku. Więcej informacji na temat tej metody można znaleźć w dokumentacji Scrapy.

Inną, znacznie prostszą opcją jest wykorzystanie innego typu pająka — CrawlSpider (połączyć). Jest to rozszerzona wersja podstawowego Spider , zaprojektowany dokładnie dla naszego przypadku użycia.



CrawlSpider

Będziemy używać tego samego projektu Scrapy z ostatniego samouczka, więc pobierz kod z repozytorium, jeśli go potrzebujesz.


Utwórz płytę kotłową

W katalogu „stack” zacznij od wygenerowania szablonu pająka z crawl szablon:

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

Projekt Scrapy powinien teraz wyglądać tak:

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

Oraz stack_crawler.py plik powinien wyglądać tak:

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

Musimy tylko dokonać kilku aktualizacji tego schematu…



Zaktualizuj start_urls lista

Najpierw dodaj pierwszą stronę pytań do start_urls lista:

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


Zaktualizuj rules lista

Następnie musimy powiedzieć pająkowi, gdzie może znaleźć linki do następnej strony, dodając wyrażenie regularne do rules atrybut:

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

Scrapy będzie teraz automatycznie żądać nowych stron na podstawie tych linków i przekazywać odpowiedź do parse_item metoda wyodrębniania pytań i tytułów.

Jeśli zwracasz szczególną uwagę, to wyrażenie regularne ogranicza indeksowanie do pierwszych 9 stron, ponieważ w przypadku tej demonstracji nie chcemy zeskrobać wszystkich 176 234 stron!



Zaktualizuj parse_item metoda

Teraz musimy tylko napisać, jak parsować strony za pomocą xpath, co już zrobiliśmy w poprzednim samouczku - więc po prostu skopiuj to:

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

To wszystko dla pająka, ale nie zacznij to jeszcze.



Dodaj opóźnienie pobierania

Musimy być mili dla StackOverflow (i każdej strony, jeśli o to chodzi), ustawiając opóźnienie pobierania w settings.py :

DOWNLOAD_DELAY = 5

Dzięki temu Scrapy musi odczekać co najmniej 5 sekund między każdym nowym żądaniem. Zasadniczo ograniczasz swoje tempo. Jeśli tego nie zrobisz, StackOverflow ograniczy ci szybkość; a jeśli nadal będziesz przeszukiwać witrynę bez narzucania limitu szybkości, Twój adres IP może zostać zbanowany. Bądź więc miły — Traktuj każdą zeskrobaną witrynę jak własną.

Teraz pozostała tylko jedna rzecz do zrobienia - przechowywanie danych.




MongoDB

Ostatnim razem pobraliśmy tylko 50 pytań, ale ponieważ tym razem pobieramy znacznie więcej danych, chcemy uniknąć dodawania zduplikowanych pytań do bazy danych. Możemy to zrobić za pomocą upsert MongoDB, co oznacza, że ​​aktualizujemy tytuł pytania, jeśli jest już w bazie danych, a wstawiamy inaczej.

Zmodyfikuj MongoDBPipeline zdefiniowaliśmy wcześniej:

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

Dla uproszczenia nie zoptymalizowaliśmy zapytania i nie zajmowaliśmy się indeksami, ponieważ nie jest to środowisko produkcyjne.



Test

Uruchom pająka!

$ scrapy crawl stack_crawler

Teraz usiądź wygodnie i obserwuj, jak Twoja baza danych wypełnia się danymi!

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


Wniosek

Możesz pobrać cały kod źródłowy z repozytorium Github. Komentarz poniżej z pytaniami. Pozdrawiam!

Bezpłatny bonus: Kliknij tutaj, aby pobrać szkielet projektu Python + MongoDB z pełnym kodem źródłowym, który pokazuje, jak uzyskać dostęp do MongoDB z Pythona.

Szukasz więcej skrobania sieci? Koniecznie sprawdź kursy Real Python. Szukasz profesjonalnego skrobaka internetowego? Sprawdź GoScrape.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB $lt Operator potoku agregacji

  2. Usuwanie klucza/wartości z istniejącego wpisu MongoDB

  3. Jak zatrzymać niekontrolowaną kompilację indeksu w MongoDB?

  4. Zapobiegaj podwójnemu logowaniu za pomocą FOSUserBundle

  5. Metoda wyszukiwania Mongoose z $lub warunkiem nie działa poprawnie