URLとXPathを渡すと値のリストを返してくれるツールを書いた
画像一覧からXPathでぶっこぬいてダウンローダにわたす、といった用途から簡単なマイニングまでべんりにつかう予定。
つかいかた
% ./exthtml.pl [ -a [AGENT] -e [REFERER] -c [COOKIE_JAR] ] -x [XPATH] [URL] % perl exthtml.pl -a Mozilla -c ~/Library/Cookies/Cookies.plist -X "//img[@class='image']/@src" http://www.tumblr.com/dashboard http://media.tumblr.com/XEVQcg9Zzaj7driiTV8baCRJ_400.gif http://media.tumblr.com/G6je7UvklaklnqmgypCVpgBg_100.jpg http://media.tumblr.com/G6je7UvklaklnqmgypCVpgBg_400.jpg http://media.tumblr.com/Bcb4YCrxh3s3i9vsNiAe7H0C_100.jpg http://media.tumblr.com/Bcb4YCrxh3s3i9vsNiAe7H0C_400.jpg http://media.tumblr.com/cqRYWOkYRambcyqr1PmewR2r_100.jpg http://media.tumblr.com/cqRYWOkYRambcyqr1PmewR2r_400.jpg http://media.tumblr.com/vj8toQRftakozppfgR7H0j40_400.jpg http://media.tumblr.com/51bAnBJJUamcm3iu9QVSYb2t_100.png http://media.tumblr.com/51bAnBJJUamcm3iu9QVSYb2t_400.png % perl exthtml.pl -a Mozilla -c ~/Library/Cookies/Cookies.plist -X "//table[@id='timeline']//img[@class='photo fn']/@alt" http://twitter.com/ | sort | uniq -c 1 Tadaki Osawa 1 Vol.2% 2 Yusuke Yanbe 1 hanemimi 2 log_070702 1 oklahomamixer 2 totoounk皇子 2 ☆ギンギンギラギラギンギンのもりひろ☆ 2 オリハタ 3 ヤマタケ 1 小池 陸 1 岸田渉 1 星村
code
#!/usr/bin/perl use strict; use warnings; use Encode; use Getopt::Long; use LWP::UserAgent; use HTTP::Cookies::Guess; my $url = pop @ARGV; my ($xpath, $referer, $cookie, $agent); my $result = GetOptions( "x|xpath=s" => \$xpath, "e|referer=s" => \$referer, "c|cookie-jar=s" => \$cookie, "a|agent=s" => \$agent, ); die "usage: ./exthtml.pl [ -a [AGENT] -e [REFERER] -c [COOKIE_JAR] ] -x [XPATH] [URL]" unless ($url && $xpath); $xpath = decode('utf-8', $xpath); my $ua = LWP::UserAgent->new; $ua->cookie_jar(HTTP::Cookies::Guess->create(file => $cookie)) if ($cookie); $ua->agent($agent) if ($agent); my $tree = HTML::TreeBuilder::XPath::Remote->new_from_uri($url, $ua, $referer); for my $node ($tree->findnodes($xpath)) { print encode('utf-8', $node->getValue."\n"); } package HTML::TreeBuilder::XPath::Remote; use strict; use warnings; use List::Util qw( first ); use Encode; require Encode::Detect; use HTML::TreeBuilder::XPath; use HTML::ResolveLink; use LWP::UserAgent; use HTTP::Request; use HTTP::Response::Encoding; sub new_from_uri { my ($pkg, $uri, $ua, $referer) = @_; my $resolver = HTML::ResolveLink->new( base => $uri, ); return HTML::TreeBuilder::XPath->new_from_content( $resolver->resolve( $pkg->get($uri, $ua, $referer) ) ); } sub get { my ($self, $uri, $ua, $referer) = @_; my $html; $ua ||= LWP::UserAgent->new(); my $req = HTTP::Request->new('GET', $uri); $req->header(referer => $referer) if ($referer); my $res = $ua->request($req); # this detection is based on Web::Scraper. if ($res->is_success) { my @encoding = ( $res->encoding, ($res->header('Content-Type') =~ /charset=([\w\-]+)/g), "Detect", "shift-jis" ); my $encoding = first { defined $_ && Encode::find_encoding($_) } @encoding; $html = Encode::decode($encoding, $res->content); return $html; } return; }
WedataにあるLDR Full Feedのsiteinfoを使ってフィードを全文入りにupgradeするPlagger::Plugin::Filter::EntryFullText::LDRFullFeed
lib/Plagger/Plugin/Filter/EntryFullText/LDRFullFeed.pm
http://b.hatena.ne.jp/otsune/20080530#bookmark-8776502
SITEINFOはwedata版に書き換えたほうがよさそう
とのことなので書き換えた。HTML::Featureを使ってもっと便利にしたかったのだけど、UAがうまく置き換えられない問題とかが残ってるのであいかわらずEntryFullTextを継承。なのでPPF::EFT::SiteInfoのEFTのyamlがうまく一緒に使えない問題が残ってます。あとPPF::EFT::SiteInfoも野良siteinfoがあればまだ使えるのでこっちは名前かえました。Webservice::WedataはkoyachiさんのところからとってきてくださいWebService::WedataもCPANからインストールできるようになりました、とてもよかったですね。
config.yaml
plugins: - module: Subscription::Config config: feed: - url: http://kichiku.oq.la/rss - url: http://kawamurayukie.cocolog-nifty.com/blog/ - module: Filter::EntryFullText::LDRFullFeed config: force_upgrade: 1 # impersonate: 1
pixiv.yaml
# upgrade http://pixiv.net/ author: fuba custom_feed_handle: http://www\.pixiv\.net/ custom_feed_follow_link: member_illust\.php\? handle: http://www\.pixiv\.net/member_illust\.php\? extract_xpath: body: //div[@id="content2"] author: //div[@id="profile"]/div/text()
config.yaml
ブラウザのcookieを使う。なにかがexpiredだとmypage.phpにリダイレクトされるっぽいので一度www無しのedit.phpに捨てアクセス。custom_feed_handleはwww入りになってるのでEFTは動かないで、2つ目以降のURLだけ有効になる。
global: user_agent: cookies: /Users/ec/Library/Cookies/Cookies.plist timezone: Asia/Tokyo plugins: - module: Subscription::Config config: feed: - url: http://pixiv.net/edit.php - url: http://www.pixiv.net/ranking_r18.php - module: Filter::EntryFullText
HTML::Feature::Engine::LDRFullFeed - WedataにあるLDR Full FeedのSITEINFOを使ってWebページの本文を抽出するPerlモジュール
LDR Full FeedのSITEINFOがWedataに移動して便利になったので、そろそろHTML::Featureのエンジンが必要だと思って書いてみた。HTML::FeatureについてはHTML::Feature - 重要部分を抽出するモジュール - - download_takeshi’s diaryを、エンジンの拡張についてはHTML::Featureはエンジンをいろいろ拡張できるよ! - download_takeshi’s diaryを参照ください。
HTML::Feature::Engine::LDRFullFeed
koyachiさんのWebService::Wedataを使ってWedataからSITEINFOを取得、そのSITEINFOをpriority順にソートして、最初にURLがマッチしたSITEINFOのXPathでHTML::Elementをとってくるという仕組み。LDRFullFeedの今の実装みてないのでかなり適当な気もするけど、動いてるので多分大丈夫。priority→typeの仕様変更に対応。
package HTML::Feature::Engine::LDRFullFeed; use strict; use warnings; use base qw(HTML::Feature::Engine); use HTML::Feature::Result; use HTML::TreeBuilder; use HTML::TreeBuilder::XPath; use WebService::Wedata; sub run { my ($self, $c, $html_ref, $opt) = @_; die unless $opt->{url}; return $self->_extract($c, $html_ref, $opt->{url}); } sub _tag_cleaning { my ($self, $html) = @_; return unless $html; # preprocessing $html =~ s{<!-.*?->}{}xmsg; $html =~ s{<script[^>]*>.*?<\/script>}{}xmgs; $html =~ s{ }{ }xmg; $html =~ s{"}{\'}xmg; $html =~ s{\r\n}{\n}xmg; $html =~ s{^\s*(.+)$}{$1}xmg; $html =~ s{^\t*(.+)$}{$1}xmg; # control code ( 0x00 - 0x1f, and 0x7f on ascii) for ( 0 .. 31 ) { my $control_code = '\x' . sprintf( "%x", $_ ); $html =~ s{$control_code}{}xmg; } $html =~ s{\x7f}{}xmg; return $html; } sub _extract { my ($self, $c, $html_ref, $url) = @_; my $result = HTML::Feature::Result->new; my $root = HTML::TreeBuilder::XPath->new_from_content($$html_ref); my @contents; my $siteinfo = $self->_detect_siteinfo($c, $url); if ($siteinfo) { @contents = $self->_xpath2elems($siteinfo->{data}->{xpath}, $root); return unless (@contents); if (my @title = $self->_xpath2elems('//title', $root)) { $result->title($title[0]->as_text); } } else { return; } my $element = HTML::TreeBuilder->new_from_content( $self->_tag_cleaning( join '', map {$_->as_HTML('<>"&')} @contents ) ); $root->delete; $result->element($element); return $result; } sub _xpath2elems { my ($self, $xpath, $context) = @_; my $nodes; if (eval { $nodes = $context->findnodes($xpath); }) { return $nodes->get_nodelist; } return; } sub _detect_siteinfo { my ($self, $c, $url) = @_; my $wedata = WebService::Wedata->new; $wedata->{ua} = $c->user_agent; my $i = 0; my %priority = qw/ SBM 1000 INDIVIDUAL 100 IND 100 SUBGENERAL 10 SUB 10 GENERAL 1 GEN 1 /; my $db = $wedata->get_database('LDRFullFeed'); for my $item ( sort { $a->{data}->{priority} <=> $b->{data}->{priority} } map { $_->{data}->{priority} ||= ($_->{data}->{type}) ? $priority{$_->{data}->{type}} : 0; $_; } @{$db->get_items} ) { if (($item->{data}->{url}) && ($url =~ /$item->{data}->{url}/)) { return $item; } } return; } 1;
HTML::Featureをちょっといじる
HTML::Featureはデフォではuser_agentが変更できないのと、Engineが自分の処理してるページのURLを知る事ができないって仕様になってて、特に後者はSITEINFOを使う上で致命的になる。ので$self->engine->runにオプションも渡せるようにして、HTML::Feature::Engine::LDRFullFeed側でそこからURLを貰ってくるという実装にしてます。でもこれだとHTML::Feature::parseにURLを2個渡さなくちゃいけなくてださいので、なんとかしてほしいなー>id:download_takeshi
package HTML::Feature_; use base qw/HTML::Feature/; sub user_agent {_user_agent(@_)}; sub _user_agent { my $self = shift; return $self->{user_agent} ||= SUPER->_user_agent; } sub _run { my ($self, $html_ref, $opts) = @_; $opts ||= {}; local $self->{element_flag} = exists $opts->{element_flag} ? $opts->{element_flag} : $self->{element_flag}; $self->engine->run($self, $html_ref, $opts); }
extract.pl
今の仕様だとテスト書くのがめんどくさかったので、とりあえず適当にコマンドラインで使えるサンプルをオレオレtsubuanをベースに書いた。たぶんCGIでもうごきます。CGIで動かしたときの仕様はtsubuan互換、コマンドラインでの実行例は以下のようなかんじ。SITEINFOが存在しなかったときにはデフォルトEngineのTagStructureが動くようにしてます。
% perl extract.pl http://d.hatena.ne.jp/fuba/20070401/1175418910 LDRFullFeed <html><head></head><body><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%c3%e6%b3%d8%c0%b8">中学生</a>からは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%a4%cf%a4%c6%a4%ca%a5%a2%a5%f3%a5%c6%a5%ca">はてなアンテナ</a>! <a class="keyword" href="http://d.hatena.ne.jp/keyword/%a4%cf%a4%c6%a4%ca%a5%a2%a5%f3%a5%c6%a5%ca%cd%df%a4%b7%a4%a4%a1%aa">はてなアンテナ欲しい!</a><p class="sectionfooter"><a href="/fuba/20070401/1175418910">Permalink</a> | <a href="/fuba/20070401/1175418910#c">コメント(0)</a> | <a href="/fuba/20070401/1175418910#tb">トラックバック(0)</a> | 18:15</body></html>
firefox.jpの転送先をしらべる
ランダムな転送という実装は、つまり十分な回数のアクセスを奨励している。とりあえず1001回の連続アクセスを試みる。
$ perl -MYAML::Syck -e 'my %urls;for my $i (0..1000) {if (`curl -l http://firefox.jp/`=~/src\=\"([^\"]+)\"/) {sleep 1; $urls{$1}++}} warn Dump(\%urls)'
stderr
http://archive.ncsa.uiuc.edu/mosaic.html: 60 http://browser.netscape.com/: 58 http://icab.de/: 65 http://java.sun.com/products/archive/hotjava/index.html: 50 http://jp.opera.com/: 53 http://kmeleon.sourceforge.net/: 53 http://links.sourceforge.net/: 41 http://v3.vapor.com/voyager/: 42 http://www.apple.com/jp/safari/: 64 http://www.avantbrowser.com/: 37 http://www.fenrir.co.jp/sleipnir/downloads/: 55 http://www.flock.com/: 61 http://www.geocities.co.jp/SiliconValley-Bay/6049/: 60 http://www.jp.access-company.com/products/nf_mobile/browser/index.html: 45 http://www.lunascape.jp/: 40 http://www.maxthon.com/: 55 http://www.microsoft.com/japan/windows/products/winfamily/ie/default.mspx: 55 http://www.omnigroup.com/applications/omniweb/: 67 http://www.w3.org/Amaya/: 40
stderr2
増えてるらしいのでもう一回やってみた。acid testはブラウザじゃねーよ
ftp://ftp.funet.fi/pub/networking/services/www/erwisE/: 16 http://3b.net/browser/: 22 http://acid2.acidtests.org/: 13 http://acid3.acidtests.org/: 14 http://archive.ncsa.uiuc.edu/mosaic.html: 24 http://browser.netscape.com/: 20 http://curl.haxx.se/: 14 http://galeon.sourceforge.net/: 20 http://grail.sourceforge.net/: 24 http://home.arachne.cz/: 7 http://ia-world.jp/: 25 http://icab.de/: 15 http://java.sun.com/products/archive/hotjava/index.html: 39 http://jp.opera.com/: 24 http://kids.knowledgewing.com/: 15 http://kmeleon.sourceforge.net/: 19 http://links.sourceforge.net/: 20 http://links.twibright.com/: 19 http://lobobrowser.org/java-browser.jsp: 15 http://lynx.isc.org/lynx2.8.6/index.html: 19 http://shiira.jp/: 17 http://v3.vapor.com/voyager/: 14 http://w3m.sourceforge.net/: 18 http://www.apple.com/jp/safari/: 14 http://www.avantbrowser.com/: 17 http://www.cyberdog.org/: 15 http://www.dillo.org/: 17 http://www.din.or.jp/~blmzf/: 19 http://www.elinks.cz/: 24 http://www.fenrir.co.jp/sleipnir/downloads/: 22 http://www.flock.com/: 20 http://www.geocities.co.jp/SiliconValley-Bay/6049/: 19 http://www.geocities.jp/after9q/butterfly.html: 17 http://www.gnome.org/projects/epiphany/: 17 http://www.ibrowse-dev.net/: 20 http://www.ioage.com/en/: 15 http://www.jikos.cz/~mikulas/links/: 9 http://www.jp.access-company.com/products/nf_mobile/browser/index.html: 17 http://www.konqueror.org/: 12 http://www.lunascape.jp/: 19 http://www.maxthon.com/: 12 http://www.micromind.com/slipknot.htm: 14 http://www.microsoft.com/japan/windows/products/winfamily/ie/default.mspx: 27 http://www.mozilla-japan.org/products/firefox/: 11 http://www.msfirefox.com/microsoft-firefox/index.html: 26 http://www.ne.jp/asahi/art/forum/kidsie/: 17 http://www.nintendo.co.jp/ds/browser/: 14 http://www.nintendo.co.jp/wii/features/wii_channel.html: 20 http://www.omnigroup.com/applications/omniweb/: 23 http://www.spacetime.com/: 19 http://www.torchmobile.com/products/: 27 http://www.w3.org/Amaya/: 18 http://www.wyzo.com/: 17 http://www.xcf.berkeley.edu/~wei/viola/violaHome.html: 11 http://www.xsmiles.org/: 19
結論
fubあった。
LDR Full Feedのsiteinfoを使ってフィードを全文入りにupgradeするPlagger::Plugin::Filter::EntryFullText::SiteInfo(2008/2/27仕様変更)
LDRでgを押せば全文出てくるべんりなLDR Full Feedだけど、個人的には自作の自動抽出のをつかってるのでいいなーと思いながら指をくわえてその進化を見つめています。ただ本文XPathのsiteinfoはものすごく使えそうなので、とりあえず試しにPlaggerでただのりさせてもらうことにしました。siteinfoはキャッシュしてないのでちょっとまずいかもしれないけど、たぶんこのプラグインを使う人はほとんどいないので大丈夫でしょう。siteinfoの取得もPlagger::UAを使うようにしました。これでキャッシュできてる! と思う…。
lib/Plagger/Plugin/Filter/EntryFullText/SiteInfo.pm
元のEntryFullTextは継承して全機能がそのまま動くようになってるはずです。今つかってるconfig.yamlのmodule: Filter::EntryFullTextの行を入れ替えてください。いろいろ修正するついでによく確認してみたらEFT継承部分がちゃんと動いてなかった! 申し訳ない…。ということでこのへん真面目に実装しようとしてassets周りを読んでみたところかなりめんどくさそうなので、てっとりばやくEFTを流用するためにclass_id偽装機能をつけました。impersonateを1にするとまるでこのクラスがPPF::EntryFullTextであるかのようにふるまい、assets/plugins/Filter-EntryFullText以下にあるファイルも読むようになります。とりあえず問題はでてないんですが、しらないところでまずいことになってそうな気がするのでデフォルトでは動かないようにしておきます。EFTを完全に入れ替えて既存のassetsを変更することなく使いたい場合だけ1にしてください。
もちろんassets/plugins/Filter-EntryFullText-SiteInfoってディレクトリをつくれば大丈夫なので、EFTのfeed-upgraderを使いたい場合は下のような2つのやり方があることになります。とりあえず個人的には偽装してやってみるつもり。
- impersonate:1にしてassets/plugins/Filter-EntryFullTextに置いたまま(こわい)
- impersonate:0にしてassets/plugins/Filter-EntryFullText-SiteInfoを作ってassets/plugins/Filter-EntryFullTextの中身をコピーする(安全)
config.yaml
plugins: - module: Subscription::Config config: feed: - url: http://kawamurayukie.cocolog-nifty.com/blog/ - module: Aggregator::Simple - module: Filter::EntryFullText::SiteInfo config: impersonate: 0 # これを1にするとEFTのassetsも読むけど… force_upgrade: 1 - module: Publish::Gmail
ふぁぼったーで自分と同じのをfavってるユーザを数える
不評だったのでぐりもんにしました。入れるとふぁぼりページの左上にリンクがでるので、それクリックするとvisualize twitterersみたいに表示します。ダブルクリックでもどります。
http://userscripts.org/scripts/show/20789
greasemonkeyにするのもめんどくさいのでAutoPagerizeしてからFireBugで実行してください。
var you="fuba"; //ここかきかえる var entries=$x('//*[@class="info meta entry-meta"]'); var coo={}; entries.forEach(function(d){ var imgs=d.getElementsByTagName('IMG'); var users=[]; for(var i=0;i<imgs.length;i++){ users.push(imgs[i].title) } if(users.filter(function(user){ return(user==you) }).length>0){ users.forEach(function(user){ if(user!=you)coo[user]=(coo[user])?coo[user]+1:1 }) } }); console.log(coo);