7. Web API、スクレイピング

節サブタイトル

自動でデータを収集する方法

7.1. Web APIとスクレイピングとは

Web API はインターネット上に用意されているAPIをプログラムから呼び出す技術のことです。 スクレイピング はウェブサイトから情報を抽出する、コンピュータソフトウェア技術のことをいいます。

ここではPythonを使った実用的なプログラムの例として、Web APIとスクレイピングの演習を行います。

7.2. 環境構築

前章の「 venvとは 」を参考に、venvモジュールを利用して、スクレイピング用のvenv環境を構築します。 venv環境を activate コマンドで有効にし、Web APIとスクレイピングに使用する RequestsBeautiful Soup 4pip コマンドでインストールします。

リスト 7.1 演習用のvenv環境を構築(macOS、Linux)
$ mkdir scraping
$ cd scraping
$ python3 -m venv env
$ source env/bin/activate
(env) $ pip install requests
(env) $ pip install beautifulsoup4
リスト 7.2 演習用のvenv環境を構築(Windows)
> mkdir scraping
> cd scraping
> python -m venv env
> env\Scripts\Activate.ps1
(env) > pip install requests
(env) > pip install beautifulsoup4

7.2.1. Requests

Requests について簡単に紹介します。 Requests はウェブサイトにアクセスしてHTMLなどのデータを取得するためのライブラリです。 Pythonの標準ライブラリ urllib.request でも同様のことは行なえますが、より便利な Requests をここでは使用します。

7.2.2. Beautiful Soup 4

Beautiful Soup 4はHTMLやXMLの中身を解析して、任意の情報を取得するためのライブラリです。 Pythonの標準ライブラリ html.parser でも同様のことは行なえますが、より便利な Beautiful Soup 4 をここでは使用します。 なお、beautifulsoupとbeautifulsoup4が存在しますが、新しい beautifulsoup4 を使うようにしてください。

7.3. シンプルなWeb APIのコード

Web APIの例として、SWAPI.INFOの「Star Wars API」で映画『スター・ウォーズ』シリーズに関する情報を入手してみましょう。

下記のコードを films.py という名前で、先ほど作成したscrapingフォルダ内に保存します(リスト 7.3)。

リスト 7.3 films.py
import requests


def main():
    url = "https://swapi.info/api/films"

    response = requests.get(url)
    films = response.json()

    for film in films:
        print(f"Episode {film['episode_id']}: {film['title']} ({film['release_date']})")


if __name__ == "__main__":
    main()

このコードを実行すると、『スター・ウォーズ』シリーズの旧三部作、新三部作のタイトルと公開日時の一覧を取得できます。(リスト 7.4)。 なお、『スター・ウォーズ』シリーズは制作順序とエピソード番号の順序が異なるので注意しましょう。

リスト 7.4 Star Wars APIを実行
(env) $ python films.py
Episode 4: A New Hope (1977-05-25)
Episode 5: The Empire Strikes Back (1980-05-17)
Episode 6: Return of the Jedi (1983-05-25)
Episode 1: The Phantom Menace (1999-05-19)
Episode 2: Attack of the Clones (2002-05-16)
Episode 3: Revenge of the Sith (2005-05-19)

7.3.1. コードの解説

上記のコードがどういった内容なのかを解説します。

  • Web APIを実行するために requests をインポートします

リスト 7.5 モジュールのインポート
import requests
  • メインとなる処理を main 関数として定義しています。 なお、関数の名前に特に決まりはなく、必ずしも main である必要はありません。

リスト 7.6 main()関数の定義
def main():
  • APIのエンドポイントとなるURLを設定します。映画以外にも登場人物や乗り物、惑星などの情報を取得できるので、興味がある方はドキュメントを読んで試してみてください。

リスト 7.7 APIのエンドポイントURLの設定
    url = "https://swapi.info/api/films"
  • requests.get() にURLとパラメーターを指定して結果を取得します。

  • 結果はJSON形式で返ってくるので、 .json() メソッドでPythonのデータ型(辞書、リスト等)に変換します。

リスト 7.8 Web APIを実行して結果を取得
    response = requests.get(url)
    films = response.json()
  • Pythonデータ型の映画に関する情報から、エピソード番号、タイトル、公開日付を取得して出力します。

リスト 7.9 エピソード番号、タイトル、公開日付の出力
    for film in films:
        print(f"Episode {film['episode_id']}: {film['title']} ({film['release_date']})")
  • 最後に、このスクリプトが実行された時に、 main() 関数を実行するように指定します。

リスト 7.10 main()関数を実行
if __name__ == "__main__":
    main()

コラム: JSON形式

Web APIにデータを送ったり、受け取ったりするときによく使われるデータの形式です。JavaScript Object Notationという、JavaScriptのオブジェクト表記法を使っています。この表記法は項目名と値のペアをテキストで記述したもので、階層構造にも対応しています。人間が簡単に読み取れて、コンピュータからも扱いやすいため、Pythonを含めさまざまな言語で活用されています。

7.3.2. 新たなるWeb API

今回紹介したStar Wars API以外にも興味深いWeb APIがたくさんあります。 いくつか紹介します。

名称

Webサイト

内容

NASA API

https://api.nasa.gov

NASA(アメリカ航空宇宙局)が所有するデータが扱えるAPI

PokeAPI

https://pokeapi.co

ゲームソフト『ポケットモンスター』シリーズに関するデータを取得できるAPI

国立国会図書館サーチ API

https://ndlsearch.ndl.go.jp/help/api/

国立国会図書館の所蔵検索APIや書影検索API

また、Public-APIsには種類別にWeb APIがリストアップされています。 気になるWeb APIがあれば、是非試してみましょう。

7.4. シンプルなスクレイピングのコード

スクレイピングの例として、docs.python.orgの組み込み関数一覧のページ(https://docs.python.org/ja/3.10/library/functions.html)から関数名の情報を抜き出します。

../_images/func-list.png

図 7.1 組み込み関数一覧ページ

下記コードを funcs.py という名前で、scrapingフォルダ内に保存します(リスト 7.11)。

リスト 7.11 funcs.py
import requests
from bs4 import BeautifulSoup


def main():
    url = 'https://docs.python.org/ja/3.10/library/functions.html'
    res = requests.get(url)
    content = res.content
    soup = BeautifulSoup(content, 'html.parser')
    functions = soup.find_all('dl', class_='py function')
    print('件数:', len(functions))
    for func in functions:
        func_name = func.dt.code.text

        # 上記記述だと@staticmethodの関数名が正しく取れないので、取りたい場合はこちら
        # func_name = func.dt.find_all('code', class_='sig-name')[0].text

        print(func_name)


if __name__ == '__main__':
    main()

このコードを実行すると、以下のように関数名の一覧が取得できます(リスト 7.12)。

リスト 7.12 スクレイピングを実行
(env) $ python funcs.py
件数: 52
abs
aiter
all
anext
any
ascii
bin
breakpoint
:

コラム: Pythonのコーディング規約「PEP8」

Pythonには PEP8(ペップエイト) というコーディング規約があります。 チームで開発をする際、人によってプログラムコードの書き方がバラバラだと読みにくいコードになってしまいます。 そのため、PEP8のルールに従う習慣を身につけておくとよいでしょう。

コードがPEP8のルールに従っているかは、 pycodestyle というツールで検証できます(以前はツールの名前もpep8でした)。

pycodestyleは pip install pycodestyle でインストールして使用します。 funcs.py を検証するには、 pycodestyle funcs.py を実行します。

7.4.1. コードの解説

上記のコードがどういった内容なのかを解説します。

  • 以下のコードはRequestsとBeautiful Soup 4をimportして利用できるようにしています。

リスト 7.13 モジュールのimport
import requests
from bs4 import BeautifulSoup
  • メインとなる処理を main 関数として定義しています。 なお、関数の名前に特に決まりはなく、必ずしも main である必要はありません。

リスト 7.14 main()関数の定義
def main():
  • Requestsを使用して、Webページの内容(HTML)を取得します。res.contentにHTMLの中身が文字列データとして入っています。

リスト 7.15 ページの内容を取得
    url = 'https://docs.python.org/ja/3.10/library/functions.html'
    res = requests.get(url)
    content = res.content
  • 次にHTMLをBeautiful Soup 4に渡して解析します。HTMLの解析についてはもう少し詳しく説明します。

リスト 7.16 WebページをBeautiful Soup 4で解析
    soup = BeautifulSoup(content, 'html.parser')
    functions = soup.find_all('dl', class_='py function')
    print('件数:', len(functions))
    for func in functions:
        func_name = func.dt.code.text

        # 上記記述だと@staticmethodの関数名が正しく取れないので、取りたい場合はこちら
        # func_name = func.dt.find_all('code', class_='sig-name')[0].text

        print(func_name)
  • 最後に、このスクリプトが実行された時に、main()関数を実行するように指定します。

リスト 7.17 main()関数を実行
if __name__ == '__main__':
    main()

7.4.2. HTMLの解析の解説

Beautiful Soup 4でHTMLを解析して、値が取り出せましたが、どのように指定しているのでしょうか? 組み込み関数一覧のHTMLを見てみると、以下のような形式になっています。(リスト 7.18)

リスト 7.18 組み込み関数一覧のHTML
<dl class="py function">
    <dt id="abs">
        <code class="sig-name descname">abs</code>
        <span class="sig-paren">(</span><em class="sig-param">
        <span class="n">x</span></em>
        <span class="sig-paren">)</span>
        <a class="headerlink" href="#abs" title="この定義へのパーマリンク"></a>
    </dt>
    <dd>
        <p>数の絶対値を返します。引数は整数、浮動小数点数または
            <code class="xref py py-meth docutils literal notranslate">
                <span class="pre">__abs__()</span>
            </code>
            が実装されたオブジェクトです。引数が複素数なら、その絶対値 (magnitude) が返されます。
        </p>
    </dd>
</dl>

<dl class="py function">
    <dt id="aiter">
        <code class="sig-name descname">aiter</code>
        <span class="sig-paren">(</span>
        <em class="sig-param">
            <span class="n">async_iterable</span>
        </em>
        <span class="sig-paren">)</span>
        <a class="headerlink" href="#aiter" title="この定義へのパーマリンク"></a>
    </dt>
    <dd>
        <p>:term:
            <a href="#id1">
                <span class="problematic" id="id2">`</span>
            </a>
            asynchronous iterable`から :term:
            <a href="#id3">
                <span class="problematic" id="id4">`</span>
            </a>
            asynchronous iterator`を返します。
            <a href="#id5">
                <span class="problematic" id="id6">``</span>
            </a>x.__aiter__()``を呼び出すのと等価です。
        </p>
        <p>なお、:func:
            <a href="#id1">
                <span class="problematic" id="id2">`</span>
            </a>
            iter`とは異なり、:func:
            <a href="#id3">
                <span class="problematic" id="id4">`</span>
            </a>
            aiter`は第二引数を持ちません。
        </p>
        <div class="versionadded">
            <p>
                <span class="versionmodified added">バージョン 3.10 で追加.</span>
            </p>
        </div>
    </dd>
</dl>
(以下続く)

このHTMLを見ると、関数の名前とURLは以下のようにして取得できそうです。

  • 1つの関数の情報は <dl class="py function"> の中に入っている

  • 関数名は <code class="sig-name descname"> タグで囲まれた中に入っている

HTMLの構造がわかったところで、もう一度HTMLを解析しているコードを見てみます。

リスト 7.19 WebページをBeautiful Soup 4で解析
    soup = BeautifulSoup(content, 'html.parser')
    functions = soup.find_all('dl', class_='py function')
    print('件数:', len(functions))
    for func in functions:
        func_name = func.dt.code.text

        # 上記記述だと@staticmethodの関数名が正しく取れないので、取りたい場合はこちら
        # func_name = func.dt.find_all('code', class_='sig-name')[0].text

        print(func_name)

まず、 soup.find_all() メソッドで、全関数の情報が含まれている dl 要素を取得しています。 次に、各関数情報(func変数に入っている)から値を取り出しています。 関数名を取得して、出力しています。

7.5. 作り変えてみよう

RequestsやBeautiful Soup 4の動作を変えて、さまざまなWebページから色んな要素を取得できます。

以下にそれぞれのライブラリの簡単な使い方を紹介します。それ以外にもいろいろな使用方法があるので、ドキュメントを参考にしていろいろ作り変えてみてください。

7.5.1. Requests の主な使い方

ここでは Requests の主な使い方の例をいくつか載せます。 詳細については以下の公式ドキュメントを参照してください。

以下は認証つきのURLにアクセスして、結果を取得する例です。

リスト 7.20 認証付きURLにアクセスする
>>> import requests
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200

POST を行う場合は以下のように、POSTのパラメーターを辞書で定義します。

リスト 7.21 requests で POST する
>>> payload = {'key1': 'value1', 'key2': 'value2'} # POST するパラメーター
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)

GET に ?key1=value1&key2=value2 のようなパラメーター付きでアクセスする場合も同様に、辞書で定義します。

リスト 7.22 requests でパラメーター付で GET する
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
http://httpbin.org/get?key2=value2&key1=value1
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
http://httpbin.org/get?key1=value1&key2=value2&key2=value3

7.5.2. Beautiful Soup 4の主な使い方

ここではBeautiful Soup 4の主な使い方の例をいくつか載せます。 詳細については以下の公式ドキュメントを参照してください。

リスト 7.23 Beautiful Soup 4の使用例
>>> import requests
>>> from bs4 import BeautifulSoup
>>> r = requests.get('https://www.python.org/blogs/')
>>> soup = BeautifulSoup(r.content, 'html.parser') # 取得したHTMLを解析
>>> soup.title # titleタグの情報を取得
<title>Our Blogs | Python.org</title>
>>> soup.title.name
'title'
>>> soup.title.string # titleタグの文字列を取得
'Our Blogs | Python.org'
>>> soup.a
<a href="#content" title="Skip to content">Skip to content</a>
>>> len(soup.find_all('a')) # 全ての a タグを取得しt len() で件数を取得
164

また、 find() find_all() などでタグを探す場合には、タグの属性などを条件として指定できます。

リスト 7.24 find/find_all の使用例
>>> len(soup.find_all('h1')) # 指定したタグを検索
3
>>> len(soup.find_all(['h1', 'h2', 'h3'])) # 複数のタグのいずれかにマッチ
24
>>> len(soup.find_all('h3', {'class': 'event-title'})) # <h3 class="event-title"> にマッチ
5

7.6. まとめ

本節では、Pythonでスクレイピングをする方法を解説しました。

RequestsとBeautiful Soup 4を使いこなすことにより、さまざまなウェブサイトから情報を取得できるようになります。

なお、短時間にWebサイトに大量にアクセスをすると迷惑となるので、そういうことがないようにプログラムを実行するときには注意してください。

7.7. 参考書籍

Pythonでのスクレイピングについてもいくつか書籍が出ています。