【Webプログラミング - enterprise.003】「Webページダウンローダー(ネットワークプログラミング)」他

                                                          2002年07月15日発行
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■Webプログラミング - enterprise.003
                                              http://www.ichikoro.com/webp/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ───────────
   What's WebPrograming
  ───────────
   ▼毎週月曜日に配信しています。
   ▼等幅フォントでご覧いただくとキレイに見えます。
   ▼登録・解除はこちらから可能です。
      < http://www.ichikoro.com/webp/ >

   ▼以下の2種類を毎週交互に配信しています。
    (諸所の事情により変更になる場合もあります(^^;)
      (1)academic版
          入門者向け。比較的丁寧な解説。
          C言語入門、Web基礎知識など

       (2)enterprise版
          上級者向け。「分からんことは自分で調べろ」的な解説。
          データベース入門、ネットワークプログラミングなど。

  ──────────
     Editor's Voice
  ──────────
    こんにちは、編集者の勝部です。

    ビックリするくらい、ここに書くネタが今日は浮かびません(笑)
    いざ書いてみるとシンミリするような話だったので編集後記にパス。
    夜中(AM3:00)だということもあるのでしょうが、まぁ、こういう日も
    あるということですね(^^;

    書きたいネタは色々あるハズなのですが、あまり公衆の面前で言えるよ
    うな事じゃなかったり、まだ今の段階で言っていいものかよく分からん
    物を初め、取り留めのない収集が付かなくなりそうな物ばかり(^^;

    そういうわけで(どういうわけだ(笑))
    今週も行って見ましょう!

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 01. データベース アラカルト
──────────────────────────────────────

※今回はネットワークプログラミングの原稿量(+コード量)が予想以上に
  大きくなった関係でお休みです。次回にご期待くださいませ。


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 02. Tips:「ネットワークプログラミング:Webページダウンローダー」
──────────────────────────────────────

このコーナーは、Perlを使ったネットワークプログラミングについて
色々と解説いたします。根底の部分ではなく特定のモジュールに偏って
いるのは、ワザトです。


前回(enterprise.002)ご紹介した「LWP」。これはLibWww-Perlの略称で、いとも簡単
にWWW上でのやりとりを行うことのできるPerlのモジュールです。今回はこのLWPを使っ
た、もっと面白いプログラムをご紹介したいと思います。

    ■Webページダウンローダー
        毎回チェックするWebページが増えてくると、ブックマークからチクチク
        探して一つずつ見ていくのはちょっと面倒になってきます。そこで今回は
        練習がてら、下のおようなプログラムを組んでみましょう。

        ・目的
            指定されたURLからデータを取得し、ローカルにファイルとして保存する。

        ・処理内容
            (1)ファイルにURLのリストを記述する。
            (2)ファイルからURLのリストを取得し、WWW上からデータを取得する
            (3)取得したデータをローカルのファイルに保存


    ■準備はOK?
        まずは巻末についている「Webページダウンローダー」のソースをテキスト
        エディタなどで適当に保存してください。ファイル名は何でもかまいませ
        んが、ここでは「getpage.pl」と付けることにします。

        その後、取得したいURLのリストを記述したファイルを用意します。
        テキストエディタで「url.dat」などとつけたファイルの中に

            Yahoo <タブ> http://www.yahoo.co.jp/ <改行>
            ISIZE <タブ> http://www.isize.com/ <改行>
            about <タブ> http://allabout.co.jp/ <改行>

        と言った具合に

            適当な名前 <タブ> URL(http://から) <改行>

        という形式で書かれたファイルを用意します。
        Webページダウンローダー君は「HTML」しか取ってきませんので、なるべく
        テキストのみで構成されたページを指定する方が、実感値が大きいでしょう。
        また、いくつでも記述できますが最初は2つくらいにしましょう。

        ここまでは準備OKですか?


    ■実行しよう!
        いよいよ実行してみましょう。
        実行に必要な環境は以下です。

            (1)Perl 5.6以上を推奨 (5.004以上でも動作するが未確認)
            (2)LWP (詳しくは前号enterprise.002を)
                ※Windows版のActivePerlの場合、特に何もしなくても
                  最初から入っているようです。

        したらば、おもむろにCUI環境(WindowsならMS-DOSプロンプト)で、以下の
        ように入力し、エンターキーを押します。

            perl getpage.pl url.dat

        処理が終了すると「inex_getpage.html」というファイルが作成
        されますので、これをWebブラウザで開きます。すると...。


    ■LWP::Simple
        LWPにはいくつかのクラスがあり、その中の一つにLWP::Simpleという
        物があります。今回使用したのもこれです。

        LWPの中でも比較的簡単にWebページの取得やヘッダの確認が出来るた
        め、使い捨てプログラムなど面倒なコトはいいからサクッと使いたい
        場合、また今回のように一番最初に触る際などに便利です。

        では、この LWP::Simple ではどんなことが出来るのでしょうか?
        今回使用したいくつかの関数もあわせて解説しましょう。

            get($url)
                $urlには、"http://www.isize.com/"などのURLを指定し、
                上手く取得できればその内容を返し、失敗すればundefを
                返却します。

                    use LWP::Simple;
                    $buff = get("http://www.isize.com/");

            head($url)
                $urlには、"http://www.isize.com/"などのURLを指定し、
                上手く取得できればヘッダ情報を返却します。下で出て
                くるis_successなどと組み合わせて使うこともできます。

                    use LWP::Simple;

                    #-- ヘッダ情報を取得 --#
                    ($content_type, $document_length,
                        $modified_time, $expires, $server) = head("http://www.isize.com/");

                    #-- スカラーの場合は成功 or 失敗(is_successなどで使用) --#
                    $res = head("http://www.yahoo.co.jp");

            getprint($url)
                $urlで指定したデータを取得し、そのまま標準出力に出力します。
                戻り値には20Xや40XなどのHTTP応答コードが返って来ます。
                (↑is_successなどで判定できます)

            getstore($url, $file)
                getprintと違うところは、標準出力ではなく指定したファイル
                に記録してくれる点です。
                    ※今回のプログラムでは、上部にBASEタグをくっつけた
                      かったので使用しませんでした。

            mirror($url, $file)
                getstorと違うのは、「更新されていれば」ファイルに記録して
                くれる点です。データが更新されているかどうかは、ファイルの
                更新日とファイルサイズを自動的に見比べて判定しています。

                    use LWP::Simple;
                    mirror("http://www.isize.com/", "isize.html");

                何度となく起動し、実際にローカルのファイルの更新日を
                確認してみましょう。

            is_success($rec)
                $recにはHTTP応答コードが入ります(つまりheadやgetprintの
                戻り値のこと)。その応答コードが「成功」であれば真を、失敗
                していれば偽を返します。

                    use LWP::Simple;

                    if( is_success(head("http://www.isize.com/")) ){
                        print "成功!(^-^)";
                    }
                    else{
                        print "失敗...(ーー;)";
                    }

            is_error($rec)
                is_successの逆で、応答コードがエラーかどうか判定します。

    ■問題点
        このWebページダウンローダー君にはいくつかの欠点があります。

            (1)どんな巨大なページであろうと、取得する際に全てメモリに
               突っ込んでしまう。
            (2)現在の進行状況が分からないため、リストが巨大になると
               精神的につらい。
            (3)Last-Modifiedを返してこないサーバの場合、更新されている
               かどうか分からない。

        他、上げ出すと切りがありません(笑) ということもありますので、
        あくまで学習用の物として取り扱ってくださいね。これらの改善策は
        今後の誌面で取り上げられるハズです。ゆっくりとステップアップ
        していきましょう!


    ■応用してみよう
        例えば head と is_success を用いれば、簡単なリンクチェックが
        出来ます。またmirrorを使えば文字通りミラーリングが簡単に実現
        できます。

        他にも応用できる方法は山のようにあるハズです。
        ぜひ活用してみてください(^-^)/


面白いですよね!
ベースはたった数行のプログラムで、いとも簡単にネットワークプログラ
ミングが行えてしまうのです。使い道にもよりますが、個人的にはこれだ
けで十分だという感じもします(笑)

さて次回は「パケット代 削減プロクシCGI(簡易版)」をお届けしたいと思います。
応用すれば大阪弁や名古屋便変換ツールもお手の物ですよ〜。

    参考文献
        ・Perlの小技より
            ・LWP::Simple
              http://member.nifty.ne.jp/hippo2000/perltips/LWP/Simple.html
            ・lwpcook v5.48
              http://member.nifty.ne.jp/hippo2000/perltips/lwpcook.html

        ・perldoc
            ※コマンドラインから実行せよ(笑)



━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 03. インタネット業界の一週間
──────────────────────────────────────

───────
  7月8日(月)
───────
・台湾の研究者グループが35億ページをインデックスしたサーチエンジンを発表
  http://www.watch.impress.co.jp/internet/www/article/2002/0708/openf.htm

───────
  7月9日(火)
───────
・Yahoo!BBが約66万回線に
  http://www.zdnet.co.jp/news/0207/09/njbt_15.html

・仏ユーザーの電子メール利用、HTMLメールがテキストメールを凌駕
  http://www.watch.impress.co.jp/internet/www/article/2002/0709/jdnet.htm

・米eBayがPayPalを15億ドルで買収、買収後はeBayと統合したサービス提供も
  http://www.watch.impress.co.jp/internet/www/article/2002/0709/ebaypal.htm

───────
  7月10日(水)
───────
・フリーソフトウェアに関するNPO団体「FSIJ」が活動を開始
  http://www.watch.impress.co.jp/internet/www/article/2002/0710/fsij.htm

・W3CがWebサービス記述言語「WSDL 1.2」のドラフト公開
  http://www.watch.impress.co.jp/internet/www/article/2002/0710/wdsl.htm

───────
  7月11日(木)
───────
・米Yahoo!の第2四半期決算、増収増益で7四半期ぶりの黒字
  http://www.watch.impress.co.jp/internet/www/article/2002/0711/yahoo.htm

───────
  7月12日(金)
───────
・東京都、都庁など都内10ヶ所にて無線LANの実験を実施
  http://www.watch.impress.co.jp/internet/www/article/2002/0712/musenlan.htm


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                           編    集    後    記
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ドコモの10周年記念系のCMでも使われているMr.Childrenの「Any」が最近お気に
入りです。ずっと気になっていたのですが、先日7月10日にようやくCDが発売&レ
ンタル開始になり、早速借りてきてエンドレスで聞いていました。この原稿を書
いている今も後ろで流れています。もちろん着メロも変更しました(笑)

メロディーもなのですが、今回は詩にグッとひかれました。
特にファンと言うわけではないのですが、ミスチルの中では今回の曲が
一番好きかもです。聞き終わった後に、もうヒトコエって感じはしますが(^^;

    「今 僕のいる場所が 探していたの(望んだもの)と違っても、
     間違いじゃない(悪くはない)、きっと答えはひとつじゃない」

仕事に疲れた時に聞くといいかもしれません(笑)


「一言」といえば、先日読み終わった松永真里さんの「iモード以前」の最後
のフレーズもぐっと来ました。上手いなぁって感じです。内容はほとんど(と
いうかほぼ全て)リクルートのお話なのですが、私も今の会社を卒業する時に
は、同じ一言を残して去りたいと思いました。それはもうちょっと先の話だと
思いますが。

    ・iモード以前(松永真里)
      http://www.amazon.co.jp/exec/obidos/ASIN/4000220098

    ・Any(Mr.Children)
      http://www.amazon.co.jp/exec/obidos/ASIN/B00006954Y
        →公式サイト
          http://www.mrchildren.jp/

ではでは、また来週お会いしましょう (^-^)/~~

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

                   【 Webプログラミング Code Sample 】

                    発  行 : ichikoro.com
                発行責任者 : 勝部 麻季人
                              < katsube@ichikoro.com >
                  発行部数 : 2151部(前回)
                 Webサイト : < http://www.ichikoro.com/webp/ >
            お問い合わせ先 : < mm-webp@ichikoro.com >

                            Powerd by まぐまぐ
    All Right Reserved, CopyRight(C) 2001 Webプログラミング Code Sample
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


#--------------------------------------------------------------------------#
#                   ▼ Webページダウンローダーはコチラ ▼                  #
#--------------------------------------------------------------------------#

use strict;
use LWP::Simple;

;#
;#Webページダウンローダー
;#

#----------------------------------------------------------#
#                        定数定義                          #
#----------------------------------------------------------#
#最終更新日
use constant MODIFIED_TIME  => 2;        #headが返すリストの順番


#----------------------------------------------------------#
#                      メインルーチン                      #
#----------------------------------------------------------#
main:{
    my $url_file = $ARGV[0] || showhelp();    #URLのリストが記録されたファイル
    my $save_dir = $ARGV[1] || ".";           #保存ディレクトリ(指定がない場合はカレント)
    my @url_list;
    my %idx;
    my $count;

    #-----------------------------#
    #        保存先存在確認       #
    #-----------------------------#
    if( not -d $save_dir ){
        error("$save_dir is NotFound or NotDirectory.");
    }

    #-----------------------------#
    #      URLのリストを取得      #
    #-----------------------------#
    open(DAT, $url_file) or die "Can not open $url_file ($!)";
    while(<DAT>){
        chomp;
        push(@url_list, $_);
    }
    close(DAT);

    #-- カレントを保存先ディレクトリに移動 --#
    chdir($save_dir) or die "Can not change directory $!";

    #-----------------------------#
    #        Webページ取得        #
    #-----------------------------#
    $count = 0;
    foreach (@url_list){
        my ($name, $url) = split(/\t/);
        my @head_list;

        #-----------------------------#
        #       ヘッダ情報を取得      #
        #-----------------------------#
        if( @head_list = head($url) ){   #LWP::Simple::head()でhead情報を取得

            #-----------------------------#
            #        Webページを取得      #
            #-----------------------------#
            my $buff = get($url);        #LWP::Simple::get()でWebページを取得

            #-----------------------------#
            #       ファイルに保存        #
            #-----------------------------#
            if( $buff ){
                #-- ファイル名作成(連番) --#
                my $file = sprintf("%d", $count);

                #-- インデックス用 --#
                $idx{$count} = join("\t"
                                        ,$name                         #リンク名
                                        ,$head_list[MODIFIED_TIME]);   #最終更新日

                #-- 保存 --#
                open(DAT, ">$file.html") or die "Can not open $file $!";
                print DAT qq{<BASE href="$url">\n};            #画像とリンク対策
                print DAT $buff;
                close(DAT);

                $count++;
            }
        }
    }

    #-----------------------------#
    #       インデックス作成      #
    #-----------------------------#
    open(DAT, ">index_getpage.html") or die "Can not file index_getpage.html $!";
    foreach ( sort keys %idx ){
        my ( $name, $mod_time ) = split(/\t/, $idx{$_});
        my $local_time = timeFormat($mod_time);

        print DAT qq{■<A href="$_.html">$name</A>};
        print DAT qq{($local_time)} if($mod_time ne "");
        print DAT qq{<BR>\n};
    }
    close(DAT);

}



#----------------------------------------------------------#
#■timeの戻り値を独自フォーマットに整形
#      目的:timeの戻り値をlocaltimeにかけ、その後独自フォ
#            ーマット("YYYY/MM/DD hh:mm:ss")に整形した文字
#            列を返却する。
#      引数:timeの戻り値
#    戻り値:"YYYY/MM/DD hh:mm:ss"
#----------------------------------------------------------#
sub timeFormat
{
    my $time = shift;
    my ($yyyy, $mm, $dd, $hou, $min, $sec) = (localtime(time))[5,4,3,2,1,0];

    $yyyy += 1900;
    $mm   += 1;

    return( sprintf("%d/%02d/%02d %02d:%02d:%02d", $yyyy, $mm, $dd, $hou, $min, $sec) );
}


#----------------------------------------------------------#
#■使い方(HELP)を表示して終了
#      目的:使い方を表示
#      引数:なし
#    戻り値:なし
#----------------------------------------------------------#
sub showhelp
{
    print <<"END_OF_HELP";
-------------------------------
■Webページダウンローダー
-------------------------------
・実行方法
  perl $0 <URLリスト(ファイル)> [<保存先ディレクトリ>]

・ファイルのフォーマット
  適当な名前 <タブ> URL(http://から) <改行>

・使い方:実行したら保存先ディレクトリに自動的に作成される
          inex_getpage.htmlをブラウザで開き、閲覧する。

END_OF_HELP

    exit;
}


#----------------------------------------------------------#
#■エラーを表示して終了
#      目的:エラーをSTDERRに出力後、プログラム終了
#      引数:メッセージ
#    戻り値:なし
#----------------------------------------------------------#
sub error
{
    my $msg = shift;

    print STDERR $msg;
    exit(1);
}


__END__

#--------------------------------------------------------------------------#
#                                 ▲ ココまで ▲                           #
#--------------------------------------------------------------------------#