プリキュアで学ぶワンライナーWebスクレイピング

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">「確かに人事にはLinuxができる人が欲しいとは言ったが、まさか顔合わせでcurlとgrepでプリキュア全員の名前を端末で出力するような奴が来るとは思わなかった。」</p>&mdash; ぐれさん (@grethlen) <a href="https://twitter.com/grethlen/status/524497036528582656">2014, 10月 21</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

[クローラー／Webスクレイピング Advent Calendar 2015](http://qiita.com/advent-calendar/2015/crawler)の7日目です。

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

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

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

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

# 下準備
* 環境
  + この記事に出てくるコマンド類はLinux Mint 16 Petraのターミナル上で動作確認しました。

* 情報源（2017-10-27時点）[^pagechange]
  + [Wikipediaの「プリキュアシリーズ」のページ](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AA%E3%82%AD%E3%83%A5%E3%82%A2%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA)を下記の短縮URLにしたものを使います。
  + http://goo.gl/AqZuEg

[^pagechange]: この記事自体は2015-12-05日に出されました。2017-10-27に更新しました。そこからまたページの構造が変わっていると、上手く動かないかもしれません。

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

```bash: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と正規表現を使った標準的なアプローチ
```bash:Terminal
$ curl -Lso- goo.gl/AqZuEg | grep -oP '<td><a[^>]*?>\K[\p{Katakana}ー]{5,9}(?=<)' | 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  キュアマジカル
    46  キュアフェリーチェ
    47  キュアホイップ
    48  キュアカスタード
    49  キュアジェラート
    50  キュアマカロン
    51  キュアショコラ
    52  キュアパルフェ
```

`curl`, `grep`の２つのコマンドを使うだけで、最新のプリキュア一覧（2017年10月現在50人、Splash☆Star組2名は重複して数えてるので52行）がわかります。

## 解説

### curl

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

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

```bash:Terminal
$ curl -so- example.org
```

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

### grep

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

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

```text:プリキュアシリーズページにあるプリキュア名記載パターンにマッチするPerl正規表現
<td><a[^>]*?>\K[\p{Katakana}ー]{5,9}(?=<)
```

``<td><a[^>]*?>\K``が``\K``パターン利用箇所[^saitan]。``(?=<)``の箇所が正規表現の「肯定的先読み」のパターンです。`[\p{Katakana}ー]{5,9}`の箇所がプリキュア名に該当し、出力対象となる箇所です。

` \p{Katakana} `というのはカタカナにマッチする表現で「Unicode 文字プロパティ」と呼ばれるものです。漢字や平仮名など、特定の文字一文字にマッチさせることができます。これはPerlの正規表現エンジンであるPCREや[^pcre]、鬼雲[^onigmo]で使えます。
伸ばし棒である全角ダッシュ（ー）はカタカナ扱いではないらしいので、これを含めた`[\p{Katakana}ー]`は「カタカナあるいは全角ダッシュ（ー）一文字」にマッチします。これに`{5,9}`という表現をつけると5文字以上9文字以下連続したものにマッチします。現在プリキュア名はこのレンジにおさまっているので、結果的にプリキュア全員を出力することができます。

[^saitan]: ``.*?``の表現の中にある``?``は最短マッチを意味します。
[^pcre]: Precureではありません。
[^onigmo]: Rubyなどで採用される正規表現エンジンです。[k-takata/onigmo](https://github.com/k-takata/Onigmo)

# 解答例2. Xpathを使ったアプローチ
```bash:Terminal
$ curl -Lso- goo.gl/AqZuEg \
  | sed 's|</a>|\n&|g' \
  | xmllint --xpath '//*[@id="mw-content-text"]/div/table[7]/*/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  キュアスカーレット
    44  キュアミラクル
    45  キュアマジカル
    46  キュアフェリーチェ
    47  キュアホイップ
    48  キュアカスタード
    49  キュアジェラート
    50  キュアマカロン
    51  キュアショコラ
    52  キュアパルフェ
```

## 解説
### xmllint

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

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

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

```bash:Terminal
$ xmllint --html file.html
```

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


```bash:Terminal
$ cat file.html | xmllint --html -
```

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

例えばWikipediaのプリキュア一覧テーブルから一番最初のプリキュアを抜き出すXPathはこんな感じ。[Google Chromeのデベロッパーツールを使えば簡単に特定できます。](http://ptech.g.hatena.ne.jp/noromanba/20120528/1338307299)

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

つまり端末上ですと。

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

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

```bash:Terminal
$ curl -Lso- goo.gl/AqZuEg | \
xmllint --xpath '//*[@id="mw-content-text"]/div/table[7]/tr[3]/td[1]/a/text()' --html -
キュアホワイト
```
ということでテーブルのtr要素をXPathのワイルドカード（\*）で全部列挙したこちらのXPathで、プリキュア全員を出すことができるわけです。

```text:XPath
//*[@id="mw-content-text"]/div/table[7]/*/td[1]/a/text()
```

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

短縮URLのページは[ニコニコ大百科のプリキュアの一覧](http://dic.nicovideo.jp/a/%E3%83%97%E3%83%AA%E3%82%AD%E3%83%A5%E3%82%A2%E3%81%AE%E4%B8%80%E8%A6%A7)

```bash: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	キュアミラクル
    46	キュアマジカル
    47	キュアフェリーチェ
    48	キュアホイップ
    49	キュアカスタード
    50	キュアジェラート
    51	キュアマカロン
    52	キュアショコラ
    53	キュアパルフェ
    54	キュアフラワー
    55	キュアモフルン
```

キュアエコー、キュアフラワー、キュアモフルンが余計に入っていますが、全員はカバーしています。

## 解説
### w3m

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

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

* シンプルなテーブル要素があるページを`w3m`で表示

```bash:Terminal
$ w3m -dump http://elze.tanosii.net/d/kenmei.htm
県番号，都道府県名，県庁所在地一覧

┌────────┬──────────────────────┬────────────────────┐
│ 県番号 │      都道府県名      │     県庁所在地     │
├────────┼──────────────────────┼────────────────────┤
│   1    │ 北海道(ほっかいどう) │ 札幌(さっぽろ)     │
├────────┼──────────────────────┼────────────────────┤
│   2    │ 青森県(あおもり)     │ 青森(あおもり)     │
├────────┼──────────────────────┼────────────────────┤
│   3    │ 岩手県(いわて)       │ 盛岡(もりおか)     │
（以下続く...）
```
WikipediaのようなDOM要素がキッチリしたページではXPathが使いやすいですが、シンプルなページのスクレイピングでは`curl`などでDOM要素を直接見るよりも、`w3m`を使ったほうが簡単にスクレイピングできる場合が結構あります。このコマンドを使うと、全ての結果をXMLではなく、純粋なプレーンテキストで表示してくれます。また、色々面倒なこともやってくれます[^about-w3m]。

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

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


* `w3m`を使って47都道府県を表示

```bash:Terminal
$ w3m -dump http://elze.tanosii.net/d/kenmei.htm | awk '$0=$4' | sed '1d'
北海道(ほっかいどう)
青森県(あおもり)
岩手県(いわて)
宮城県(みやぎ)
（以下続く...）
```

* (参考)XPathを使って47都道府県を表示

```bash: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のコマンドをつなげる作業も、そんな感じがありますね。
「こんなやり方あるよ！」という方はぜひコメント欄に別解をお願いします。