Pythonを使ったスクレイピングの方法!初心者向けに解説

現在、機械学習の関係でデータを収集するスクレイピングの技術が必要とされています。
しかしながら、スクレイピングといっても、注意てすべき点やそもそもどのフレームワークで開発すべきか
といった部分がわかりにくく困っている人もいるでしょう。

そこで、今回はpythonを使用したスクレイピングの実装方法とそれに付随した技術を紹介していきます。



スクレイピングとは

概要

スクレイピングとは、ウェブページを巡回して必要な情報を収集していくことをさします。
これは機械学習をする時に、データを取ってくる時によく使われます。
例えば、競馬の情報がほしいとなった場合は、競馬の戦績や馬の情報などの情報が保存されてるウェブページを探して、そのページ内の情報を収集していくという感じになります。

また、スクレイピングは特定のURLにアクセス、つまりhttpリクエストから帰ってきたhtmLから情報を抽出していきます。なので上の図のように、最終的に手元に帰ってくるのはHTMLとなります。

注意点

ただし、一つだけ注意点があります。それは収集速度のみを追求したスクレイピングはウェブページのサーバーにひどい負荷を書けることになります。
そして最悪の場合、威力業務妨害などで訴えられる可能性があります。
仮にそうでなくても、著作権の関係で訴えられる可能性もあります。

なので、ここらのことを念頭において実装に注意してください。

スクレイピングの方法

先程の構成図ように、スクレイピングはHTTPリクエストを投げたり、HTMLから情報を抜いたりなど、複数の要素で構成されています。そのため普通に行う場合は複数のライブラリを使い分けて、且つ対象のページに合わせて作らなければならず、開発コストが非常に高いです。
例えば、

  • リクエスト
  • request

  • htmlの抽出
  • beautifulsoup

さらに、条件によっては、ページを巡回し、データをDBにためて、などの要素が絡むと、実装のみでなく設計も重要になってきて、開発するだけでも結構な時間を使ってしまいます。

便利なライブラリ: scrapy

そこで、今回使用するのが、scrapyです。
scrapyは先程解説した、リクエストや、クローリングといった機能をすべて組み込んで簡単に設定できるスクレイピングフレームワークです。
さらにscrapyの基本構造をもとにカスタマイズしていくことで、設計などを気にせずにスクレイピングツールを作成することができます。

https://cdn-ak.f.st-hatena.com/images/fotolife/t/todays_mitsui/20160924/20160924231058.png

scrapyの構造図

install

まず、scrapyをインストールしましょう。

 pip install scrapy 
 conda install scrapy 

プロジェクト作成

scrapyには起動に必要な機能をコマンドから生成する機能があります。

 scrapy startproject <プロジェクト名> 

これにより以下の構造が生成されます。

testProject/
├── scrapy.cfg
└── testProject
    ├── __init__.py
    ├── items.py // スクレイピングする対象の変数を作成する
    ├── middlewares.py //
    ├── pipelines.py //
    ├── __pycache__
    ├── settings.py // クローリングやダウンロードの設定を行う
    └── spiders // ここに自分のスクレイピングのスクリプトを作成する
        ├── __init__.py
        └── __pycache__

実装例

さて、実装例です。
今回は、特定のドメインを持つウェブサイトを巡回(クローリング)してスクレイピングしていく
というのをしていきたいと思います。

まず例として今回は yahoo のトップページからニュースの情報を抜いていきたいと思います。
https://news.yahoo.co.jp/

取得する情報の決定

まずなんの情報を取得するのかを決定しましょう。

まず、 testProject/items.pyを開きそこに実装していきます。

scrapyの場合では、 Fieldというクラスから変数を作成して収集した情報を
格納する変数を作ります。

import scrapy

class TestprojectItem(scrapy.Item):
    # define the fields for your item here like:の
    name = scrapy.Field()
    pass

クローリング

巡回(クローリング)について実装していきます。
ここで必要なものは

  1. クローリングスタート地点
  2. クローリングのルールの決定
  3. クローリングの対象

です。

今回はシンプルな構造のため、
クローリングのスタート地点のみで実装出来るものを行います。

# -*- coding: utf-8 -*-
import scrapy
from testProject.items import TestprojectItem

class TestSpider(scrapy.Spider):
    name = "test_spider"
    start_urls = [
        "https://news.yahoo.co.jp/" // これがクローリングのスタート地点のURL
    ]

    def parse(self, response):
        """
        ページの巡回経路を記述する関数。
        :return: None
        """
        news_list = response.xpath("//body//ul[@class='toptopics_list']//li")
        self.logger.critical(news_list)
        for news in news_list:
            self.logger.critical(news.xpath("a/@href").extract())
			# ニュースのリンクから情報からもう一度リクエストを投げる
            yield scrapy.Request(url=news.xpath("a/@href").extract()[0],callback=self.parse_page)

スクレイピング

次は情報取得(スクレイピング)です。
先程の図のように、リクエストを投げて帰ってきたHTMLから情報を取得します。
scrapyの場合は css selector と xpathが使えます。
今回はあまり知られていないxpathを用いて実装していきましょう。

  • 要素の指定
  • `//h1`

  • 要素のテキスト
  • `//p/text()`

  • クラス名などのプロパティを指定
  • `//a[@id=”test”]`

  • 兄弟要素の特定の物だけを指定
  • //li[1]/text()

これだけで来ていれば、大抵のデータ抽出は問題ないでしょう。

これ以外の記法は下を参照してください。

実装例

/TestProject/spiders/以下に testSpider.pyというファイルを作り以下のコードを書いていきましょう。

# -*- coding: utf-8 -*-
import scrapy
from testProject.items import TestprojectItem

class TestSpider(scrapy.Spider):
    name = "test_spider" # スパイダーの名前(scrapyを実行する際に必要なスクリプトの名前のようなもの)
    start_urls = [
        "https://news.yahoo.co.jp/" # どの地点からクローリングを始めるかの指定
    ]

    def parse(self, response):
        """
        ページの巡回経路を記述する関数。
        :return: None
        """
        news_list = response.xpath("//body//ul[@class='toptopics_list']//li") 
        for news in news_list:
            self.logger.critical(news.xpath("a/@href").extract())
            yield scrapy.Request(url=news.xpath("a/@href").extract()[0],callback=self.parse_page)

    def parse_page(self, response):
        item = TestprojectItem()
        item["title"] = response.xpath("//h2[@class='tpcNews_title']/text()").extract()
        item["summary"] = response.xpath("//p[@class='tpcNews_summary']/text()").extract()
        yield item

これは少々ややこしいので解説していきます。
まず流れとしては以下のサイクルを回しています。

* ニュースリストのHTML情報を抽出
* ニュースリストの部分からaタグのリンク先のURL情報を取得
* 取得したURLをもとにもう一度リクエストを投げ、どのように抽出するかをcallbackで指定する
* 情報を取得してitemに格納して戻り値として返す

↑を繰り返す

まずscrapyは実行されると指定したURLにリクエスト投げて、その結果がレスポンスとして帰ってくるのですが、
これは 11行目のparseの関数の第二引数に格納されます。(以下レスポンスオブジェクトと呼びます)

このレスポンスオブジェクトには指定したURLのレスポンスつまりHTMLがbodyという変数として格納されていて、
それに対して抽出を行う関数がいろいろ実装されています。

今回はxpathを使用して抽出していますが、それに該当するのが、16~行目の部分です。

今回はニューズのリストの部分のリンクから飛んでその先の情報を取得しています。

まず具体的にいうと、
16~19行目でニュースのリ。スト部分を取得してそこから aタグのリンク先のURL情報を取得しています。
その取得したURLをもとに再度Requestを投げているのが、19行目です。

リクエストに対してどのように値を抽出するかの関数が21行目のparse_pageとなります。

実行

それでは、実行してみましょう

実行コマンドは、自身で実装したスクリプトの階層で以下のコマンドを実装します。
今回は確認しやすいようにcsvで吐いてみましょう。

scrapy runspider <スパイダーの名前> -o <ファイル名> -t <ファイルフォーマット>

なので、今回の場合だと、↓の感じになります。

cd testProject/spiders
scrapy runspider test_spider -o test.csv -t csv

これで、
testProject/spiders
にcsvが生成されれば成功です。

debug

これで実行ができたわけですが、毎回 HTMLの構造を見て、pythonにソースコードを書いて、
結果を検証するというのは、非常に面倒です。特に、得たい情報を抽出できているのか、それはxpathの問題なのかをチェックぐらいはチェックをしたいです。
なので、scrapyのshellを使って検証しましょう。

shell の起動

scrapy shell

URLの指定

fetch (<URL>)

xpathで指定してみる

resposen.xpath(<xpath>).extract()

これで結果を表示してみます。

設定

scrapyはsetting.pyというものからデータを諸々の設定を行います。
例えば、クローリングのスピードや一回取得したページは無視するのかといったものです。

たいていの場合は

 <パラメーター名>: パラメータ

となっています。
今回は、一回のリクエストに数秒待つという処理を入れましょう。

setting.pyに

DOWNLOAD_DELAY: 2

の一行を加えましょう、これで一つのページからデータをダウンロードした後2秒待つという設定になりました。

その他のパラメータの設定などは以下のページに一覧があるのでそこを参照してください。
https://docs.scrapy.org/en/latest/topics/settings.html

SPAやログインが必要なページ

さて、これだけスクレイピングが出来るようになったのですが、これだけでは不十分です。
例えば、ログインしないと見れないページ、やjavascruptを非同期でページをロードする構成のページ(SPA)ではscrapyだけでは完全な情報を取得することができません。
なので、ブラウザをソースコードから操作できるライブラリを導入することで、直接フォームに値をいれてログインしたり、
ロードボタンを押したりなどを行うことができようになるので、これを組み合わせて使いましょう。

selenium

今回はseleniumというブラウザ操作ツールを使用することで開発していきます。
公式サイト

  • install
  •  pip install selenium 
     conda install selenium 
  • setup
  • このツールはブラウザをコードを書いて操作できる為、ログインフォームに自身の情報を入力してログインなんてことも出来ちゃいます。

    1. 要素の取得
    2.         driver.find_element_by_id("id_name") # HTMLのID名でデータを取得する
              driver.find_element_by_class("class_name") # HTMLのID名でデータを取得する
          

      もっと詳しい情報は
      こちらを参照

    3. フォーム入力
    4.         form = driver.find_element_by_tag("form")
              form.sendKey("inout text")
          
    5. 送信
    6.         form = driver.find_element_by_tag("form")
              form.sendKey("inout text")
              form.submit()
          

    ログイン処理のサンプル

        form = driver.find_element_by_tag("form")
        email = driver.find_element_by_id("email") # email入力フォームの情報を取得
        email.sendKey("inout text")
        pass = driver.find_element_by_id("password") # password入力フォームの情報を取得
        pass.sendKey("inout text")
        form.submit() # 送信
    

    scrapyとの連携

    さて Seleniumの構成と役割の解説はおわりましたので、Scrapyと連携させてみましょう。

    scrapyにはmiddlewareといって特定の処理のタイミングに(URLにリクエスト投げる前に、エラーが発生した時に等)に別の処理を挟める機能があるのですが、今回はわかりやすくするため、一つのファイルに記述します。

    # -*- coding: utf-8 -*-
    import scrapy
    from testProject.items import TestprojectItem
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.chrome.options import Options
    
    class TestSpider(scrapy.Spider):
        name = "test_spider"
        start_urls = [
            "https://news.yahoo.co.jp/"
        ]
        def parse(self, response):
            """
            ページの巡回経路を記述する関数。
            :return: None
            """
            options = Options()
            options.add_argument("--headless") # ヘッドレスモードのオプションを追加
            driver = webdriver.Chrome(options=options)
            self.driver.get(url="https://news.yahoo.co.jp/") # URL を指定
    
            # ここでログインやクリックなどの処理を挟む
    
            response.replace(body=driver.page_source) #レスポンスオブジェクトのHTMLをseleniumのものと差し替える
            news_list = response.xpath("//body//ul[@class='toptopics_list']//li")
            for news in news_list:
                self.logger.critical(news.xpath("a/@href").extract())
                yield scrapy.Request(url=news.xpath("a/@href").extract()[0],callback=self.parse_page)
    
        def parse_page(self, response):
            item = TestprojectItem()
            item["title"] = response.xpath("//h2[@class='tpcNews_title']/text()").extract()
            item["summary"] = response.xpath("//p[@class='tpcNews_summary']/text()").extract()
            yield item
    

    構成としては、ページへのリクエストと巡回はseleniumにまかせて、データの抽出処理をscrapyに任せるという形になります。
    このように、クリックやログインなどややこしい処理はseleniumにまかせて、クリックやログイン後のHTMLを取得してそれらのHTMLを取得します。
    そして、そのHTMLを取得してそれをscrapyに渡して、scrapyの機能でデータを抽出していきます。

    まとめ

    スクレイピングは機械学習に限らず、自分で特定の情報のデータベースを構築したいなどの場合でも必要となるスキルです。しかし、設定を間違えると最悪、警察のお世話になるかもしれないので、そこを注意した上で良きデータ収集ライフを送りましょう。









    この記事をかいた人

    assa

    京都でエンジニアをやっています。assaまたはえつーと覚えてください。 本業はwebで趣味でいろいろいじっています。よしなに