inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

HTMLのTABLEの中身をスクレイピングする(正規表現編)

この記事では、CSSセレクタが使用できない言語系でのスクレイピングについて、TABLE(TD)タグを例に解説します。

さて、今回はCSSセレクタではなく、正規表現を使用したスクレイピングです。

さきほどはお答えいただきありがとうございます。

ただ、残念ながらNokogiriといった便利なものは、この言語にはないんです。
正規表現ならなんとかあるのですが…

なるほど…
それだと相当大変だと思いますが、手順を整理しながら解説いたしますね。

Rubyをお使いの方は、下記参考記事をご参照ください。

inuinu-tech.hatenablog.jp

前提条件

まず、正規表現を使ったスクレイピングでは、以下の関数・メソッドが用意されていることが大前提になります。

  • 正規表現で不要な文字列を除去できること
    • さらに、複数箇所を一気に処理できること
  • 正規表現で文字列を抜粋できること
    • さらに、複数箇所を抜粋し配列に展開できる関数があること

「複数箇所を一気に」がキーワードですね。

これがないと、まず無理かと思います。
言語を変えましょう。

プログラム作成の4ステップ

言語によって作風が異なりますので、ソースは貼りませんが、概ねこの4ステップで作成できると思います。
(すでにhtmlテキストがGETできていることが前提です。)

STEP1:不要な箇所を正規表現で削る(任意)
HEADタグやSCRIPTタグなど、邪魔なタグは消しましょう。

STEP2:TABLEタグの中身を抜き出す
TABLEタグの中身を正規表現で抜き出します。

STEP3:TABLE内の文字列からTRタグの中身を抜き出す
TRタグの中身を正規表現で抜き出します。
配列形式で抜き出せることが必須条件です。

STEP4:TR配列をループしながらTDの中身を抜き出す
TDタグの中身を正規表現で抜き出します。
配列形式で抜き出せることが必須条件です。

STEP5:数値のカンマを除去する、型や桁数チェックを行う
INSERT時に異常終了になるようでしたら、数値のカンマを除去します。

STEP1:不要な箇所を正規表現で削る(任意)

これは任意の作業ですが、まず、不要な箇所を削って、ソースをスッキリさせます。
初心者的には、だいぶ見やすくなり、正規表現での絞り込みがしやすくなると思います。

Rubygsub()のように、複数箇所を一気に除去できる関数があると助かりますが、ない場合はループするか、あきらめて、Ruby等の言語で行ってください。

ループできたとしても、どの段階でループを抜けられるか?が判断できないと、まず無理だと思ってください。

以下に代表的な、いらないタグを正規表現付きで列記します。
1行づつコピーして、ソースの任意の関数に貼り付けてください。

<!DOCTYPE.*?>
<html.*?>
<head>.*?</head>
<body.*?>
</body>.*?</html>
<!--.*?-->
<script.*?>.*?</script>
<style.*?>.*?</style>
<svg.*?>.*?</svg>
<img.*?>
<b>
</b>
<u>
</u>
<br>

まず、HEADタグの中身は除去しましょう。
次にBODYの不要箇所を除去しますが、注釈の優先順位は高めにしてください。

JavascriptCSSは外部にあったりHEAD内にあるのが多いですが、BODY内にもあったりするケースもあるので、入れておきます。

BRタグは/がついているケースもありますね。

</\sbr>

でしょうか。

以下は任意です。
必要ならばこちらも追加しましょう。
Aタグは中身が必要ならば閉じタグとは別々に引っこ抜いていきます。

<div.*?>
</div>
<span.*?>
</span>
<a\s.*?>
</a>

STEP2:TABLEタグの中身を抜き出す

以下の正規表現でTABLEタグを抜粋します。

<table.*?>(.*?)</table>

()内を取得できるような関数がある場合はTABLEタグなしで取得できますが、ない場合は取得後にTABLEタグと閉じタグを消去します。

言語や関数(メソッド)によっては、配列で取得できるものもあります。その場合は複数のテーブルが取得でき、任意のテーブルのデータが取得できますが、できない場合は、そのTABLEが取得できるまで、TABLEを消去すれば行けそうです。

STEP3:TABLE内の文字列からTRタグの中身を抜き出す

<tr.*?>(.*?)</tr>

さらに、TABLEタグと同様に抜き出します。
その際、カッコ内を配列で取得できればOKです。
それ以外は…大変なので諦めた方が良いかも…

STEP4:TR配列をループしながらTDの中身を抜き出す

<td.*?>(.*?)</td>

前のステップで取得したTR配列をループしながら、TDデータを抜き出します。
TD配列(TR配列とは別に用意)は、行・列の2次元配列が良いでしょうね。

この時点で余計なタグ情報がついている場合は、前の段階「不要な箇所を正規表現で削る」の処理に、その余計なタグ情報を消すロジックを追加します。

ここではなく、前の段階で消したほうが楽です。

私が参考にしたサイトでは、日付欄にTIMEタグが付いていました。

STEP5:数値のカンマを除去する、型や桁数チェックを行う

ここまで読んだ方は、上手く抜き出したと思いますが、多くの場合、そのデータをDBにINSERT/UPDATEしたいのでは?と思います。

数値の場合は、カンマを除去しないと、SQLでINSERTする際に、落ちてしまうかもしれませんね。
そういった場合も、除去しましょう。
これは正規表現以外の文字列関数等で除去できるかなとは思います。

INSERT時に異常終了するもう一つの原因は、型の違いや桁数オーバーです。

  • 数字の場合は小数点の有無や、桁数の妥当性、数値チェック
  • 日付・時刻の場合は日付や時刻のチェック
  • 文字列の場合は、文字数(あるいはバイト数)チェック

を行い、問題があればログに残し、DBの見直し等を行いましょう。

文字数チェックは、サロゲートペアや絵文字を扱う場合、言語が限られてくるかもしれません。

また、DBの文字化けにも気をつけ、妥当な型に変更しましょう。

最後に

これでなんとかなると思います。
が、なんとか頑張って抜き出しても、文字が化けるだの、文字数カウントがおかしいといった障害に悩まされるかも。

ここで、Rubyでやっておけばよかったと思うかもしれませんね。

inuinu-tech.hatenablog.jp

それでは、また。