宇宙怖い><とかオモタ、つーか宇宙ヤバイ。
夜空を見て呑気に「奇麗だな」と思えなくなったというか.....
数学者はキノコ狩りの夢を見る〜ポアンカレ予想・100年の格闘
ニコニコ動画のコメントがちょっとした解説にもなっていて面白い。
天才数学者の頭の中は想像できないけど、関わるものを破滅に追いやる宝石の話を思い出しました。
タイトルは「アンドロイドは電気羊の夢を見るか」を元につくったんでしょうけど、センス良いですね。
IKEAに行ってきたのでスクレイピングしてみる。
IKEA港北に付き添いで行ってきました。付き添いと言っても野郎ですが(w。
シンプルで好感が持てるデザイン、さすが北欧家具だなと(でも今の自分の家には合わないな...それなりに広い所に住まなくては)。日本のメーカには真似できなさそう。
そして買い物の仕方が独特。ショールームにある家具の番号をメモしておいて、最後に米軍の武器庫のような巨大倉庫から家具の材料をカート(!)に入れるシステム(だから連れが必要)。買った材料は家に持ち帰って自力で組み立てる。
そんな訳で、IKEAの新製品情報をスクレイピングしてみました。
左の新製品情報ナビを辿って新製品一覧をとりました。さすが家具屋だけあって構造が奇麗、スクレイピングしやすいわ。
CSSセレクタ版
my $domein = 'http://www.ikea.com'; my $url = 'http://www.ikea.com/jp/ja/catalog/news/range/'; my $scraper = scraper { process 'title','title[]' => 'TEXT'; process 'div.productNavigation div.productItem span.prodName','products[]' => scraper { process 'a','category' => 'TEXT'; process 'a','list' => sub{ my $cat_url = $domein.$_->attr_get_i('href'); scraper { process 'div.productPadding ','data[]' => scraper { process 'a','link' => '@HREF'; process 'img','image' => '@SRC'; process 'span.prodName','name' => 'TEXT'; process 'span.prodDesc','dec' => 'TEXT'; process 'span.prodPrice','price' => 'TEXT'; }; result qw/data/ }->scrape(URI->new($cat_url)); }; }; result qw/title products/ }->scrape(URI->new($url)); print YAML::Dump($scraper);
XPathだとこんな感じ
my $domein = 'http://www.ikea.com'; my $url = 'http://www.ikea.com/jp/ja/catalog/news/range/'; my $scraper = scraper { process '//title','title' => 'TEXT'; process '//div[@class="productNavigation"]//div[@class="productItem"]/span[@class="prodName"]','products[]' => scraper { process '//a','category' => 'TEXT'; process '//a','list' => sub{ my $cat_url = $domein.$_->attr_get_i('href'); scraper { process '//div[@class="productPadding"]','data[]' => scraper { process '//a','link' => '@HREF'; process '//img','image' => '@SRC'; process '//span[@class="prodName"]','name' => 'TEXT'; process '//span[@class="prodDesc"]','dec' => 'TEXT'; process '//span[@class="prodPrice"]','price' => 'TEXT'; }; result qw/data/ }->scrape(URI->new($cat_url)); }; }; result qw/title products/ }->scrape(URI->new($url)); print YAML::Dump($scraper);
出力
- category: スウェーデンフード list: - dec: ダブルチョコクリスプ image: /PIAimages/74505_PE191681_S2.jpg link: /jp/ja/catalog/products/70124686 name: DUBBLA CHOKLADFLARN price: \ 795 - dec: オートクリスプ image: /PIAimages/66444_PE179371_S2.jpg link: /jp/ja/catalog/products/49650057 name: HAVREFLARN price: \ 695
友人曰く「IKEAの店舗がこのまま増えていくとユニクロみたいになりそうだ」と。
なるほど、周りの家が自分と同じ家具ばっかりなのはちょっと嫌だな、恥ずかしいかも。
Flickrの検索APIでJSONが指定できるわけだが
通常はXMLだけど、format=jsonでJSON形式で結果を出力できます(XMLをパースして取り出すは非常に面倒)。
しかし、この機能は現在のところドキュメントには載っていないようです。
PerlのモジュールにFlickr::APIがあります。
しかしこれがXMLが返ってくるを前提に書かれててJSONが返ってきた場合にうまく処理してくれない。
#これだと動かない my $request = new Flickr::API::Request({ 'method' => 'flickr.photos.getrecent', 'args' => {tag=>'dog','format'=>'json'}, });
ツーわけでこんな感じにcontent-typeを見てxmlでなければそのままチェックせずに返すようにしてみました。
--- Flickr/API.pm 2007-10-18 22:18:58.000000000 +0900 *************** *** 96,101 **** --- 96,105 ---- return $response; } + if ($response->header('Content_Type') =~ /text?/(?:plain|json)/){ + return $response; + } + my $tree = XML::Parser::Lite::Tree::instance()->parse($response->{_content}); my $rsp_node = $self->_find_tag($tree->{children});
Flickr::APIって中の人が作ってるんじゃなかったっけ??
PlaggerとWeb::Scraperでアクセスランキングを出してみる
久々にPlaってみました。アクセスログを集計して結果(上位10件)をRSSで出力するようにしてみました。
処理の流れ(変に遠回りしてる気もしますが)
- ログファイルをもとにアクセス数をカウント
- アクセス数が多い順にソート
- ページにアクセス
- Web::Scraperでスクレイピングしてtitleとメタタグのdescriptionを取得
- エントリーオブジェクトを生成
- エントリーオブジェクトをPlagger(Publish::Feed)に渡して出力
そこまでスクリプト書いたんならPlaggerに渡さなくても(XML::Feed使え)...とか言われるかなぁ。
まぁ一応やってみたんで。
以下レシピとスクリプトです。
- rank.yaml
global: timezone: Asia/Tokyo plugins: - module: CustomFeed::Script - module: Subscription::Config config: feed: - script: /home/akihito/rank.pl - module: Publish::Feed config: format: RSS dir: /home/akihito/ filename: rank.rss
- rank.pl
use strict; use YAML::Syck; use IO::File; use Web::Scraper; use URI; my $log = '/etc/httpd/logs/access_log'; my $domain = 'http://example.com'; my $f = new IO::File(); $f->open($log); my %url; while(my $line = <$f>){ if( $line =~ /[GET|POST]+\ (\S+)\ HTTP/ ){ $url{$1} = $url{$1}?$url{$1}+1:1; } } $f->close; my @rank; my $count = 0; for my $key ( reverse sort {$url{$a} <=> $url{$b}} keys %url){ last if( $count >= 10 ); if( $key !~ /\.(jpeg|jpg|gif|png|css|js|ico)$/ ){ my $uri = $domain.$key; $count++; my $links = scraper { process 'title','title[]' => 'TEXT'; process 'meta','description[]' => sub{ return $_->attr_get_i('content') if($_->attr_get_i('name') =~ /description/i && $_->attr_get_i('content')); return; }; result qw/title description/ }->scrape(URI->new($uri)); push @rank,{ link => $uri, title => $links->{title}->[0], body => $links->{description}->[0], }; } } my $output = {}; $output->{entry} = \@rank; print YAML::Syck::Dump $output;
POEでmemcachedらしきものをつくってみる
perlのPOEを使ってmemcachedを再現してみました。
esecached.pl
memcachedの偽物なので、こんな名前です。
できるのはset,get,delete,flush_allだけ。flagもexptimeも無視します(笑)。
Cache::Memcachedを騙せる事を確認しました。
POEをはじめて使ったので割とテキトーです。
動けば良いかな程度に作ってます。
use warnings; use strict; use POE; use POE::Component::Server::TCP; my $ESECACHE_DATA = {}; my $port = 11211; POE::Component::Server::TCP->new( Port => $port, ClientConnected => \&handle_client_connect, ClientInput => \&client_input, ClientFlushed => \&handle_client_flush, Concurrency => -1, ); POE::Kernel->run; exit; sub handle_client_connect{ my ($kernel, $heap, $input) = @_[KERNEL, HEAP, ARG0]; my $id = $heap->{client}->ID; $heap->{'input_buff'.$id} = ['']; } sub handle_client_flush { my ( $heap, $input ) = @_[HEAP, ARG0]; } sub client_input { my ($kernel, $heap, $input) = @_[KERNEL, HEAP, ARG0]; my $id = $heap->{client}->ID; my $input_buff = $heap->{'input_buff'.$id}; if( $input_buff->[0] =~ /^set\ (\S+)\ \d+\ \d+\ \d+/i ){ my $key = $1; $ESECACHE_DATA->{$key} = $input; $heap->{client}->put('STORED'); } elsif( $input =~ /^get\ (\S+)/i ){ my $key = $1; my $value = $ESECACHE_DATA->{$key}; my $l = length($value); $heap->{client}->put("VALUE $key 0 $l"); $heap->{client}->put($value); $heap->{client}->put("END"); } elsif( $input =~ /^flush_all/i ){ $ESECACHE_DATA = {}; $heap->{client}->put('OK'); } elsif( $input =~ /^delete\ (\S+)/i ){ my $key = $1; if( $ESECACHE_DATA->{$key} ){ $ESECACHE_DATA->{$key} = ''; $heap->{client}->put("DELETED"); } else{ $heap->{client}->put("NOT_FOUND"); } } elsif( $input =~ /^quit/i ){ $heap->{shutdown} = 1; $heap->{client}->put("quit"); } else{ $heap->{client}->put("ERROR"); } unshift @{$heap->{'input_buff'.$id}},$input; } __END__
Javascriptでmemcachedにソケット通信する
MOONGIFTで紹介されていたJNEXTを使うとブラウザからソケット通信をする事ができます。これを使ってmemcachedにソケット通信するモジュールを作ってみました。
使い方
クライアント側
JNEXTをインストールします。
インストールが完了したら、auth.txtを編集してJNEXTが実行できるURLとライブラリを追記します。
- MSIE → C:\Program Files\Optimistec\JNEXT\ActiveX\jnext\auth.txt
- Firefox → C:\Program Files\Mozilla Firefox\plugins\jnext\auth.txt
file:// * http://127.0.0.1 Sockets http://example.com Sockets
以下のライブラリをBODYタグに読み込みます。
<body> <script type="text/javascript" src="jnext/jnext.js"></script> <script type="text/javascript" src="jnext/sockets.js"></script> <script type="text/javascript" src="cache_memcached.js"></script>
jnex.js,socket.jsはJNEXのライブラリでcache_memcached.jsがmemchashedに通信するモジュールです。
memcachedに接続
IPアドレスとポート番号を渡してnewします。これで接続が開始されます。
var mm = new CacheMemcached({ server: ['192.168.0.10',11211] });
get
keyと関数を渡して値を取得します。サーバから値が返ってくると関数が実行され引数に値が入ります。
mm.get('hoge',function(r){alert('get-response-value>' + r);});
remove
指定したkeyの値を削除します。
mm.remove('hoge');
flush_all
すべてのデータを削除します。
mm.flush_all();
memcachedのプロトコル
protocol.txtにプロトコルが書かれています。telnetで動作を確認する事ができます。
$ telnet 192.168.0.10 11211
set foo 0 0 7 aabbccd STORED
get foo VALUE foo 0 7 aabbccd END
このやり取りをソケット通信で行っている訳です(割とシンプル?)。
モジュール名に"Cache"とか付けましたが、全然キャッシュじゃないですね(笑)。Cache::MemcachedみたいなAPIにしたかったんで、そう命名しました。
「何に使えるの?」と言われるとあまり思いつかないです、memcachedをクリア(flush_all)したいとかでしょうかね?考え中。