30 posts tagged “perl”
本棚.orgと同じ要領で。だいたい、WWW::Mechanize::Shellを使ってWWW::Mechanizeなスクリプトを作り、scraperを使ってWeb::Scraperなスクリプトを作り、つなげれば終わりなのです。といってここではWeb::Scraper使ってないのだけど。
ということで、メディアメーカー。
#!perl -w
use strict;my $uname = 'tsukamoto';
my $pass = 'mypassword';use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis 7bit-jis/;
binmode STDOUT, ":encoding(sjis)";use WWW::Mechanize;
use URI::URL;use Text::CSV;
use YAML;my $agent = WWW::Mechanize->new( autocheck => 1 );
$agent->env_proxy();$agent->get('http://mediamarker.net/login');
$agent->form_name('login');
$agent->current_form->value('uname', $uname);
$agent->current_form->value('pass', $pass);
$agent->submit();
$agent->get(URI->new_abs('config?op=export', $agent->base));
$agent->form_number(1) if $agent->forms and scalar @{$agent->forms};
$agent->current_form->value('ex_code', 'utf-8');
$agent->current_form->value('exterm', 'all');
$agent->submit();
my $content = decode('Guess', $agent->content);my @lines = split(/\n/, $content);
my $csv = new Text::CSV({
binary => 1,
allow_loose_escapes => 1,
allow_loose_quotes => 1,
escape_char => undef,
});my @fields;
while(@lines){
next unless $csv->parse(shift(@lines) . "\n");
@fields = $csv->fields;
last;
}my $result = [];
while(@lines){
next unless $csv->parse(shift(@lines) . "\n");
my @values = $csv->fields;
my $row = { map { $fields[$_] => $values[$_] } (0 .. $#fields) };
push(@$result, $row);
}
print YAML::Dump($result);
メディアメーカーの場合はCSVエキスポートになるので、Text::CSVで解析。Text::CSVってもっとスマートに使えなかったっけな。なんだかひどく下手な書き方感があるのだけど。
さて、他に僕の読書情報が入っているのは各所OpenPNEなんだけど、それ以外にアカウント作ってデータ入れてまでこれをやるべき本棚サービスってあるかな。
あっちこっちの本棚の情報を同期するのがめんどくさいな、と思ってまずデータを抜き出すことを考えてみる。そういうのもどこかにありそうで、探せば良さそうなものだけど、そこは「手を動かすなんてめんどくさい」じゃなくて「手を動かしたほうが楽しい」になるから不思議。
まず本棚.org。
#!perl -w
use strict;my $url = 'http://www.hondana.org/walrus/datalist';
use utf8;
use Encode;
use Encode::Guess qw/euc-jp shiftjis 7bit-jis/;
binmode STDOUT,':encoding(shiftjis)';use Web::Scraper;
use WWW::Mechanize;
use URI::URL;use JSON;
use YAML;my $agent = WWW::Mechanize->new( autocheck => 1 );
$agent->env_proxy();
$agent->get($url);
my $content = $agent->content;
$content = decode('Guess', $content) unless (Encode::is_utf8($content));my $scraper = scraper { process "#shelf pre", "json" => "text"; };
my $json = $scraper->scrape($content)->{'json'};
my $result = from_json($json);
print YAML::Dump($result);
本棚.orgはログインが要らなかったりデータがJSONだったりするのでかなり簡単。$resultはハッシュリファレンスの配列リファレンスになってます。
Windows環境で「定期的にRSSなどをチェック、新着情報をポップアップ表示」させたいと思って、簡易的なフィードクローラ“feed2growl”を書いてみました。次のような特徴があります。
- 新着情報をGrowlに送ります。
Growlなので、新着情報がポップアップ表示されます。Growl for WindowsやWhineでもOKです。 - タスクに登録して定期実行しやすいように配慮しています。
exeファイルは、定期実行時にコマンドプロンプト風の画面が出ないようになっています。
プロキシ情報はInternet Explorerから取得するので、環境変数などを気にしなくてOKです。
ただし使おうと思うと、Growlの設定ができること(私はWhineを使っています)、config.ymlを見て、何となく設定ができることが必要です。Perlスクリプトとしては、「LWP::UserAgent::ProxyAnyを使う」「Net::GrowlClientを使う」「タスクに登録しやすいexeファイル化をする」が個人的な今回のチャレンジ。むしろ、これらのサンプル程度にでもお役に立てば。
先日、Web::ScraperというPerlモジュールを初体験したのですが、これが非常に便利です。「今日のCPANモジュール」で紹介されているように、対話式のコマンドラインインターフェースがあって、これで実験しながらスケルトンを作成できるのも便利。Web::ScraperはHTMLの解析に強いのですが、クローリングに強いWWW::Mechanizeと組み合わせるとその素晴らしさは筆舌に尽くし難いものがあります。
ちょっと感動したので、今まで自分が使ったことのある、PerlのWebクライアントライブラリの系譜をまとめてみました。これを、以下のように理解して、使い分けています。
- クローリングのトレンドはWWW::Mechanize、Plagger、Gungho辺りを使い分け
- クローリング系ライブラリの基本はLWPとHTTP::*、どのライブラリでもこれらの知識は必要(たぶん必須)
- HTMLパースのトレンドはHTML::TreeBuilder::XPathを中心に、使えるところではWeb::Scraper
- ほんのちょっとのHTMLパーシングや特定のコメントタグ間(たとえばgoogle_ad_section)を抜き出したい時はHTML::TokeParserかHTML::TokeParser::Simple
図のコメントは私の個人的な使用感、使い分けについても、多分こうだろうと思うのですが、最近のPerl界の動向を追いかけられてないので、もしかしたらもう違うかもしれません。ただまず間違いないところでは、本気でPerlでWebクライアントする気なら「HTTP::LiteとかLWP::Simpleいいよね、パースは正規表現で」とか言ってないで、LWPを使い始め、その先の世界に踏み込むのです。絶対便利だから。
余談だけど、Web::Scraperは一昨日使い始めたのだけど、コードを晒してWassrで突っ込みをもらって修正して、で一気に理解できました。「わからないことは、決して恥ずかしいことじゃない」の話そのまま。
Wassrで悲鳴をあげてはtokuhiromさんに「Web::Scraper::Filterのドキュメントなんて現存しないから」とコードを教えてもらい、コードを晒せば「Scraper入れ子でok」とotsuneさんに添削され、自分で調べてたらいつまでも「わかんねーよー」と悲鳴をあげ続けるところを3時間でマスターしました。しかしこういうことには、今使ってるTumblrとかVoXは、トラックバックやオープンなコメントのやり取りができない分、弱いな。
ActivePerl 822の上書きインストール後に、次のようなエラーが発生。
C:\temp> h2xs -X -A MyModule
C:\temp> cd MyModule
C:\temp\MyModule> perl Makefile.pl
C:\temp\MyModule> nmakeMicrosoft (R) Program Maintenance Utility Version 1.50
Copyright (c) Microsoft Corp 1988-94. All rights reserved.Can't use string ("pmfiles.dat") as a HASH ref while "strict refs" in use at C:/Perl/site/lib/ExtUtils/Install.pm line 990.
NMAKE : fatal error U1077: 'C:\WINDOWS\system32\cmd.exe' : return code '0xff'
Stop.
いろいろと試してみたのですが、どうやらMakefile.plができるところまでは問題がなくて、「perl Makefile.pl」で作られるMakefileに謎の「<< pmfiles.dat」なる文字列が残っているのが問題。
更に言うと、Makefile.plから呼び出されているExtUtils::MakeMakerが悪くて、実は旧ActivePerl時代にC:\Perl\site\lib\に更新インストールされたMakeMakerが、ActivePerl 822に同梱されている新しいものより優先して使用されているのがいけないようです。
それならば、C:\Perl\site\lib\からExtUtils::MakeMakerは要らないはずだからアンインストール、と思ってトライ。
C:\temp\MyModule> ppm remove --area site ExtUtils::MakeMaker
C:\temp\MyModule> perl Makefile.pl
Can't locate ExtUtils/MM_Win32.pm in @INC (@INC contains: C:/Perl/site/lib C:/Perl/lib .) at (eval 4) line 3.
Compilation failed in require at C:/Perl/lib/ExtUtils/MakeMaker.pm line 43.
Compilation failed in require at Makefile.pl line 3.
BEGIN failed--compilation aborted at Makefile.pl line 3.
なぜかC:\perl\libからもExtUtils::MakeMakerが消えてるようです。
PPMで起動するGUIの一覧に、MakeMakerが見当たらないので、ここからZipファイルを取得。でてきたPPDファイルを指定してインストール。
C:\temp\MyModule> ppm install CPAN-MakeMaker.ppd
C:\temp\MyModule> perl Makefile.pl
Can't locate ExtUtils/MM_Win32.pm in @INC (@INC contains: C:/Perl/site/lib C:/Perl/lib .) at (eval 4) line 3.
ExtUtils::MakeMakerは入ったのだけど、ExtUtils/MM_Win32.pmは相変わらずないまま。他のMM_*.pmファイルはインストールされてるんだけど。
ということで、ActivePerlのzipファイルを取得して、この中のExtUtils/MM_Win32.pmをC:\perl\libにコピーして解決。
皆様には、こんな試行錯誤をするよりは、一度ActivePerlを削除して、きれいな環境に新しいのを入れることをお奨めします。PPMなりcpanなりで追加インストールしたライブラリ群を再インストールするのが嫌だけど、安全確実な方法ではあるんじゃないかと思います。
そういえば、ActivePerl Beta DownloadにActivePerl 5.10.0 Build 1000 Betaが出てきてますね。うーん、まだ5.6と5.8と5.8のヘッドの間でどたばたしてるのに、もう5.10系が来ちゃってるのか...。
WYSIWYGなWin32::GUI用GUI作成ツール“The GUI Loft”のサイトが移動していました。
Win32::GUIと合わせて再インストールしようと思ったのだけど、各所でリンクされている旧URLではアクセスできなくて、一慌て。他サイトでもやっぱり「The GUI Loftはどこにいった?」という声を見かけたので、メモしておきます。しかし、readmeにも旧URLが記されているんだけど、もしかして勝手ミラーサイトだったりするんだろうか?
●
メモついでに、インストール方法もメモ。
- 上記サイトから“Loft_binary.zip”を取得。
- “Loft_binary.zip”を展開。“TheGUILoft”というディレクトリができます。
- “TheGUILoft/PPM/AdHoc”ディレクトリで“install.bat”を実行。
- “TheGUILoft/PPM/Loft”ディレクトリで“install.bat”を実行。
- “TheGUILoft”ディレクトリは、“C:\Program Files\”などに移動し、残しておきます。
“TheGUILoft/Demo”以下の*.plファイルを実行できれば、インストールは成功です。
“TheGUILoft/tgl.exe”を実行すると、WYSIWYGなGUIデザイナが起動します。
●
インストールついでに、インストール時に出たエラーもメモ。インストールに失敗する時は、以下のようなメッセージが出ていないかチェック。
ppm install failed: No file at C:\PROGRAM FILES\TheGUILoft\gz
TheGUILoftディレクトリを、パスに空白を含む場所に展開した時に、上記のようなメッセージでinstall.batが失敗しました。Cドライブ直下など、空白を含まないパスに展開しなおしてやり直すと出なくなりました。
ppm install failed: File conflict for 'C:/Perl/html/site/lib/Win32/GUI/BitmapInline.html'.
The package Win32-GUI has already installed a file that package Win32-GUI-AdHoc
wants to install. Uninstall Win32-GUI, or use --force to allow files
to be overwritten.
“AdHoc”ディレクトリで“install.bat”実行時、Win32::GUIが既にインストールされていると、このエラーになるようです。ここではエラーメッセージで言われているように、
C:\TheGUILoft\PPM\AdHoc>ppm install --force Win32-GUI-AdHoc.ppd
として--forceオプション付きでインストールしました。
ppm install failed: File conflict for 'C:/Perl/site/lib/Win32/GUI/build_ppm.pl'.
The package Win32-GUI-AdHoc has already installed a file that package Win32-GUI-Loft
wants to install. Uninstall Win32-GUI-AdHoc, or use --force to allow files
to be overwritten.
“Loft”ディレクトリで“install.bat”実行時、このエラーになりました。Win32::GUI::AdHocは未インストールだとWin32::GUI::Loftをインストールできないので、やはり--forceオプション付きでインストールしました。
C:\TheGUILoft\PPM\Loft>ppm install -force Win32-GUI-Loft.ppd
これで完了。
ようやくWebプログラマに復帰しつつある(リハビリしてる)最近で、ただいまDBの勉強中。実はDBを使っている業務アプリケーション、Webアプリケーションをそれなりに弄っていた割に、自分でDB作成やテーブル作成といったステップは踏んでいませんでした。
日曜日にmysqlをインストールしてDB&テーブル作成、DBIでinsert、select。このコードをDBMSに依存しない部分をできるだけきれいに書こうとがんばっていて、ふと「今僕がやろうとしてるのってO/Rマッパの再発明?」と気づいたところで中断。月曜日にはPerlでO/Rマッパといえばまずはこれから、のClass::DBIでデータの取得に手を出してみました。
気になるのはこれでCGIを作成したとして、Walrus, Digit.などがあるinetd(QUE::NETWORK)で借りているスペースにおけるのかというところ。モジュールリストを見るとDBIは入っている、Class::DBIは入っていないとして、Class::DBIの前提モジュールはとみると、以下がないようです。
- Class::Accessor
- Class::Data::Inheritable
- Class::Trigger
- Clone
- Ima::DBI
- UNIVERSAL::moniker
...インストールする気にもなれない。そこでふと気になって、もう一つスペースを借りているXREAでも、昔メモしておいたPerlモジュールの確認の方法で利用できるモジュールを調査してみました。結果はXREA/Perlモジュールの確認の通り。
Class::DBIも、最近流行りのDBIx::Classも入ってますね。“What's right with LWP & Regexp?”で「これが使えないから!」と嘆いていたWWW::MechanizeやHTML::TreeParserも使えます。Template-Toolkitもばっちりある様子。
うーん、今まで使い慣れたinetdに愛着を持っていたのだけど、それよりXREAが魅力的に見えてきました。さくらインターネットはどうなんでしょう。確認方法が「サーバーコントロールパネルから確認」なので、利用者じゃない私には分からないのですが。しかしまいったな、digit.que.ne.jpのドメインは外に持ち出せないし、inetd、どうしたものか。
YAPC::EUで宮川さん自ら、Web::Scraperを解説したプレゼン資料がblog.bulknews.netのエントリ「Web::Scraper プレゼン@YAPC::EU」で公開されています。英語だけど、文章で説明しているような部分はごくわずかで、基本的にスライドタイトルとコードを見れば書いていることは分かるようになっています。
Web ScrapingはLWP+正規表現(UAのコントロールもHTMLも文字列としてガリガリ解析)から、WWW::Mechanize+HTML::Parser(UAにはブラウザ操作イメージを伝え、解析はHTML構造を利用)に移り、昨年頃から解析にHTML::TreeBulder::XPath+HTML::Selecter::XPath(HTML構造やCSSなどの外観を決める要素で解析)が使われるようになって来ました。Web::Scraperはこの方式を容易にするための、必要なライブラリの読み込みや分かりやすいメソッド化などを行うGood Wrapperのように見えます。
この中で、LWP+正規表現方式の(おそらくもっとも狂ったような)例としてWWW::Mixiも取り上げられており、この方式には以下の3つの問題があると説明されています。
- Fragile(壊れやすい)。ほんのわずかな(改行やアトリビュートの出現順のような)HTMLの変更でも簡単に壊れる。
- Hard to maintain(メンテナンスが困難)。正規表現ベースのScrperは、write-onlyのスクリプトにしか適さない。
- Improper HTML & encoding handling(無作法なHTMLと文字エンコードの取り回し)。
これらの指摘は全て正しく、実際にWWW::Mixiのメンテナンスは非常に苦痛な作業です。また私自身が既にWWW::Mixi以外で、こうした方法をほとんど使っていません。
WWW::Mixiは当時、WWW::Mechanizeを利用できないサーバを私が使っていて、mixiのための代替品として作成を始めました。“What's wrong with LWP & Regexp?”に対して、それでも“What's right with LWP & Regexp?”(LWP+正規表現の正しいところは?)な点を挙げるならば、やはり“It works...”、保留つきであれ、とにかくどこでも動くという点です。LWPが利用できないサーバも、まだ一部にありますが、しかしさすがに少なくなりました。
私がメインで使っているinetdでは、モジュールリストを見るといまだにWWW::Mechanizeも、HTML::TreeBulderも使えないようです。こうした環境のための手法が、LWP+正規表現だといえます。めんどくさいけど。
JavaScriptでのパラメータのエンコードは、従来であればescape、最近はencodeURI、encodeURIComponentで行うことが多いようです。
- JavaScriptにおけるURLエンコードの処理
- javascript: escape(), encodeURI(), encodeURIComponent() 比較 (groundwalker.com)
困ったことに、同じUnicode文字列をescape()では"%uXXXX"、encodeURIとencodeURIComponentでは"%xx%xx%xx"の形式でエスケープ/エンコードし、しかもこれらの値をCGIモジュールのparamメソッドで受け取ると、別の値になっていました。以下のスクリプトが簡単な例です。
use CGI;
my $query = CGI->new('by_escape=%u3042&by_encodeURI=%E3%81%82');
my $escaped = $query->param('by_escape');
my $encoded = $query->param('by_encodeURI');
print "escaped : $escaped\nencoded : $encoded\n";
Unicodeの細かな部分だろうとは思うのですが、不勉強で、何が違い、どう合わせるのが正しいのか良く分かりませんでした。それでもCGI、CGI::Utilなどを読んだり弄り回していて、"%uXXXX"が含まれているURLもCGIモジュールのquery_stringを呼ぶと"%xx%xx%xx"形式に変換されていることに気づいたので、とりあえず何とかなる解を。
CGI::Util::escapeしてから、CGI::Util::unescapeすると同じ値が得られるようです。
my $escaped = CGI::Util::unescape(CGI::Util::escape($query->param('by_escape')));
my $encoded = CGI::Util::unescape(CGI::Util::escape($query->param('by_encodeURI')));
とりあえず、Perlメモ/CGIモジュール - Walrus, Digit.にも掲載。しかしこれが最適解かといわれるとどうにもなところです。「普通はこうするんだ」みたいな方法があれば、ぜひコメントやメールなどくださいませ。
livedoor ReaderのピンをURL順で表示したいとか、携帯livedoor Readerのピン一覧の動きが自分の使い方に合わないとか思ったので、とりあえずピンの一覧を取得することにチャレンジしてみました。
livedoor ReaderのJavaScript処理では、サーバから各種データをJSON形式のAPIで受け取っていて、「つまらないものですがヨコナビです - livedoor ReaderのAPI一覧」に多くのAPIがリストアップされています。ピンのように、ここに挙がっていないものが知りたいときには、reader_main.*.jsやreader_addon.*.js、reader_manage.*.jsを開いて、"/api"でgrepすると良いでしょう。
ということで、スクリプト。
use WWW::Mechanize;
use CGI;
use JSON;my $id = ""; # livedoor ID
my $pass = ""; # livedoorパスワード
my $mech = WWW::Mechanize->new;# ログイン
my $uri = URI->new('http://member.livedoor.com/login/index');
$uri->query_form(['livedoor_id' => $id, 'password' => $pass]);
$mech->post($uri);# pinを取得
my $api = URI->new('http://reader.livedoor.com/api/pin/all');
$api->query_form([]);
my $res = $mech->post($api);
my $obj = JSON::jsonToObj($res->content);# 出力
my @pins = @{$obj};
foreach my $pin (@pins) {
my $time = substr(HTTP::Date::time2iso($pin->{'created_on'}), 2, 14);
printf("%s (%s)\n%s\n\n", CGI::unescapeHTML($pin->{'title'}), $time, $pin->{'link'});
}
ここで使った“/api/pin/all”の他に、ピン関連で“/api/pin/add”、“/api/pin/remove”を試してみたのですが、これらは“Permission denied”というHTMLが返ってきてしまいました。外部からは利用できないのかな?いやでも、JSって外部で実行するよな。
何でだか分からないけど、しっかりJSファイルを読む気力がないので、断念。うーん、これらが使えると自分用ピンReaderが作れるんだけど、残念。