ex_mecab.plを短く修正

CGI化するためにシンプルに修正してみました。

どういうわけかTermExtractにUTF8で通すと結果が何も返らずEUCに変換しないとダメなので
そのようにしてみました。

何か原因があると思いますが・・・

#! /usr/bin/perl -w
use strict;
use warnings;
use Text::MeCab;
use TermExtract::MeCab;
use Jcode;
use Encode;
my $data = new TermExtract::MeCab;

my $m = Text::MeCab->new();
my $s = "全域が太平洋側気候であるが、標高差が大きいため地域による寒暖の差が激しい。冬の平野部や沿岸>部は黒潮の影響で本州の中でも非常に温暖であり、寒気の影響を受けにくいために放射冷却によって朝晩は氷点
下まで下がることがあっても、日中は10°Cを超えることがほとんどである.";
my $n = $m->parse($s);
my $t="";
my $str="";
my $txtfile="in.txt";
while ($t = $n->next) {
    $str.=sprintf("%s\t%s\n",
           $n->surface,          # 表層
           $n->feature          # 現在の品詞
           );
    $n = $t;
}
# 何故かEUCじゃないとTermExtractの結果が返らないのでEUCに変換.
$str = &Jcode::convert($str,"euc");

# 出力モードを指定
# 1 → 専門用語+重要度、2 → 専門用語のみ
# 3 → カンマ区切り
my $output_mode = 1;

#
# 「形態素解析」済みのテキストファイルから、データを読み込み
#  専門用語リストを配列に返す
#  (累積統計DB使用、ドキュメント中の頻度使用にセット)
#
my @noun_list = $data->get_imp_word($str,'var');      # 入力が変数
#my @noun_list = $data->get_imp_word("in.txte");  # 入力がファイル

#
#  専門用語リストと計算した重要度を標準出力に出す
#
foreach (@noun_list) {
   # 日付・時刻は表示しない
   next if $_->[0] =~ /^(昭和)*(平成)*(\d+年)*(\d+月)*(\d+日)*(午前)*(午後)*(\d+時)*(\d+分)*(\d+秒)*$/;
   # 数値のみは表示しない
   next if $_->[0] =~ /^\d+$/;

   my $prt=&Jcode::convert($_->[0],"utf8");

   # 結果表示
   printf "%-60s %16.2f\n", $_->[0], $_->[1] if $output_mode == 1;
   printf "%s\n",           $_->[0]          if $output_mode == 2;
   printf "%s,",            $_->[0]          if $output_mode == 3;
}

カンマ区切りで出力してみました。

影響,太平洋側気候,平野部,沿岸部,放射冷却,°C,差,標高差,黒潮,寒暖,全域,冬,本州,地域,日,氷点下,寒気

 

次はCGI化してみます。

ex_mecab.plのオプションを変化させて結果を見る(2)

次は重要度計算になります。

重要度計算で、「ドキュメント中の用語の頻度」と「連接語の重要度」
のどちらに比重をおくかを設定する。
値が大きいほど「ドキュメント中の用語の頻度」の比重が高まる

6.設定値0.1(連接後の重要度高)

デフォルト値は1です。
少し順位が入れ替わってるかぐらいにしか見えませんかね。

 

7.設定値0.5(ドキュメント中の用語の頻度と連接語の重要度の中間)

当たり前ですが、違いが減りましたね。

 

8.学習機能

とは言え、

# 前回読み込んだ「形態素解析」済みテキストファイルを元に
# モードを変えて、専門用語リストを配列に返す

の部分です。学習機能をオンにしておかないと機能しないからです。
事前準備で
$data->use_stat; # 学習機能を使う

# 学習機能用DBにデータを蓄積するかどうか選択
$data->use_storage; # 蓄積する
の2つを有効にしおいて一度出力させて”stat.db”と”comb.db”を出力させてから
実行します。

これ31個にまとまっていい感じです!

 

難しい

どの設定が良いのかというのは難しいです。
となれば、デフォルトでも良いような気がしてきます・・・。

気になるキーワードが幾つかありました。

くに静岡
・・・ふじのくに静岡県がそのように分解されてしまっているようです。

アメリカ海軍
・・・イージス艦の事故の件でヒットしてしまっているようです。

らんnet
・・・じゃらんnetが分解されてしまっています。

公式ホームページふじ
・・・静岡県公式ホームページ ふじのくに総合トップページ

やはりどうしても変なところで切れたりするようですね。

もしかするとmecabの辞書に何かを追加したりすると良いのですかね??

試してみたいと思います。

ex_mecab.plのオプションを変化させて結果を見る(1)

オプションを検証してみます。

解析する例文は「静岡」で検索した一覧です。
URL先の文章まで読み込むと文章が大きすぎるので結果一覧のものだけとしました。

静岡県公式ホームページ ふじのくに総合トップページ
静岡の観光スポットランキングTOP10 – じゃらんnet
ハローナビしずおか 静岡県観光情報公式ホームページ
静岡市ホームページ
@S[アットエス] | 静岡新聞SBSオフィシャルサイト
絶対に行きたい!“静岡”のオススメ観光スポットランキングTOP40
静岡銀行
富士山静岡空港
ふじのくに静岡県の公式ホームページです。観光,産業,県庁案内、申請、申込など いろいろご利用いただけます。
静岡でおすすめの観光スポット3110ヶ所をセレクト!人気の富士サファリパークや大 室山などを口コミランキングでご紹介。静岡周辺で観光スポットを探すならじゃらんnet。
地理院地図 Googleマップ Bing GeoHack MapFan Mapion Yahoo! NAVITIME ゼンリン. ウィキポータル, 日本の都道府県/静岡県 表示・ノート・編集・履歴 ウィキ プロジェクト. 静岡県(しずおかけん)は、太平洋に面する日本の県の一つ。県庁所在地 は静岡市..
17日未明、静岡県南伊豆町の石廊崎から、およそ20キロ沖合で、フィリピン船籍の コンテナ船から「アメリカ海軍の船と衝突した」と、第3管区海上保安本部に通報がありま した。アメリカ海軍の船はイージス駆逐艦で、船体の右側の側面が
静岡県観光協会が提供する静岡県観光ホームページ ハローナビしずおかです。宿泊・ 温泉・グルメ・お土産情報から富士山情報まで、静岡の観光を楽しむ情報が満載です。
静岡県静岡市(政令指定都市)のホームページです。生活情報・観光情報ほか、住む人 にも来る人にも便利な情報を掲載しています。
静岡新聞社・静岡放送(SBS)の総合ポータルサイト「@S[アットエス]」。静岡新聞掲載の ニュース、スポーツ、静岡放送の番組情報・動画ニュース、グルメ、イベント、映画、施設 などの静岡県内の生活情報などを紹介しています。
静岡といえばやはり世界遺産に登録された富士山が有名ですが、その他でも伊豆・熱海 の温泉も人気が高く日本国内でも有数の観光名所があります!素敵なスポットをあます ところなく絶対に外せない静岡の人気スポットを紹介していきます.
静岡銀行のホームページです。総合口座から外貨預金、ローン、カードとさまざまな商品 ・サービスをご用意しております。
富士山静岡空港の公式ウェブサイト。国内線・国際線の時刻表、空港へのアクセス方法 、レストランやイベント情報など情報満載。富士山静岡空港は、人と自然にやさしい空港 を目指しています。

 

1.異なり数

順位が変わっていますが、結果そのものはあまり違いがないように見えます。

 

2.パープレキシティをとる

「情報理論的に見ていくつの単名詞が連接可能か」というのがパープレキシティだそうです。
右側ですが、結果が極端に減ります。このくらいのほうが多すぎず良いかもしれません。
「くに静岡」というのは誤検知でしょうか・・・。

 

3.隣接情報を使わない

こちらも結果が極端に減ります。「くに静岡」がなくなりましたね。その代わり「らんnet」と言うのが入っています。

 

4.Term Frequency

用語頻度情報のとり方のようです。分解された用語が増えたように思います。

  • Frequency ———— 用語が他の用語の一部として使われていた場合にカウントしない
  • TF(Term Frequency) — 用語が他の用語の一部として使われていた場合もカウントする

このような違いがあるそうです。

 

5.頻度情報をつかわない

結果が少し減るようです。ただ、共起語は「よく使われる言葉」なので頻度情報はあったほうが良いかもしれません。

 

ちょっと多くなりましたので、

続きは次回とします。

 

ex_mecab.plの内容をみてみた

ex_mecab.pl の中でやっていることをみてみました。

# 出力モードを指定
# 1 → 専門用語+重要度、2 → 専門用語のみ
# 3 → カンマ区切り
my $output_mode = 1;

出力形式を変えられるようになっているんですね。

 

#
# 重要度計算で、連接語の"延べ数"、"異なり数"、"パープレキシティ"のい
# ずれをとるか選択。パープレキシティは「学習機能」を使えない
# また、"連接語の情報を使わない"選択もあり、この場合は用語出現回数
# (と設定されていればIDFの組み合わせ)で重要度計算を行う
# (デフォルトは"延べ数"をとる $obj->use_total)
#
#$data->use_total;      # 延べ数をとる
#$data->use_uniq;       # 異なり数をとる
#$data->use_Perplexity; # パープレキシティをとる(TermExtract 3.04 以上)
#$data->no_LR;          # 隣接情報を使わない (TermExtract 4.02 以上)

結果の横に出てくる数値が”重要度”のようです。
設定しないと「延べ数」になるようですね。
これ設定を変えて結果がどうなるか見てみるのも良いかも??

 

# 重要度計算で、連接情報に掛け合わせる用語出現頻度情報を選択する
# $data->no_LR; との組み合わせで用語出現頻度のみの重要度も算出可能
# (デフォルトは "Frequency" $data->use_frq)
# TFはある用語が他の用語の一部に使われていた場合にもカウント
# Frequency は用語が他の用語の一部に使われていた場合にカウントしない
#
#$data->use_TF;   # TF (Term Frequency) (TermExtract 4.02 以上)
#$data->use_frq;  # Frequencyによる用語頻度
#$data->no_frq;   # 頻度情報を使わない

これよくわからないですね。
またこんどどう変わるか確認してみます。

 

#
# 重要度計算で、学習機能を使うかどうか選択
# (デフォルトは、使用しない $obj->no_stat)
#
#$data->use_stat; # 学習機能を使う
#$data->no_stat;  # 学習機能を使わない

学習機能を使うと、どうなるのか?

単名詞の連接情報は、元となるデータが多ければ多いほど正確な統計データが
得られると推定されます。この学習機能は、いままでに処理対象としたテキストか
ら単名詞の連接情報を蓄積し、重要度計算で用いるものです。ただし、雑多な
文献を扱うと、結果として一般すぎる語が上位にきます。分野を特定した上でお使
い下さい。

とのことです。
どんな原文を解析させていくかで、結果が変わっていくということでしょうか?
 

# 重要度計算で、「ドキュメント中の用語の頻度」と「連接語の重要度」
# のどちらに比重をおくかを設定する。
# デフォルト値は1
# 値が大きいほど「ドキュメント中の用語の頻度」の比重が高まる
#
#$data->average_rate(0.5);

なるほど。これは面白そうです。
これも値を変えてみてどうなっていくか調べたいですね。

# 学習機能用DBにデータを蓄積するかどうか選択
# 重要度計算で、学習機能を使うときは、セットしておいたほうが
# 無難。処理対象に学習機能用DBに登録されていない語が含まれる
# と正しく動作しない。
# (デフォルトは、蓄積しない $obj->no_storage)
#
#$data->use_storage; # 蓄積する
#$data->no_storage;  # 蓄積しない

これは、学習機能を使う、とセットでないと意味はなさそう。

# 学習機能用DBに使用するDBMをSDBM_Fileに指定
# (デフォルトは、DB_FileのBTREEモード)
#
#$data->use_SDBM;

# 過去のドキュメントの累積統計を使う場合のデータベースの
# ファイル名をセット
# (デフォルトは "stat.db"と"comb.db")
#
#$data->stat_db("stat.db");
#$data->comb_db("comb.db");

DBの種類の設定とDBファイル名の設定ですね。
これは別にそのままでいいと思います。
DBM(バークレーDB)はデフォルトでは入っていないからだそうです。
しかし、今はレンタルサーバーでも入っていると思います。

# データベースの排他ロックのための一時ディレクトリを指定
# ディレクトリ名が空文字列(デフォルト)の場合はロックしない
#
#$data->lock_dir("lock_dir");

ロックですね。シングルで使うには必要ないでしょう。

# 「形態素解析」済みのテキストファイルから、データを読み込み
#  専門用語リストを配列に返す
#  (累積統計DB使用、ドキュメント中の頻度使用にセット)
#
#my @noun_list = $data->get_imp_word($str,'var');      # 入力が変数
my @noun_list = $data->get_imp_word($InputFile);  # 入力がファイル

変数読み込みもできるように配慮されています。
WEBからの読み込みと組み合わ焦ることも簡単にできそうですね。

# 前回読み込んだ「形態素解析」済みテキストファイルを元に
# モードを変えて、専門用語リストを配列に返す
#$data->use_stat->no_frq;
#my @noun_list2 = $data->get_imp_word();
# また、その結果を別のモードによる結果と掛け合わせる
#@noun_list = $data->result_filter (\@noun_list, \@noun_list2, 30, 1000);

ここ謎ですね?
すべてコメントですから、デフォルトでは動作していません。
get_imp_word という関数を使って何かをしマージしているように見えます。
30,1000という引数が何を意味するのでしょうかね・・・?

#
#  専門用語リストと計算した重要度を標準出力に出す
#
foreach (@noun_list) {
   # 日付・時刻は表示しない
   next if $_->[0] =~ /^(昭和)*(平成)*(\d+年)*(\d+月)*(\d+日)*(午前)*(午後)*(\d+時)*(\d+分)*(\d+秒)*$/;
   # 数値のみは表示しない
   next if $_->[0] =~ /^\d+$/;

   # 結果表示
   printf "%-60s %16.2f\n", $_->[0], $_->[1] if $output_mode == 1;
   printf "%s\n",           $_->[0]          if $output_mode == 2;
   printf "%s,",            $_->[0]          if $output_mode == 3;
}

出力形式をいじれますね。

次回はこれを踏まえて検証をしてみたいと思います。

解析結果を見てみる

静岡、でグーグル検索した結果は以下の様なものです。

静岡県公式ホームページ ふじのくに総合トップページ
https://www.pref.shizuoka.jp/
静岡の観光スポットランキングTOP10 – じゃらんnet
http://www.jalan.net/kankou/210000/
静岡県 – Wikipedia
https://ja.wikipedia.org/wiki/%25E9%259D%2599%25E5%25B2%25A1%25E7%259C%258C
静岡 のニュース検索結果

静岡市ホームページ
http://www.city.shizuoka.jp/
ハローナビしずおか 静岡県観光情報公式ホームページ
http://hellonavi.jp/
@S[アットエス] | 静岡新聞SBSオフィシャルサイト
http://www.at-s.com/
絶対に行きたい!“静岡”のオススメ観光スポットランキングTOP40 …
https://retrip.jp/articles/5381/
静岡銀行
http://www.shizuokabank.co.jp/
富士山静岡空港
http://www.mtfuji-shizuokaairport.jp/

この中で、
・Wiki・・・スクレイピング不可
・静岡のニュース検索結果・・・ニュースについての検索の相対パスなので除去
の2つは除去しています。

これらのサイトからテキスト情報を抜き出して、

mecabコマンドに通し

TermExtractにかける(ex_mecab.plの実行)

という手順でおなじみですね。

とは言え、元々のテキストが結構大量になりまして、解析結果も2095行も出力されました。

なので、
・スコアが1000以上
かつ
・明らかにおかしなものを覗いた
のが以下のような感じでした。

観光スポット 8573.39
温泉 6995.03
伊豆 6942.60
スポット 6617.74
富士山 6481.85
観光 6333.73
体験 5508.25
グルメ 4738.28
情報 4603.37
浜松 3971.11
富士山静岡空港 3697.26
観光情報 3447.38
月 3439.74
観光スポット静岡 2958.55
プラン一覧 2780.55
日本 2673.95
静岡県内 2600.64
富士 2498.77
一覧 2300.66
イベント 2291.29
イベント情報 2121.40
一覧静岡 2106.79
熱海 1899.49
動物園 1872.21
公園 1651.76
写真 1628.00
下田 1624.81
施設 1609.97
旅 1583.92
温泉地 1576.92
観光スポット-静岡 1526.24
観光情報静岡 1501.26
静岡放送 1435.43
観光名所 1398.21
人気 1365.21
世界 1324.69
静岡新聞 1238.64
即時予約OK空き状況 1228.39
観光スポットランキングTOP 1207.21
富士山情報 1129.04
観光協会 1092.60
ホームページ 1067.39
世界遺産 1043.25
伊東 1008.17

右側の数値がスコアです。
結構良さげなキーワードが取れていると思います。

多すぎるので1000以上を見ましたが、
もっと下位のものも結構使えそうです。

今はちょっと手順が多いので、もう少し簡単にするのが次の課題ですかね。

検索結果のURL先のテキストをすべて読み込む(修正版)[Perl]

というわけで、修正版です。

これで、「静岡」という検索結果のURL先のテキストをすべて抜き出せます。

スパゲッティ的ではありますが、実現が第一ということで・・・。

#!/usr/bin/perl

use strict;
use warnings;
use LWP::UserAgent;
use HTML::TreeBuilder;
use Jcode;

# urlを指定する
# my $url = 'http://www.yahoo.co.jp';
my $search_word = '静岡';
# UTF8
#$search_word = jcode($search_word,'euc')->utf8;
# URLエンコード
$search_word =~ s/(\W)/'%' . unpack('H2', $1)/eg;
my $url = 'http://www.google.co.jp/search?hl=ja&q=' . $search_word . '&lr=';
# IE8
my $user_agent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";

# HTMLを取得
my $ua = LWP::UserAgent->new('agent' => $user_agent);
my $res = $ua->get($url);
my $content = $res->content;

# HTML::TreeBuilderで解析
my $tree = HTML::TreeBuilder->new;
$tree->parse($content);

# DOM操作
my @items =  $tree->find('h3');
my @a;
my @urls;
for (@items){
    print $_->as_text."\n";
    @a=$_->find('a');
    for ( @a ){
        # print $_->attr('href')."\n";
        my $url = $_->attr('href');
        $url = urlget($url);
        print $url."\n";
        push( @urls , $url )if chkUrl($url);
    }
}
# 一覧の詳細
@items =  $tree->look_down('class', 'st');
print $_->as_text."\n" for @items;

#URLを1つずつ開く.
for (@urls){
    my $url=$_;
    print $url . "   ...get\n";
    my $res = $ua->get($url);
    my $content = $res->content;
    my $code = Jcode::getcode($content);
    if ( $code ne 'utf8' ){
        $content = Jcode::convert( $res->content ,"utf8" , $code);
    }
    $content=~ s/&/\xef\xbc\x86/g;
    $content=~ s/&/&/g;

    $tree->parse($content);
    my @items =  $tree->find('html');
    print $_->as_text."\n" for @items;
}

sub urlget{
    my $url=shift;
    if ( $url=~ /\?url\=(.*)\&rct/ ){
        return $1;
    }
    else{
        #return $url;
        return "";
    }
}

sub chkUrl{
    my $url=shift;
    if ( $url=~ /wikipedia\.org/ ){
        $url="";
    }
    return $url;
}

 

HTML::TreeBuilderでの文字化け対策

サンプルを作り特殊文字を含めてみて検証を行いました。

テストページ

そのままでテキストデータを取得します。

テストページ これはテストコンテンツです。 テストとは、英語のtestのカタカナ表記です。 試験、試す、 という意味です。 画像を表示できます。

このように取得できます。

前回の検証結果から、「’」を含めて試してみます。

a??a?1a??a??a??a?, a??a??a? ̄a??a?1a??a?3a?3a??a?3a??a?§a??a?? a??a?1a??a?¨a? ̄a??e?±ea?a?Rtesta?Ra?≪a??a?≪a??e!¨e¨?a?§a??a?? ec|e¨?a??ec|a??a??a?¨a??a??a??a?3a?§a??a?? c?≫a??a??e!¨c?oa?§a??a??a??a??

見事に化けました。

 

他のコードは?

こちらを参考にしました。
矢印 & 矢印に使える記号、使えそうな記号

試してみたところ・・・
ほとんどNG
というか
全部NGでした。
以下は大丈夫のようでした。

  • &
  • >
  • <
  •  

対策は?

&を&に変換してしまえばいいか?と考えました。
ところが、&を使った語句がありそうな気がします。

・トム&ジェリー
・タイガー&ドラゴン

ふ・・・古い・・・ちょっと他に思いつきませんでした^^;

ということで

  1. & → 全角&
  2. & → &

という順番で変換すれば良さそうです。

1.はこんな感じでいけます

$content=~ s/&/&/g;

 

が文字コードを使って書くとこうなります。

$content=~ s/&/\xef\xbc\x86/g;

Unicode対応 文字コード表

 

2.はこうです。

$content=~ s/&/&/g;

 

組み込むと以下のようになります。

#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use HTML::TreeBuilder;
use Jcode;
# urlを指定する
my $url = 'http://retrip.jp/articles/5381/';
# IE8
my $user_agent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";
# LWPを使ってサイトにアクセスし、HTMLの内容を取得する
my $ua = LWP::UserAgent->new('agent' => $user_agent);
my $res = $ua->get($url);
my $content = $res->content;
# 文字コードを調べる
my $code = Jcode::getcode($content);
if ( $code ne 'utf8' ){
    $content = Jcode::convert( $res->content ,'utf8' , $code);
}
# 特殊文字を変換する.
$content=~ s/&/\xef\xbc\x86/g;
$content=~ s/&/&/g;
# HTML::TreeBuilderで解析
my $tree = HTML::TreeBuilder->new;
$tree->parse($content);
# DOM操作
my @items =  $tree->find('html');
print $_->as_text."\n" for @items;

これでやっと文字化け無く取得できるようになりました。

HTML::TreeBuilderで文字化けしたので検証した

https://retrip.jp/
をHTML::TreeBuilderで解析しようとすると文字化けしてしまう。
headタグのみ
titleタグのみ
など絞るとOKなのですが、
html全体
bodyタグ内
を取得しようとすると文字化けとなってしまいます。

 

HTML5だから?

まずソースを見てみて、ちょっと違うと感じました。
見慣れないタグ
header
footer
section
というタグがありました。
これがどうやらhtml5らしいです。恥ずかしながらよく知りませんでした。
というわけで、html5だからダメなのでしょうか?

そこでごくごく簡単なサンプル
http://html5.imedia-web.net/sample/html5_sample1/html5_sample1.html
を取得してみたところ正常に取得できました。
どうやら原因は違うようです。

 

ソース内に使用できない文字があるのでは?

経験的にパーサーでエラーになる場合は文字の泣き別れが多いです。
3バイト文字や2バイト文字が1バイトで切れ
表示しない1バイト文字、いわゆるNull文字が残っている場合です。
htmlをブラウザで見ているとまったく気が付かないのですが、パーサーで解析不能となり落ちてしまうのです。

これを検証するには、
ソースの半分を消して

解析

4分の1を消して

解析


を繰り返します。
こうやってどの部分が存在すると解析が不能になるのかを絞り込んでいきます。

 

原因発覚!

そしてこれを繰り返して・・・とうとう判明しました!

’

これが

次へ’’

となっていました。
これを消すと解析可能となりましたので、これで間違い無いようです・・・。

 

’ってなに?

記号>のようです。
本来>だと思うのですが、微妙に違う特殊文字なのでしょうか・・・。

 

対策は・・・?

HTML::TreeBuilderを使うのならこの;’を削除するしかないですね。。。

せっかくなので、他にもダメになる特殊文字を探してみようと思います。

検索結果のURL先のテキストをすべて読み込む[Perl]

今まで作ったものをつなげただけです。

#!/usr/bin/perl

use strict;
use warnings;
use LWP::UserAgent;
use HTML::TreeBuilder;
use Jcode;

# urlを指定する
# my $url = 'http://www.yahoo.co.jp';
my $search_word = '静岡';
# UTF8 に変換
#$search_word = jcode($search_word,'euc')->utf8;
# URLエンコードも忘れずに
$search_word =~ s/(\W)/'%' . unpack('H2', $1)/eg;
my $url = 'http://www.google.co.jp/search?hl=ja&q=' . $search_word . '&lr=';
# IE8
my $user_agent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";

# LWPを使ってサイトにアクセスし、HTMLの内容を取得
my $ua = LWP::UserAgent->new('agent' => $user_agent);
my $res = $ua->get($url);
my $content = $res->content;

# HTML::TreeBuilderで解析
my $tree = HTML::TreeBuilder->new;
$tree->parse($content);

# DOM操作
my @items =  $tree->find('h3');
my @a;
my @urls;
for (@items){
    print $_->as_text."\n";
    @a=$_->find('a');
    for ( @a ){
        # print $_->attr('href')."\n";
        my $url = $_->attr('href');
        $url = urlget($url);
        print $url."\n";
        push( @urls , $url )if chkUrl($url);
    }
}
# 一覧の詳細
@items =  $tree->look_down('class', 'st');
print $_->as_text."\n" for @items;

 

Wikipediaはこのやり方では読み込めない仕組みになっています。
あと、グーグルの相対パスも読み込まないようにします。
2つサブルーチンを作って対応しました。

sub urlget{
    my $url=shift;
    if ( $url=~ /\?url\=(.*)\&rct/ ){
        return $1;
    }
    else{
        return "";
    }
}

sub chkUrl{
    my $url=shift;
    if ( $url=~ /wikipedia\.org/ ){
        $url="";
    }
    return $url;
}

 

そして、すべての検索結果URLを読み込みます。

ちょっとスパゲッティ的ですが、続けて読み込み処理をつけました。

#URLを1つずつ開く.
for (@urls){
 my $url=$_;
 print $url . " ...get\n";
 my $res = $ua->get($url);
 my $content = $res->content;
 my $code = Jcode::getcode($content);
 if ( $code ne 'utf8' ){
     $content = Jcode::convert( $res->content ,"utf8" , $code);
 }
 $tree->parse($content);
 my @items = $tree->find('html');
 print $_->as_text."\n" for @items;
}

これでうまく行ったか・・・?
と思いきや途中から激しく文字化けをしました。

具体的には
https://retrip.jp/articles/5381/
を読み込むとそうなってしまいます。
試しにトップページも試しましたが、同じでした。

HTMLそのものは文字化けしていないのですが、TreeBuilderで解析すると文字化けしてしまいます。

次回はそこを掘り下げていきます。

文字コードを自動的にチェックしてテキストを取得する[perl編]

実は文字コードは、かなり色々あるのですが、基本的には3つです。

  • ShiftJIS
  • UTF-8
  • EUC-JP

ですね。
個人的にはEUC-JPが好みですが、
世の中のUTF-8化の流れには逆らえませんね・・・。
ShiftJISはWindowsがしつこく使用しているので無くなりそうにありません。

#!/usr/bin/perl

use strict;
use warnings;
use LWP::UserAgent;
use HTML::TreeBuilder;
use Jcode;

# urlを指定する
my $url = 'https://www.pref.shizuoka.jp/';
#my $url = 'http://www.jalan.net/kankou/210000/';
# IE8
my $user_agent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)";
# LWPを使ってサイトにアクセスし、HTMLの内容を取得する
my $ua = LWP::UserAgent->new('agent' => $user_agent);
my $res = $ua->get($url);
my $content = $res->content;
# 文字コードを調べる
my $code = Jcode::getcode($content);
if ( $code ne 'utf8' ){
    $content = Jcode::convert( $res->content ,'utf8' , $code);
}
# HTML::TreeBuilderで解析
my $tree = HTML::TreeBuilder->new;
$tree->parse($content);

# DOM操作
my @items =  $tree->find('html');
print $_->as_text."\n" for @items;

 

具体的に見てみましょう。
まず文字コードをチェックします。

my $code = Jcode::getcode($content);

UTF8の場合には変換しなくていいので、UTF8以外のときだけ変換するようにします。

if ( $code ne 'utf8' ){
    $content = Jcode::convert( $res->content ,'utf8' , $code);
}

変換元の文字コードを第3引数に入れています。
第3引数は省略できるのですが、たまにうまく変換できないことがあるため、入れています。