« 2007年8月 | メイン | 2007年10月 »

2007年9月 アーカイブ

2007年9月 3日

楽天APIを使って楽天アイテムのURLから商品情報を取得するPerl

楽天の個別商品のURLからAPI経由で商品情報をゲットしたい。 例えば「http://item.rakuten.co.jp/shopcode/itemname」みたいな感じのURLから。 だけど、楽天ウェブサービスって、ショップのコードを指定して キーワードでアイテムを所得するAPIはあるんだけど、 ある特定のアイテムの情報をピンポイントで得ることはできないのね。 だから、ショップコードはURLから正規表現でとってきて、 キーワードはアイテムのページのタイトルタグからとってくる。 そんでもって検索APIにかければ、俺のやりたいことができた。 WebService::Rakuten使ってます。 こんな感じ。

#!/usr/bin/perl

use strict;
use WebService::Rakuten;
use LWP::Simple;
use HTML::TokeParser;
use Encode;

our $DEV_ID = 'YUORDEVELOPERID';
our $AFF_ID = 'YOURAFFILIATEID';

my $item = &rakuten_item_from_url("http://item.rakuten.co.jp/amexalpha/ca-sr-500gt/");
print $item->itemName;

sub rakuten_item_from_url {
  my $url = shift;
  my ($title, $shop_code, $keyword, $item);
  
  #get shop code and keyword from title tag
  $shop_code = $1 if $url =~ m!item.rakuten.co.jp/(.*?)/!;
  my $content = get($url);
  my $p = HTML::TokeParser->new(\$content);
  $p->get_tag("title");
  $title = $p->get_trimmed_text;
  Encode::from_to($title,"euc-jp","utf-8");
  $keyword = $1 if $title =~ m/【楽天市場】(.*?):.*/;

  my $api = WebService::Rakuten->new(
    dev_id => $DEV_ID,
    aff_id => $AFF_ID,
    );
  
  my $res = $api->item_search(
    $keyword,
    {
      shopCode => $shop_code,
      hits     => 1,
      page     => 1,
    }
    );
  if ($res->status eq 'Success'){
    return @{$res->items}[0];
  }else{
    return;
  }
}

2007年9月 5日

ディレクトリ内にある写真を「Lightbox JS v2.0」のスライドショーで表示するPerl

ディレクトリの中に写真をぶちこみ、「Lightbox JS v2.0」で一度にスライドショーとして見えるようにしたい。手書きでパスを書かずに。ということでTemplate Toolkitを使って、HTMLを生成すればOK。 以下、HTMLを生成するPerl。

#!/usr/bin/perl

use strict;
use File::Find;
use Template;
use IO::File;
use Encode;

my @directories = qw/photos/;
my @photos;

find(\&wanted, @directories);

sub wanted{
        if(m/.*jpg$/i){
                my $path = $File::Find::dir .  '/' .  $_;
                warn "$path\n";
                push(@photos, $path);
        }
}

my $tt = Template->new;
my $html;
$tt->process('index.tt',{photos=>\@photos},\$html) || die $tt->error(), "\n";
my $io = IO::File->new('index.html', 'w');
$io->print(encode("utf-8",$html));
$io->close;

以下、ttの例

[% SET title = 'hoge' %]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja">
<head>
  <title>[% title %]</title>
  <link rel="stylesheet" href="css/lightbox.css" type="text/css" media="screen" />
  <script src="js/prototype.js" type="text/javascript"></script>
  <script src="js/scriptaculous.js?load=effects" type="text/javascript"></script>
  <script src="js/lightbox.js" type="text/javascript"></script>
  <style type="text/css">
    body{ color: #333; font: 13px 'Lucida Grande', Verdana, sans-serif;    }
  </style>
</head>
<body>

  <h1>[% title %]</h1>

  <h2>写真</h2>
  [% FOREACH photo = photos %]
  <a href="[% photo %]" rel="lightbox[roadtrip]"><img src="[% photo %]" alt="" width="120px"></a>
  [%- END -%]

</body>
</html>

「YouTube Data API」のJSONPを使ってJSのみでYouTubeを検索する

作っておいて後から気づいたんだけど、同じようなことしている人がいた。 けども、せっかくなので解説&コード晒し。 Google Data API の中にYouTube Data API というのがある。 最近でたらしい。 Youtube API Blog によると来年あたりをめどに、 今までのAPIからGData形式のAPIに移行するとのこと。

このYouTube Data API、もちろんGData互換なので、 Atom、RSS、JSONという形式で検索結果などを取得できる。 例えば、以下のURLでは「cat」というキーワードでYouTubeの動画を検索し、 RSSフォーマットで取得することができる。

http://gdata.youtube.com/feeds/videos?vq=cat&start-index=10&max-results=20&orderby=viewCount&alt=rss

各種パラメータはリファレンスを参照していただくとして、 今回はJSONPでデータを取得し、JavaScriptのみでYouTube検索をするというスクリプトの例を紹介する。 実はそのリファレンスには正式に記載されていないが、 URLのaltパラメータにjson-in-script、callbackパラメータにメソッド名を指定すると、 JSONPで返してくれる。 例えばこのようなURLだ。

http://gdata.youtube.com/feeds/videos?format=1&vq=cat&max-results=20&alt=json-in-script&callback=functionName

返ってくるデータ構造、いろいろあるんだけどそのの中で興味深いのが、JSで書くと、

jsonData.feed.entry[0].content.$t

というもの。これはYouTube動画の情報をHTMLで書いてあるコンテンツが入ってる。 それを利用して検索結果をYouTubeの情報付きでhtmlという変数に入れるコールバック関数は以下ような感じだ。

function listVideos(data) {
  var html = '';
  if(data.feed.openSearch$totalResults.$t > 0){
    var entries = data.feed.entry;
    for(var i=0;i<entries.length;i++){
      html += entries[i].content.$t;
    }
  }else{
    html += "<p>not found</p>";
  }
}

てなわけで、フォームにキーワードを入力して、 マッチしたYouTubeの検索結果を表示する簡単なHTML+JSは以下の通り。

<html>
  <head></head>
  <body>
    <p>
      <input type="text" id="query" />
      <input type="submit" value="search" onClick="onClick()"/>
    </p>
    <div id="result"></div>

    <script type="text/javascript">
      function onClick(){
        document.getElementById("result").innerHTML = "loading...";
        var query = document.getElementById("query").value;
        query = encodeURIComponent(query);
        var jsonpURL =
          "http://gdata.youtube.com/feeds/videos?vq="
            + query + "&max-results=20&alt=json-in-script&callback=listVideos";
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = jsonpURL;
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(script);
      }
     function listVideos(data) {
       var html = '';
       if(data.feed.openSearch$totalResults.$t > 0){
         var entries = data.feed.entry;
         for(var i=0;i<entries.length;i++){
           html += entries[i].content.$t;
         }
       }else{
         html += "<p>not found</p>";
       }
       document.getElementById("result").innerHTML = html;
     }
    </script>
  </body>
</html>

以下が動いているサンプルです。 どうも、YouTubeのHPでの検索と比べると取得できるコンテンツの数が少ないようだ。 まだ、移行をしている最中かもしれない。 というわけで、JavaScriptだけで済ましたい時にとってはこのAPIは便利。

YouTube Data API 検索サンプル

2007年9月10日

Plagger::Plugin::Subscription::HatenaBookmark

はてぶで、指定したタグを含むエントリー達をSubscriptionするPlugin。 今のところ「注目のエントリー」のみ対応。 はてぶはそれぞれのページでフィード吐いてるけど、大量に購読するときとかにだるいからさー。 以下がコード。 こういうのこそ、 CodeRepos使いたいところだけど、 Subversion使ったことない小学生なので、ちょっと待ってね。 勉強するから!

package Plagger::Plugin::Subscription::HatenaBookmark;
use strict;
use base qw( Plagger::Plugin );

use Plagger::Util;
use URI;
use URI::Escape;

our $VERSION = '0.01';

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'subscription.load' => \&load,
    );
}

sub load {
    my($self, $context) = @_;
        foreach my $tag ( @{$self->conf->{tags}} ){
                my $uri = "http://b.hatena.ne.jp/t/" . 
                        URI::Escape::uri_escape_utf8($tag)    ."?mode=rss&sort=hot&threshold=3";
                $context->log(info=> $uri);
                my $feed = Plagger::Feed->new;
                $feed->url($uri);
                $context->subscription->add($feed);
        }
}

1;

__END__

=head1 NAME

Plagger::Plugin::Subscription::HatenaBookmark

=head1 SYNOPSIS

  - module: Subscription::HatenaBookmark
    config:
      tags:
        - plagger
        - catalyst

=head1 AUTHOR

Yusuke Wada

=head1 SEE ALSO

L<Plagger>

=cut

Plagger::Plugin::Filter::RakutenWebService

続々とPlaggerのPlugin晒し。 Entryのlinkが楽天商品へのものだった場合に、楽天ウェブサービスを使って情報を取得して、 Entryのbodyの中に入れたりするPlugin。 ってかたぶん俺しか使わないと思うよ。

package Plagger::Plugin::Filter::RakutenWebService;

use strict;
use base qw( Plagger::Plugin );
use WebService::Rakuten;
use HTML::TokeParser;
use Encode;
use URI;

our $VERSION = '0.01';

sub register {
    my($self, $context) = @_;
        defined $self->conf->{developer_id} or 
                Plagger->context->error("config 'developer_id' is not set.");
        $self->{ua} = Plagger::UserAgent->new;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
        my $entry = $args->{entry};
        my ($shop_code, $keyword,$title);
        $shop_code = $1 if $entry->link =~ m!item.rakuten.co.jp/(.*?)/!;
        return unless $shop_code;

        # can't regex 'keyword' with exactness by Util::load_uri and extract_title
        # i don't know why ...
    my $file = $self->cache->path_to('rakuten_search_result.html');
    my $res = $self->{ua}->fetch($entry->link);
    if($res->is_error){
                $context->log( error => $res->status );
                return;
        }
        my $content = $res->content;
        my $p = HTML::TokeParser->new(\$content);
        $p->get_tag("title");
        $title = $p->get_trimmed_text;
        Encode::from_to($title,"euc-jp","utf-8");
        $keyword = $1 if $title =~ m/【楽天市場】(.*?):.*/;
        my $api = WebService::Rakuten->new(
                                   dev_id => $self->conf->{developer_id},
                                   aff_id => $self->conf->{affiliate_id},
                                               );
        my $res = $api->item_search(
                                        $keyword,
                                        {
                                         shopCode => $shop_code,
                                         hits     => 1,
                                         page     => 1,
                                         }
                                         );

        if ($res->status eq 'Success'){
                $context->log(info=> "success : " . $entry->link );
                my $item = @{$res->items}[0];
                $entry->title($item->itemName);
                $entry->body($item->itemCaption);
                $entry->icon({
                                            url    => $item->mediumImageUrl,
                                    });
                $entry->link($item->affiliateUrl) if $self->conf->{set_url};
                $entry->meta->{affiliate_url} = $item->affiliateUrl;
                $entry->meta->{icon_url} = $item->mediumImageUrl;
        }else{
                $context->log(info=> "fail : " . $entry->link );
                $entry->title($keyword);
        }
    
}


1;
__END__

=head1 NAME

Plagger::Plugin::Filter::RakutenWebService - Set information to entries from Rakuten Web Service

=head1 SYNOPSIS

  - module: Filter::RakutenWebService
    config:
      developer_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      affiliate_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      set_url : 1 # default is off

=head1 DESCRIPTION

This plugin fetches related data from Rakuten Web Service and
sets summary to the entry if entry link is rakuten product URL.

=head1 AUTHOR

Yusuke Wada

=head1 SEE ALSO

L<Plagger>

=cut

Plagger::Plugin::Filter::AddAmazonDescription

お次はこちら。EntryのlinkがAmazon商品へのURLだった場合に、 Amazon Web Serviceから情報を取得して、Entryのbodyとかに入れるFilter。 templatizeさせて、好きなようにbodyの中身を表示させることも可能。 これも俺しか使わないと思うよ。

package Plagger::Plugin::Filter::AddAmazonDescription;

use strict;
use base qw( Plagger::Plugin );
use Net::Amazon;
use Net::Amazon::Request::ASIN;

our $VERSION = '0.01';

sub register {
    my($self, $context) = @_;
    $context->register_hook(
        $self,
        'update.entry.fixup' => \&filter,
    );
}

sub filter {
    my($self, $context, $args) = @_;
    my $entry = $args->{entry};
        $entry->link =~ 
                m!(?:www.amazon.co.jp/)(?:.*?)(?:ASIN|product-description|product|dp)/([^/]+)(?:/.*)*$!;
        return unless $1;

        # delete '?' etc.  param
        $1 =~ m!(.*?)\?.*!; 
        $1 =~ m!(.*?)%3f.*!;
        my $asin = $1;
    my $attr;
    $attr->{token}  = $self->conf->{developer_token};
    $attr->{locale} = $self->conf->{locale};
    $attr->{affiliate_id} = $self->conf->{associate_id};
        
        my $item = search_aws($attr, $asin);
        my $body = $entry->body;
        if( $self->conf->{template} ) {
                $body  = $self->templatize($self->conf->{template}, {item => $item} );
        }else{
                $body = $item->ProductDescription if $item->ProductDescription;
        }

        $entry->body( $body );
        $entry->title( $item->ProductName ) if $self->conf->{set_title};
        $entry->link( $item->url) if $self->conf->{set_url};
        $entry->icon({
                                    url    => $item->ImageUrlSmall,
                            });

        # add meta data for after use
        $entry->meta->{affiliate_url} = $item->url;
        $entry->meta->{icon_url} = $item->ImageUrlSmall;
        
}

sub search_aws {
    my($attr, $asin) = @_;
    my $ua = Net::Amazon->new(%$attr);
    my $req = Net::Amazon::Request::ASIN->new( asin  => $asin );
    my $response = $ua->request($req);
    my $item = ($response->properties())[0];
    return $item;
}

1;
__END__

=head1 NAME

Plagger::Plugin::Filter::AddAmazonInformation - Set information to entries from Amazon Web Service

=head1 SYNOPSIS

  - module: Filter::AddAmazonInformation
    config:
      associate_id: xxxxxxxxxx-22
      developer_token: XXXXXXXXXXXXXXXXXXXX
      locale: jp
      set_title: 1 # default is off
      set_url: 1 # default is off

=head1 DESCRIPTION

This plugin fetches related data from Amazon Web Service and
sets summary to the entry if entry link is amazon product URL.


=head1 AUTHOR

Yusuke Wada

=head1 SEE ALSO

L<Plagger>

=cut

2007年9月16日

Web::Scraperで配列の中にハッシュを入れる

ようは

- links:
  - title: タイトル1
    url: http://url.1
  - title: タイトル2
    url: http://url.2
  - title: タイトル3
    url: http://url.3

というようなデータ構造を作りたい。 この場合、あるWebページにリンクがたくさんあって、そこからタイトルとURLをスクレイピングしてハッシュにし、それを一つの配列に入れるというもの。 「Web::Scraper プレゼン@YAPC::EU: blog.bulknews.net」のプレゼン資料に例が載ってた。 processの中にもう一つscraperを走らせればよいみたい。

追記

miyagawaさんにはてブでコメントいただきました。

その後のスライドにあるけど、単に "links[]" => { title => 'TEXT', link => '@href' } でもいい

とのことです!

#!/usr/bin/perl

use strict;
use warnings;
use URI;
use Web::Scraper;

my $links = scraper {
        process "td.tcell",
                'links[]' => scraper{
                        process 'a',
                                'title' => 'TEXT', 'url' => '@href';
                };
};
my $res = $links->scrape( URI->new("http://jp.sun.com/mashupaward/entry1.html") );

URIの正規化

ドメイン名そのものだったり拡張子がついていないURLがある。 そして、その中でも末尾に「/」がついていないものに「/」をつけたい。

例えば、

http://www.perl.com

http://www.perl.com/

という具合に。こういうのをURIの正規化と呼ぶのかな? Perlでそれをやる場合はURIモジュールでできる。

my $u = URI->new("http://www.perl.com");
print $u->canonical;
# result : http://www.perl.com/

2007年9月20日

ImagerでRGBの画像をグレースケール(モノクロ調)に変換する

自分用メモ。 簡単っぽいんだけど、やり方がわからなくて、やっとできた。 ImagerでRGBの画像をモノクロ調に変換するやりかた。 こんだけ。

#!/usr/bin/perl
use strict;
use warnings;
use Imager;

my $file = 'picture.jpg';
my $image = Imager->new->read(file => $file);
$image = $image->convert(preset=>'grey');
$image->write(file => 'output.jpg');

2007年9月29日

KwikiでKwiki::HatenaAuthなどを使う時の注意

ErogeekのHPをKwikiで作ってるんだけど、 Kwiki::HatenaAuthとかではまる点がいくつかあったのでメモ。 Yappoさんにいろいろ教えてもらいました。yappo++

まず、Kwiki::HatenaAuthを使う場合は、Kwiki::Theme::Hatenaを使った方が無難。 次に、Kwiki::Edit::HatenaAuthRequiredを有効にしたい場合、 config.yamlに必ず

hatenaauth_required_pages:
- hoge

というようにhatenaauth_required_pagesを設定しておかないとエラーがでる。 yamlの書き方は「-」の先頭には空白があってはいけない。 ちなみにこれで指定したページは、はてな認証でログインした状態ではなくてもEditできるという意味。俺は逆に捉えていた。

また、Hatena仕様にしてかつKwiki::CoolURIを使ったときに、日本語のタイトルページを作ると、 本文だけが文字化けるという現象が起こる。これは、何故か、はてなキーワードを本文の中に入れると解決する。いろいろバッドノウハウがあるがKwikiはなかなか楽しい。

About 2007年9月

2007年9月にブログ「Yusukebe::Tech」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2007年8月です。

次のアーカイブは2007年10月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。


ブログSEO対策:track word seo