okpy

Pythonエンジニア兼テックリーダーが、多くのプロジェクトとチーム運営から得た実践的な知識を共有するブログです。

PythonでWebデータ収集、まだ1ページずつ手作業でやっていませんか? Scrapyで自動化しよう!

PythonでWebデータ収集、まだ1ページずつ手作業でやっていませんか? Scrapyで自動化しよう!

📝 TL;DR (3行要約)

Scrapyは、Webサイトから構造化されたデータを効率的に抽出するためのPythonフレームワークです。 複数のページを自動で巡回し、必要な情報だけを高速に抜き出す「クローリング」や「スクレイピング」に特化しています。 非同期処理による高速性と、プロジェクトを体系的に管理できる拡張性の高さが最大の利点です。


1. 🤔 一体Scrapyとは何?(核心的な役割と主な使用例)

PythonでWebサイトの情報を集めようと思ったとき、多くの人が Requests でページを取得し、BeautifulSoup でHTMLを解析する方法を学びます。これは1ページや数ページの情報を取得するには非常に優れた方法です。しかし、もし対象がニュースサイト全体、ECサイトの全商品、ブログの全記事だったらどうでしょう?

何百、何千ものページを巡回し、それぞれから同じ形式でデータを抽出し、次のページへのリンクをたどり、エラーが起きても止まらずに処理を続ける…これを RequestsBeautifulSoup だけで作るのは、まるで小さなボートで太平洋を横断しようとするようなものです。不可能ではありませんが、非常に大変で、多くの問題に直面するでしょう。

核心的な役割: 優秀な自動収集ロボット軍団の司令塔 🤖

ここで登場するのが Scrapy です。

Scrapyをひとことで言うなら、「インターネットという巨大な図書館を自動で巡回し、必要な本(情報)だけを効率的に集めてきてくれる、超優秀なロボット軍団とその司令塔」です。

  • 司令塔 (フレームワーク): あなたは「どの図書館(Webサイト)の」「どの棚(ページ)から」「どんな情報(データ)を」「どんな順番で」集めるかという「指令書」を書くだけ。
  • ロボット軍団 (非同期エンジン): Scrapyはあなたの指令書に基づき、多数のロボットを同時に動かします。1体のロボットがあるページを読んでいる間に、別のロボットは次のページを取りに行き、また別のロボットは集めた情報を整理する…といった具合に、無駄なく並行作業でタスクをこなしてくれるのです。

この「司令塔」としての役割こそが、Scrapyが単なるライブラリではなくフレームワークと呼ばれる理由です。データ収集に必要な面倒な処理(リクエストのスケジューリング、エラーハンドリング、データの出力など)の骨組みをすべて用意してくれているので、私たちは「何を抽出するか」という本質的な部分に集中できるのです。

主な使用例: Scrapyが輝く瞬間 ✨

この強力なフレームワークは、特に以下のような大規模なデータ収集タスクで真価を発揮します。

  1. ECサイトの商品データ収集: 特定カテゴリの全商品リストから、商品名、価格、レビュー数、在庫状況などを定期的に収集。競合分析や価格変動のモニタリング、自社サイトへの商品データ登録などに活用されます。Scrapyを使えば、カテゴリページから各商品ページへ自動で遷移し、情報を体系的に集めることができます。

  2. ニュースサイトやブログの記事収集: 特定のキーワードを含むニュース記事や、特定のブロガーの全記事を自動で収集。テキストデータを集めて自然言語処理の分析にかけたり、最新情報を追跡するシステムを構築したりします。ページネーション(「次のページへ」のリンク)を自動でたどる機能は、まさにこのためにあると言っても過言ではありません。

  3. 不動産や求人情報サイトのデータ集約: 複数のサイトに散らばっている物件情報や求人情報を、指定した条件(エリア、給与、職種など)で横断的に収集し、自分だけのデータベースを作成。手作業では不可能な規模の情報を一元管理し、最適な選択肢を見つけ出す手助けをします。

このように、Scrapyは「体系的」かつ「大規模」なデータ収集を「自動化」したい、あらゆる場面であなたの強力な武器となります。


2. 💻 インストール方法

Scrapyのインストールは、Pythonのパッケージ管理ツールであるpipを使えば非常に簡単です。ターミナル(またはコマンドプロンプト)を開いて、以下のコマンドを実行してください。

pip install Scrapy

これだけで、Scrapyとその動作に必要な関連ライブラリがすべてインストールされます。とても簡単ですね!


3. 🛠️ 実際に動作するサンプルコード

百聞は一見に如かず。実際にScrapyがどのように動作するのか、シンプルなサンプルコードで体験してみましょう。

ここでは、スクレイピングの練習用として有名なサイト「Quotes to Scrape」から、名言とその作者、タグを抽出し、次のページも自動で巡回するスパイダー(Spider)を作成します。

以下のコードを quotes_spider.py という名前で保存してください。

import scrapy

class QuotesSpider(scrapy.Spider):
    # スパイダーを識別するためのユニークな名前
    name = "quotes"

    # スパイダーがクロールを開始するURLのリスト
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    # custom_settingsでスパイダーごとの設定を上書きできる
    # サーバーに優しく、1秒間隔でリクエストを送る設定
    custom_settings = {
        'DOWNLOAD_DELAY': 1,
    }

    def parse(self, response):
        """
        start_urlsの各URLからレスポンスを受け取った際に呼び出されるメソッド。
        このメソッドでデータを抽出し、次のリクエストを生成します。
        """
        # ページ内の各名言(quote)をループ処理
        for quote in response.css('div.quote'):
            # CSSセレクタを使ってデータを抽出
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

        # 「Next →」ボタンのリンクを取得
        next_page = response.css('li.next a::attr(href)').get()

        # もし次のページへのリンクが存在すれば
        if next_page is not None:
            # response.urljoin()を使って絶対URLに変換し、
            # 再帰的にparseメソッドを呼び出すリクエストを生成
            yield response.follow(next_page, callback=self.parse)

コードを保存したら、ターミナルでそのファイルがあるディレクトリに移動し、以下のコマンドを実行してください。

scrapy runspider quotes_spider.py -o quotes.json

実行が完了すると、同じディレクトリに quotes.json というファイルが作成されているはずです。ファイルを開いてみてください。サイトの全ページの名言が、きれいに構造化されたJSON形式で保存されているのが確認できるでしょう。手作業では考えられない速さですよね!


4. 🔍 コードの詳細説明

先ほどのサンプルコード、一見すると少し複雑に見えるかもしれませんが、各ブロックの役割を理解すれば、その構造が非常に合理的であることがわかります。チャンクごとに見ていきましょう。

1. インポートとSpiderクラスの定義

import scrapy

class QuotesSpider(scrapy.Spider):
    # ... (以降のコード)
  • import scrapy: まずはScrapyライブラリ本体をインポートします。
  • class QuotesSpider(scrapy.Spider):: ここがScrapyの核心です。私たちは、Webサイトを這い回って情報を集める「クモ(Spider)」の設計図を作ります。Scrapyが用意してくれている scrapy.Spider という基本設計図を継承(専門用語で「サブクラス化」)することで、自作のSpiderに必要な基本機能(リクエストを投げたり、設定を読み込んだり)をすべて受け継ぐことができます。

2. Spiderの初期設定

    name = "quotes"

    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    custom_settings = {
        'DOWNLOAD_DELAY': 1,
    }
  • name = "quotes": このSpiderを識別するための名前です。scrapy crawl quotes のようにコマンドラインから実行する際に使います。プロジェクト内に複数のSpiderがいる場合、この名前で区別します。
  • start_urls: Spiderが仕事を開始する出発点のURLをリスト形式で指定します。Scrapyは起動すると、このリストにあるURLに自動的にリクエストを送信します。
  • custom_settings: このSpiderだけに適用したい特別な設定です。ここでは DOWNLOAD_DELAY1 に設定し、「リクエストを1秒に1回」というペースに制限しています。これは相手のサーバーに負荷をかけすぎないための、非常に重要なマナーです。

3. メイン処理 (parseメソッド)

    def parse(self, response):
        # ... (以降のコード)
  • parseメソッドは、このSpiderの心臓部です。Scrapyが start_urls からダウンロードしたWebページの情報(responseオブジェクト)を引数として、このメソッドが自動的に呼び出されます。
  • responseオブジェクトには、ページのHTMLソースコードはもちろん、HTTPステータスコードやヘッダーなど、リクエストに関するあらゆる情報が含まれています。私たちはこの response を使って、お目当てのデータを抽出していきます。

4. データの抽出と出力 (yield)

        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }
  • response.css('div.quote'): responseオブジェクトが持つ css() メソッドは、CSSセレクタを使ってHTML要素を簡単に選択できる強力なツールです。ここでは「divタグでquoteクラスを持つ要素」をすべて取得しています。
  • for quote in ...: 取得したすべての名言要素を1つずつ処理します。
  • yield { ... }: ここがポイントです!returnではなくyieldを使っています。Scrapyでは、抽出したデータをyieldで次々と放り投げるように返します。すると、Scrapyのエンジンがそれらを裏側で受け取って、コマンドで指定したファイル(今回はquotes.json)に効率的に書き込んでくれるのです。これにより、すべてのデータをメモリに溜め込む必要がなく、非常に効率的な処理が可能になります。
  • quote.css('...').get(): get()は、セレクタに一致した最初の要素のテキストや属性を取得します。
  • quote.css('...').getall(): getall()は、セレクタに一致したすべての要素をリストとして取得します。タグが複数ある場合に便利です。

5. 次のページへの巡回 (response.follow)

        next_page = response.css('li.next a::attr(href)').get()

        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)
  • response.css('li.next a::attr(href)').get(): 「liタグでnextクラスを持つ要素の中にあるaタグ」を探し、その href 属性(つまりリンク先のURL)を取得しています。
  • if next_page is not None:: 次のページへのリンクが見つかった場合のみ、処理を続けます。最終ページではこのリンクは存在しないので、ここでクロールが自然に終了します。
  • yield response.follow(next_page, callback=self.parse): これがScrapyの魔法です!response.follow()は、見つけたリンク先(next_page)に対して、自動で次のリクエストを生成してくれます。そして callback=self.parse という指定により、「次のページをダウンロードし終えたら、またこのparseメソッドを使ってデータを抽出してね」とScrapyにお願いしています。これにより、自分自身を再帰的に呼び出す形で、サイトの全ページを自動で巡回していくのです。

5. ⚠️ 注意点またはヒント

Scrapyは非常に強力ですが、その力を正しく使わなければ思わぬトラブルに見舞われることもあります。初心者が特に気をつけるべき点を2つ紹介します。

  1. サーバーへの配慮を絶対に忘れないこと (倫理的・技術的注意点) 🚫 Scrapyはデフォルトでは非常に高速に動作するため、何も設定しないと相手のWebサイトに短時間で大量のリクエストを送りつけてしまいます。これはサーバーに大きな負荷をかける行為(DoS攻撃とみなされる可能性も)であり、最悪の場合、あなたのIPアドレスがアクセス禁止(IP BAN)にされてしまいます。

    【ヒント】必ず DOWNLOAD_DELAY を設定しよう! サンプルコードでも紹介したように、custom_settings やプロジェクト全体の settings.py ファイルで DOWNLOAD_DELAY を設定する癖をつけましょう。

    # 最低でも1秒は間隔を空けるのがマナー
    DOWNLOAD_DELAY = 1
    # ランダムに遅延させることで、より人間らしいアクセスに見せかけることも可能
    # RANDOMIZE_DOWNLOAD_DELAY = True
    # DOWNLOAD_DELAY = 3 # この場合、1.5〜3秒のランダムな遅延が発生
    
  2. 自分が何者かを正直に名乗る (User-Agentの設定) 🕵️ Webサーバーにアクセスする際、ブラウザやプログラムは「User-Agent」という自己紹介のような情報を送信します。ScrapyのデフォルトのUser-Agentは Scrapy/2.x.x (+https://scrapy.org) のような形式で、一目で「Scrapyのボットだ」とわかります。サイトによっては、このようなボットからのアクセスを機械的に弾く設定になっていることがあります。

    【ヒント】一般的なブラウザのUser-Agentを設定しよう! これも settings.pycustom_settings で設定可能です。あたかも通常のブラウザからアクセスしているかのように見せかけることで、ブロックされるリスクを軽減できます。

    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    

    (※User-Agentの文字列は、ご自身のブラウザのものを調べたり、公開されているリストからコピーしたりして使いましょう)

    もちろん、サイトの利用規約robots.txtを含む)を尊重することが大前提です。自動収集を明確に禁止しているサイトに対してスクレイピングを行うことは絶対にやめましょう。


6. 🔗 一緒に見ておくと良いライブラリ

Scrapyでデータを集めた後、そのデータをどうしますか?多くの場合、集めたデータを分析したり、グラフ化したり、機械学習モデルの入力として使ったりするでしょう。その際に、ほぼ間違いなく必要になるのが Pandas です。

  • Pandas (パンダス) 🐼: Pythonでデータ分析を行うためのデファクトスタンダード・ライブラリです。Scrapyで収集したJSONCSVファイルをPandasの DataFrame という強力な表形式のデータ構造に読み込むことで、データのクリーニング、集計、フィルタリング、可視化などが驚くほど簡単に行えます。

    【学習の流れ】

    1. Scrapyで -o data.csv のようにしてデータをCSV形式で出力する。
    2. 別のPythonスクリプトやJupyter Notebookで、import pandas as pd する。
    3. df = pd.read_csv('data.csv') でデータを読み込む。
    4. df.describe() でデータの要約統計量を見たり、df['author'].value_counts() で著者ごとの名言数をカウントしたりして、データを分析する。

Scrapyで「集める」、Pandasで「分析する」。この2つをマスターすれば、データ収集から分析までの一連の流れをPythonで完結させることができ、あなたのスキルは格段にレベルアップします!


7. 🎉 まとめ

今回は、Webデータ収集を自動化・効率化する強力なフレームワーク「Scrapy」について学びました。

  • Scrapyは単なるツールではなく、データ収集の骨組みを提供してくれるフレームワークであること。
  • Spiderを定義し、データの抽出ロジック (parse) と巡回ロジック (response.follow) を書くだけで、複雑なクローリングが実現できること。
  • yield を使って効率的にデータを処理する仕組みになっていること。
  • サーバーに配慮した丁寧なクローリング設定(DOWNLOAD_DELAYなど)が非常に重要であること。

Scrapyの世界は非常に奥が深いですが、今日学んだ基本だけでも、これまで手作業でやっていた多くのデータ収集作業を自動化できるはずです。

最後に、あなたのスキルを確かなものにするための「挑戦課題」です。

【挑戦課題】 🚀 今回のサンプルコードを改造して、各名言の著者名(例: Albert Einstein)をクリックした先の詳細ページにアクセスし、その著者の「生年月日」と「出身地」も一緒に取得して、JSONファイルに出力してみましょう!

ヒント: 1. parseメソッド内で、著者詳細ページへのリンクを取得します。 2. yield response.follow() を使ってそのリンクをたどりますが、今度は callback に新しいメソッド(例: parse_author)を指定します。 3. 新しい parse_author メソッドを定義し、その中で生年月日と出身地を抽出します。 4. parseメソッドから受け取った名言データと、parse_authorで取得した著者データをうまく結合して yield する必要があります。(response.followmeta 引数を使うとデータを引き継げますよ!)

この課題がクリアできれば、あなたはもうScrapy初心者を卒業です!ぜひチャレンジしてみてください。Happy Scraping!