• 158
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

クローラー/Webスクレイピング Advent Calendar 2015の7日目です。

IT企業を志望する就活生の方々は「FizzBuzzで100まで数えろ」とか「素数を表示しろ」とかいきなり言われても、得意な言語でならサクッと実装できるスキルは身につけておきたいですね。いつ面接で聞かれるかわかりません。
そして当然のごとく「プリキュア全員表示しろ」と言われても対応できるようにしておくべきでしょう。文字列処理、Webスクレイピングの基本が身についているかどうかがこの問い一つでわかります。面接で既に出題された方も少なくないのでは?

そこで、いくつかのアプローチでWeb上にすでにある情報からプリキュア全員1の情報を表示するシェル芸2を紹介します。この記事を通してWebスクレイピングに必要なUNIXのコマンドを学びましょう。

下準備

curlコマンドでこのURLにアクセスするとそのページのHTMLがザザーッと流れてくるので、そこからプリキュア全員の名前を抽出します。

Terminal
$ curl -Lso- goo.gl/AqZuEg
<!DOCTYPE html>
<html lang="ja" dir="ltr" class="client-nojs">
<head>
<meta charset="UTF-8" />
<title>プリキュアシリーズ - Wikipedia</title>
<script>document.documentElement.className = document.documentElement.className.replace( 
(以下続く...)

解答例1: Grepと正規表現を使った標準的なアプローチ

Terminal
$ curl -Lso- goo.gl/AqZuEg | grep -oP '<td><a.*#.*?>\K.*?(?=</a>(<br />|<s))' | nl
     1  キュアブラック
     2  キュアホワイト
     3  シャイニールミナス
     4  キュアブルーム
     5  キュアブライト
     6  キュアイーグレット
     7  キュアウィンディ
     8  キュアドリーム
     9  キュアルージュ
    10  キュアレモネード
    11  キュアミント
    12  キュアアクア
    13  ミルキィローズ
    14  キュアピーチ
    15  キュアベリー
    16  キュアパイン
    17  キュアパッション
    18  キュアブロッサム
    19  キュアマリン
    20  キュアサンシャイン
    21  キュアムーンライト
    22  キュアメロディ
    23  キュアリズム
    24  キュアビート
    25  キュアミューズ
    26  キュアハッピー
    27  キュアサニー
    28  キュアピース
    29  キュアマーチ
    30  キュアビューティ
    31  キュアハート
    32  キュアダイヤモンド
    33  キュアロゼッタ
    34  キュアソード
    35  キュアエース
    36  キュアラブリー
    37  キュアプリンセス
    38  キュアハニー
    39  キュアフォーチュン
    40  キュアフローラ
    41  キュアマーメイド
    42  キュアトゥインクル
    43  キュアスカーレット

curl, grepの2つのコマンドを使うだけで、最新のプリキュア一覧(2015年12月現在43人)がわかります。

解説

curl

-L -- リダイレクトがあったらリダイレクト先の情報を取る
-s -- 余計な出力をしない
-o -- レスポンスボディの出力先を指定する

curlコマンドは、URLを入力すると、HTML(レスポンスボディ)を出力してくれます。そのまま使うと通信量などの情報を表示してくれるのですが-sオプションをつけると消してくれます。それと併せ、-oとハイフン(-)を指定すると標準出力に通信で得られたHTMLのみを出してくれます。

Terminal
$ curl -so- example.org

なお、curlで短縮URLを扱う場合-Lオプションを使うと良いです。リダイレクトが入っても、きちんと短縮される前のURLにアクセスしてくれます。

grep

-o -- マッチした部分のみを表示
-P -- Perl正規表現を使う

ご存知、特定の文字列を含んだ行を残すコマンドです。オプションをよく見てみるとちゃっかり色々できちゃうのがgrepコマンドの面白さです。-Pオプションを使うとPerlと同じ正規表現がつかえます。そしてPerlの正規表現で使える\K(Keep)という文字より左側にあるパターンや、先読みと後読みの中のパターンはgrepでは"マッチ部分"としてみなされません。なのでマッチ部分のみを出力できる-oオプションをつけた時に、出力の対象にはなりません。結果的にgrepのみで特定の文字列を切り取ることができます。
Wikipedia上では、プリキュアの名前は下記のパターンのどちらかで記載されているので、プリキュア名前後のパターンを\Kや先読みで指定して、プリキュア名だけを抜き出します。

  • プリキュアシリーズページにあるプリキュア名記載パターン

    • 1: <td><a.*#.*?>プリキュア名</a><br />
    • 2: <td><a.*#.*?>プリキュア名</a><s
  • 上記2パターンにマッチするPerl正規表現

RegularExpression
<td><a.*#.*?>\K.*?(?=</a>(<br />|<s))

<td><a.*#.*?>\K\Kパターン利用箇所。(?=</a>(<br />|<s))の箇所が正規表現の「肯定的先読み」のパターンです。.*?の箇所がプリキュア名に該当し、出力対象となる箇所です。4

解答例2. Xpathを使ったアプローチ

Terminal
$ curl -Lso- goo.gl/AqZuEg | \
sed 's|</a>|\n&|g' | \
xmllint --xpath '//*[@id="mw-content-text"]/table[4]/*/td[1]/a/text()' --html - | nl
     1  キュアブラック
     2  キュアホワイト
     3  シャイニールミナス
     4  キュアブルーム
     5  キュアブライト
     6  キュアイーグレット
     7  キュアウィンディ
     8  キュアドリーム
     9  キュアルージュ
    10  キュアレモネード
    11  キュアミント
    12  キュアアクア
    13  ミルキィローズ
    14  キュアピーチ
    15  キュアベリー
    16  キュアパイン
    17  キュアパッション
    18  キュアブロッサム
    19  キュアマリン
    20  キュアサンシャイン
    21  キュアムーンライト
    22  キュアメロディ
    23  キュアリズム
    24  キュアビート
    25  キュアミューズ
    26  キュアハッピー
    27  キュアサニー
    28  キュアピース
    29  キュアマーチ
    30  キュアビューティ
    31  キュアハート
    32  キュアダイヤモンド
    33  キュアロゼッタ
    34  キュアソード
    35  キュアエース
    36  キュアラブリー
    37  キュアプリンセス
    38  キュアハニー
    39  キュアフォーチュン
    40  キュアフローラ
    41  キュアマーメイド
    42  キュアトゥインクル
    43  キュアスカーレット

解説

xmllint

--xpath -- XPathを読んでDOM要素を取り出す。
--html  -- HTMLをパースする。

xmllintという、XMLを整形したりパースしたりできるコマンドがあります。HTMLのDOM要素を取り出すアプローチはいくつかありますが、その中でもxmllintは多くのLinuxやMacには標準で入ってるのが強みです。人様のサーバに入っても大抵使えるので、いざというときのために使い方を覚えておいて損はないでしょう。

--htmlというオプションをつけると、HTMLのような多少マナーの悪いXMLでもよしなにパースしてくれます。

Terminal
$ xmllint --html file.html

また、下記のようにハイフン" - "をつけると、パイプから受け取った標準出力を扱ってくれます。

Terminal
$ cat file.html | xmllint --html -

このコマンドは、XPathという表現を使って特定のDOM要素やそのテキストを抽出できます。WebスクレイピングにおいてXPathは大きな威力を発揮します。XPathってなに?どう書くの?ということについては、同じAdvent Calendarにある他の記事によくまとめられているのでそちらに譲ることにします。

例えばWikipediaのプリキュア一覧テーブルから一番最初のプリキュアを抜き出すXPathはこんな感じ。Google Chromeのデベロッパーツールを使えば簡単に特定できます。

XPath
//*[@id="mw-content-text"]/table[4]/tr[2]/td[1]/a/text()

つまり端末上ですと。

Terminal
$ curl -Lso- goo.gl/AqZuEg | \
xmllint --xpath '//*[@id="mw-content-text"]/table[4]/tr[2]/td[1]/a/text()' --html -
キュアブラック

となります。皆さんご存知、最初のプリキュアであるキュアブラックが表示されましたね。また、下記のようにtr[2]tr[3]にすればキュアホワイトが表示されます。

Terminal
$ curl -Lso- goo.gl/AqZuEg | \
xmllint --xpath '//*[@id="mw-content-text"]/table[4]/tr[3]/td[1]/a/text()' --html -
キュアホワイト

ということでテーブルのtr要素をXPathのワイルドカード(*)で全部列挙したこちらのXPathで、プリキュア全員を出すことができるわけです。

XPath
//*[@id="mw-content-text"]/table[4]/*/td[1]/a/text()

解答例3.XMLに束縛されないアプローチ

短縮URLのページはニコニコ大百科のプリキュアの一覧

Terminal
$ w3m -dump http://goo.gl/LAom | \
grep -oP '(?<=/ ).*(?=●)' | \
sed 's/・/\n/' | nl
     1  キュアブラック
     2  キュアホワイト
     3  シャイニールミナス
     4  キュアブルーム
     5  キュアブライト
     6  キュアイーグレット
     7  キュアウィンディ
     8  キュアドリーム
     9  キュアルージュ
    10  キュアレモネード
    11  キュアミント
    12  キュアアクア
    13  ミルキィローズ
    14  キュアピーチ
    15  キュアベリー
    16  キュアパイン
    17  キュアパッション
    18  キュアブロッサム
    19  キュアマリン
    20  キュアサンシャイン
    21  キュアムーンライト
    22  キュアメロディ
    23  キュアリズム
    24  キュアビート
    25  キュアミューズ
    26  キュアハッピー
    27  キュアサニー
    28  キュアピース
    29  キュアマーチ
    30  キュアビューティ
    31  キュアハート
    32  キュアダイヤモンド
    33  キュアロゼッタ
    34  キュアソード
    35  キュアエース
    36  キュアラブリー
    37  キュアプリンセス
    38  キュアハニー
    39  キュアフォーチュン
    40  キュアフローラ
    41  キュアマーメイド
    42  キュアトゥインクル
    43  キュアスカーレット
    44  キュアフラワー
    45  キュアエコー

キュアエコーとキュアフラワーが余計に入っていますが、全員は網羅しています。

解説

w3m

-dump -- Webページをテキスト化して出力する

本来はCLI上でブラウジングするためのソフトウェアです。URLを指定すると、端末上でテキスト化されたWebページを対話画面で閲覧できます。ただし-dumpオプションを指定すると、対話画面にならず、テキスト化されたページをそのまま標準出力できます。

  • シンプルなテーブル要素があるページをw3mで表示
Terminal
$ w3m -dump http://elze.tanosii.net/d/kenmei.htm
県番号,都道府県名,県庁所在地一覧

┌────────┬──────────────────────┬────────────────────┐
│ 県番号 │      都道府県名      │     県庁所在地     │
├────────┼──────────────────────┼────────────────────┤
│   1    │ 北海道(ほっかいどう) │ 札幌(さっぽろ)     │
├────────┼──────────────────────┼────────────────────┤
│   2    │ 青森県(あおもり)     │ 青森(あおもり)     │
├────────┼──────────────────────┼────────────────────┤
│   3    │ 岩手県(いわて)       │ 盛岡(もりおか)     │
(以下続く...)

WikipediaのようなDOM要素がキッチリしたページではXPathが使いやすいですが、シンプルなページのスクレイピングではcurlなどでDOM要素を直接見るよりも、w3mを使ったほうが簡単にスクレイピングできる場合が結構あります。このコマンドを使うと、全ての結果をXMLではなく、純粋なプレーンテキストで表示してくれます。また、色々面倒なこともやってくれます5

例えば第12回シェル芸勉強会のQ4で、北から順に47都道府県全てを出力せよという問題がありました。参加者の一人が、w3mを使って非常に短くシンプルに解かれていたため、関心しました。

  • w3mを使って47都道府県を表示
Terminal
$ w3m -dump http://elze.tanosii.net/d/kenmei.htm | awk '$0=$4' | sed '1d'
北海道(ほっかいどう)
青森県(あおもり)
岩手県(いわて)
宮城県(みやぎ)
(以下続く...)
  • (参考)XPathを使って47都道府県を表示
Terminal
$ curl -so- http://elze.tanosii.net/d/kenmei.htm | \
xmllint --xpath '/html/body/table/*/td[2]/text()' --html - | \
nkf | sed 's/)/&\n/g'

むすび

いかがでしたでしょうか。一つの課題を解くだけでも、いくつものアプローチがあり、多くのコマンドを学べるのでたのしいですね。PerlのモットーにThere's More Than One Way To Do It.「やり方はひとつじゃない」という言葉がありますが、まさにCLIのコマンドをつなげる作業も、そんな感じがありますね。
「こんなやり方あるよ!」という方はぜひコメント欄に別解をお願いします。


  1. 基本的に、TVシリーズに出演するメインキャラクターのみを対象とします。キュアエコーやキュアフラワー、キュアゴリラ、アローハプリキュア、キュアテンダーなどのプリキュアは必須ではないとします。シャイニールミナスやミルキィローズは正確に言うと妖精の力を使って変身をしていないため「伝説の戦士 プリキュア」かどうか微妙ですが今回は含みます。ただし夢見る乙女はみんな「プリキュア」です。 

  2. マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。 https://blog.ueda.asia/?page_id=1434 

  3. この記事自体は2015-12-05日に出されました。2015-12-24に一回更新しました。そこからまたページの構造が変わっていると、上手く動かないかもしれません。 

  4. .*?の表現の中にある?は最短マッチを意味します。 

  5. 文字エンコーディングがおかしくても、表示できるようにしてくれます。加えて、本来はブラウジング用途のコマンドなだけあって、短縮URLにアクセスしても勝手にリダイレクトしてくれます。