Archive for the ‘perl’ Category

    はてなブックマーク - Google Base Data APIで商品検索
    このエントリーをはてなブックマークに追加

    Fusic 平田です。
    1回くらいはさっさと当たれーとか思ってたら、ここまで引っ張りました。

    前提

    本題の前に。
    数日前に弊社の化学系新卒君が書いたAmazon APIの記事
    これとの繋がりが微妙にあるような話です。

    要は商品検索をいろいろ調べたり調べてもらったりしていたのです。
    Amazonだと多種多様の商品調べられてすごく便利、なんですが。
    1時間で2000アクセス~だと、あまり思い切ってリクエスト投げられない。
    # amazletでも最近引っかかっていましたし。
    てことでGoogle先生に頼ってみたところ、思いのほかいい感じだったのでご紹介。

    Google Base Data API

    Google Product Searchと言えばピンと来る人もいるでしょうか。
    旧Froogleだったりしながら未だにbetaなのですが。
    このデータ群を検索するためのAPIがGoogle Base Data APIです。
    # 商品に限らずいろいろ検索できます。

    とりあえず使ってみる

    # 以下のリンクはIEやFirefoxで開くとより分かりやすいです。
    仕組みは単純で、クエリを投げたら結果がFeedで返ってきます。
    例えば「モンスターハンターポータブル」で検索してみると
    www.google.com/base/feeds/snippets/?bq=モンスターハンターポータブル
    こんな感じです。

    突っ込んだ使い方

    bq=で渡す値をいろいろ変えることで、いろいろ検索できます。

    例えば中古品だけを探したい場合。
    スペース空けて後ろに[condition:used]を追加すればOK。
    「モンスターハンターポータブル」を中古のみ検索

    値段で絞りたい場合は[price >= 2000 JPY]とか付ければいいです。
    「モンスターハンターポータブル」を2000円以上で検索

    これらは組み合わせて使えるので[condition:new][isbn][price <= 1000 JPY]とか後ろに付ければ
    「デジタルカメラで新品でISBNコード持って(書籍かCDあたり)て1000円以内」
    とかもできます。

    更に値段でソートしたければ後ろにorderbyとsortorderを渡せばいいです。
    「更に値段の昇順でソートした結果」

    プログラムで取ってくる

    単にリクエスト投げてFeedを受け取るだけなので、割と簡単です。
    例えば引数でクエリの値を渡すようなプログラムだとこんな感じになります。

    #!/usr/bin/env perl
    use warnings;
    use strict;
    use Furl;
    use XML::Feed;
    use URI::Escape;
     
    if (@ARGV != 1) {
        die 'Usage: perl /path/to/snippet.pl [bq parameter]';
    }
     
    # 引数を受け取る
    my $query = shift;
     
    # Furl初期化
    my $furl = Furl->new(
        agent => 'Sample script for Google Base Data API',
        timeout => 10,
    );
     
    # リクエスト送信
    my $request_url = 'http://www.google.com/base/feeds/snippets/';
    my $res = $furl->get($request_url . '?bq=' . uri_escape($query));
     
    # レスポンスのXMLをパース
    my $feed = XML::Feed->parse(\$res->content);
     
    # SnippetのURLとタイトルを表示
    foreach my $entry ( $feed->entries ) {
        my $snippet_url = $entry->id;
        my $title = $entry->title;
        print "$snippet_url : $title \n";
    }
     
    exit;

    で、実行すると

    debility@xebius:~/works$ perl ./snippet.pl "デジタルカメラ [condition:new][isbn][price <= 1000 JPY]"
    http://www.google.com/base/feeds/snippets/9861142365767353685 : 55才から楽しむデジタルカメラで写真工房 Windows XP版 お父さんお母さんのための 押すだけ撮影から卒業・デジカメで思い出を残そう
    http://www.google.com/base/feeds/snippets/8908453582924256996 : デジタルカメラとパソコンを合わせて使える本
    http://www.google.com/base/feeds/snippets/12581094022531619238 : デジタルカメラ批判序説 あるいは、デジタルカメラで写真は撮れない
    http://www.google.com/base/feeds/snippets/1086636419406988656 : FMVでもっと楽しむデジタルカメラ わかりやすい図解入り
    http://www.google.com/base/feeds/snippets/13607998448764488416 : デジタルカメラ進化論
    http://www.google.com/base/feeds/snippets/10565006307493625118 : デジタルカメラで「趣味」を撮ってらくらく印刷
    http://www.google.com/base/feeds/snippets/13954829398014624305 : 大人のためのはじめてのデジタルカメラ 賢い選び方と快適活用術
    http://www.google.com/base/feeds/snippets/16925320360818721412 : デジタルカメラエクスプレス Vol.1
    http://www.google.com/base/feeds/snippets/13769101776078837942 : デジタルカメラエクスプレス Vol.2
    http://www.google.com/base/feeds/snippets/11531105340749645450 : デジタルカメラLabs Vol.1
    http://www.google.com/base/feeds/snippets/16563827664371922494 : いっきにわかるデジタルカメラの買い方・使い方―あなたのパソコンライフをいっきにひろげるデジタルカメラのすべて
    http://www.google.com/base/feeds/snippets/15904047532666634991 : 今日から上達デジタルカメラスーパー活用!
    http://www.google.com/base/feeds/snippets/8964847794878919955 : デジタルカメラ選び方・撮り方・楽しみ方 最新43機種の○と×がイッキにわかる!
    http://www.google.com/base/feeds/snippets/7419790109938360232 : デジタルカメラマガジン No.4
    http://www.google.com/base/feeds/snippets/6668800394078123731 : Best GearデジタルカメラSpecial ミラーレス一眼の真価キャッチせよ!激変のコンデジ
    http://www.google.com/base/feeds/snippets/5458330732187518314 : デジタルカメラマガジン Vol.16
    http://www.google.com/base/feeds/snippets/3282137698886167565 : デジタルカメラを始めよう
    http://www.google.com/base/feeds/snippets/16178408780302372761 : こんなに簡単デジタルカメラ徹底活用術 撮影・プリント・レタッチ・Web・デジカメの基本がよくわかる!すぐできる!
    http://www.google.com/base/feeds/snippets/15687552409826306759 : 55歳から楽しむデジタルカメラで写真三昧
    http://www.google.com/base/feeds/snippets/15331179909448343256 : デジタルカメラを始めよう
    http://www.google.com/base/feeds/snippets/6890264039679668486 : こんなに簡単デジタルカメラ&デジカメ写真のためのパソコン活用術 日本でいちばんやさしく丁寧な解説本
    http://www.google.com/base/feeds/snippets/6673485326686974641 : デジタルカメラを始めよう―For Windows & Macintosh (2002年版)
    http://www.google.com/base/feeds/snippets/5869285652009149385 : デジタルカメラを始めよう (2003年)
    http://www.google.com/base/feeds/snippets/3661117677961004259 : 中高年のための最新デジタルカメラ使いこなし術
    http://www.google.com/base/feeds/snippets/12355831548414530192 : 超図解ビギナーズデジカメ

    てな具合です。
    他の言語でも実装は非常に簡単でしょう。

    制限

    リクエスト制限については
    code.google.com/apis/base/faq.html#Limits

    > The Google Base data API supports up to approximately 5 queries per second, per user.

    てことで秒間5クエリくらいまでOKなので、だいぶ思い切って投げられます。多い日も安s

    てことで

    次の方にバトンタッチ。

      はてなブックマーク - Beccoame Ver0.1の話 (Tatsumaki, Sys::Virt)
      このエントリーをはてなブックマークに追加

      平田です。
      先日のFukuoka Perl Workshop #16で話したBeccoameについて、つらつらと。

      資料は英語ですが、喋りは全て日本語でした。

      Beccoame = Bogus elastic computing, like Amazon EC2 or Eucalyptus
      ざっくり訳すると「Amazon EC2やEucalyptusっぽい偽クラウド」。
      Eucalyptusを使っている上で
      ・-n 40とかで大量のインスタンスを一気に起動すると、処理が追いつかずにタイムアウト扱いになってしまう
      ・以前のEC2互換であるため、インスタンスの再起動ができない(停止=インスタンス消滅)
      などなど、ちょっと困ったなーの部分を解消できないもんかと思い。
      簡素化したもので構わないので、勉強ついでに作れないものかと考えたのが実装のきっかけです。
      で、libvirtまわりの調査がてらざっくり組んでみたというお話を。

      EucalyptusとAmazon EC2の関係を簡単に表すと、こうなります。

      これにBeccoameを挟むと、こんな感じです。

      で、今回はTatsumakiを採用しました。
      理由は単純で、僕が勉強がてらTatsumakiを触ってみたかっただけです。

      サーバ環境は以下になります。
      Dell PowerEdge T105
      CPU: Quad-core AMD Opteron 1354
      メモリ: 4GB
      HDD: 250GB 7,200rpm
      # 要するに、Eucalyptus環境の1台を使いまわしています1

      これにUbuntu 10.04 LTS Server(x86_64)をインストール。
      追加でKVMをapt-getでさくっとインストール。
      操作するためのlibvirtもインストールします。

      $ sudo apt-get install kvm libvirt

      libvirtは、Linuxの仮想化ライブラリです。
      いろんな仮想環境に対する操作を行うAPIを提供します。
      付属のvirshを用いれば、いろいろと操作が行えます。

      $ virsh
      virsh にようこそ、仮想化対話式ターミナルです。
       
      入力方法: 'help' コマンドに関するヘルプ
                'quit' 終了します
       
      virsh # help
      コマンド:
       
          help            ヘルプの表示
          attach-device   XML ファイルからデバイスを追加
          attach-disk     ディスク装置の接続
          attach-interface ネットワークインターフェースを接続します
          autostart       ドメインの自動起動
      ...
      (以下略)
      ...
       
      virsh #

      で、libvirtのPerlバインディングがSys::Virtです。
      要は「PerlでKVMなどの仮想環境を操作できる」モジュールです。
      # 他の言語のバインディングについては、公式サイトをご覧ください。
      # PHPバインディングも存在するんですね。。。

      というわけで、必要なCPANモジュール群をインストール。

      $ sudo cpan install Tatsumaki
      $ sudo cpan install Sys::Virt
      ...
      (以下略)
      ...

      で、コードはこんな感じになります。
      # 久しぶりに書いたPerlなもので、いろいろ作法違いなどあってもご容赦ください。。。
      app.psgi

      use strict;
      use warnings;
       
      use Tatsumaki::Application;
      use RunInstancesHandler;
      use TerminateInstancesHandler;
      use DescribeInstancesHandler;
       
       
      package MainHandler;
      use base qw(Tatsumaki::Handler);
       
      sub get {
          my $self = shift;
          $self->write("Work well.\n");
      }
       
      package main;
      use File::Basename;
       
      my $app = Tatsumaki::Application->new([
              '/' => 'MainHandler',
              '/RunInstances' => 'RunInstancesHandler',
              '/TerminateInstances' => 'TerminateInstancesHandler',
              '/DescribeInstances' => 'DescribeInstancesHandler',
              ]);
      return $app;

      RunInstancesHandler.pm

      # run instance handler
      package RunInstancesHandler;
       
      use parent qw(Tatsumaki::Handler);
      use strict;
      use warnings;
       
      use Sys::Virt;
      use XML::Simple;
      use Data::Dumper;
      use String::Random;
      use File::Basename;
      use File::Copy;
      use File::Spec;
       
      sub post {
          my $self = shift;
       
          # create XML::Simple object
          my $xs = new XML::Simple(forcearray => 1, KeyAttr => [], RootName => '');
       
          # receive XML request
          my $xmlRequestRef = $xs->XMLin($self->request->content);
          my $xmlRef = $xs->XMLin($self->request->content);
       
          my $currentDir = dirname(__FILE__);
       
          # image id (default: lucid)
          my $imageId = 'lucid';
       
          # image file name
          my $baseImageFileName = $currentDir . '/images/' . $imageId . '.qcow2';
       
          # base xml file name
          my $baseImageXmlFileName = $currentDir . '/images/xml/' . $imageId . '.xml';
       
          # generate MAC address script path
          my $genMacAddrPath = $currentDir . '/tools/gen_mac_addr.sh';
       
          # instance memory size (default: 121072)
          my $memorySize = '131072';
       
          # Open & read base XML file
          open my $fh, '<', $baseImageXmlFileName or die "Could not open file: $!";
          sysread $fh, my $baseImageXml, -s $fh;
       
          # variables
          my $instanceName, my $uuid, my $macAddress, my $imageFileName, my $imageXmlRef;
       
          # get max instance count from request
          my $maxCount = $xmlRef->{maxCount}->[0];
       
          # connect QEMU
          my $vmm = Sys::Virt->new(address => "qemu:///system");
          my $domain;
       
          my $responseXmlTemplate = $currentDir . '/xml/run_instances_response.xml';
          my $responseItemXmlTemplate = $currentDir . '/xml/item.xml';
       
          # Open & read base XML file
          open $fh, '<', $responseXmlTemplate or die "Could not open file: $!";
          sysread $fh, my $responseXml, -s $fh;
       
          # Open & read item XML file
          open $fh, '<', $responseItemXmlTemplate or die "Could not open file: $!";
          sysread $fh, my $responseItemXml, -s $fh;
       
          my $responseXmlRef = $xs->XMLin($responseXml);
       
          my $itemXmlRef;
          my $domainXmlRef;
       
          # create images
          for (my $i = 0; $i < $maxCount; $i++) {
       
              # generate new instance name
              $instanceName = 'i-' . String::Random->new->randregex('[a-f0-9]{8}');
       
              # generate new UUID
              $uuid = `uuidgen`;
       
              # generate MAC address
              $macAddress = `$genMacAddrPath`;
              chomp($macAddress);
       
              # copy image file
              $imageFileName = File::Spec->rel2abs($currentDir . '/store/' . $instanceName . '.qcow2');
              copy($baseImageFileName, $imageFileName);
       
              # create XML Reference for booting
              $imageXmlRef = $xs->XMLin($baseImageXml);
       
              # change XML values
              $imageXmlRef->{domain}->[0]->{name}->[0] = $instanceName;
              $imageXmlRef->{domain}->[0]->{memory}->[0] = $memorySize;
              $imageXmlRef->{domain}->[0]->{currentMemory}->[0] = $memorySize;
              $imageXmlRef->{domain}->[0]->{uuid}->[0] = $uuid;
              $imageXmlRef->{domain}->[0]->{devices}->[0]->{disk}->[0]->{source}->[0]->{file} = $imageFileName;
              $imageXmlRef->{domain}->[0]->{devices}->[0]->{interface}->[0]->{mac}->[0]->{address} = $macAddress;
       
              $itemXmlRef = $xs->XMLin($responseItemXml);
              $itemXmlRef->{instanceId}->[0] = $instanceName;
              $itemXmlRef->{instanceState}->[0] = 'pending';
              $responseXmlRef->{RunInstancesResponse}->[0]->{item}->[0]->{instancesSet}->[0]->{item}->[$i] = $itemXmlRef;
              $domainXmlRef = '';
       
              # create domain
              $domain = $vmm->create_domain($xs->XMLout($imageXmlRef));
          }
          $self->response->headers([ 'Content-Type' => 'text/xml; charset=utf-8' ]);
          $self->write($xs->XMLout($responseXmlRef));
          $self->finish;
      }
       
      1;

      # DescribeInstancesHandler.pmとTerminateInstancesHandler.pmは略。

      これをplackupで起動し、

      $ plackup &
      [1] 16392
      Twiggy: Accepting connections at http://0.0.0.0:5000/

      起動したサーバに対して、XMLでリクエストを送信します。
      run_instances.pl

      #!/usr/bin/perl -w
       
      use strict;
       
      use LWP::UserAgent;
      use HTTP::Request::Common;
      use File::Basename;
       
      my $userAgent = LWP::UserAgent->new(agent => 'perl post');
       
      my $requestXmlFile = dirname(__FILE__) . '/run_request.xml';
      my $requestUrl = 'http://localhost:5000/RunInstances';
       
      open my $fh, '<', $requestXmlFile or die "Could not open file: $!";
      sysread $fh, my $message, -s $fh;
       
      my $response = $userAgent->request(POST $requestUrl,
              Content_Type => 'text/xml',
              Content => $message);
       
      print $response->error_as_HTML unless $response->is_success;
       
      print $response->as_string;

      リクエストに使用するXMLは、EC2公式のAPIドキュメントのものに合わせています。

      <RunInstances xmlns="http://ec2.amazonaws.com/doc/2010-06-15/">
          <imageId>ami-60a54009</imageId>
          <minCount>1</minCount>
          <maxCount>3</maxCount>
          <keyName>example-key-name</keyName>
          <groupSet/>
          <placement>
              <availabilityZone>us-east-1b</availabilityZone>
          </placement>
          <kernelId>aki-ba3adfd3</kernelId>
          <ramdiskId>ari-badbad00</ramdiskId>
          <blockDeviceMapping>
              <item>
                  <virtualName>ami</virtualName>
                  <deviceName>sda1</deviceName>
              </item>
              <item>
                  <virtualName>root</virtualName>
                  <deviceName>/dev/sda1</deviceName>
              </item>
              <item>
                  <virtualName>instancestore0</virtualName>
                  <deviceName>sdb</deviceName>
              </item>
              <item>
                  <virtualName>instance1</virtualName>
                  <deviceName>sdc</deviceName>
              </item>
          </blockDeviceMapping>
          <userData version="1.0" encoding="base64">
              <data>"VGhpcyBpcyBiYXNlIDY0IQ==</data>
          </userData>
          <addressingType>public</addressingType>
          <monitoring>enabled</monitoring>
      </RunInstances>

      これを実行すると、

      $ perl run_instances.pl
      HTTP/1.0 200 OK
      Content-Type: text/xml; charset=utf-8
      Client-Date: Wed, 28 Jul 2010 02:56:47 GMT
      Client-Peer: 127.0.0.1:5000
      Client-Response-Num: 1
        <RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2006-10-01">
          <groupSet>
            <item>
              <groupId>default</groupId>
            </item>
          </groupSet>
          <instancesSet></instancesSet>
          <item>
            <instancesSet>
              <item>
                <amiLaunchIndex>0</amiLaunchIndex>
                <dnsName></dnsName>
                <imageId>ami-60a54009</imageId>
                <instanceId>i-ea19a11c</instanceId>
                <instanceState>pending</instanceState>
                <keyName>example-key-name</keyName>
              </item>
              <item>
                <amiLaunchIndex>0</amiLaunchIndex>
                <dnsName></dnsName>
                <imageId>ami-60a54009</imageId>
                <instanceId>i-e89ae6b5</instanceId>
                <instanceState>pending</instanceState>
                <keyName>example-key-name</keyName>
              </item>
              <item>
                <amiLaunchIndex>0</amiLaunchIndex>
                <dnsName></dnsName>
                <imageId>ami-60a54009</imageId>
                <instanceId>i-fb30dc5a</instanceId>
                <instanceState>pending</instanceState>
                <keyName>example-key-name</keyName>
              </item>
            </instancesSet>
          </item>
          <ownerId>495219933132</ownerId>
          <reservationId>r-47a5402e</reservationId>
        </RunInstancesResponse>

      といった感じで、起動要求を投げたインスタンスの情報がXMLで返ってきます。
      レスポンスもEC2公式のAPIドキュメントに準拠する形にしています。
      # と言っても、すごく半端な実装です。
      # 起動時はmaxCountしか今のところ見てないですし。
      # 返ってくるXMLも、instanceIdしか書き換えていない状態です。

      実際に起動したかどうかは、virshで確認できます。

      $ virsh list
       Id 名前               状態
      ----------------------------------
       47 i-ea19a11c           実行中
       48 i-e89ae6b5           実行中
       49 i-fb30dc5a           実行中

      無事に起動できているようです。:-)

      と言った感じで、とりあえずここまで。
      次は処理を非同期化したり、エラー処理をきちんと行ったりといったところでしょうか。
      # 実は各インスタンスのIPアドレスを取得するのが、結構面倒だったり2 しますが。
      また、インストールした直後だとNATで構成されているので、これをBridgeに変えたり。
      そもそもの互換性がまだ低いので、勉強しながらちまちまと実装していく予定です。

      個人的な感想としては、
      ・TatsumakiがモダンなPerlを学ぶ上で非常によい教材になる
      ・しばらく仕事でPHPを書いていたので、php-funcref-in-perlが非常に役立った
      といったところです。

      なお今回、弊社の新サービスを用いてプレゼンを行ないました。
      # 終わってみたら、そっちに話を持って行かれて嬉しいやら寂しいやら。
      新サービスついては、おいおい当blogのほうで紹介させていただくと思います。

      1. 直前はCentOS5.5上でEucalyptus NCが動いていました。 []
      2. arpの結果から引っ張るくらいしか思いつかず。いい方法あったら教えてください。。。 []