2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Perl の HTML::Parser モジュールの使い方

Posted at

この記事を書いた経緯

Perl の HTML::Parser に関して使い方を紹介している日本語記事が上手く見つけられなかったので書き残す。
HTML をパースするなら他にもいろいろあるかもしれないが、著者が自作のツールを公開する際に使っているレンタルサーバはこれ以外入っていなかったので必要だったのである。

また、オプションについては書いてない。そこについては別の記事として書くかもしれない。
この記事の内容は HTML::Parser - HTML parser class - metacpan.org 記載の内容を確認しながら書いている。もっと正確な情報とか書いてないことを確認するにはこちらから。
書いた時点でのモジュールのバージョンは Release 3.72 である。

挙動の概要

parse メソッドが実行されると HTML を上から順に読み込み、何らかの要素を見つけるたびに「イベント」が発生し、parser はこの「イベント」に対応する「ハンドラ」を実行する という挙動をする。そのため、このモジュールを使う際はどのイベントに対してどのハンドラを呼ぶのかを定義すればよい。

使い方例

解析される HTML

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>サンプルページ</title>
  </head>
  <body>
  <h1>サンプルページ</h1>
  <h2>誰が書いたの</h2>
  <p><span class="name">@Chrysanthemum94</span> が書いてます</p>
  <h2>hogehoge</h2>
  <p>fugafuga</p>
  <!-- コメントだよ! -->
  <hr/>
  <p><a href="../index.html">戻る</a></p>
</body>
</html>

perl

use HTML::Parser ();
use LWP::UserAgent;

my $browser = LWP::UserAgent->new;

# 架空の場所から上述の HTML を取得している
my $response = $browser->get("http://chrysanthemum94.example.com/help/index.html");
my $parse_target_html = $response->content;

my $parser = HTML::Parser->new(
  api_version => 3,
  start_h     => [\&when_open_tag_found, "self, tagname, attr"],
  end_h       => [\&when_close_tag_found, "self, tagname"],
  text_h      => [\&when_text_found, "self, text"],
  comment_h   => [\&when_comment_found, "self, text"]
);
$parser->parse($parse_target_html);

sub when_open_tag_found {
  my ($self, $tagname, $attr) = @_;
  print "OPEN $tagname\n";
}

sub when_close_tag_found {
  my ($self, $tagname) = @_;
  print "CLOSE $tagname\n";
}

sub when_text_found {
  my ($self, $text) = @_;
  print "TEXT $text\n";
}

sub when_comment_found {
  my ($self, $text) = @_;
  print "COMMENT $text\n";
}

実行結果

TEXT

OPEN html
TEXT

OPEN head
TEXT

OPEN meta
TEXT

OPEN title
TEXT サンプルページ
CLOSE title
TEXT

CLOSE head
TEXT

OPEN body
TEXT

OPEN h1
TEXT サンプルページ
CLOSE h1
TEXT

OPEN h2
TEXT 誰が書いたの
CLOSE h2
TEXT

OPEN p
OPEN span
TEXT @Chrysanthemum94
CLOSE span
TEXT  が書いてます
CLOSE p
TEXT

OPEN h2
TEXT hogehoge
CLOSE h2
TEXT

OPEN p
TEXT fugafuga
CLOSE p
TEXT

COMMENT <!-- コメントだよ! -->
TEXT

OPEN hr/
TEXT

OPEN p
OPEN a
TEXT 戻る
CLOSE a
CLOSE p
TEXT

CLOSE body
TEXT

CLOSE html

何が起こっているのか

先述の通り HTML を先頭から順番に解析していく。最初の数手について以下に書き起こしてみる。

  1. <!DOCTYPE html>を見つけ、declarationイベントが発火する。しかし、ハンドラを設定していないため特に何も起こらない
  2. 改行を読み込んだことでtextイベントが発火する。textイベントにはwhen_text_foundがハンドラとして設定されているため、これが実行される
  3. <html lang="ja">が読み込まれ、startイベントが発火する。同イベントにはwhen_open_tag_foundがハンドラとして設定されているため、これが実行される
  4. 改行とスペース2つが読み込まれ、textイベントが発火してwhen_text_foundが実行される
  5. <head>が読み込まれstartイベントに対応してwhen_open_tag_foundが実行される
  6. 改行とスペース4つが読み込まれtextイベントに対応するwhen_text_foundが実行される
  7. 同様に HTML の構成要素ごとにイベントが発生、対応するハンドラが実行される、が末尾まで繰り返される

ハンドラと引数

イベントに対するハンドラの定義

my $parser = HTML::Parser->new(
  api_version => 3,
  start_h     => [\&when_open_tag_found, "self, tagname, attr"],
  end_h       => [\&when_close_tag_found, "self, tagname"],
  text_h      => [\&when_text_found, "self, text"],
  comment_h   => [\&when_comment_found, "self, text"]
);

このように XXXX_h に対してハンドラへの参照とハンドラに渡す引数を記載する。
上述の start_h => [\&when_open_tag_found, "self, tagname, attr"] であれば次のようになる。

  • start イベントに対するハンドラの宣言
  • start イベントが起こったら when_open_tag_foundself, tagname, attr の引数を渡して実行する

イベント

次のようなイベントがある。

イベント名称 説明
start HTMLの開始タグを見つけた際に発生するイベント
end HTMLの終了タグを見つけた際に発生するイベント。なお、self-closing な開始タグ(<img/> 等)の場合はこのイベントは発火しない
text HTML中のプレーンなテキストを見つけた際に発生するイベント
comment HTMLのコメントアウトされた部分を見つけた際に発生するイベント
start_document HTMLを読み込み始める前に発生するイベント
end_document HTMLの解析を途中で外部から停止した場合に発生するイベント
process HTML中に埋め込まれた処理命令を見つけた際に発生するイベント
declaration DOCTYPE 宣言を見つけた際に発生するイベント
default ハンドラが特に設定されていないイベントが発生した際に発生するイベント

ハンドラに渡す引数

引数名 説明
self parserそのものが格納される
event 発生したイベントが格納される。start 等が入る
tagname start や end のイベントのハンドラに対して与えることができる。タグの名称が格納される
tag start や end のイベントのハンドラに対して与えることができる。タグの名称が格納されるが、end 等の / が含まれる
text text のイベントのハンドラに対して与えることができる。タグとタグの間の文字列が格納される
dtext text と同じだが、デコードされたものが格納される。例えば text の値が &lt;&gt; になった場合 <> が格納される
is_cdata 値が CDATA ないしリテラルである場合か否かが格納される。この値が False であるならば一般には text ではなく dtext を使って値を処理する方が良い
attr start のイベントのハンドラに対して与えることができる。その要素に与えられた属性がハッシュで格納される
@attr start のイベントのハンドラに対して与えることができる。その要素に与えられた属性の名称と、その属性の値が格納された配列が格納される。偶数番目の要素(0, 2, 4...)に名称、奇数番目の要素(1, 3, 5...)に値が格納される
attrseq start のイベントのハンドラに対して与えることができる。その要素に与えられた属性の名前が配列で格納される
length そのイベントで取得した HTML の構成要素の長さが格納される。単位は byte
line そのイベントが HTML の何行目で発生したのかが格納される
column そのイベントが行の何文字目で発生したのかが格納される
offset そのイベントが HTML の先頭から何 byte 目で発生したのかが格納される
offset_end そのイベントが HTML の先頭から何 byte 目で終了したのかが格納される
tokens そのイベントの元になった HTML の構成要素を分解したものが格納される。各イベントごとに何が入るのかはまちまち。例えば start であれば要素名に続いて属性名、属性値、属性名、属性値……と続く
token0 tokens の先頭の要素が格納される
tokenpos HTML の構成要素内において tokens が出現した位置とその token の長さが格納された配列が格納される。偶数番目の要素(0, 2, 4...)に位置、奇数番目の要素(1, 3, 5...)に長さが格納される
skipped_text 前回ハンドラが処理したイベントから現在ハンドラが処理しているイベントの間に処理されなかった HTML の構成要素が格納される
undef 未定義が格納される。複数のイベントに対して単一のハンドラを渡す際に便利
'任意の文字列' シングルクオートないしダブルクオートで囲った文字列が格納される。複数のイベントに対して単一のハンドラを渡す際に便利

メソッド群

HTML::Parser->new

新しい Parser を作るのに使う。引数としてイベントと対応するハンドラが格納されたハッシュを与える。
また、引数のハッシュに api_version3 の値が入ったものを与えない場合はバージョン2との互換性が有効になる。これを使いたくない場合はapi_version3 の値を入れた状態で引数を渡してやる必要がある。

parse

引数に渡した文字列を html として解析する。解析の際にはハンドラなどの設定を参照しながら実施される。
このモジュールを使うならまず呼び出すことになるであろうメソッド。

parse_file

引数としてファイルを渡したい場合は parse ではなくこちらを利用する。引数にはファイルへのパスないしファイルハンドラを渡すことができる。
指定されたファイルが開けなかった場合はこのメソッドは未定義の値を返す。また、 $! にファイルを開けなかった理由が記載される。
問題なくファイルが開けていればこのメソッドは自分自身(parser)を返す。

eof

parse ないし parse_file を中断させる。このメソッドは自分自身(parser)を返す。
ハンドラの外で呼んだ場合は end_document イベントが発火される。まだ未処理の html が残っている場合は残った html で text イベントが発火する。
このメソッドをハンドラの中で呼んだ場合は発火されない。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?