今日は市内は雨だったが、広島空港は使えなくなるぐらい雪が降った。気温は最低気温が1℃ぐらいまで下がったらしい。
昼からは小さな書斎を片付ける。大袈裟に言えば、足の踏み場もないほど文献や雑誌類で床は埋まっている。ライティングデスクも本や文献で山積みだ。大晦日だし、途中で閉まるといけないので、散髪に出掛ける。あぶないところで、もう店じまいの最中だった。年末最後の仕事をお願いする。さっぱりした頭で、書斎の整理を継続。昨日DIKで買ってきたファイルボックス(5ケ)と山付きファイル(4山×4)を開封して追加セットする。20ケぐらいのファイルボックスになんとか文献を整理して、周辺に溢れている本は本棚にお帰り願う。固く絞った雑巾で埃を吸ったPCやディスプレィを拭く。なんとか、すっきりと片付いた。しかし、本の収容は限界に近づいているなあ。
「多重トピックテキストの確率モデル」に引用されている文献を一つのファイルにまとめた。引用文献は書籍を除いて、すべてインターネットから入手可能だ。書籍についても解説サイトを見ることができる。Googleに文献名や書籍名をコピーして入れるとすぐトップに表示される。正に打ち出の小槌だ。プリンタも進化しているので、文献はすぐに印刷物として実体化する。
amadeusのアンテナに更新日記は捕捉されているのだが、このアンテナへの収録部分の変化を見ているとページ変更前後の差分を表示するようになっているようだ。おもしろいね。
「人間は言葉によって文化を子孫に伝え、文化を進化させ続けることによって繁栄を継続している。言葉は第二の遺伝子である。」昨晩のNHKスペシャル選、「地球大進化」の中でリチャード・ドーキンス(Richard Dawkins)がそのようなことを言っているのを聞いて、なるほどと思った。さすが「利己的な遺伝子(Selfish Gene)」を書いたドーキンスだけのことはあるといっても、まだ未読のリストにしか存在していないのだが。進化の中に言葉を位置づけた興味深い考え方である。「地球大進化」はよくできた番組だ。これまでじっくり見ることができなかったし、DVDを買うかな。最近は書籍の出費が嵩んでいるし、5月連休まで楽しみに取っておくか(^^;)
一階の表側の窓拭きを済ませた。窓ガラスに水をかけて、長い柄の付いたT字型のスクレパーでまずスポンジ側で擦って汚れを落とし、ゴムのブレード側で上から下に水を拭き取っていく。この方法はスマートで簡単にきれいになる。
今年はどういうわけか、12/30に年賀状がすべて刷り上った。毎年大晦日の声を聞かないと出来上がらなかったのだが。文面の一部は商業印刷に回したりしたので作業量が減ったこともあったが、表書きと一部の文面は自分でインクジェットプリンタで150部余りを印刷した。やれやれ。明日早く出してこよう。余裕だねえ(^^;)しかし、ここまで時間を掛けるのはもったいないと思うぐらいに住所録の整備に時間が掛かる。日頃からきちんとしておけばいいんだけど、筆まめの住所録を起動するのは年末だけになる。来年は少し工夫してみよう。メモるシステムを応用するかな。名刺管理との連動も考える必要がある。
情報処理学会に入ると電子図書館で会誌を過去に遡って閲覧が可能になる。そのために入ったようなものかもしれないのだが、早速、電子図書館のオンデマンドサービスに登録して、(翌日には承認が得られたので)探索。2004年2-3月号に表題解説が掲載されている。「多重トピックテキストの確率モデル」というのは、文書テキストには一つのトピックだけが載っているとは限らないので、複数のトピックの存在に対応して、文書モデルにおいてトピック(内容)の多重性を取り扱えるようにしようという意図を持つわけだ。実際、更新日記を書いていてもカテゴリを決める上で内容の多重性をどう取り扱うのかという問題は大きい課題である。これはコンピュータ上で文書を取り扱うのに必要というだけでなく、どのように文書を書くべきかという問題に通ずるのである。文書を単一のトピックに分類するのに無理があることは当然であり、もしそのような分類をするにしても実用的な意味がどこまであるのかどうか、疑問を持たざるを得ないわけで、人間が分類したとしても適切かどうかはわからないのである。ここには文書を書いたり、理解するための本質的な問題が含まれているので、このようなアプローチが出てきていることに大変興味がある。副題には「テキストモデル研究の最前線」とある。
現在使われている単語出現頻度によるテキスト分類方法を単にトレースしてみようと思わないのはその実用的な意味に疑問を感じるからである。多重トピックの存在を考慮するのは一歩前進と考えられるだろう。もう一つの方向性は語順を考慮するNグラムモデルだろうと思われるが。
メイン機の40GBのハードディスクが満杯になりつつあるせいか、起動・終了に時間が大変掛かる。ダウンロードファイルだけでも他に移したい。2-3GBはあるだろう。コジマ電機に出掛けた。結局、I-O DATAのHDHU120という120GBのUSB接続タイプHDDを購入。13,800円也。コストパフォーマンス的にはこのクラスがベストだろう。当面の目的にはこんな大容量は必要ないのだが。コンピュータと連動して電源が自動に入るスイッチがある。ファンレス設計で静かだ。電源は必要だがドライバインストール不要で、FAT32にフォーマット済み。USBフラッシュメモリと同様にすぐ使い始めることができる。
12/26のスマトラ沖大地震の津波の被害状況が日々伝えられる。3m以上の高さの津波は大津波に分類される。5-6m程度はあったそうだから、大津波である。どちらかというと波が押し寄せる時よりも、引く時がスピードが速く破壊されたものが一緒に流れるので大変危険ということだ。
はつかいち大橋の温度計も10℃を切る数値を示すようになった。昨日は7℃。「寒くなりましたね」と挨拶が出るようになったけど、まだ山陰のほうも雨らしい。まだ今年は雪を見ていない。こんなことは初めてかもしれない。昨晩は帰宅して会社の地域つながりの友人達と自宅近くの飲み屋で最後の忘年会。まったくクリスマスの雰囲気はなかったが・・・
今年の10大ニュースがテレビや新聞紙面を賑わしている。さて、今年は残り少ない年をどう過ごすかな。何をしていたかと、昨年の更新日記を眺めると、12月後半の日記はない。「実践実用Perl」執筆の追い込みで、全体を通して書き上げるのに必死だったから。
今年の成果は前半の5件に、後半の5件は来年への展開につながるだろう。
Simon Cozensさんの執筆記事で、Template::ExtractとXML::RSSモジュールを使用して、RSS作成用CGIドライバにサイト毎の抽出用データを入力するというスマートなシステムを作る話になっている。おもしろいけど、実際に適用しようとすると限界がある。例えば、更新日記のRSSの生成は無理。データ構造がTemplate::Extractが想定しているのとは違うからである。更新日記RSS作成用CGIは「実践実用Perl」のサンプルに同梱している。
記事のサンプルはMovable Type Ver.2.661のブログを対象としているが、既にブログのバージョンが上がっているので、適当に試してみることができない。身代わりのTeacupレベル1掲示板のデータ構造はシンプルだが、なぜかうまく動かない。rssify.cgiを動かしても、一つの記事しか取得できない。例えば、こんな具合にスクレーピング方法を記述する。UTF-8で記述する必要がある。
title: ・・・ link: http://nnnn.teacup.com/xxxx/bbs description: ・・・ language: ja <HR> [% FOREACH records %] <font size="4" color="#ff0000"><b>[% title %]</b></font> 投稿者:<font color="#555555"><b>[% ... %]</b></font> <font size="2"> 投稿日:[% date %] </font> <p> <blockquote> [% content %] </blockquote> <p> [% END %]
Template::ExtractモジュールはPerl 5では動きそうもないので、あきらめて、正規表現礼賛「実践実用Perl」風に書く。XML::RSSモジュールはjperlでも問題なく動く。切り出したRSS用データをUTF-8に変換してモジュールに渡している。
use LWP::Simple; use XML::RSS; use Unicode::Japanese; $s = Unicode::Japanese->new(); $alloutsw = 1; # 新着20件のうち未読のみ表示なら、0;新着20件すべて表示なら、1 if($ENV{'QUERY_STRING'}){ @in = split(/&/, $ENV{'QUERY_STRING'}); ($server = $in[0]) =~ s/^server=(.*)$/$1/; ($user = $in[1]) =~ s/^user=(.+)$/$1/; $getfile = "http://${server}.teacup.com/$user/bbs"; }else{ $getfile = "http://6620.teacup.com/take/bbs"; $server = "6620"; $user = "take"; } print "Content-type: text/xml\n\n"; $rss = new XML::RSS (version => '1.0'); my $doc = get($getfile); @content = split(/\n+/, $doc); foreach $line (@content){ if($line =~ /^<title>([^>]+)<\/title>$/i){ $bbs_title = $1; }elsif($line =~ /^<font size="4" color="#[\w\d]{6}"><b>([^<]+)<\/b><\/font> 投稿者:<font color="#[\w\d]{6}"><b>([^<]+)<\/b><\/font>$/i){ $title = $1;$writer = $2; }elsif($line =~ /^<font size="4" color="#[\w\d]{6}"><b>([^<]+)<\/b><\/font> 投稿者:<b><a href="mailto:[^"]+">([^<]+)<\/a><\/b>$/i){ $title = $1;$writer = $2; }elsif($line =~ /^<font size="2"> 投稿日: *(.+) (<![^>]+>)*<\/font>$/i){ $date = $1; $sw = 1; } if($line =~ /^<blockquote>$/i && $sw == 1){ $dsw = 1; }elsif($line =~ /^<\/blockquote>$/i && $sw == 1){ $rss->add_item( title => $s->set("$title ($writer)", 'sjis')->utf8, link => $s->set($getfile,'sjis')->utf8, dc => { date => $s->set($date,'sjis')->utf8, }, description => $s->set(&entities_encode($bbs_content . $line),'sjis')->utf8, ); $sw = 0;$dsw = 0;$bbs_content = ""; } if($dsw == 1){ $bbs_content .= $line, "\n"; } } $rss->channel( title => $s->set($bbs_title, 'sjis')->utf8, link => $getfile, description => $s->set("サーバー、$server 番、ユーザ名、$user のTeacupレベル1掲示板",'sjis')->utf8, language => 'ja' ); print $rss->as_string; sub entities_encode{ local($str) = @_; $str =~ s/&/&/g; $str =~ s/</</g; $str =~ s/>/>/g; $str =~ s/"/"/g; return $str; }
このようなスクリプトの出力を次のようにしてrss2html.cgiに渡せば、そのままHTML出力を得ることができる。
http://localhost/cgi-bin/awakening/rss2html.cgi?rss=http://localhost/cgi-bin/rss_teacupbbs2.cgi
「実践実用Perl」サンプルに同梱しているrss2html.cgiの出力デザインには工夫の余地がある(^^;)Teacupの掲示板の場合は、タイトルから各記事へリンクできないが、RSSで記事内容を全部読めるのでリンクは不要である。それなら、掲示板そのものを読めばいいということになるが、rss2html.cgiの出力からは、各記事へのメモを書くことが可能になる。
今日、The Perl Journalを整理していたら、November 2002 号に、「Perl & LWP」の著者、Sean M. Burke氏の"Turning HTML into an RSS Feed"という記事を見つけた。RSSを持たないサイトのRSSを生成しようという話である。この中で、HTML::TokeParserやHTML::treeBuilderを用いる進歩したアプローチの代わりに正規表現を使うと書いている。なぜわざわざ進歩したアプローチを使わないのかについては明確には言及していないが、テンプレートも予期しない変化を示す可能性があるとしている。HTMLのパーサーを使うのと正規表現による切り出しとどちらがrobustかは、ケースバイケースだろう。すべてがHTMLのテンプレートで表現できるかというとまずできない場合が多いと思う。正規表現こそが最もフレキシブルなテンプレートを提供し得るのである。
日付が変わったので、別記事にするが、「いちばん大事なこと」を読了。目次の最後のほうに参勤交代というのがあるのでなんのことかと思っていたけど、おもしろい。読むべし。久しぶりに痛快な本を読んだよ。
perlカテゴリの記事が多くなっているが、仕方ないかな。Perl本を書いた以上ね。久しぶりかな、本カテゴリ。養老孟司著、「いちばん大事なこと--養老教授の環境論」、集英社文庫0219B、2003年11月19日第1刷(2004年1月26日第6刷)、198ページ、660円。まだ、全部読んだわけではないけど、大変おもしろい、バカの壁よりも。本当に養老先生が書きたいことを書いている感じ。システムの重要性をわかりやすく説く。自然はシステムである。遺伝子組み換えに対する批判も、そうだよねえと頷くばかりだ。どこで問題が起きるか予測ができない。養老先生の人生の中の純粋な時間を取り出すと虫取りなんだそうだ。虫取りの話も示唆に富んでいる。種明かしは例によって止めておこう。是非読んでみよう。なぜマイマイカブリが書いた本になるのか・・・
さて、スパイダリングの正攻法は、HTMLを解析するモジュールを使うということになっているが、結局モジュールを使っても簡単にはうまくいかないケースも多い。そこで、正規表現によるパターンマッチングを使って必要な部分を抽出する方法を使うことになる。私の意見では、これまでの例でも見たように正規表現を直接使う方法が簡単で、早く解決に結びつく。もともとPerlのテキスト処理プログラミングの効率を高めているのは、正規表現によるパターンマッチングなのだから、これを使うのが最もよいはずだ。テキストを加工するのにモジュールを使うのは二度手間である。この項は最も的を得たHACKかもしれない。項のタイトルどおり、正規表現礼賛(^^)v プリンタの持つウェブサーバーが返すステータスページを解析するサンプルは動かせないのでパス。もっとも正規表現がすべてなので、jperlでも変わらないだろう。
とうとう届いた(^^)v、Issue 1。表紙はカラーで、写真があるせいか、光沢紙が使ってある。なーんて、まあ内容とは関係ないことだが、スクリプトが見やすいのは大変良い。The Perl Journalはスクリプト部分の文字が小さ過ぎて見難い。TPRは紙ベースで出すということだったが、PDF配布で値段を下げる選択肢を設定している。印刷する手間もないし、本の好きな僕としては紙媒体のほうがうれしい。封筒に貼ってある6枚の切手の額を計算すると、3.50$。年間20$ではペイしないね。申し訳ない感じ。郵送費は別に取ればよいのに。うーむ、よく見ると外国の購読者は年間30$の設定になっている。出版構想時に支払ったときは20$だったはずだけど。いいのかなあ。
最初のDown Translating XMLの記事にあるXML::DTモジュールはおもしろいテキストツールになるかも。
TPRv1i1Live Journalも出た。
引き続き、WWW::Mechanizeモジュールを使ったスクレーピングの例。「次のページ」のようなボタンをクリックして別のページからも情報を取り出すという多少面倒な作業を行う。HTMLのフォームを解析して、クリックするような動作をシミュレートするメソッドを持つHTML::Formというモジュールがベースになっているのだろう。しかし、このようなモジュールを調べても、動かない原因を調べるのは面倒なだけで、実際のHTMLを調べれば簡単に目的のスクリプトは書けるはずだ。
bbsimage.plは具体的なサイトを対象としていないので、テストが簡単にはできない。掲示板サイトでページを辿って、記事をすべて読み出すスクリプトを考えてみよう。Teacupレベル1掲示板で新着のトップページだけでなく過去のページからも記事を切り出すスクリプトを考える。掲示板のHTMLにフォームのボタンに表示される「次のページ」という文字列があれば、順に古いページを読み取る。掲示板は20件ずつ記事を表示する。bbsというCGIにpage=20というようにして20の倍数をパラメータを渡すだけで、次のページを表示できる。このようなことは掲示板を動かし、各ページでソースを表示させればすぐわかる話だ。掲示板には200件しか記事は保存されない。「実践実用Perl」環境下で動くローカルCGI、na_teacupbbs2.cgiの改造版。
use LWP::Simple; $docroot = $ENV{'DOCROOT'};# 「実践実用Perl」設定環境変数 $teacupL1dir = "teacupL1"; $cgidir = $ENV{'CGIDIR'};# 「実践実用Perl」設定環境変数 $alloutsw = 1; # 新着20件のうち未読のみ表示なら、0;すべて表示なら、1 if($ENV{'QUERY_STRING'}){ @in = split(/&/, $ENV{'QUERY_STRING'}); ($server = $in[0]) =~ s/^server=(.*)$/$1/;# serverにサーバー番号 ($user = $in[1]) =~ s/^user=(.+)$/$1/; # userにユーザー名 $getfile = "http://${server}.teacup.com/$user/bbs"; }else{ $getfile = "http://6620.teacup.com/take/bbs"; $server = "6620"; $user = "take"; } $i = 0; print <<HEADER; Content-type: text/html <HTML> <HEAD> <TITLE>Teacup掲示板新着チェック</TITLE> <META HTTP-EQUIV="Content-Type" content="text/html; charset=Shift_JIS"> <link rel="stylesheet" type="text/css" href="/mystyle.css"> </HEAD> <BODY> <div class="emph">TeacupLevel1掲示板</div> HEADER while(1){ $num = $i * 20; $content = get($getfile . "?" . "page=" . $num); if($content){ @content = split(/\n+/, $content); foreach $line (@content){ if($line =~ /^<title>([^>]+)<\/title>$/i){ $bbs_title = $1; }elsif($line =~ /^<font size="4" color="#[\w\d]{6}\t*"><b>([^<]+)<\/b><\/font> 投稿者:<font color="#[\w\d]{6}"><b>([^<]+)<\/b><\/font>$/i){ $title = $1;$writer = $2; }elsif($line =~ /^<font size="4" color="#[\w\d]{6}\t*"><b>([^<]+)<\/b><\/font> 投稿者:<b><a href="mailto:[^"]+">([^<]+)<\/a><\/b>$/i){ $title = $1;$writer = $2; }elsif($line =~ /^<font size="2"> 投稿日: *(.+) (<![^>]+>)*<\/font>$/i){ $date = $1; $sw = 1; } if($line =~ /^<blockquote>$/i && $sw == 1){ $dsw = 1; }elsif($line =~ /^<\/blockquote>$/i && $sw == 1){ $data{"$date\t$title\t$writer\t$getfile"} = $bbs_content . $line; $sw = 0;$dsw = 0;$bbs_content = ""; } if($dsw == 1){ $bbs_content .= $line, "\n"; } } if($content !~ /value="次のページ"/){ last; } }else{ last; } $i++; } print "<p><a href=\"$getfile\" target=\"main\">$bbs_title</a></p>\n"; dbmopen %TEACUPDB, "teacup", 0666 or die "<p>Can't open teacupdb: $!</p>\n</BODY>\n</HTML>\n"; print "<table border=0 width=\"90%\">\n"; $newsw = 0; foreach $key (sort keys(%data)){ @array = split(/\t/,$key); if($TEACUPDB{$key}){ if($alloutsw == 1){ print "<tr bgcolor=\"#eeeeee\"><td><font color=\"#000000\"><a href=\"/$teacupL1dir/$TEACUPDB{$key}\" target=\"main\">$array[0]\t$array[1]\t$array[2]</a></font></td></tr>\n"; } }else{ $id = time + $newsw; $outfile = "teacup_${server}_${user}_${id}.html"; $TEACUPDB{$key} = $outfile; print "<tr bgcolor=\"#eeeeee\"><td><font color=\"#000000\"><a href=\"/$teacupL1dir/$outfile\" target=\"main\">$array[0]\t$array[1]\t$array[2]</a></font></td></tr>\n"; open(OUT, ">$docroot/$teacupL1dir/$outfile"); print OUT "<HTML><BODY>\n"; print OUT "<h3><a href=\"$array[3]\">$bbs_title</a></h3>\n"; print OUT "<p>$array[0] $array[1] 投稿者: $array[2]</p>\n"; print OUT $data{$key}; print OUT "</BODY></HTML>\n"; close(OUT); $newsw++; } } dbmclose %TEACUPDB; print "</table>\n"; if($newsw == 0){ print "<p><font color=\"red\">未読はありません。</font></p>\n"; } print "<p><font color=\"red\">現在、", $i + 1, "ページ、記事数: ", scalar keys(%data), "です。</font></p>\n"; print "</BODY>\n</HTML>\n";
jperlで、URIモジュールの動作は確認できたが、HTML::Formモジュールの動作は未確認状態。
慶応大学の授業を学外から聴講できるというSFC-GCに登録した。MITのOpen Course Wareに似ているが、教材だけでなく、実際の授業をビデオで聴講できるというのがおもしろい(村井研のWIDE School of Internetの仕事)。2002年の秋から実験的に行われている。昨日は2004年春学期の石崎俊先生の「自然言語論」第1回。久しぶりに大学の授業を楽しめた。他にも聞いてみたいコンテンツに溢れている。過去の授業を見ることも可能だ。少しずつ勉強していこう。ついでにWIDE SOIにも入学。
Teacupレベル1掲示板CGIブラウザを「実践実用Perl」サポートサイトに発表。といっても原型は1年以上前に書いた、企画構想時のもの。読んだ記事はローカルに保存される。新着記事のブラウザであり、保存した古い記事へのアクセスはまだ考慮されていないが・・・HTMLで保存されるので、検索して表示するスクリプトは簡単に書けるだろう。Teacupの掲示板は老舗で安定しているので、スパイダリングの対象としては確実なものだろう。
HACK#21はWWW::Mechanizeモジュールの応用例。このモジュールは、Perl 5.6以降である必要がある。our宣言をPerl 5はサポートしていない。our宣言を削除して、use strictをはずせば、エラーは出なくなるがスクリプトがまともに動くことは期待できない。CPANで古いバージョンを探しても、Perl 5に対応していたものはない。スクリプトの内容は、CPANを著者名で検索して、配布モジュールのリストを調べ、ファイルをダウンロードして、サイズを表示するというものである。この程度のスクリプトであれば、LWP::Simpleだけで十分である。jperl用に「実践実用Perl」風のスクリプトを書いてみよう。LWP::Simpleのgetとgetstoreメソッドを使う。本スクリプトの場合はCGIのクエリ文字列がASCII文字列なので、URLエンコーディングは行っていない。
use LWP::Simple; use Unicode::Japanese; my $s = Unicode::Japanese->new(); my $base_url = "http://search.cpan.org"; my $query = "Lester"; my $mode = "author"; # CPANを著者名、"Lester"で検索する my @page = split(/\n/,get("${base_url}/search?query=${query}\&mode=${mode}")); # 検索結果の"Andy"とマッチする行から、著者ページへのリンクを取得する。 foreach my $line (@page){ if($line =~ /Andy/){ ($authorurl = $line) =~ s/^.+<a href="(\/author\/\w+?)"><b>[\w\s]+<\/b><\/a>.*$/$1/i; last; } } # 著者ページを取得する。 my @distpage = split(/\n/, get($base_url . $authorurl)); my %fileurl; # ダウンロード用のファイル名とファイルのURLを取得する。 foreach $line (@distpage){ if($line =~ /<a href="(\/CPAN\/authors\/.+\/)(.+?)">Download<\/a>/){ $fileurl{$2} = $base_url . $1 . $2; } } print "ダウンロード対象となる", scalar keys(%fileurl), "個の tarball があります。\n"; # 対象ファイルをダウンロードして、ファイルサイズと一緒に表示する。 foreach my $file (keys(%fileurl)){ print $file, " --> "; if(getstore($fileurl{$file}, $file)){ print -s $file, " バイト\n"; }else{ print "\n"; } }
出力結果は次の通り。
C:\Scripts\Perl\spiderhks\hack021>jperl ls.pl ダウンロード対象となる24個の tarball があります。 WWW-Mechanize-Cached-1.32.tar.gz --> 15622 バイト Pod-Outline-0.01.tar.gz --> 2307 バイト Test-Taint-1.04.tar.gz --> 12404 バイト Test-Pod-1.20.tar.gz --> 6817 バイト Apache-Pod-0.10.tar.gz --> 4350 バイト Test-WWW-Mechanize-1.00.tar.gz --> 6003 バイト WDDX-1.02.tar.gz --> 26453 バイト MARC-Record-1.39_01.tar.gz --> 116742 バイト Acme-PM-Chicago-0.01.tar.gz --> 1628 バイト Module-Starter-1.22.tar.gz --> 7353 バイト HTML-Lint-1.30.tar.gz --> 64083 バイト File-Find-Wanted-0.01.tar.gz --> 2430 バイト HTML-Tidy-1.05_01.tar.gz --> 19396 バイト Test-Memory-Cycle-0.02.tar.gz --> 3615 バイト Apache-Lint-0.10.tar.gz --> 2664 バイト release-0.16.tar.gz --> 8101 バイト Test-Regex-0.01.tar.gz --> 2890 バイト Test-HTML-Tidy-1.00.tar.gz --> 8140 バイト Test-Harness-2.44.tar.gz --> 59821 バイト Template-Timer-0.02.tar.gz --> 2506 バイト WWW-Mechanize-1.06.tar.gz --> 99455 バイト Carp-Assert-More-1.08.tar.gz --> 7734 バイト Test-Pod-Coverage-1.06.tar.gz --> 5149 バイト Data-Hash-Totals-0.10.tar.gz --> 3076 バイト
スクリプトの仕様はご都合主義だが、下手に使いやすくすると、大量のファイルをダウンロードしてしまうことになりかねない。注意して使うこと。
HACK#20はHTML::TokeParserモジュールの使用例だが、残念ながら対象としているサイトが消滅している。インターネットは生々流転。単に消滅するものもあれば、発展的解消あるいはHTML構造変更という形の変化もある。HTMLを対象としたスパイダリング技術の応用は常に変化を覚悟しておく必要がある。
「実践実用Perl」サポートサイトがニューデザインで新しく立ち上がりました(^^)v → http://script.mono966.org/
新サイトの立ち上げ記念に何かスクリプトを考えよう。
昼から市民病院にお見舞いに出掛けた。県病院前から市内電車でゴトゴト。本通りで降りて地下街のシャレオから市民病院へ。もう元気でスタスタ歩かれているので安心。帰りはデオデオで少しコンピュータ関係を眺めて、隣のフタバ図書へ移動。グレッグ・イーガン著、山岸真訳、「万物理論」、創元SF文庫、2004年10月29日初版、616ページ、1200円を購入。文庫本で、1200円は高いねえ。帯にある「宇宙消失」は持っているけど、途中で積読に・・・SFも感情移入するまでは集中して読めない。その世界に入りきるまでは、なかなか先に進めない。特殊な世界の設定はむずかしく、一歩間違うと読みにくい本になってしまう。「順列都市」は読みきることができたが、結構時間が掛かった。さて、この機会に「宇宙消失」にも再チャレンジしよう。Greg Egan's Homepage
HTML::TreeBuilderモジュールを使って、スクレーピングする例。HTML::Tagsetモジュールも必要である。このモジュールはPerl 5に対応しているので、CPANから最新版の3.1.3をダウンロードして、libをコピーすればjperlで使える。サンプルのoreilly_catalog.plはそのままでは、Perl 5.8でも動かない(もちろん、ppmでモジュールをインストールしておこう)。なぜなら、オライリーのサイトが変化しているからである。文字コードはshiftjisでなく、euc-jpでdecodeする必要があるし、HTMLの構造も大幅に変化している。
jperlで動かすには次のようにする。まず、encoding関係の2行とuse Encode;の行を削除して、次の2行を追加しておく。
use Unicode::Japanese; my $s = Unicode::Japanese->new();
LWP::Simpleのgetメソッドで取得したページは、EUCからSJISに文字コードを変換する。2箇所変更。jperlはそれだけ考えていればよいので、スクリプトがシンプルになる。タイトルデータをEncodeモジュールでdecodeしている部分は不要なので、はずしておくこと。
$page = $s->set($page,'euc')->sjis;
オライリーの全書籍リストから各書籍ページへのリンクを取得する部分のマッチをサイトの現状に合わせて変更。HTML::TreeBuilderのモジュールのlook_downメソッドを使っている。jperlでqr(quote regex)って動くんだね。発見。
my @links = $p->look_down( _tag => 'a', href => qr{^ ..\/books\/ \w+ / $}x );
最後のforループは相当書き換えているので、載せておこう。HTML::TreeBuilderモジュールの使い方を調べるより、自分でパターンマッチングを書いたほうが速いと思うが(^^;)元のスクリプトのままでデータが取得できないので戦略を変更、look_downメソッドを使って、divタグのclass属性の値から出版データ部分を取得している。as_trimmed_textメソッドは前後の空白や改行を削除する。取得されるURLは相対URLになっているので、絶対URLに変換している。
for my $book ( @books ) { next unless $book->{title} =~ /Google Hacks/; # Google Hacksのみ処理する。 my $url = $book->{url}; my $base_url = "http://www.oreilly.co.jp/"; $url =~ s/\.\.\/(books\/\d+)/$base_url$1/; my $page = get( $url ); $page = $s->set($page,'euc')->sjis; my $tree = HTML::TreeBuilder->new_from_content( $page ); my @pubinfo = $tree->look_down( 'class' => 'bookinfo' ); next if ($#pubinfo != 8); (my $pages = $pubinfo[5]->as_trimmed_text) =~ /(\d+)ページ/; (my $original = $pubinfo[8]->as_trimmed_text) =~ /原書:(.+)$/; (my $date = $pubinfo[4]->as_trimmed_text) =~ /(\d+年\d+月)発行/; print "\nページ数:$pages\n原書タイトル:$original\n発行年月:$date\n"; my $img_node = $tree->look_down( 'class' => 'bookimage' )->as_trimmed_text; (my $img_url = $img_node) =~ s/^.+\.\.\/\.\.\/([^"]+)".+$/$base_url$1/; print $img_url,"\n"; my $cover = get( $img_url ); # ここで$coverをファイルに保存します。 open(OFH, ">cover.gif"); binmode(OFH); print OFH $cover; close(OFH); $tree = $tree->delete; }
出力結果は次のようになる。
C:\Scripts\Perl\spiderhks\hack019>jperl -IC:/Scripts/Perl/lib oreilly_catalogj.p l 1 945 Perl/Tk デスクトップリファレンス 2 1260 Perl5 デスクトップリファレンス 第3版 3 3045 続・初めてのPerl 4 3360 Perl & XML 5 3360 初めてのPerl Win32システム 6 3780 Perlクックブック 第2版 VOLUME 2 7 3780 初めてのPerl 第3版 8 3990 入門 Perl/Tk 9 3990 入門 Perl DBI 10 3990 Perl/GNUソフトウェアによるWebグラフィックスプログラミング 11 4725 実用 Perlプログラミング 12 4725 Perl クイックリファレンス 13 4935 プログラミングPerl 第3版 VOLUME 2 14 5040 Perlによるシステム管理 15 5040 バイオインフォマティクスのためのPerl入門 16 5145 Perlクックブック 第2版 VOLUME 1 17 5565 プログラミングPerl 第3版 VOLUME 1 Perl本は17冊あり、Java本は33冊あります。 Perl本よりもJava本の方が16冊多いです。 ページ数:400ページ 原書タイトル:原書: Google Hacks 発行年月:2003年08月 発行 http://www.oreilly.co.jp/books/images/picture4-87311-136-6.gif
Perl 5.8では、最後の$img_nodeの取得がなぜかうまくいかないし、HTML::TreeBuilderのlook_downメソッドの戻り値をSJIS文字列を出力するために、utf8でdecodeする必要があるというのも直感に反するというか、わかりにくい。
HACK#18の続き。Term::ProgressBarモジュールを使う例だが、2.00以降では、TermReadKeyの2.14以降が必要。ActivePerl Build 522には、2.12しか準備されていない。もう一つ必要なClass-MethodMakerは問題ないが、Perlも5_005.62が必要とある。Term::ProgressBarの1.0は他のモジュールを必要としないので動くが・・・これで我慢するか(^^;)
ダウンロード時のプログレスバーの表示。うーむ、LWP 5.45で対応しきれないメソッドが出てきた。:content_cbヘッダをgetメソッドにURLと一緒に引数として取っている。他の書き方ではcallbackサブルーチンがうまく動かない。
5.69以上が必要ということなので、CPANのOther Releasesから、libwww-perl-5.69をダウンロードして適当な場所に解凍する。encoding関連の2行を削除し、libフォルダをインクルードして、jperlでスクリプトを実行してみる。あっさりと動く。最近のバージョンのライブラリは次第にPerl 5.6以降の文法に依存したライブラリとなりつつあると思われるが、あきらめずに必要なバージョンでできるだけ古いものを使ってみよう。これでこれまで避けてきたLWPのgetメソッドがそのまま使える。WindowsユーザーもCPANを活用すべし。
LWPモジュールによるAccept-Encodingヘッダの取得とCompress::Zlibモジュールによる圧縮データの展開の例。ActivePerl Build 522のCompress::Zlib(1.03)にはmemGunzipメソッドがないので、Perl 5.8に軍配が上がる。XSで拡張しているモジュールは単にコピーしても使えない。1勝1敗8分。コマンドライン版のgzip抽出ツール7zを使う手はある。
アプリケーションを設計するのに二つの選択肢がある。プラットフォームに依存する方法と、できるだけプラットフォームに依存しない方法だ。The Perl Journal Nevember 2004に、Blair Suttonさんの"Building GUIs with Win32::GUI::XMLBuilder"とSimon Cozensさんの"Using the Web as a GUI"の二つの記事が並んだ。
Win32::GUIでSJIS日本語を表示させる方法はどうするんだろう。最初に躓いたので、さて・・・プラットフォームへの依存も難しい(^^;)
GUIとしてWebを使うというアイデアは今や当然な選択であるわけで、「実践実用Perl」は積極的にその方向を指向している。ただ、文字コードの問題があるために英語圏ユーザーのようにプラットフォーム非依存というわけにはいかない。プラットフォーム非依存になるには、日本語文字コードが各プラットフォームで統一される必要がある。
Perl 5.8をUTF-8の文字コードで使うというのが最もクールな方法と考えられる。これはブラウザ上では有効だからである。この有効性を検証してみるのもよいかもしれない。
LWPモジュールを使ったHTTPヘッダのEtagの使用例。LWP::UserAgentの新しいgetメソッドは確かに便利だが、ブラックボックス化の度合いが進んでいるだけ、古い記法でも当然問題ない。jperlでヘッダをリクエストして取得する部分。
my %headers = ( 'If-None-Match' => $etag ); my $browser = LWP::UserAgent->new; my $request = HTTP::Request->new("GET", $url); $request->header(%headers); my $response = $browser->request($request);
まだ、LWPモジュールがメインだが、HTTP::Dateモジュールの使用例である。秒数を時刻文字列に変換するメソッドを持っている。jperl 5.005_03(ActivePerl build 522)にもライブラリが標準で存在するので、問題なく動く。
Floating Antenna - 更新日記のようにして使えるみたい。Fe2+さんのサイトで見つけた。Awakening Projectは様々な場面で実用化段階に入っているってこと。まあ、Projectと直接関係はないんだけど。同様なアイデアはあらゆるところで同時発生的に具体化されていくことだろう。
http://japan.cnet.com/news/tech/story/0,2000047674,20078203,00.htm?ref=rss
人を乗せて動く歩行ロボットが出てきたことには驚いた。アニメの世界が現実となりつつある。ある意味、不気味な感じもする。技術の進化に限界はないのかと最近思うことが多い。
変身して様々な任務をこなす、自己変形ロボットの現在(上)の記事もある。
LWPによるSSL接続の例。LWPでSSL接続するためには、Crypt::SSLeayモジュールが必要だが、ActivePerl 5.8には準備されていないので動かすことができない。ActivePerl 5.005_03 Build 522にはモジュールが準備されているので、ppmでインストールして動かすことが可能だ。このHACKに関しては、現時点では、jperl 5.005_03のほうに軍配が上がった。
思わぬ展開だが、なぜ、ActivePerl 5.8にCrypt::SSLeayのモジュールがないのだろう。CPANからCrypt::SSLeayのソース(Crypt-SSLeay-0.51)をダウンロードして、makeしてみようと思ったが、OpenSSLまでインストールする必要もあるし、Build 618と522にバイナリがあるというREADMEを見て中断。Perl 5にインストールされたものをコピーしてもWindowsのエラーになって動作しない。
これもLWPモジュールの古いバージョンに対応するように書き直すだけ。URI->new_abs(相対URL, HTTP::Messageモジュールの基底URL)で、相対URLを絶対URLに変換する例。LWP::UserAgentモジュールはいつもながらややこしい。
http://hotwired.goo.ne.jp/news/technology/story/20041202303.html
TopicMapsは既に有効に活用されているみたいだ。どんなふうに使っているのだろう。要調査。
これは、LWP::UserAgentのcredentialsメソッドを使ったHTTP認証の自動化。ユーザー名とパスワードが公開されているサイトでテストしている。例によって、LWP::UserAgentのgetメソッドの代わりに、HTTP::RequestのGETを使うだけで良い。第5面クリア。クッキーやプロキシを使う例も出ている。まだしばらくLWPモジュールを使うHACKが続く。当面同じ料理法で対応可能だろう。
zipsearch.plもhakusho.plの改造と同様の方法で良い。LWPとURIモジュールを使っているので、jperlでは、LWP::UserAgent(5.64以降)のgetメソッドの代わりにHTTP::Requestを直接使い、URIモジュールの代わりにjuri_encodeサブルーチンを使用する。第4面もクリア。
ここらのモジュールまでは「実践実用Perl」の守備範囲だが、次第に未体験ゾーンに突入していくことになる。わくわくするね。HACKは訳者補の#101 Perlと日本語処理で終わるので、100日分くらいはネタがあることになる。全516ページのうち、現在位置は45ページだ。