宇宙怖い><とかオモタ、つーか宇宙ヤバイ。

夜空を見て呑気に「奇麗だな」と思えなくなったというか.....
数学者はキノコ狩りの夢を見る〜ポアンカレ予想・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=jsonJSON形式で結果を出力できます(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で出力するようにしてみました。

処理の流れ(変に遠回りしてる気もしますが)
  1. ログファイルをもとにアクセス数をカウント
  2. アクセス数が多い順にソート
  3. ページにアクセス
  4. Web::Scraperスクレイピングしてtitleとメタタグのdescriptionを取得
  5. エントリーオブジェクトを生成
  6. エントリーオブジェクトをPlagger(Publish::Feed)に渡して出力

そこまでスクリプト書いたんならPlaggerに渡さなくても(XML::Feed使え)...とか言われるかなぁ。
まぁ一応やってみたんで。

以下レシピとスクリプトです。

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らしきものをつくってみる

perlPOEを使って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にソケット通信するモジュールを作ってみました。

10/9 ソースをcoderepos移しました add,replaceが使えるようになりました(使ってる人なんかいるのかね?)。

使い方

サーバ側

memcachedを起動させるだけです。CGIスクリプトを置く必要はありません。

memcached -d -m  -l 192.168.0.10 -p 11211 -u root
クライアント側

JNEXTをインストールします。
インストールが完了したら、auth.txtを編集してJNEXTが実行できるURLとライブラリを追記します。

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] });
set

keyとvalueを渡してセットします。日本語を渡す事も可能です(モジュール内で値をescapeしています)。

mm.set('hoge','hoge-value');
get

keyと関数を渡して値を取得します。サーバから値が返ってくると関数が実行され引数に値が入ります。

mm.get('hoge',function(r){alert('get-response-value>' + r);});
remove

指定したkeyの値を削除します。

  mm.remove('hoge');
flush_all

すべてのデータを削除します。

  mm.flush_all();
データオブジェクト

json.js等でJSONに変換すればデータオブジェクトの受け渡しも可能です。

var myObject = new Array('aa','bb','cc');
var myJSONText = myObject.toJSONString();
mm.set('hoge',myJSONText} );
mm.get('hoge',function(r){returnValue=r.parseJSON();})

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)したいとかでしょうかね?考え中。