June 2008 Archives

実用! PerlでコマンドラインからTwitter投稿

| 0 Comments | 1 TrackBack | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

(2010-06-10追記)
Twitter APIのBASIC認証が2010-06-30で終了することになっています。それを受けて、BASIC認証終了後でも対応できるOAuth認証を使った記事を新たに用意しましたので、そちらを参照してください。

実用! PerlでコマンドラインからTwitter投稿(OAuth対応) - perl-mongers.org

~ ~ ~

最近WindowsからLinuxへログインして作業していることが多いんですけれども、なんかつぶやきたくなったときにこんなの。Twitterにポスト。Net::Twitterモジュールを使います。

今回の流れは、

  • とりあえず投稿できるスクリプトを作ってみる
  • コマンドラインから投稿できるものにグレードアップ

となっています。

とりあえず投稿できるスクリプトを作ってみる

まず、固定文字列だったらこんな感じで。「もへもへ」という文字列を投げます。username、passwordは自分のを使おう。

Filename:twit_test.pl (UTF-8で)

#!/usr/bin/perl
use strict;
use warnings;
use Net::Twitter;
my $twit = Net::Twitter->new(
  username => 'username',  #ユーザー名
  password => 'password',  #パスワード
);
$twit->update('もへもへ');

実行すると、Twitterに投稿されるはずです。文字化けしてたら、おそらく文字エンコーディングがUTF-8以外です。UTF-8で保存し直すか、Encodeモジュールで適切に変換してあげましょう。

コマンドラインから投稿できるものにグレードアップ

上のスクリプトを生かして、コマンドラインからの文字列を投稿するスクリプトに改造します。今回はタイプ量減らしたいので、コンソールがUTF-8の場合に限定。他の環境の場合、Encodeモジュールやらなにやら使用のこと。

Filename: twitter

#!/usr/bin/perl
use strict;
use warnings;
use Net::Twitter;
my $twit = Net::Twitter->new(
  username => 'username',  #ユーザー名
  password => 'password',  #パスワード
);
$twit->update($_) for @ARGV;

上記twitterというファイルに実行権限を与えておいて、個人用実行ファイルを置くディレクトリ($HOME/bin とかにpath通しておくと便利)に入れるとかしておくと、

$ twitter エロス

とか

$ twitter 'エロス カワユス'

とかで投稿できます。

net-twitter-post.jpg

今回のスクリプトを改造して、定期的にビーコン打ち出すとか、作業が終わったらnotify出すとか応用できます。ちょっと考えたら、うちの業界のサーバには、製版が終わったらメールを送信する機能が付き始めていますが、こういうのもついていてもいいかとも思いました。

※調子こいて投稿しまくっていると連投制限に引っかかるみたいなので注意しましょう。

codereposのコミット数ランキングページを作る

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

先日tokuhiromさんのブログで、ohlohというサービスを知りました。

凄いですね、1位tokuhiromさん、2位cho45さん両方とも1000commits超えています。さて今日はこのohlohという開発者向けサイトが提供するAPIを使って、codereposコミット数ランキングを作ってみたいと思います。

ohlohのAPIドキュメントによると、ContributorFactというAPIでtokuhiromさんの記事にある様なランキングが取得出来る事が分かりました。

APIを利用する為にはAPIキーが必要になりますので、アカウントを取得後にAPI Keysのページでキーを取得、リクエストクエリの後にapi_keyというパラメータで送信すればAPIが実行出来ます。単純にDumpしても面白くないので、今日はXMLで結果取得したデータをJSONで出力し、Ajaxで扱えるマッシュアップサイトぽい物を作ってみます。

perlのコードは単純に

#!/usr/bin/perl

use strict;
use warnings;
use LWP::Simple;
use XML::Simple;
use JSON::Syck;
use CGI;

my $api_key = "your-api-key";
my $callback = "";

my $q = new CGI;
$callback = $q->param("callback") if defined $q->param('callback');
my $content = get("http://www.ohloh.net/projects/8359/contributors.xml?api_key=$api_key");
my $parser = XML::Simple->new();
my $xml = $parser->XMLin($content);
my $json = JSON::Syck::Dump($xml);

print $q->header(
  -type    => 'text/javascript',
  -charset => 'utf-8'
  );
print "$callback($json)";

という、XMLinからJSON::Syckへの流し込みのコードになります。

これをCGIとして実行し、JSONを生成します。またクライアント側は

<script type="text/javascript" src="jquery-latest.js"></script>
<script type="text/javascript"><!--
$(function() {
	$('#coderepos-committers').html('');
	$.getJSON("http://your.example.com/coderepos-ranking.cgi?callback=?", function(data) {
		$.each(data.result.contributor_fact, function(index, item) {
			$('<div>').html(
				'<b>' + item.contributor_name + '</b><br />' +
				'<blockquote>' +
				'main language: ' + item.primary_language_nice_name + '<br />' +
				'commits: ' + item.commits + '<br />' +
				'</blockquote>').appendTo('#coderepos-committers');
		});
	});
})();
--></script>
<div id="coderepos-committers"></div>

とすれば、以下の様な画面が表示されます。

http://perl-mongers.org/2008/06/20/coderepos-ranking/coderepos-ranking.png

XMLinからJSON::Syck::Dumpの流れが便利すぎますね。:-)

このランキングを作るロジック自体、手数の多そうな処理になりますからこのAPI便利かもしれません。他のプロジェクトでこの様なランキングページを作ってみても面白いかもしれませんね。

S3に写真を一括アップロード

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Livedoor picsだと容量を気にする必要があるので、AmazonのS3にバックアップするスクリプトを書いてみました。これで、いざFlickrのデータが飛んでも大丈夫ですね。

#!/usr/bin/env perl
use strict;
use warnings;
use Net::Amazon::S3;
use Perl6::Say;
use Path::Class qw(dir);
use List::MoreUtils qw(any);

# config
my $bucketname            = 'Your backect name for backup';
my $aws_access_key_id     = 'Your access id';
my $aws_secret_access_key = 'Your secret access key';

my $s3 = Net::Amazon::S3->new(
    {   aws_access_key_id     => $aws_access_key_id,
        aws_secret_access_key => $aws_secret_access_key,
        retry                 => 1,
    }
);
my $bucket_for_backup = create_backet_for_backup();

main();
sub main {
    my $dir = dir('photos');
    upload_photos_in_dir($dir);
    list_files_in_bucket($bucket_for_backup);
}

sub create_backet_for_backup {
    my $bucket = $s3->add_bucket( { bucket => $bucketname } )
        or die $s3->err . ": " . $s3->errstr;
    return $bucket;
}

sub upload_photos_in_dir {
    my $dir = shift;
    dir($dir)->recurse(
        callback => sub {
            my $file = shift;
            return unless -f $file;
            upload_file($file);
        }
    );
}

sub upload_file {
    my $file = shift;
    return unless ( $file->basename =‾ /¥.jpg$/ );
    my $keys_in_bucket = keys_in_bucket($bucket_for_backup);
    return if any { $file->basename eq $_->{key} } @{$keys_in_bucket};

    my $body   = $file->slurp;
    my $status = $bucket_for_backup->add_key( $file->basename, $body,
        { 'content_type' => 'image/jpeg', } );
    say "Uploaded:" . $file->basename if $status;
}

my $cache_keys;

sub keys_in_bucket {
    my $bucket = shift;
    return $cache_keys if $cache_keys;
    my $response = $bucket_for_backup->list_all
        or die $s3->err . ": " . $s3->errstr;
    $cache_keys = $response->{keys};
    $cache_keys;
}

sub list_files_in_bucket {
    my $bucket = shift;
    say '### list all files in bucket';
    my $response = $bucket->list_all
        or die $s3->err . ": " . $s3->errstr;
    foreach my $key ( @{ $response->{keys} } ) {
        my $key_name = $key->{key};
        my $key_size = $key->{size};
        say "Bucket contains key '$key_name' of size $key_size";
    }
}|

Flickrから一括ダウンロードして、Amazon S3に一括バックアップというのをするのもいいかもしれないですね。


# photosディレクトリ決めうちなので適当に変えてください。写真のあるディレクトリの場所が変わるようであれば、Getoptで写真のあるディレクトリを指定できるように変更するのがいいですね。

Livedoor picsに写真を一括アップロード

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Flickrをメインに使っているのですが、Flickrだけにアップロードしておくというのも少し不安があるので、他のところにバックアップしておくのがいいかもしれない!と思いました。


そこで、ディレクトリ内の写真を一括Livedoor picsにアップロードするスクリプトを作ってみました。


写真の置いてあるディレクトリをphotosディレクトリに決めうってますが、そこらは適当に改造して使ってください。

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Atom::Client;
use XML::Atom::Entry;
use XML::Atom::Content;
use Path::Class qw(dir);
use Perl6::Say;
use Data::Dumper;

# config
my $livedoor_id = 'Your Livedoor ID';
my $password    = 'Your password';

my $endpoint_url = 'http://ws.pics.livedoor.com/atom/' . $livedoor_id;
my $api          = api_client();

sub main {
    my $dir = dir('photos');
    upload_photos_in_dir($dir);
}

sub upload_photos_in_dir {
    my $dir = shift;
    dir($dir)->recurse(
        callback => sub {
            my $file = shift;
            return unless -f $file;
            upload_file($file);
        }
    );
}

sub upload_file {
    my $file  = shift;
    my $entry = create_entry($file);
    my $edit_uri   = $api->createEntry( $endpoint_url, $entry );
    say 'Uploaded file uri:' . $edit_uri if $edit_uri;
}

sub create_entry {
    my $file = shift;

    my $body    = $file->slurp;
    my $content = XML::Atom::Content->new;
    $content->body($body);
    $content->type('image/jpeg');
    my $entry = XML::Atom::Entry->new;
    $entry->title( $file->basename );
    $entry->content($content);
}

sub api_client {
    my $client = XML::Atom::Client->new;
    $client->username($livedoor_id);
    $client->password($password);
    $client;
}

main();

# picsにアップロードできるサイズを考えると、一括リサイズしてからアップロードできるようにするなんていう処理をいれてもいいかもしれないですね。

WebService::SimpleでFlickrのfavoritesの写真を一括ダウンロード

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

最近カメラが空前のマイブームで、お気に入りの写真をiPodに入れて持ち歩いたりして、もっと綺麗な写真に触れる機会を増やしたいと思っていました。


そんなこんなで、Flickrのfavoritesにお気に入り写真を登録していってるんですが、Flickrから写真をダウンロードするのがだるい!ということで、favoritesの写真を一括ダウンロードするスクリプトを書いてみました。

#!/usr/bin/env perl
use strict;
use warnings;
use WebService::Simple;
use Digest::MD5 qw(md5_hex);
use Path::Class qw(dir);
use Perl6::Say;
use HTTP::Async;
use HTTP::Request;

# config
my $api_key = "Your API Key";
my $user_id = 'Your nsid';

my $api = WebService::Simple->new(
    base_url => "http://api.flickr.com/services/rest/",
    param    => { api_key => $api_key, }
);

sub main {
    fetch_favorite_photos();
}

sub fetch_favorite_photos {
    my $photos = get_favorite_photo_lists();
    return unless $photos;
    my $photo_urls = collect_photo_urls($photos);

    download_photos($photo_urls);
}

sub get_favorite_photo_lists {
    my $response = $api->get(
        {   method   => "flickr.favorites.getPublicList",
            user_id  => $user_id,
            per_page => 500,
        }
    );
    my $ref = $response->parse_response;
    return unless $ref->{photos}->{total};
    return $ref->{photos}->{photo};
}

sub collect_photo_urls {
    my $photos     = shift;
    my $photo_urls = [];
    foreach my $photo_id ( keys %{$photos} ) {
        my $url = _get_photo_url($photo_id);
        push @{$photo_urls}, $url if $url;
    }
    return $photo_urls;
}

sub _get_photo_url {
    my $photo_id = shift;
    my $response = $api->get(
        {   method   => 'flickr.photos.getSizes',
            photo_id => $photo_id,
        }
    );
    my $ref = $response->parse_response;
    my $url;
    foreach my $size ( @{ $ref->{sizes}->{size} } ) {
        if ( $size->{label} eq 'Original' || $size->{label} eq 'Large' ) {
            $url = $size->{source};
        }
    }
    say "Collecting Photo urls: " . $url if $url;
    $url;
}

sub download_photos {
    my $photo_urls = shift;
    my $ua         = LWP::UserAgent->new;
    my $dir        = setup_download_dir();
    say "### Downloading photos ###";
    foreach my $url ( @{$photo_urls} ) {
        my $file = $dir->file( md5_hex($url) . ".jpg" );
        my $res = $ua->mirror( $url, $file->absolute );
        say "Download status : " . $res->status_line . " - $url";
    }
}

sub setup_download_dir {
    my $dir = dir("photos");
    $dir->mkpath;
    $dir;
}

main();

頻繁にfavoritesを登録する人は、HTTP::Asyncなどを使ってダウンロード部分を並列化したりしてみるといいかもしれないですね。

実用! Perlで縦長連続画像を1フレームずつバラして鑑賞

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

(このエントリのオリジナルは、M.C.P.C.: 縦長連続画像を分割してムフフです)

縦長に連続したコマが連なっている画像、どうやって見ていますか? 最近のグラフィックビューアだと、画像サイズに合わせて拡大縮小してくれたりするのですけれども、そもそも縦長に過ぎる画像だと、拡大率を、長辺に合わせたら画像がよく見えないし、かといって短辺に合わせたら、全部見るのにスクロールして見なくてはなりません。

だから、そんな画像、Perlと言う名の青龍刀で切り刻んでしまいましょう!

モジュールの準備

今回使うモジュールは、Image::Magickというものですのが、一昔前まではインストールにちょっとコツが必要なモジュールだったと思います。また、使っているOSやPerlによっても入れ方が微妙に違うので説明が面倒ですから、各自調査してください。

WindowsのActivePerl 5.8.xを使っていて、スタートフォルダのプログラムグループに、Perl Package Managerというプログラムが表示されている場合は、こちらにインストールの仕方を掲載してあります。

スクリプト

Image::Magickの使い方は、

:: Cepheid :: - ImageMagickの使用例に詳しいです。

Filename: split_image.pl

#!/usr/bin/perl
# Filename: split_image.pl
# 400x300の画像に切り出します
 
use strict;
use warnings;
use Image::Magick;
use File::Basename;
 
our $frame_width  = 400;
our $frame_height = 300;
 
# main 
 
foreach ( @ARGV ){
  my $filename = $_;
  print "$filename\n";
  if ( -e $filename ) {
    cut_img( $filename );
  }
}
exit;
 
 
sub cut_img {
  my $filename = shift;
  our ( $frame_width, $frame_height );
  my $img_orig = Image::Magick->new;
  $img_orig->Read( $filename );
  # via http://www.ss.iij4u.or.jp/~somali/web/im_example/get.html
  my ($width, $height) = $img_orig->Get('width', 'height');
  my $frame = $height / $frame_height;
  
  if ( $frame > 1 ) {
    for ( my $i = 0; $i < $frame; $i++ ) {
      my $img = $img_orig->Clone();
      # via http://www.ss.iij4u.or.jp/~somali/web/im_example/crop.html
      $img->Crop( width  => $frame_width,
                     height => $frame_height,
                     x => 0,
                     y => $frame_height * $i,
            );
      $img->write( make_filename( $filename, $i ) );
      undef $img;
    }
  }
  
  undef $img_orig;
  return;
}
 
 
sub make_filename {
  my $path = shift;
  my $num  = shift;
  my($filename, $directories, $suffix)
    = fileparse($path, qr/\.[^.]*/ );
  return "$directories${filename}_$num$suffix";
}
 
 
__END__
おもしろくない解説は、[続きを読む]以降に。


実行例

実行前に警告! ファイル上書きされても関知しません!

懐かしの外人4コマでやってみました。

使用前

4koma-01.jpg

コンソール

D:\4koma>split_image.pl 01_0001_2.jpg
01_0001_2.jpg
 
D:\4koma>
使用後

4koma-02.jpg

というわけで、今回は、縦長連続画像を分割するという、インターネットに長くいる人ほどなぜか有用性が分かるというスクリプトを作ってみました。

これ何に役立つの、ていう人もいるかもしれませんけれども、こういう地味ーなのって、Webのバックヤードでこっそり役立っていたりするんですよね。

URLを触る時は、URI(モジュール名)以外も知っておくと吉

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんばんは!


URLを触る時は、URI(perlモジュール名)が超便利だと思う。


という話を書きましたが、実は他のモジュールをもう少し知っておくと、さらにハッピーになります。


おっと、それを説明する前に

 my $uri = URI->new('http://perl-mongers.org');
 print $uri->as_string . "\n";
 print "$uri\n";

上記の出力はどちらも、「http://perl-mongers.org」となり、同じ結果です。

これを話しておかないと、めんどうに思う方がいたりすると、Dan the Perl Monger さんにコメントで教えてもらいました。


また、タイトルにURIと、あまり深く考えず書いてたのを、C_L さんに指摘してもらいました。


ありがとうございます。 

URI::QueryParamを活用しよう!

query_form() メソッドは便利なのですが、困るときがあります。例をあげてみましょう。

use strict;
use warnings;
use URI;

use Data::Dumper;

my $uri = URI->new('http://perl-mongers.org');

$uri->query_form( { hoge => 'hoge', foo => [ 1 , 2 ] } ) ;

my %params = $uri->query_form;
warn Dumper \%params;

my @params = $uri->query_form;
warn Dumper \@params;

結果

$VAR1 = {
          'foo' => '2',
          'hoge' => 'hoge'
        };
$VAR1 = [
          'foo',
          '1',
          'foo',
          '2',
          'hoge',
          'hoge'
        ];

そうですね。 query_form の戻り値は、配列なので、fooというパラメータを二つ渡した際に、場合によっては扱いにくくなってしまいます。下記のような、形式でデータを手に入れたいケースがありますよね?

$VAR1 = {
          'foo' => [
                     '1',
                     '2'
                   ],
          'hoge' => 'hoge'
        };

そこで、URI::QueryParamですね。このモジュールを use するといくつかのモジュールが使えるようになります。下記のコードを実行すると、上記の欲しい形で結果を手に入れることができます。

use strict;
use warnings;
use URI;
use URI::QueryParam;

use Data::Dumper;

my $uri = URI->new('http://perl-mongers.org');

$uri->query_form( { hoge => 'hoge', foo => [ 1 , 2 ] } ) ;

my $params = $uri->query_form_hash;
warn Dumper $params;

便利ですね!

URI::Escape

私達は、日本人または、日本語を扱う方々のはずなので、URI::Escapeは、必ずお世話になると思います。


具体的に何をするかというと、URLにマルチバイトや記号などのurlに使うことができない文字を、安全な文字に変換してくれます。

my $a = URI::Escape::uri_escape('日本語') ;
my $b = URI::Escape::uri_escape_utf8(  Encode::decode( 'utf-8' , '日本語' ) ) ;
print "$a\n" ;
print "$b\n" ;
print URI::Escape::uri_unescape($a) ."\n";
print URI::Escape::uri_unescape($b) ."\n";

この結果は、以下のようになります。安全ですね!

%E6%97%A5%E6%9C%AC%E8%AA%9E
%E6%97%A5%E6%9C%AC%E8%AA%9E
日本語
日本語

注意として、utf8のフラグが立っている文字列の場合、専用の関数を使う必要があります。


これで、さらにURLを触るのが楽しくなりますね!


:wq!

URLを触る時は、URI(perlモジュール名)が超便利だと思う。

| 1 Comment | 1 TrackBack | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんばんは!


urlにパラメータをつけたり、取り除いたり、パスだけ取得したり、クエリーを作りたいとか、そういった機会に出くわすことが多々ありませんか? そういうときは、URI というモジュールが便利です。


こんな感じで使います。

use URI;
use Data::Dumper;
use strict;
use warnings;

my $uri = URI->new('http://hogehoge.com/hoge/hoge/?hoge=hoge&foo=1&foo=2');

print $uri->as_string . "\n";
print $uri->path . "\n";
print $uri->query . "\n";
print $uri->path_query . "\n";

この表示結果は、こんな風になります。色々なパーツが取れて嬉しいですね。

http://hogehoge.com/hoge/hoge/?hoge=hoge&foo=1&foo=2
/hoge/hoge/
hoge=hoge&foo=1&foo=2
/hoge/hoge/?hoge=hoge&foo=1&foo=2

またurlを構築したいときにも使えます。

use URI;
use Data::Dumper;
use strict;
use warnings;

my $uri = URI->new('http://hogehoge.com/hoge/');

$uri->query_form( hoge => 'hoge' , foo => [1 ,2 ] );

print $uri->as_string . "\n";

結果は、以下のようになりますね。

http://hogehoge.com/hoge/?hoge=hoge&foo=1&foo=2

便利ですね! URL を構築する時には、ぜひ使ってみて下さいね!


:wq!

perl - use warnings; # -w でなくて

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Original: http://blog.livedoor.jp/dankogai/archives/51068305.html

-w って何? warnings って何?

まず、以下のプログラムを見てみよう。

#!/usr/local/bin/perl
use strict;

sub distance {
    my ( $a, $b ) = @_;
    return sqrt( $a**2 + $b**2 );
}

print distance(@ARGV), "\n";
% perl scratch.pl 3 4
5

use strict;しているし、そして期待どおりに動いている。


しかし、以下の場合は期待どおりだろうか。

% perl scratch.pl 3
3

distance()は引数を二つ要求するのに、一つしか入れなくても動いてしまう。これは$bがundefとなり、数値コンテキストでは0として解釈されるため当然なのだけど、あなたは本当は$bを初期化しておきかったとする。どうすればよいか。


こういう時に活躍するのが、-wスイッチであり、use warnings;である。


まずは-wを見てみよう。

% perl -w d0.pl 3
Use of uninitialized value $b in exponentiation (**) at warnings.pl line 6.
3

確かに"Use of uninitialized value $b"という警告が出た。


次にuse warnings;を見てみよう。

% cat scratch.pl 
#!/usr/local/bin/perl
use strict;
use warnings;

sub distance {
    my ( $a, $b ) = @_;
    return sqrt( $a**2 + $b**2 );
}

print distance(@ARGV), "\n";
% perl scratch.pl 3
Use of uninitialized value $b in exponentiation (**) at warnings.pl line 7.
3

こちらもきちんと警告を出している。

-w だと何がいけないの?

それでは、なぜuse warnings;を使うべきで、-wでないのか。-wの方がuse warnings;より短いではないか。


理由は二つある。

-wは.plには有効でも.pmには有効ではない

これが一番の理由である。-wはあくまでも実行時のスイッチなので、スクリプト、すなわち実行ファイルで指定しないと有効にならない。


Distance.pm

package Distance;
use strict;

use base 'Exporter';
our @EXPORT = qw/distance/;

sub distance {
    my ( $a, $b ) = @_;
    return sqrt( $a**2 + $b**2 );
}

1;

scratch.pl

#!/usr/local/bin/perl
use strict;
use Distance;
print distance(@ARGV), "\n";

と別れている場合、-wだと.plの方で指定しないと有効にならないのだ。ところが、use warnings;であれば.pmの方で指定してもきちんと警告してくれるのだ。

no warnings 'whatever';できない

use warnings;ということは、no warnings;も存在するということである。実際これは存在し、かつレキシカルなので、以下のように局所的に警告を止めることも出来る。

sub distance {
    no warnings 'uninitialized';
    my ( $a, $b ) = @_;
    return sqrt( $a**2 + $b**2 );
}
  • wでもこれに相当することは不可能ではないが、
sub distance {
    local $^W = undef;
    my ( $a, $b ) = @_;
    return sqrt( $a**2 + $b**2 );
}

という極めて醜いものである。

まとめ

というわけで、今後スクリプトを書く時には、

use strict;
use warnings;

をお忘れなきよう。


モジュールを書くのであれば、

package MyModule;
use strict;
use warnings;

としていただきたい。ちなみにh2xsやModule::Starterを使ってモジュールを初期生成した場合、この二行は必ず入っている。あと、Mooseにはこの二行と同等の効果も含まれている。


use warnings; # and be safe!


Dan the Perl Monger

実用! Perlで少しでもSBMのブックマーク数を多く見せる

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

ソーシャルブックマーク(SBM)が結構使われるようになっていますが、自分のblogのブックマーク数、気にしてますか? 僕がいる業界の人はあんまり気にしてないみたいで、僕のblogにブックマーク数表示がつきまくっているのがなんだか恥ずかしいです。

そんな中、Perlを使い、ブックマーク数を少しでも多く見せる技を編み出すことに成功しました!

インチキじゃないよ!

まずはブックマーク数のおさらい

ブックマーク数というのは、SBMサービス業者が提供している特定の手続きの通りすることで、ブックマーク数が得られたり、ブックマーク数を表す画像が得られたりするものです。

よくblogに付けられているのは、はてなブックマークブックマーク数API によるものが多いです。これは、はてなブックマークというSBMサービス上でのブックマーク数が表示されます。

また、livedoor クリップでも、クリップ数を画像で取得する API - livedoor クリップ まとめサイト - livedoor Wiki(ウィキ)にて、同様に画像でクリップ数(ブックマーク数と同義)が得られます。

というわけで、このサイトのtopをこれらを使ってブックマーク数を取得すると、

<img src="http://b.hatena.ne.jp/entry/image/http://perl-mongers.org/" />
<img src="http://image.clip.livedoor.com/counter/http://perl-mongers.org/" />
と書いて、

こうなります。

......画像並べてたらあんま意味ないので、こいつらを合計してしまいましょう。

SBMのAPIからブックマーク数を数値で取得

画像でブックマーク数をとってもしょうがないので、コンピュータで計算できる数値でブックマーク数を取得します。

はてなブックマーク:はてなブックマーク件数取得APIとは - はてなダイアリー
livedoorクリップ:クリップ件数取得 API - livedoor クリップ まとめサイト - livedoor Wiki(ウィキ)

上記から引用すると、

はてなブックマーク

#!/usr/local/bin/perl
use strict;
use warnings;
use XMLRPC::Lite;
 
our $EndPoint = 'http://b.hatena.ne.jp/xmlrpc';
 
my @urls = (
    'http://d.hatena.ne.jp/',
    'http://b.hatena.ne.jp/',
    'http://www.hatena.ne.jp/',
);
 
my $map = XMLRPC::Lite
    ->proxy($EndPoint)
    ->call('bookmark.getCount', @urls)
    ->result;
 
printf("%d\t%s\n", $map->{$_}, $_) for @urls;
livedoorクリップ
#!/usr/local/bin/perl
use strict;
use warnings;
use XMLRPC::Lite;
 
my @urls = qw(
    http://clip.livedoor.com/
    http://b.hatena.ne.jp/
    http://del.icio.us/
);
 
my $proxy = 'http://rpc.clip.livedoor.com/count';
 
my $result = XMLRPC::Lite->proxy($proxy)
    ->call( 'clip.getCount', @urls )
    ->result;
 
printf( "%d\t%s\n", $result->{$_}, $_ ) for keys %$result;
そっくりすぎるので、ルーチン共用できそうですねー(ちなみに、PingKingのAPIもこれと似ていますので共用できるのでした)。

今回は、こんなふうにしてみました。

Filename: hatena_livedoor.pl

use strict;
use warnings;
use XMLRPC::Lite;
 
our $sbms = { 
       hatena =>
       {
         proxy   => 'http://b.hatena.ne.jp/xmlrpc',
         method  => 'bookmark.getCount',
       },
       livedoor =>
       {
         proxy   => 'http://rpc.clip.livedoor.com/count',
         method  => 'clip.getCount',
       },
};
 
my $url = 'http://perl-mongers.org/';
print get_sbm( 'hatena'  , $url )
    + get_sbm( 'livedoor', $url );
print "\n";
 
exit;
 
 
sub get_sbm {
  my $service = shift;
  my $url     = shift;
  return get_sbm_xmlrpc( $service, $url )
}
 
# XMLRPCによるブックマーク件数取得(livedoor,hatena)
sub get_sbm_xmlrpc {
  our $sbms;
  my $service = shift;
  my $url     = shift;
  my $result  = eval {
    XMLRPC::Lite->proxy(
                  $sbms->{$service}->{proxy},
                  timeout  => 10,
    )->call( $sbms->{$service}->{method}, $url )
     ->result
  };
  return $result->{ $url } || 0;
}
実行例:
$ perl hatena_livedoor.pl 
145
$

ついでにdel.icio.usも

del.icio.usのブックマーク数取得もくっつけて、

Filename: hatena_livedoor_delicious.pl

use strict;
use warnings;
use XMLRPC::Lite;
use JSON::Syck;
use LWP::Simple;
 
our $sbms = { 
       hatena =>
       {
         proxy   => 'http://b.hatena.ne.jp/xmlrpc',
         method  => 'bookmark.getCount',
       },
       livedoor =>
       {
         proxy   => 'http://rpc.clip.livedoor.com/count',
         method  => 'clip.getCount',
       },
       delicious =>
       {
         proxy   => 'http://badges.del.icio.us/feeds/json/url/data?url=',
         entry   => 'http://del.icio.us/url/',
       },
};
 
my $url = 'http://perl-mongers.org/';
print get_sbm( 'hatena'   , $url )
    + get_sbm( 'livedoor' , $url )
    + get_sbm( 'delicious', $url );
print "\n";
 
exit;
 
 
# SBMサービスからブックマーク件数取得
sub get_sbm {
  my $service = shift;
  my $url     = shift;
  if ( $service eq 'delicious' ) {
    return get_sbm_delicious( $url );
  }
  else {
    return get_sbm_xmlrpc( $service, $url );
  }
}
 
# XMLRPCによるブックマーク件数取得(livedoor,hatena)
sub get_sbm_xmlrpc {
  our $sbms;
  my $service = shift;
  my $url     = shift;
  my $result  = eval {
    XMLRPC::Lite->proxy(
                  $sbms->{$service}->{proxy},
                  timeout  => 10,
    )->call( $sbms->{$service}->{method}, $url )
     ->result
  };
  return $result->{ $url } || 0;
}
# del.icio.usブックマーク件数
sub get_sbm_delicious {
  my $url = shift;
  my $data = JSON::Syck::Load(
               get( $sbms->{delicious}->{proxy}.$url )
             );
  return $data->[0]->{total_posts} || 0;
}
実行例:
$ perl hatena_livedoor_delicious.pl 
173
$
となります。

JavaScriptとして貼り込めるようにする

いままではコマンドラインで実行する用だったので、これをblogに貼れるように、JavaScriptをはき出すようにします。

今回は、document.writeをはき出すようにしました。AJAXにしないのは、業界的な要請でいまだにMac IE5が多いものですから、ついついそれに合わせてしまったのです。

あと、ローカルにキャッシュを持たせたり、ブラウザキャッシュを効かせたりにしたりなどしまして、ソースがでかくなったので、[続きを読む]以降に置きました。

使うときには、<script src="http://labo.dtpwiki.jp/sbm/sbm.cgi?url=http://perl-mongers.org/"></script>とかします。このblogは本文中、script要素使用不可なため、使用例は僕のblogに来てみてね。イメージ的には、 みたいになります。

超重要! 複数SBMに重複ブックマークしている人にブックマークしてもらう

あとは、

はてなブックマーク - [観] 複数のソーシャルブックマークサイトに重複投稿されているコメントがうざい件

のコメントしている人の中で複数SBMサービスを利用していそうな人にブックマークしてもらうだけ。

ね、かんたんでしょ?

このエントリのオリジナルは、

M.C.P.C.: 様々なオンラインブックマークサービスのブックマーク件数を画像ではなくて数値で取得(高機能版)です。

フォームを使ってデータをPOSTしてみよう!

| 2 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちは!


今日はperlを使って、フォームからのポストのやり方を解説しようと思うよ!一番したにコードを張っておいたので、そのソースの解説をするよ。


作ってるのはこれ

解説

準備

最初の行は、perlのある場所をしていしていますね。もしかしたら、あなたの場合だと、#!/usr/local/bin/perl などと、他のパスにあるかもしれません。

#!/usr/bin/perl 

使うモジュールを読み込みます。strict, warnings はエラーを作った場合に教えてくれるから、書いています。 CGI は、フォーム関連の作業をするときに役にたつモジュールです。

use strict;
use warnings;
use CGI;

HTTPヘッダーしょり

httpへの出力を、$cgi->header() という関数で作ることができるよ。

文字コードなどの指定もできて、今回は utf-8を指定しています。

my $cgi = CGI->new();
print $cgi->header( -charset => 'utf-8' );

きも

nameという値が取れればという処理をしています。取れるということは、フォームからデータをPOSTしたってことですね。


それで、取れた場合、プリントアウトしています。htmlのタグとかを、フォームに入れることができてしまうと、タグが壊れたりするので、escapeHTMLというモジュールで、タグをエスケープするようにしています。

if ( $cgi->param('name') ) {
    my $name = $cgi->param('name');
    $name = $cgi->escapeHTML($name);
    printf( 'あなたのナマエは<b>%s</b>ですね', $name );
}

これは、最初のフォームを表示してる部分です。<DATA> をprintすると、

__END__以下に書いた部分がそこに入ります。便利ですね。

else { 
    print <DATA>;
}


__END__
<html>
<head>
<title>POST Sample</title>
</head>
<body>
    <h1>ポストしようぜ!</h1>
    <form method="post">
        ナマエ : <input type="text" name ="name"><br>
        <input type="submit" value="ポストするぜ">
    </form>
</body>
</html>

まとめ

簡単に違うページに、データを送ることができましたね。実際、複雑なデータを次のページになげたりしてるのも、ただ数が多かったりするだけで、同じような仕組みでできています。


perlで動きのある、webサイトを作るのは結構簡単だったりします。

プログラミングは、難しいと思ってるあなた、以外と簡単なので遊んでみてはどうでしょうか?

全コード

#!/usr/bin/perl 

use strict;
use warnings;
use CGI;

my $cgi = CGI->new();
print $cgi->header( -charset => 'utf-8' );
if ( $cgi->param('name') ) {
    my $name = $cgi->param('name');
    $name = $cgi->escapeHTML($name);
    printf( 'あなたのナマエは<b>%s</b>ですね', $name );
}
else { 
    print <DATA>;
}

__END__
<html>
<head>
<title>POST Sample</title>
</head>
<body>
    <h1>ポストしようぜ!</h1>
    <form method="post">
        ナマエ : <input type="text" name ="name"><br>
        <input type="submit" value="ポストするぜ">
    </form>
</body>
</html>

CPAN::Mini と glastree をつかって CPAN のデイリースナップショットを作ろう!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

CPAN::Mini って便利ですね。

CPAN::Mini って何をするモジュールかというと、CPAN に登録されているモジュールの最新版をまるごと取ってくる便利なモジュールです。(CPAN::Mini について詳しくは、富田さんの use CPAN::Mini; - 今日のCPANモジュール をごらんください)

CPAN のデイリースナップショットがほしい

日頃使っている CPAN モジュール群に大きな変更が入るとしたら、確実に動くことが分かっている現時点でのアーカイブがほしいです。欲しいと思ったときにミラーを取るのでもいいのですが、できれば、デイリースナップショットを自動的に生成したいですね。Debian Linux の場合は、snapshot.debian.net という神サイトがあります。今回は CPAN の配布物で同じようなものを作りたいと思います。

デイリースナップショットを作るソフトウェア

こういうデイリースナップショットを作る場合、まず思い浮かぶのが ruby で書かれた pdumpfs というバックアップソフトではないでしょうか。pdumpfs は、まず、初回動作時に丸ごとコピーを取ります。次回からは増加分のみをコピーし、変更のない部分はハードリンクをはるという素晴らしいソフトウェアです。snapshot.debian.net では pdumpfs を使っているようですね。

今回は、pdumpfs と同趣旨で、perl でかかれたバックアップソフト glastree (配布先) を少し hack してデイリーアーカイブを作ることにします。

glastree を hack

基本的に、minicpan で取ってきたファイルをさらにコピーしてバックアップする必要ないと思います。というのも、毎日のスナップショットに /archive/200806/09/CPAN/ などというディレクトリでアクセスできることが今回の目的だからです。この目的に合うように glastree の初回動作を少し変更したいと思います。

glastree は、初回動作時にコピー動作をしていますが、これをハードリンク動作に変更します。

glastree の変更点は以下のようになります。ハードリンクを作ってみて、だめならコピーという動作にします。

--- glastree.orig       2003-01-27 09:26:26.000000000 +0900
+++ glastree    2008-06-09 00:54:56.980871566 +0900
@@ -193,7 +193,10 @@
 {
     my ($frompath, $topath, $stat) = @_;

-    copy  ($frompath, $topath);
+    my $res = link ($frompath, $topath);
+    unless ($res) {
+        copy ($frompath, $topath);
+    }
     chown ($stat->uid, $stat->gid, $topath) if $EUID == 0;
     chmod ($stat->mode, $topath);
     utime ($stat->mtime, $stat->mtime, $topath);

cron に登録しての運用

glastree にパッチを当てたら、以下のようにスクリプトを書いておいて、一日一回実行されるように cron に登録しておきましょう。

cd ~/cpanmirror
minicpan -r http://your/favorite/mirror/of/CPAN/ -l CPAN
mkdir -p archive
glastree CPAN archive

これで、~/cpanmirror/archive/200806/09/CPAN/ などの日付ディレクトリで過去の CPAN にアクセスできるようになります。

会社の LAN 内サーバなどで apache から見える場所に置いておくといいかもしれませんね!

ディスク消費量

初回実行分で 850MB 消費していました。

次回実行時からは、日々追加されるモジュールの分程度の消費なので、ディスクが CPAN のミラーでいっぱいであたまを抱えるようなこともなくていいですね!


デイリースナップショットを作っておけば、備えあれば憂いなしですね!

レンタルサーバーでhello world!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちわ、まめこです。
yusukebe先生に「cgiでflickr検索」という宿題を貰ってcgiに挑戦しています!

自分はレンタルサーバーなので、いろいろと設定に苦労しました。
まだflickr検索は出来ていないのですが、helloworldだけ表示することに成功!

ソースはこんな感じです。

#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\"><HTML><HEAD><TITL        E>hello world!</TITLE></HEAD><BODY>\n";
print "hello world!\n";
print "</BODY></HTML>\n";

exit;


print文でヘッダ情報を表示します。この行です。

print "Content-type: text/html\n\n";


調べてみると

この行では、CGI から出力されるものをブラウザにHTMLページですよ と指示しています。http://www.aimix.jp/cgi/syohohtmlv.html


ということらしいです。

後はhtmlを頭からprintしていくだけでした。結構簡単。
毎回print文でこんな長文打つのはめんどくさいよー。という場合はCGI.pmを使うと楽できるようです。

#!/usr/local/bin/perl

use strict;
use CGI;

my $q = new CGI;
print $q->header;
print $q->start_html(-title=>'hello world');
print "Hellow World!";
print $q->end_html."\n";

exit;


これだけ! 作ってくれた人に感謝ですね。モジュールってスゴい!
あとは、ファイル名をcgiにしてレンタルサーバーに転送しました。

以下のことに気をつけるとすんなり出来るかもですよ!

1.perlのパスは合っているか
2.ヘッダ情報はちゃんと書いているか content-type=text/html
3.権限は合っているか

自分はなかなか動かなかったです><レンタルサーバーによってcgiの設定が微妙に違うので注意が必要かもです。

引き続き、yusukebe先生の宿題をやっつけなくちゃー!

実用! Perlでプリンタのメーター確認をする

| 1 Comment | 1 TrackBack | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

みなさんの会社では裏紙を使っていますか?

裏紙使うことの是非はともかく [www.itmedia.co.jp]、環境コンサルに相談すると「裏紙使わないのが許されるのは小学生までだよね」、とか言われるようですので、言われてしまったからには素直に裏紙使いましょう。

それで、裏紙を使うだけではなくて、実際に使った量を測定しないと環境活動にならないわけですけれども、プリンタに通した紙の枚数のチェック、いわゆるメーター確認をしないと、効果測定の計算ができず、会議で報告できないですよね。

というわけで、メーター確認をしてくれるPerlスクリプトです。

スクリプトの内容

最近のプリンタは、Webサーバを内蔵しているものが多く、ブラウザからアクセスして、特定のページにアクセスすると、メーター確認ができるようになっています。そこで、そのページをPerlのLWP::Simpleという一番簡単なアクセス手段を使って丸ごと引っ張ってきて、正規表現で、カウンタ数値を取得、表示します。

というわけで、これ作るには、プリンタにブラウザでアクセスし、HTML構造をやフレームの構造を解析して、正規表現を作る必要があります。HTMLソース見て自由気ままに作ってください。

今回は、ゼロックスのDocuPrint用です。プリンタのファームウェアのバージョンによって動かない可能性もあるので注意。

ソース

Filename: check_counter.pl (UTF-8です)

#!/usr/bin/perl
#DocuPrint Counter Check
use strict;
use warnings;
use LWP::Simple;
use utf8;
use Encode;
my $printers = [
                 { 
                   name => 'DocuPrint No.1',
                   ip   => '192.0.2.110',
                 },
                 { 
                   name => 'DocuPrint No.2',
                   ip   => '192.0.2.111',
                 },
               ];
 
foreach my $printer ( @$printers ) {
  print "$printer->{name}\n";
  print "$printer->{ip  }\n";
  print getDocuPrint_count($printer->{ip})."\n\n";
}
 
exit;
 
 
sub getDocuPrint_count {
  my $ip   = shift;
  my $html = decode( 'cp932', get('http://' . $ip . '/prcnt.htm') );
  my $regexp = q(var info=\['総プリントページ数',([\d,]+?)\]);
  my $count;
  if ( $html =~m|$regexp| ) {
    $count = $1;
  }
  return $count;
}
※実際のIPから例示用IPに書き換えていますので注意

実行結果

今回動作確認はWindowsのActivePerl 5.8.8で行っています。

D:\>check_counter.pl
DocuPrint No.1
192.2.0.110
66570

DocuPrint No.2
192.2.0.111
131128


D:\>
※実際の表示から例示用IPに書き換えていますので注意

ポイント

プリンタから出てくるHTML文書のcharsetがShift_JISだったので、LWP::SimpleモジュールのgetメソッドでHTML文書を読み込んだあと、キャラクターエンコーディングがCP932であると見なし、use utf8;でutf8プラグマを有効にしたPerlにおいて扱いやすい、UTF-8 flagが上がったUTF-8文字列に変換しちゃっています(decodeっていうところ)。

UTF-8 flagが上がったUTF-8文字列になってしまえば、正規表現で日本語文字列による検索がしやすくなりますので、キャラクターエンコーディングは意識した方がいいというお話でした。

補足

このプログラムをメールで送信するように改造して、cron(UNIXライクなOS)やらAT(Windows)やらで定時実行させるようにすると、いちいちプリンタまで行ってメーター確認しなくてすみますね!

但し、プリンタの電源が付いていないとダメ。これを実行するために常にプリンタの電源を付けておくなどという本末転倒なことは、くれぐれもなさらぬよう。

perl/Encode - 7bit-jis != iso-2022-jp

| 1 Comment | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Original: http://blog.livedoor.jp/dankogai/archives/51061651.html


ちょちょまwww


http://perl-mongers.org/2008/06/is2022jp.html

WIDE系 IRCで使われている、IS2022JPの(いわゆる)半角カナにはいろいろ種類がありますが、これをPerlでコード変換しようとした場合、 Encodeモジュールではコード変換に失敗してしまいます。(2008年6月6日0:20追記:Encodeモジュールでは半角カナの認識に失敗するだけで他のコードは自動認識します)

それは、文字コードそのものが微妙ながら決定的に違うのです。


http://search.cpan.org/perldoc?Encode::JP

  7bit-jis    /\bjis$/i         7bit JIS
  iso-2022-jp                   ISO-2022-JP                  [RFC1468]
                = 7bit JIS with all Halfwidth Kana 
                  converted to Fullwidth

半角カナをそのまま使いたかったら、7bit-jisをiso-2022-jpの代わりに指定すればよいだけです。ちなみにjisは、7bit-jisの別名として扱われます。


Dan the Encode Maintainer

IS2022JPの半角カナ対応

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

WIDE系 IRCで使われている、IS2022JPの(いわゆる)半角カナにはいろいろ種類がありますが、これをPerlでコード変換しようとした場合、Encodeモジュールではコード変換に失敗してしまいます。(2008年6月6日0:20追記:Encodeモジュールでは半角カナの認識に失敗するだけで他のコードは自動認識します)(2008年6月7日0:50訂正)

筆者の間違いでした。Encodeモジュールメンテナンス各位の方へお詫びいたします。

この場合、skfを使うと解決できます。(2008年6月7日0:50訂正)

Encodeモジュールでも変換できますが、今回はskfを使った方法を紹介します。

SourceForge.JP: Project Info - skf - simple kanji filter

skfにはPerlエクステンションがあり、エクステンションを使うと解決できます。

Perlエクステンションの導入方法

上記のURLからソースコードをDLして、

>configure
>make perlext

これで、Skf.pmとSkf.soファイルができますので、

>sh ./scripts/find-perl

の結果を元にSkf.pmとSkf.soを移動します。


私の環境では、結果が /usr/local/lib/perl5/5.8.8/mach/CORE でしたので、

/usr/local/lib/perl5/5.8.8 に Skf.pm を

/usr/local/lib/ に Skf.so をおきました。

動作確認用コード

#!/usr/local/bin/perl
use Skf;
my $test = Skf::convert("--ic=CP932 --oc=UTF-8", "\x82\xA0");
print $test . "\n";

"--oc"が出力コードですので、任意のコードに変えて「あ」が出ることを確認してください。

パラメータの詳細は、ヘルプを見ましょう。


注)この記事はhttp://d.hatena.ne.jp/UnderDone/20061031/p1より筆者が転載

WWW::Mechanize::AutoPagerにWebService::Wedataから得たSITEINFOを渡しtwitterのfriends一覧を作る

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

タイトルが長いのは釣りですね。わかります。


Firefox/greasemonkeyにはAutoPagerizeというユーザスクリプトがあり、決まったフォーマットに従いページ送り出来る機能があります。Perlにおいてもmiyagawaさん作のWWW::Mechanize::AutoPagerというモジュールがあり、WWW::Mechanizeでページ送りしやすくなっています。

ただし最近AutoPagerizeのSITEINFOはwedataに移行しており、以前までのURLであるhttp://swdyh.infogami.com/autopagerizeを使っているWWW::Mechanize::AutoPagerのSITEINFOは若干古くなっているかと思います。


さて今日はwedataのデータを扱えるCPANモジュール、WebService::Wedataを使い、最新のSITEINFOを使ってWWW::Mechanize::AutoPagerを動かすサンプルをご紹介します。


SYNOPSIS通り

my $wedata = WebService::Wedata->new;
my $database = $wedata->get_database('AutoPagerize');
my @items = $database->get_items();

でデータベースの取得およびアイテム一覧が取得出来ます。

今日はtwitterのfriendsページをスクレイピングするのでurlにマッチしたSITEINFOを検索しなければなりません。

ここで少し引っかかったのがwedataにはhAtomのデータがあり、これがtwitter.comにマッチしてしまうのです。今回すべてのSITEINFOをWWW::Mechanize::AutoPagerに渡さなかったのはhAtomの方が先にヒットしてしまい、正しくAutoPager出来なくなるからです。この辺はまた今度考えたいと思います。さらに本当ならばページコンテンツをpageElementやnextLinkで確認しながら正しいSITEINFOを見付けるべきなのですが、今日は横着して

my $u = 'http://twitter.com/friends/';
my $wedata = WebService::Wedata->new;
my $database = $wedata->get_database('AutoPagerize');
# workarrond for hAtom
my @info = grep $u =~ $_->{data}->{url} && $_->{name} ne 'hAtom',
    @{$database->get_items()};
my $twitter_info = $info[@info-1];

とさせて頂きます。この$twitter_infoをWWW::Mechanize::AutoPagerに渡してあげるのです。

my $mech = WWW::Mechanize->new(timeout => 10);
$mech->autopager->add_site( %{$twitter_info->{data}} );

この$mechを使い、そのままログインします。

$mech->add_header('Accept-Encoding', 'identity');
$mech->get('http://twitter.com/login');
$mech->submit_form(
    form_number => 2,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

そしてfriendsページをWeb::Scraperでスクレイピングします。

my $twitter_friends = scraper {
    process '//a[@rel="contact"]/img', 'names[]' => '@alt',
};

my @friends = ();
while($u) {
    warn "scraping: $u\n";
    my $res = $twitter_friends->scrape( $mech->get($u)->content );
    eval { push @friends, @{$res->{names}}; };
    last if ( $@ or !defined($mech->next_link) );
    $u = $mech->next_link;
}
warn Dump @friends;

これで全てのtwitter friends一覧が取得出来ます。全体のソースは以下の様になります。

#!/usr/bin/perl

use strict;
use warnings;
use WWW::Mechanize;
use WWW::Mechanize::AutoPager;
use WebService::Wedata;
use Web::Scraper;
use YAML::Syck;

my $username = 'username';
my $password = 'password';

#-- get site info from wedata.com
my $u = 'http://twitter.com/friends/';
my $wedata = WebService::Wedata->new;
my $database = $wedata->get_database('AutoPagerize');
# workarrond for hAtom
my @info = grep $u =~ $_->{data}->{url} && $_->{name} ne 'hAtom',
    @{$database->get_items()};
my $twitter_info = $info[@info-1];

#-- login to twitter
my $mech = WWW::Mechanize->new(timeout => 10);
$mech->autopager->add_site( %{$twitter_info->{data}} );
$mech->add_header('Accept-Encoding', 'identity');
$mech->get('http://twitter.com/login');
$mech->submit_form(
    form_number => 2,
    fields    => {
        username_or_email  => $username,
        password           => $password,
    },
    button    => 'commit',
);

#-- scrape twitter friends page
my $twitter_friends = scraper {
    process '//a[@rel="contact"]/img', 'names[]' => '@alt',
};

my @friends = ();
while($u) {
    warn "scraping: $u\n";
    my $res = $twitter_friends->scrape( $mech->get($u)->content );
    eval { push @friends, @{$res->{names}}; };
    last if ( $@ or !defined($mech->next_link) );
    $u = $mech->next_link;
}
warn Dump @friends;

リッチなモジュールの使い方になりましたが、wedataを使っている為ページのレイアウトが変更になっても対応出来るソースになったかと思います。(ただしfriendsのscraperは変更に対応する必要があります。)

後はこのfriends一覧に対してNet::Twitterなんかで「moooooooooose!」とでも発言するのもアリですね。私はしませんが。


スクレイピング楽しいですね。

皆さんも面白いスクレイピング試してみて下さい。


追記


よくみたらnicknameをスクレイピングしてました。Net::Twitter等で使う場合は

my $twitter_friends = scraper {
    process '//strong/a[@rel="contact"]', 'names[]' => 'TEXT',
};

で得られるスクリーン名ですね。

AV女優ブログ検索サービスのソースを公開します。

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちは。


AV女優ブログ検索というサービスを、Catalystを使用して作成いたしました。あまり参考にはならないかもしれませんが、ソースコードを公開いたしましたので、何かの役に立てば幸いです。

 svn co http://svn.sexnotes.org/public/trunk

ポイント

他インターフェイスとの連携を意識しているので、Catalyst依存を取り外せる作りにしています。 共有するLogic部分は、 Catalyst-Model-DynamicAdaptor]を使用し、他のインターフェイス(CLI,JobQueue,Mobile[未実装],etc... ) などから使用できるようになってます。

TODO

  • DBのコネクション保持実装周りが無理矢理すぎる
  • Testの欠落

最後に

何か、ご指導、ご質問等がございましたら掲示板にてご連絡ください。


では、良いperlライフを。

perlでGUI

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

一般的にperlはCGI、テキスト処理、スクレイピング等に利用されると思われがちですが、GUIだって書けるんです。

GUIを扱えるモジュールも数あるのですが、今日はGTK(GTK2)を使うサンプルをご紹介したいと思います。

GTK2-Perl - GNOME Live!
gtk2-perl Home

にドキュメントやチュートリアルがあります。cpanからは"Gtk2"をインストールします。

GUIの作り方も他の言語と変りません。以下簡単な例

use strict;
use Gtk2 qw/-init/;

my $window = Gtk2::Window->new('toplevel');
$window->signal_connect('destroy' => *Gtk2::main_quit);

my $vbox = Gtk2::VBox->new(0, 10);
$window->add($vbox);

my $label = Gtk2::Label->new('Hello, Perl!');
$vbox->add($label);

my $button = Gtk2::Button->new('close');
$button->signal_connect('clicked' => *Gtk2::main_quit);

$vbox->add($button);
$window->set_border_width(5);
$window->show_all();

Gtk2->main();

実行すると以下の様な画面が現れます。


http://perl-mongers.org/2008/06/03/perl-and-gtk/perl-gtk.png


手続きとしてはC言語やpython(PyGTK)と大して変りませんね。単順にウィンドウ作ってコンテナ作ってラベルとボタンを貼り付けているだけです。まぁOOぽく書くなら"->new"よりも"new Gtk::Window()"なんでしょうね。

use strict;
use Gtk2 qw/-init/;

my $window = new Gtk2::Window('toplevel');
$window->signal_connect('destroy' => *Gtk2::main_quit);

... snip ...

またGUIビルダであるgladeを使った場合でも簡単。"Gtk2::GladeXML"をインストールし


http://perl-mongers.org/2008/06/03/perl-and-gtk/perl-glade-thumb-400x307.png


この様な画面をgladeで作成、"project1.glade"というファイル名で保存し、以下の様な少量のコードでGUIが作成出来ます。

use strict;
use Gtk2 qw/-init/;
use Gtk2::GladeXML;

my $gladexml = Gtk2::GladeXML->new('project1.glade');
$gladexml->get_widget('button1')->signal_connect('clicked' => *Gtk2::main_quit);

Gtk2->main;

楽ちんですね。

通常、イベントハンドラはsignal_connectで行い、キーボードイベントやタイマも扱えます。シグナル名称は上記サイトからリファレンスやチュートリアルで参照して下さい。


LWPやWWW::Mechanize等といったネットワークを扱えるモジュールと組み合わせれば色んなツールも簡単に出来上がります。(手前味噌ですが「Web::ScraperなんかでXPathやCSSセレクタを決める時に便利かもしれないツール」なんてのも簡単に作れますよ)


ちなみにXMPPを使ったTwitterクライアント、TwitimもGtk2-perlを使っている様ですね。

動きのあるプログラムも楽しいですよね。


皆さんもperlでGUI、作ってみられてはどうでしょうか。

Hashを使ってユニークにしよう!

| 0 Comments | 1 TrackBack | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

皆さんHash使ってますか!


Hashを使うと、たくさんの重複したデータから、ユニークなデータだけを取り出すことが出来ます。

use strict;
use warnings;
use Data::Dumper;

my @cars = qw/honda honda suzuki toyota toyota daihatu mitubishia/;

my %report = ();
for my $car ( @cars ) {
        $report{ $car }++;
}

print Dumper \%report;

結果

$VAR1 = {
          'mitubishia' => 1,
          'toyota' => 2,
          'suzuki' => 1,
          'daihatu' => 1,
          'honda' => 2
        };

車を作ってるメーカが重複して入っている配列から、重複を取り除いたメーカを手に入れることができましたね!

実践例 : アクセスログから、ipの一覧と件数を取得する

武君のサーバは誰にも教えてないはずなのに、Webにアクセスがたまにきてることがわかりました。どのipから来てるのかしりたいので、調べることにしました。


#!/usr/bin/perl

use strict;
use warnings;
use IO::File;
use Data::Dumper;

my @files = @ARGV;
my %ips = ();
for my $file ( @files ) {
        my $fh = new IO::File;
        $fh->open( $file );
        while( <$fh> ) {
                my ( $ip ) = split(/\s/);
                $ips{$ip}++;
        }
        $fh->close;
}

print Dumper \%ips;

access_log , access_log.1 access_log.2 のような複数のファイルをチェックします。

 ./ips.pl access_log*

やったね!武君!

固定電話の市外局番一覧ページを csv にするお役立ち hack!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちは!

今日のお題は、「総務省の固定電話の市外局番一覧から csv を作る」です。

(以下の各コードでは、前提として $html に、utf-8 なデータが入っているものとします。)

正規表現を使ってスクレイプ

まずは、正規表現でやってみます。

とにかく書くのが面倒なので、td タグの中身を取り出して、そこからテキストを抽出する方針でいきます。

HTML エンティティのデコード(HTML::Entities の decode_entities 等でデコードすることになりますが、今回は &nbsp; しか出てこないのでベタに置換しました)や、いらないタグの除去やら、いろいろ自分でやることになります。

タグの配置が少し変わっただけで、正規表現の書き直しになることが多く、メンテナンス時にうんざりするかと思います。

    my ($data) = $html =~ m!<table[^>]+border=1[^>]+>(.*?)</table>!s;

    my $scraped;
    while ($data =~ m!<tr[^>]+>\s*<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)</tr>!sgi) {
        my $code;
        @{ $code }{qw/areacode areas telcode telcodetype/} = ($1, $2, $3, $4);
        for (values(%$code)) {
            s/&nbsp;/ /sg;
            s/<[^>]+>//sg;
            s/\s+//sg;
        }
        push @{ $scraped->{codes} }, $code;
    }

Web::Scraper を使ってスクレイプ

次に、miyagawa さんの神モジュール Web::Scraper を使った方法です。

たったこれだけのコードを書くだけで、ほんとサクっとデータを取れちゃいます。

正規表現を書くのと比べて、とてもシンプルにできますね!

    my $s = scraper {
        process '//table[@border="1"]//tr', 'codes[]' => scraper {
            process '//td[1]', areacode    => 'TEXT';
            process '//td[2]', areas       => 'TEXT';
            process '//td[3]', telcode     => 'TEXT';
            process '//td[4]', telcodetype => 'TEXT';
        };
    };

    my $scraped = $s->scrape($html);

    for my $code (@{ $scraped->{codes} }) {
        for (values(%$code)) {
            s/\s+//sg;
        }
    }

Web::Scraper を爆速に!

CPAN にある Web::Scraper だと PurePerl なモジュールを使っているため、どうしてもちょっとだけ重くなります。

そういうときのために、前にこそっとつくった (僕がいつも使ってる) XML::LibXML を使えるようにした Web::Scraper を使いたいと思います。

スクリプトから use lib 等して libxml ブランチ を読み込むようにして、

Web::Scraper->use_libxml(1);

とクラスメソッドを呼ぶだけで、libxml2 の parser が使われるようになります。

libxml2 ブランチについて詳しくは Web::Scraper を XML::LibXML で爆速にする hack! - woremacxの日記 をごらんください。

ベンチしてみる

あとあとのメンテナンスでうんざりすることになる正規表現が最速なのはさておき、C で書かれたライブラリである libxml2 の威力はすごいですね!ほれぼれしちゃいますね!

$ perl bench.pl
0 Default    : 47 wallclock secs (45.86 usr +  0.02 sys = 45.88 CPU) @  0.22/s (n=10)
1 XML::LibXML:  4 wallclock secs ( 4.14 usr +  0.01 sys =  4.15 CPU) @  2.41/s (n=10)
2 regexp     :  1 wallclock secs ( 1.01 usr +  0.01 sys =  1.02 CPU) @  9.80/s (n=10)

よいスクレイピングライフを!

ベンチに使ったコード

#!/usr/bin/perl

use strict;

use lib qw(libxml/lib);

use FileHandle;
use LWP::UserAgent;
use Web::Scraper;
use Benchmark;

my $html;

sub write_csv {
    my ($name, $data) = @_;

    open my $csv, ">:encoding(utf8)", $name or die $!;
    for my $code (@$data) {
        printf $csv qq{"%s","%s","%s","%s"\n}, @{$code}{qw/areacode areas telcode telcodetype/};
    }
    close $csv;
}

sub run_scraper {
    my $use_libxml = shift;
    Web::Scraper->use_libxml($use_libxml);

    my $s = scraper {
        process '//table[@border="1"]//tr', 'codes[]' => scraper {
            process '//td[1]', areacode    => 'TEXT';
            process '//td[2]', areas       => 'TEXT';
            process '//td[3]', telcode     => 'TEXT';
            process '//td[4]', telcodetype => 'TEXT';
        };
    };

    my $scraped = $s->scrape($html);

    for my $code (@{ $scraped->{codes} }) {
        for (values(%$code)) {
            s/\s+//sg;
        }
    }

    write_csv($use_libxml ? "libxml.csv" : "default.csv", $scraped->{codes});
}

sub run_regexp {
    # 37.45/s
    my ($data) = $html =~ m!<table[^>]+border=1[^>]+>(.*?)</table>!s;

    my $scraped;
    while ($data =~ m!<tr[^>]+>\s*<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)</tr>!sgi) {
        my $code;
        @{ $code }{qw/areacode areas telcode telcodetype/} = ($1, $2, $3, $4);
        for (values(%$code)) {
            s/&nbsp;/ /sg;
            s/<[^>]+>//sg;
            s/\s+//sg;
        }
        push @{ $scraped->{codes} }, $code;
    }

    write_csv("regexp.csv", $scraped->{codes});
}

sub main {
    my $file = "shigai_list.html";
    my $url = "http://www.soumu.go.jp/joho_tsusin/top/tel_number/shigai_list.html";

    unless (-e $file) {
        my $ua = LWP::UserAgent->new;
        $ua->get($url, ':content_file' => $file);
    }

    open my $fh, "<:encoding(cp932)", $file or die $!;
    $html = do { local $/; <$fh>; };
    close $fh;

    timethese(10, {
        '0 Default    ' => sub { run_scraper(0); },
        '1 XML::LibXML' => sub { run_scraper(1); },
        '2 regexp     ' => sub { run_regexp; },
    });
}

main;

perlでmicroformats

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

昨今、microformatsが熱いですね。

firefox/greasemonkey等クライアントサイドで盛り上がってますが、もちろんperlにもmicroformatsを扱えるモジュールがあります。

Text::Microformat : A Microformat parser 

テキストをパースしてmicroformatsを抽出します。現在サポートしているのは

  • hCal
  • hCard
  • hGrant(*)
  • rel-license
  • rel-tag

です。扱い方は

use strict;
use warnings;
use Text::Microformat;
use LWP::Simple;
use YAML::Syck;

my $doc = Text::Microformat->new( get('http://mattn.kaoriya.net/'));
my @formats = $doc->find;
foreach my $uf (@formats) {
	warn ref($uf)."\n";
	warn Dump $uf->ToHash;
}

といった感じですね。さて実行してみます。

Text::Microformat::Element::hCard
--- 
adr: 
  - 
    country-name: 
      - Japan
    locality: 
      - Osaka
email: 
  - mattn dot jp at gmail dot com
nickname: 
  - mattn
photo: 
  - http://mattn.kaoriya.net/images/logo.png
Text::Microformat::Element::rel_license
--- http://creativecommons.org/licenses/by-nc-sa/2.1/jp/

hCardとrel-licenseが出力されています。でも変ですね。確かにサイトにはrel-tagがあるのですが動いていないようです。調べたところ、rel属性には複数のリンクタイプが指定出来るのですが、これに対応出来ていないようです。

さらにソースを調べた所rel_tag.pmはText::Microformat::Elementを継承しており、Findプロシージャをオーバーライドして検索ロジックを書いてやればいい事が分かります。

sub Find {
    my $self = shift;
    my $tree = shift;

    my @rel = $tree->look_down(
        '_tag' => 'a',
        sub {
            my $rel = shift->attr('rel');
            return 1 if defined $rel and $rel =~ /\btag\b/;
        });
    return map(__PACKAGE__->new($_), @rel);
}

こんなコードをrel_tag.pmに付け足して再度実行します。

Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/coderepos
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/feed
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/pipes
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/yahoo
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/growl
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/perl
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/python
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/gae
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/google%20app%20engine
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/python
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/hatenaStar.js
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/vimperator
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/ajax
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/google
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/jquery
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/microformats
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/rel%2Dbookmark
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/web
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/google%20app%20engine
Text::Microformat::Element::rel_tag
--- http://mattn.kaoriya.net/tags/tips
Text::Microformat::Element::hCard
--- 
adr: 
  - 
    country-name: 
      - Japan
    locality: 
      - Osaka
email: 
  - mattn dot jp at gmail dot com
nickname: 
  - mattn
photo: 
  - http://mattn.kaoriya.net/images/logo.png
Text::Microformat::Element::rel_license
--- http://creativecommons.org/licenses/by-nc-sa/2.1/jp/

期待通りの結果が返ってきました。

Text::Microformatにはスキーマを構造で指定する物と、Findで見付ける物があり自由度は結構高いかと思います。さらにパーサとしてもHTML、XML、RSSをサポートしているので、大概のmicroformatsはパース出来るかと思います。

Elementもモジュール形式で定義出来るので新たなmicroformats draftにも簡単に対応出来る様ですね。

なお、Text::Microformatの開発は、以下のサイトで行われているようです。

ufperl - a Microformat parser for Perl

今のところhAtomやxFolkは無いようなので、どなたか作ってみてはどうでしょうか?

perlクイズ - Scalar::Lazyの実装

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

See also: http://blog.livedoor.jp/dankogai/archives/51059559.html


以下は、Scalar::Lazy 0.01 の実装です(0.03は機能を加えたのでこれより少し長い)。

package Scalar::Lazy;
use warnings;
use strict;
our $VERSION = sprintf "%d.%02d", q$Revision: 0.1 $ =~ /(\d+)/g;
use base 'Exporter';
our @EXPORT = qw/ delay lazy /;

sub new($&) { bless $_[1], $_[0] }
sub lazy(&) { __PACKAGE__->new(@_) }
*delay = \&lazy;

sub force($){
    my $pkg = ref $_[0];
    bless $_[0], $pkg . '::FORCE';
    my $val = $_[0]->();
    bless $_[0], $pkg;
    $val;
}

use overload (
    fallback => 1,
    map { $_ => \&force } qw( bool "" 0+ ${} @{} %{} &{} *{} )
);

1;    # End of Scalar::Lazy

以下、問題です。

  • overload は一体何のためにあるのでしょう?
  • force()は一体何をやっているのでしょう?
  • なぜわざわざbless()しなおしているのでしょう?

Dan the Lazy Perl Monger

Perl Best Practiceの使い方

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Perl Best Practiceという本をご存知でしょうか?


こういう風な書き方をしたらいいよ〜 というのを教えてくれる本です。

どういう風に使うかを説明する前に、下の内容を読んでみてください。

昔あるperlの会社に入社した時の話です。

  • 僕   : 一つのファイルに7000行あるグローバル関数群使うの辞めましょうよ。せめてファイル分けましょうよ。
  • 先輩達 : 複数あるとめんどいじゃん。
  • 僕   : モジュール名に名前をつけましょうよ!
  • 先輩達 : パッケージ名タイプするの面倒だからいやだ。
  • 僕   : POD書きましょうよ
  • 先輩達 : コメントはソースが重くなるから、全部昔に消した
  • 僕   : CPANモジュール使いましょうよ。
  • 先輩達 : 誰が作ったのかわからないものなんて使えるか。
  • 僕   :( DBI... Jcode.. 誰が作ってると思ってんだ... 使ってるじゃん... )
  • 僕   : mod_perl, fastcgi 使ってみましょうよ
  • 先輩達 : あれは問題があって、動かなくなるからだめだ。

などなど会社内的な権限で、グダグダなコードを強制されます。

使い方!

で、こんな時、「Perl Best Practice」の本を使うのです!以下が例。

  • 僕 : Perl Best Practiceの、#25にこう書いてますよ。スゴイ人ですし、多分これに沿った方がいいですよね。
  • 先輩達 : あ、う、うん。そうだね。

ね!説得しやすいでしょ!

おさらい

井の中の蛙というか、社内の中でおかしなコードが神様のようにあがめられている場合、それは、宗教的なもので、なかなかやり方を変更するのは難しいです。


そういった時に、こういった誰もが認めやすい内容の本を武器にし、少しずつ改善提案していけばうまくいきやすいと思います。

  • perl-mongers.org でも、このやり方は駄目だって書かれてますよ!

というのも、新しくて良いかもしれないですね!そこのあなた、気楽に何か書いてみればどうでしょうか。

その他情報

ちょっとした雑用をPerlにやらせてみよう!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちは!


みなさん、Perlをどんなことに使ってますか?私は主に業務でWebアプリケーションを製作する事に使っていますが、実はCGIやWebアプリ製作以外にも結構Perlって使えちゃったりするんですよ!


ってことで、ちょっとした一括処理に使うのに便利なPerlのネタを紹介します。


Perlの中には見た目は似てても違う意味になる演算子っていくつかあるんですが、その中でも(ちょっと古くさいですが)ファイル名グロブ演算子ってのを紹介します。


まず、今まで少しでもPerl触ったことがある方なら大体見たことあるようなコードを書いてみます。

open LOG, "log.txt";

while(<LOG>){
    print $_;
}

良く見かけますよね。log.txtをopenで開いてただ表示するだけ。それcatで・・・的な奴ですね。

最近このファイルハンドルって奴を見なくなりましたね。古くさいからでしょうか。

ベアワード(bareward: $@%&とかが頭に付かない文字列)をファイルハンドルにするとグローバルな名前空間になるので非推奨らしいです。(see also: d:id:tokuhirom:20080601:1212315386) tokuhiromさんthanks!!

my $log_fh;
open $log_fh, "log.txt";

while(<$log_fh>){
    print $_;
}

こうやってファイルハンドルを変数に代入するのも同じです。この2つのスクリプトのwhile文で使ってる 『< >』は、ファイルの内容から1行(特殊変数$/とかいじってない場合。詳しくはまたの機会に!)読み込む演算子です。『行入力演算子』っていいます。


これはこれで良く使いますよね。while(<>)とかやった場合は標準入力から読み込んだり。便利。


さて、次はファイル名グロブ演算子の例。

while(<*>){
    print $_ . "\n";
}

こんな感じに使います。似てますよね。これ、結果はスクリプトを実行した時のカレントディレクトリのファイルの一覧がずらずらっと出力されます。opendirとかreaddirとかと似たような感じ。で、このファイル名グロブ演算子ってのは結構無茶なリクエストにも答えてくれます。


例えば、iTunesの自分のライブラリ。私は基本的にすべてCDはAACで取り込んでるんですが、初音ミクなんかの音源(初音ミク好きです!)はmp3で配布されてる事が多いのでそのまま入れてます。iTunesは、アーティスト名/アルバム名/曲ってフォルダ構造になっていますので、複数のアーティストにまたがってファイルの一覧が欲しいとき、面倒ですよね!

while(<./"iTunes Music"/*/*/*.mp3>){
    print $_ . "\n";
}
./iTunes Music/cosMo@暴走P feat.初音ミク/不明なアルバム/初音ミクの消失 -DEAD END-.mp3
./iTunes Music/cosMo feat 初音ミク/不明なアルバム/ウタ箱-Vocaloids BOX-.mp3
./iTunes Music/cosMo feat.初音ミク/不明なアルバム/初音ミクの終焉 -Worst END-.mp3
(略)

こうやればワイルドカードっぽく取得できちゃいます。findとかxargs使えって言われたらもっともですが、これはこれで知ってると便利ですよ。


もちろん、ワイルドカードっぽい挙動なのでこんなことも出来ます。

while(<./"iTunes Music"/*/FINAL*/*>){
    print $_ . "\n";
}
./iTunes Music/植松伸夫/FINAL FANTASY 1987-1994/01 SCENE III.m4a
./iTunes Music/植松伸夫/FINAL FANTASY 1987-1994/02 Roaming Sheep.m4a
./iTunes Music/植松伸夫/FINAL FANTASY 1987-1994/03 Theme of Love.m4a
(略)

CPANからMusic::Tag::MP3などを入れて、iTunesで管理始める前の気合でフォルダで整理してた時代の曲のImport前に一括でタグ付けとかするときにも結構便利でした。知っておくとちょっと便利かもしれません。


あ、ちなみにこれ、『<*>』じゃなく『glob("*")』とか書けたりしますが、私は『<*>』のが好きです。なんとなく。


PerlはWebアプリ作成に使っても強力な言語ですが、Perlの強力な文字コード取扱いを生かして、MacからもらったZIPファイルが文字化けしちゃった!とか、古いEUCJPなFilesystemだったサーバーのOSを新しくしたから一括でutf8なファイル名に変換したい!とか、そんな自動でやれそうな簡単な仕事なんだけどいちいちツール探すの面倒くさいよ!っとか、convmvで出来るのは知ってるけど入れるのめんどくさい!って場合に使ったりしても便利です。


なんせUNIX系のOSだとまず間違いなく入ってるし、高性能なEncodeモジュールも標準ですしね。


ちょっとした雑用をスマートにこなすために、Perlを始めてみるってのもいいかもしれませんよ!

ドキュメントを書こう!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Perl はコメント以外にもドキュメントをソースに直接書くことが出来ます。
ソースとドキュメントが一体となっていて、コードを修正したらドキュメントもすぐ変更しやすく、よくあるコードとドキュメントの乖離が防ぎやすくなっているのが利点です。 (まぁそれでも、面倒なものは面倒なので、ドキュメントが嘘になってることもままあるけどね)


例えばこんな感じ。

=head1 NAME

モジュールの名前 - このモジュールの簡単な説明だよ

=cut

こうやって、=head1 とか書くと =cut までをドキュメントとして扱われるので、プレーンなテキストを書くことが出来ます。
Perl では、行頭に = と書くとドキュメントのコマンドとして認識されるんだね。


コメントで複数行書くときはこんな感じだよね。

# コメントで
# 複数行
# 書いてみた

POD だと、複数行書くのもいちいち行頭に # 書かなくて済むよ

=head1 複数行書きたい

ここは何行でも
かけるよ!

=cut

もうお気づきの人はいるかもしれませんが、POD には =head1 みたいなコマンドがいくつか用意されていて、それぞれにちゃんと意味があるよ。
さっきから書き続けてる =head1 は見出し的な意味なんだ。HTML で言うところの h1 とかと同じ。
スペースで区切って内容を書くと、見出しのテキストとしてとらえられるよ。

=head1 見出しレベル1

HTML に例えるとこんな感じ。

<h1>見出しレベル1</h1>

その他にもリストとか文字を装飾したりするコマンドがあるよ。
興味のある人はこっちが詳しいので参照してね。

そうそう、「これ全部コメントで書いてもいいじゃん」と思った人もいるかもしれないんだけど、POD で書いておくと変換ツールで様々な形式のファイルに変換出来るので便利ですよ!

  • pod2html POD を HTML に変換するよ!
    • ほかにも PDF にしたりするのもあったと思う。
    • CPAN は POD を解釈して HTML 表示してるね!
    • CPAN モジュールインストールするときに man 形式へ変換したりもしてるよ!

まめこタイトルからの卒業

| 3 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

4時間の格闘から戻ったPerl初心者のまめこです。えいやっ!

WebService::Simpleを使ってFlickrのタイトルを取得してみました。
12時から始めて今やっと動いた・・・。ぜーはーぜーはー。

コードはこちら。

#!/usr/local/bin/perl

use strict;
use Encode;
use WebService::Simple;
use Perl6::Say;
use Data::Dumper;

my $apikey = $ARGV[0] || "you apikey";
my $user = "userid"; #スクリーンネームじゃない方。73611000\@N00とか。

my $flickr = WebService::Simple->new(
        base_url => "http://api.flickr.com/services/",
        param   => { apikey => $apikey, }
);

my $response = $flickr->get(
        'feeds/photos_public.gne',{id=>$user}
);

my $ref = $response->parse_response;

foreach my $key ( keys %{ $ref->{entry} } ){
        my $item = $ref->{entry}{$key};
        say $item->{title};
}


苦戦の歴史

苦戦したのは全部。

ユーザーの指定が出来ずphotos_publicの新着ばかり表示させてました。正解はこうだったのね(´・ω・`)ハッシュリファレンスというらしいです。

 'feeds/photos_public.gne',{id=>$user}

をずっと

 'feeds/photos_public.gne',id=>$user,

て書いてました。教えてくれたyusukebeさんありがとう!

foreach内のあたりが一番の苦戦どころでtomyheroに教えてもらいました。ありがたやー!

foreach my $key ( keys %{ $ref->{entry} } ){

keysが何かは良く分かってません。これから覚えるよ!$xx->{xxx}の方はなんとなく覚えました!

titleを表示させたかったのにずっとcontentを表示させてて、ダンプして格納されている内容を何度も読んでやっとこ指定先すら違うことが判明。

動いたので泣きました。祝福してくれた方ありがとう!(´;ω;`)

追記 080601 15:55

otsuneさんがコメント&はてブコメントで教えてくれた方法で書いたらもっとスマートになりました!
my $user = "userid"; #まめの
my $user = q{userid}; #直した方

これだと引数に@とか入っててもエスケープしなくて済むんですね!

foreach my $key ( keys %{ $ref->{entry} } ){
        my $item = $ref->{entry}{$key};
        say $item->{title};
}


はkeysじゃなくてveluesを使うと1行短くなりました。

foreach my $item ( values %{ $ref->{entry} } ){
        say $item->{title};
}


valuesはどこから?と思ったのだけれど、ダンプするとvaluesってところに格納されてるから「values」の中のごにょごにょ。という書き方をしているのかなーと予測してみる。

otsuneさんありがとうござまーす!

余談

  • 03:37 (woopsdez) ねむい・・・
  • 03:37 (woopsdez) けど
  • 03:37 (woopsdez) あと少しだから・・・
  • 03:37 (woopsdez) が ば る
  • 03:38 がんばれ~~
  • 03:38 (woopsdez) (´;ω;`)
  • 03:38 (woopsdez) ありがとう!
  • 03:38 タイトルもってくるの?
  • 03:38 (woopsdez) そうなのれす
  • 03:38 タイトル好きだなw
  • 03:38 (woopsdez) www
  • 03:38 (woopsdez) 大好き!
  • 03:38 意味わからねぇw
  • 03:38 wwww
  • 03:38 www
  • 03:38 w

タイトルばかり取得していたらIRCでこんな流れになったのでまめこは今日限りタイトルを卒業します。あおーげーばー♪とーおーとしーぃ。

次はFlickrで検索して写真を表示させるらしいですよ! CGIですって!

でも、それはまた次のお話・・・。

About this Archive

This page is an archive of entries from June 2008 listed from newest to oldest.

May 2008 is the previous archive.

July 2008 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Categories

Pages

Creative Commons License
This blog is licensed under a Creative Commons License.
Powered by Movable Type 4.21-en