Python
スクレイピング
阿部寛
阿部寛Day 19

阿部寛さんのHPをスクレイピングしてリンク切れを検知する

この記事は阿部寛 Advent Calendar19日目の記事です。
※Webの高速化1は専門外なのでできません。ご了承ください。

阿部寛さんのHPを見て思ったこと

ご存知の通り2阿部寛さんのホームページは、高速に画面遷移するホームページです。サクサク動くのでストレスを感じません。ユーザ体験として素晴らしいです。

ですが、阿部寛さんのホームページで1つだけ残念だと思ったことがありました。それはリンク切れです。
阿部寛さんのホームページでは、サイトのいたるところに過去の出演作品ページへの外部リンクが貼られています。しかし、番組や映画が終わって一定期間が経つと、リンク先のページが閉鎖になってしまいます。そのため、自然とリンク切れが増えていってしまいます。
「このドラマ懐かしいなー」と思ってクリックしたリンク先が「404 Not Found」とか表示されると、ちょっと残念ですよね。これはユーザ体験としてはよろしくないわけです。

スクレイピングしてリンク切れをチェックする

今回は阿部寛さんのホームページをスクレイピングすることで、リンク切れになっているリンクを探してみることにしました。
Pythonでのスクレイピングではrequestsライブラリが便利なので、こちらを使ってスクレイピングしていきます。

手順

1. 仮想環境の作成(任意)

requestsライブラリをインストールするため、環境を汚したくない方はPythonの仮想環境を作成した方が良いと思います。仮想環境の作成方法はここでは割愛します。

2. ライブラリのインストール

requestsライブラリ

今回使用するrequestsライブラリは標準では使用できませんのでpipでインストールする必要があります。

$ pip install requests

lxmlライブラリ

htmlのパースに使用するlxmlライブラリもpipでインストールします。

$ pip install lxml

libxml2,libxsltのインストール(Macの場合)

lxmlライブラリの内部で使用しているモジュールをインストールします。Macの場合はhomebrewでインストールできました。

$ brew install libxml2 libxslt

3. スクレイピングするコードの作成

3-0. 完成品

完成したソースはこちらになります。

こちらを実行すると以下のような出力が得られます。

出力結果
$ python checklink.py
(前略)
リンク切れしているURLがあります
http://www.tbs.co.jp/e-housoku/
http://www.tbs.co.jp/mayonaka/
http://www.tbs.co.jp/mlc/
http://www.bunkamura.co.jp/cocoon/lineup/shosai_09_coast.html
http://www.bunkamura.co.jp/cocoon/lineup/shosai_08_dogen.html
http://www.tsuka.co.jp/kitaku/butai/monte2002/poster.htm
http://www.rlsm.bias.ne.jp/%7Esong/shashin/shashin-kan.htm
http://www.tsuka.co.jp/kitaku/butai/monte2002/poster3.html
http://www.tsuka.co.jp/kitaku/butai/monte2002/arasuji.html
http://www.s-woman.net/bebunko/016/index.html

ライブラリの使い方を中心に説明します。

3-1. ページの取得

requests.get(url)関数を使用することでページ情報を取得できます。
今回はホームページ内をクローリングする関係で、Sessionオブジェクトのgetメソッドを使用しています3が、取得できる内容は同じです。

session = requests.Session()
response = session.get(start_url)

3-2. 取得情報のパース

get(url)で取得した内容をパースします。htmlの情報はresponse.contentに記録されていますので、これをhtmlとして解釈し、パースします。

root = lxml.html.fromstring(response.content)

3-3. 相対URLの絶対URL化

requests.get(url)(またはsession.get(url))の引数にはスキームから始まる絶対URLを指定する必要があります。
今回はページ内のリンクをクローリングするので、ホームページ内遷移を記述した相対URLに対してもページの取得を行います。そのために相対URLを絶対URLに変換します。
絶対URLへの変換はパースして得られたオブジェクトのmake_links_absolute()メソッドを使うと簡単にできます。

root.make_links_absolute(response.url)

3-4. 要素・属性を取得する

パースして得られたオブジェクトでは、CSS使われるセレクタでHTML要素を取得できます。また、HTML要素のオブジェクトでは.属性名で属性にアクセスすることができます。このあたりの書き方はjQueryやJavaScriptの書き方に似ており理解しやすいです。
今回はページ内のリンクを全て取得したいため、セレクタa[href]に該当するオブジェクトのhref属性を取得する処理を書きました。

# href属性を持つ<a>要素の取得
for atag in root.cssselect('a[href]'):
    # href属性の取得
    url = atag.get('href')

3-5. ステータスコードのチェック

requests.get(url)で得られたオブジェクト内のstatus_codeをチェックすることで、HTTPステータスコードを確認できます。
今回はとりあえず400以上を対象にしました。

if res.status_code >= 400:
    # リンク切れだった場合の処理を記述
    yield url

3-6. レスポンススピードのチェック(おまけ)

阿部寛さんのHPが爆速なのに、外部リンクのレスポンスが遅いとストレスを感じかねないので、レスポンスが遅いページも一応チェックしておきます。4
レスポンスの速度はレスポンスオブジェクトのelapsed.total_seconds()で確認できます。

elif res.elapsed.total_seconds() > 1:
    # レスポンスが遅い場合の処理を記述
    print("このリンク先は遅い:" + url)

感想

requestsライブラリが便利で助かりました。スクレイピングでは非常に使いやすいライブラリだと思います。
外部リンクのリンク切れはレガシーなHPに限らず発生しうる問題ですが、リンクをいちいちクリックしてチェックするのは効率が悪いです。コード化する、専用のツール5を使うなどして管理の手間を省くのが大事だと思いました。

参考


  1. 阿部寛 Advent Calendarの説明に「阿部寛さんのHPをはじめとするレガシーなHPを今の技術でどのように高速化するかに関するアドベントカレンダーです。」とあります。 

  2. ご存知ない方はこちらを参照。 

  3. Sessionオブジェクトを使用すると一度確立したTCPコネクションを使い回すのでサーバ負荷を軽減することができるそうです。 

  4. 出力結果で「(前略)」と書いた箇所にページ取得の時間が1秒を超えたページを表示するようになっています。 

  5. リンク切れのチェックだけであれば、もっと簡単にチェックできるツールが多分あります。Googleのサーチコンソールとか。