sql >> データベース >  >> NoSQL >> MongoDB

ScrapyとMongoDBを使用したWebスクレイピングとクロール

    前回、StackOverflowから最新の質問をダウンロードし、結果をMongoDBに保存する基本的なWebスクレイパーを実装しました。 この記事では、スクレーパーを拡張して、各ページの下部にあるページネーションリンクをクロールし、各ページから質問(質問のタイトルとURL)を取得します。

    無料ボーナス: ここをクリックして、PythonからMongoDBにアクセスする方法を示す完全なソースコードを含むPython+MongoDBプロジェクトスケルトンをダウンロードしてください。

    更新:

    1. 2015年9月6日-Scrapy(v1.0.3)とPyMongo(v3.0.3)の最新バージョンに更新-乾杯!

    スクレイピングジョブを開始する前に、サイトの利用規約を確認し、robots.txtファイルを尊重してください。また、短期間に多数の要求でサイトを氾濫させないことにより、倫理的なスクレイピング慣行を順守してください。 スクレイプしたサイトを自分のものであるかのように扱います。

    これは、Real Pythonの人々とGyörgy(Python愛好家であり、ソフトウェア開発者であり、現在ビッグデータ会社で働いていると同時に新しい仕事を探している)のコラボレーション作品です。 Twitterで彼に質問することができます-@kissgyorgy。


    はじめに

    中断したところから続行するには、2つの方法があります。

    1つ目は、parse_itemの応答から次のページのリンクをすべて抽出して、既存のSpiderを拡張することです。 xpath式とyieldを使用したメソッド Request 同じparse_itemへのコールバックを持つオブジェクト 方法。このようにして、scrapyは指定したリンクに対して自動的に新しいリクエストを行います。この方法の詳細については、Scrapyのドキュメントをご覧ください。

    もう1つのはるかに簡単なオプションは、別の種類のスパイダー(CrawlSpider)を利用することです。 (リンク)。これは、基本的なSpiderの拡張バージョンです。 、私たちのユースケースにぴったり合うように設計されています。



    クロールスパイダー

    前回のチュートリアルと同じScrapyプロジェクトを使用するので、必要に応じてリポジトリからコードを取得します。


    ボイラープレートを作成する

    「スタック」ディレクトリ内で、crawlからスパイダーボイラープレートを生成することから始めます。 テンプレート:

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

    Scrapyプロジェクトは次のようになります:

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

    そして、 stack_crawler.py ファイルは次のようになります:

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

    この定型文にいくつかの更新を加える必要があります…



    start_urlsを更新します リスト

    まず、質問の最初のページをstart_urlsに追加します リスト:

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


    rulesを更新します リスト

    次に、rulesに正規表現を追加して、次のページのリンクがどこにあるかをスパイダーに伝える必要があります。 属性:

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

    Scrapyは、これらのリンクに基づいて新しいページを自動的にリクエストし、その応答をparse_itemに渡します。 質問とタイトルを抽出する方法。

    細心の注意を払っている場合、この正規表現ではクロールが最初の9ページに制限されます。これは、このデモではすべてを削りたくないためです。 176,234ページ!



    parse_itemを更新します メソッド

    ここで、xpathを使用してページを解析する方法を記述する必要があります。これは、前回のチュートリアルですでに行ったため、コピーするだけです。

    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
    

    蜘蛛は以上ですが、しないでください まだ始めてください。



    ダウンロード遅延を追加

    settings.py でダウンロードの遅延を設定することで、StackOverflow(さらに言えば任意のサイト)に対応する必要があります。 :

    DOWNLOAD_DELAY = 5
    

    これにより、Scrapyは、新しいリクエストを行うたびに少なくとも5秒待機するようになります。あなたは本質的に自分自身をレート制限しています。これを行わないと、StackOverflowによってレートが制限されます。また、レート制限を課さずにサイトをスクレイプし続けると、IPアドレスが禁止される可能性があります。ですから、よろしくお願いします-スクレイプしたサイトはすべて自分のものであるかのように扱ってください。

    今やるべきことは1つだけです。それは、データを保存することです。




    MongoDB

    前回は50の質問しかダウンロードしませんでしたが、今回はより多くのデータを取得するため、データベースに重複する質問を追加しないようにします。これは、MongoDBアップサートを使用して行うことができます。つまり、質問のタイトルが既にデータベースにある場合は更新し、そうでない場合は挿入します。

    MongoDBPipelineを変更します 以前に定義しました:

    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
    

    簡単にするために、これは本番環境ではないため、クエリを最適化せず、インデックスを処理しませんでした。



    テスト

    スパイダーを始めよう!

    $ scrapy crawl stack_crawler
    

    さあ、座ってデータベースがデータでいっぱいになるのを見てください!

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


    結論

    ソースコード全体をGithubリポジトリからダウンロードできます。以下に質問をコメントしてください。乾杯!

    無料ボーナス: ここをクリックして、PythonからMongoDBにアクセスする方法を示す完全なソースコードを含むPython+MongoDBプロジェクトスケルトンをダウンロードしてください。

    より多くのウェブスクレイピングをお探しですか?必ずRealPythonコースをチェックしてください。プロのウェブスクレイパーを雇おうとお考えですか? GoScrapeをチェックしてください。



    1. MongoDB $ range

    2. MongoDBSSPLライセンス変更の更新

    3. HLLの誤検知が多すぎます

    4. MongoDB$またはAggregationPipelineOperator