HTML::TreeBuilder::XPath+日本語で Wide character at lib/Encode.pm line 228.

特にjavascriptをONにしないでWeb上のHTMLをperlで解析・スクレイピングしたいときに、Wide character at C:/…./perl/lib/Encode.pm line 228.のようなエラーが出る時があります。その注意点があったので備忘録としてメモ。

使うモジュール

perlでスクレイピングしたいときは以下のようなモジュール使うと思います。

use strict; # エラーとか警告を出してくれる
use utf8; # 内部文字コードはutf8
use Encode; # 文字コードの変換モジュール
use Encode::Guess qw/cp932 euc-jp 7bit-jis utf8/; # 文字コード判別用。扱う文字コードを指定。
use DateTime; # 日付を簡単に表示するやつ
use LWP; # URLからHTMLや画像を取ってくるやつ
use HTML::TreeBuilder::XPath; # HTMLを解析して狙ったタグの内容を取得するモジュール
use HTML::Selector::XPath 'selector_to_xpath'; # cssセレクタ風の書き方をxPathに変換してくれるやつ
use Data::Dumper; # 変数の内容を分かりやすくダンプしてくれるやつ

基本方針

で、基本的には以下の2点が重要。

  • プログラム外から入力したデータ(ファイルから読み込みとか、インターネットから取得したときなど)は、すべて Encode::decode(‘utf8’, $data); する。
  • プログラム内のデータを出力するとき(ファイルに書き込んだりコンソールに出力するなど)は、すべて Encode::encode(‘[出力先に合わせた文字コード]’, $data); する。

起こる問題

で、この基本通りにやると問題が無いのかというとそうでもない。

こんな感じでやると順調にいく場合もあるけど、たまーに「as_text」メソッドを呼んだときに「Wide character at [perlライブラリへのパス]Encode.pm line 228.」みたいなエラーが出て止まる。(泣)

# …略… #

# htmlファイルからパース
my $tree= HTML::TreeBuilder::XPath->new;
$tree->parse_file( "mypage.html");

# ノードを検索
foreach my $node ( $TREE_SAMPLE_ALL->findnodes(selector_to_xpath('div.item span.desc')) ) {

	# …中略… #

	my $str = Encode::decode( 'utf8', $node->as_text );
	# ↑ここでエラー

	# …中略… #

}

原因と解決策

原因は、HTML::TreeBuilderのエスケープ処理が微妙で、日本語などに割り当てられている「&[アルファベット];」形式のHTMLエンティティが正しく処理できてないから。(…とか。英語圏の人が作ったモジュールにありがち…)

というわけで、解決方法としては初めにHTMLエンティティを削除するか、何か別の書式に直しておいて後で元に戻すのがいいと思う。

例えばこんな感じ。

# content=HTMLファイルの中身。
# HTMLエンティティを適当な独自形式に直しておく。
$htmlContent =~ s/&([0-9a-zA-Z#]+?);/__$1__/g;

my $tree = HTML::TreeBuilder::XPath->new_from_content($htmlContent);

# …中略… #

# テキスト抜き出し
my $str = $tree->findnodes('div# span.desc')->shift->as_text;

# utf-8フラグを付ける
$str = Encode::decode( 'utf8', $str );

# 独自形式をHTMLエンティティに戻す
$str =~ s/__(.+?)__/&$1;/g;

独自の形式を何にするかは解析するhtmlの中に現れにくい(誤検出しにくい)形式がいいと思う。今回のようにアンダースコア2個(__)で挟んでもいいし、何か長くてランダムな文字列で挟むのもいいと思う。

まとめ

  • 外から入力した文字列はEncode::decode、外に出力するときはEncode::encode。
  • エンコードモジュールがエラーを出して止まってしまう時は、原因となっている文字を削除するか独自形式にして後で使う時に戻す。
  • 外国産のXMLパーサーでエラーが出るときも同様の点が問題になっている場合があるので同じように解決できる。

参考になれば幸いです。

カテゴリーperl

質問・コメントなどあると嬉しいです