sql >> Databasteknik >  >> NoSQL >> MongoDB

Webbskrapning och genomsökning med Scrapy och MongoDB

Förra gången implementerade vi en grundläggande webbskrapa som laddade ner de senaste frågorna från StackOverflow och lagrade resultaten i MongoDB. I den här artikeln utökar vi vår skrapa så att den genomsöker sidnumreringslänkarna längst ner på varje sida och skrapar frågorna (frågans rubrik och webbadress) från varje sida.

Gratis bonus: Klicka här för att ladda ner ett Python + MongoDB-projektskelett med fullständig källkod som visar hur du kommer åt MongoDB från Python.

Uppdateringar:

  1. 09/06/2015 - Uppdaterad till den senaste versionen av Scrapy (v1.0.3) och PyMongo (v3.0.3) - hej!

Innan du påbörjar något skrapjobb ska du läsa igenom webbplatsens användarvillkor och respektera robots.txt-filen. Följ också etiska skrapningsmetoder genom att inte översvämma en webbplats med många förfrågningar under en kort tidsperiod. Behandla alla webbplatser du skrapar som om de vore din egen.

Detta är ett samarbete mellan folket på Real Python och György - en Python-entusiast och mjukvaruutvecklare, som för närvarande arbetar på ett big data-företag och samtidigt söker ett nytt jobb. Du kan ställa frågor till honom på twitter - @kissgyorgy.


Komma igång

Det finns två möjliga sätt att fortsätta där vi slutade.

Den första är att utöka vår befintliga Spider genom att extrahera varje nästa sidlänk från svaret i parse_item metod med ett xpath-uttryck och bara yield en Request objekt med en återuppringning till samma parse_item metod. På så sätt kommer scrapy automatiskt att göra en ny förfrågan till länken vi anger. Du kan hitta mer information om denna metod i Scrapy-dokumentationen.

Det andra, mycket enklare alternativet är att använda en annan typ av spindel - CrawlSpider (länk). Det är en utökad version av den grundläggande Spider , designad exakt för vårt användningsfall.



CrawlSpider

Vi kommer att använda samma Scrapy-projekt från den senaste handledningen, så ta tag i koden från repot om du behöver den.


Skapa Boilerplate

Inom "stack"-katalogen börjar du med att skapa spindelplattan från crawl mall:

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

Scrapy-projektet ska nu se ut så här:

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

Och stack_crawler.py filen ska se ut så här:

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

Vi behöver bara göra några uppdateringar av den här plattan...



Uppdatera start_urls lista

Lägg först till den första sidan med frågor till start_urls lista:

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


Uppdatera rules lista

Därefter måste vi berätta för spindeln var den kan hitta länkarna till nästa sida genom att lägga till ett reguljärt uttryck i rules attribut:

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

Scrapy kommer nu automatiskt att begära nya sidor baserat på dessa länkar och skicka svaret till parse_item metod för att extrahera frågorna och titlarna.

Om du är mycket uppmärksam, begränsar detta regex genomsökningen till de första 9 sidorna eftersom vi inte vill skrapa alla för denna demo 176 234 sidor!



Uppdatera parse_item metod

Nu behöver vi bara skriva hur man tolkar sidorna med xpath, vilket vi redan gjorde i den förra handledningen - så bara kopiera den över:

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

Det är det för spindeln, men gör inte starta det ännu.



Lägg till en nedladdningsfördröjning

Vi måste vara trevliga mot StackOverflow (och vilken webbplats som helst, för den delen) genom att ställa in en nedladdningsfördröjning i settings.py :

DOWNLOAD_DELAY = 5

Detta säger åt Scrapy att vänta minst 5 sekunder mellan varje ny begäran som den gör. Du begränsar i princip dig själv. Om du inte gör detta kommer StackOverflow att begränsa dig. och om du fortsätter att skrapa sajten utan att införa en hastighetsgräns kan din IP-adress förbjudas. Så var snäll - Behandla alla webbplatser du skrapar som om de vore din egen.

Nu är det bara en sak kvar att göra - lagra data.




MongoDB

Förra gången laddade vi bara ner 50 frågor, men eftersom vi plockar mycket mer data den här gången vill vi undvika att lägga till dubbla frågor i databasen. Vi kan göra det genom att använda en MongoDB-upsert, vilket innebär att vi uppdaterar frågetiteln om den redan finns i databasen och infogar på annat sätt.

Ändra MongoDBPipeline vi definierade tidigare:

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

För enkelhetens skull har vi inte optimerat frågan och inte hanterat index eftersom detta inte är en produktionsmiljö.



Testa

Starta spindeln!

$ scrapy crawl stack_crawler

Luta dig nu tillbaka och se din databas fyllas med data!

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


Slutsats

Du kan ladda ner hela källkoden från Github-förvaret. Kommentera nedan med frågor. Skål!

Gratis bonus: Klicka här för att ladda ner ett Python + MongoDB-projektskelett med fullständig källkod som visar hur du kommer åt MongoDB från Python.

Letar du efter mer webbskrapning? Se till att kolla in Real Python-kurserna. Vill du anlita en professionell webbskrapa? Kolla in GoScrape.



  1. Kan jag avgöra om en sträng är ett MongoDB ObjectID?

  2. Mongoose find() RegExp för fältet nummertyp

  3. MongoDB Correct Schema för aggregerad data

  4. Docker-compose - Redis vid 0.0.0.0 istället för 127.0.0.1