フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

人間のためのHTML Parseライブラリ『Requests-HTML』で楽しくデータクローリング

f:id:ikedaosushi:20190915164708p:plain

Pythonを使ったデータクローリング・スクレイピングは、エンジニア・非エンジニアを問わず非常に人気や需要のある分野です。しかし、いざデータクローリングしようとすると、複数ライブラリのAPIや、ライブラリそれぞれの関連性に混乱してしまうことがよくあります。

昨年公開された「Requests-HTML」はそういった問題を解決する「オールインワンでデータクローリングが行える」ライブラリです。ユーザーは「Requests-HTML」のAPIのみを学習するだけで、サイトへのリクエスト、HTMLのパース、要素の取得を行うことができます。またHeadless Chromeを使うこともできます。

このブログでは「Requests-HTML」が生まれた背景と使い方、そして興味深いポイントについて書きます。

なぜ「Requests-HTML」が必要だったか

データクローリング・スクレイピングの人気の高まり

データクローリング・スクレイピングは近年非常に人気が高まっている分野です。Google Trendで過去5年間の人気度の動向を調べたものが次の図です。

f:id:ikedaosushi:20190915162112p:plain

データクローリング・スクレイピングを行う際に必要なライブラリが一通り揃っているという理由で、Pythonがよく用いられます。メジャーなライブラリとしては、HTTPリクエストクライアントの「Requests」、HTMLパーサーの「Beautiful Soup」、エレメントの走査、エレメントからデータ取得をjQueryライクに行うための「PyQuery」、Headless Chromeを使ったJavaScriptの実行のための「Pyppeteer」があります。

問題: 覚えることが多すぎる

Pythonでデータクローリング・スクレイピングを行う際の問題点は、ユーザーが「使うライブラリのAPIをそれぞれ覚え、連携させなければいけない」ということです。もしユーザーがデータクローリング・スクレイピングをしようとした場合、前述した「Requests」、「Beautiful Soup」、「PyQuery」、「Pyppeteer」というライブラリのAPIを(もちろん用途に応じて使わなくて良いライブラリもありますが)覚えなければいけません。また、初学者にとっては、各ライブラリがどういった役割を担っているのか理解するのも一苦労です。

f:id:ikedaosushi:20190915162127p:plain

「Requests-HTML」の登場

2018年2月に登場した「Requests-HTML」はこれらの問題を解決してくれるライブラリです。「Requests」、「Beautiful Soup」、「PyQuery」、「Pyppeteer」をラップして、1つの統一したAPIを提供します。これによりユーザーは「Requests-HTML」のAPIを理解するだけでデータクローリング・スクレイピングを行うことができるようになります。

f:id:ikedaosushi:20190915162149p:plain

「Requests-HTML」の使い方

基本の使い方

一番基本的な使い方はHTMLSessionクラスのインスタンスを作成して、get()でURLにアクセスすることです。「Requests」とAPIは同じなのですが、必ずセッションオブジェクトを作成することが違いでうす。セッションを作成することによりログイン時にクッキーの保持などが行えるようになっています。

from requests_html import HTMLSession

session = HTMLSession()

resp = session.get("https://www.python.jp/")
resp.html.url # => https://www.python.jp/

HTML要素の取得

get()で得られたHTMLResponseオブジェクトのhtmlプロパティからHTML要素にアクセスできます。まずfind()でCSS Selectorで要素を探します。first=Trueにすることで最初の要素をElementオブジェクトとして取得でき(falseの場合Elementオブジェクトの配列)、そこからアトリビュートやテキストにアクセスできます。

# find()で要素を探索
title = resp.html.find('h4.card-title', first=True)

# 要素内部のテキストを取得
title.text # => Pythonとは

# attributesを取得(dict型で取得できる)
title.attrs # => {'class': ('card-title',)}

# 要素の中で更に別の要素を探索
title.find('a') # => [<Element 'a' href='pages/about.html'>]

# 要素内のテキストを検索
r.html.search('日本の{}コミュニティ')[0]) # => Python

非同期処理の実行

非同期処理を行いたい場合はAsyncHTMLSessionオブジェクトを用います。歴代PyCon JPのサイトにアクセスするコードは以下のようになります。

from requests_html import AsyncHTMLSession
asession = AsyncHTMLSession()

async def get_pyconjp_2017():
    r = await asession.get(f"https://pycon.jp/2017/")
    return r

async def get_pyconjp_2018():
    r = await asession.get(f"https://pycon.jp/2018/")
    return r

async def get_pyconjp_2019():
    r = await asession.get(f"https://pycon.jp/2019/")
    return r

results = asession.run(get_pyconjp_2017, get_pyconjp_2018, get_pyconjp_2019)

for result in results:
    print(result.html.url)
# => https://pycon.jp/2018/
# => https://pycon.jp/2019/
# => https://pycon.jp/2017/ja/

JSの実行

次はPypetterを使ってJSの実行を行います。と言ってもarender()を呼び出すだけで非常に簡単です。裏側ではHeadless Chromeを

from requests_html import AsyncHTMLSession
asession = AsyncHTMLSession()

async def exec_js():
    resp = await asession.get("https://pycon.jp/2019/")
    print("before:", resp.html.links) # => set()
        # JSで描画
    await resp.html.arender()
    print("after", resp.html.links) # => {'https://pyconjp.connpass.com/event/139133/', ...}

loop = asyncio.get_event_loop()
loop.run_until_complete(exec_js())

その他便利な使い方

その他、linksで「そのページからリンクされているURLの一覧」が取得できたり、HTMLのパースのみを行うことも可能です。

# linksで「そのページからリンクされているURLの一覧」が取得できる
resp.html.links # => {'http://www.google.com/calendar/...

# HTMLのパースのみを使うことも可能
from requests_html import HTML
doc = """<a href='https://pyconjp/2019'>"""
html = HTML(html=doc)
html.links # => {"https://pyconjp/2019"}

Jupyter Notebookでasyncioを動かす場合の注意

現時点でJupyter Notebook側に問題があり、そのまま動かすとエラーになる場合があるようです。(自分もそうでした)解決するためには下のライブラリをインストールしてください。

github.com

import nest_asyncio
nest_asyncio.apply()

「Requests-HTML」の注目ポイント

「Requests-HTML」について注目するべきポイントは、「ライブラリ自体はあまり仕事をしていない」ことだと思います。なんと「Requests-HTML」は1ファイルのみで775行しかないのです(v0.10.0時点)。「Requests-HTML」がしているのはあくまで「既存ライブリを組み合わせて1つのAPIを提供する」ということだけです。

それでは「Requests-HTML」は「大したことがない」ライブラリなのでしょうか?そうではないことが「公開からのGitHubスターの推移」からわかります。公開当初からユーザーに注目され、その後も広く利用されており、2019年9月現在10000以上のスターを集めています。

実は「ライブラリ自体は多く仕事をせず、既存ライブラリを組み合わせることに徹する」ことは作者であるKennith Reitzの得意技です。Kennith Reitzは「Requests」、「Pipenv」、「Responder」などの作者で、ユーザーに愛されるAPIデザインをする天才でしょう。

以前このブログでも「Responder」について書いたときにKennith Reitzの才能についても書いたので、興味ある方はこちらも参照ください。

blog.ikedaosushi.com

さいごに

この記事では「Requests-HTML」について、登場した背景や便利な機能の使い方、その興味深い性質について書きました。人間のためのHTML Parseライブラリ「Requests-HTML」を使って楽しくデータクローリング、スクレイピングをしていきましょう。

この記事で使用したコードはすべてGitHubにあげています。

github.com

関連リンク