結果だけでなく過程も見てください

日々の奮闘を綴る日記です。

Perlを使用してGmailにIMAPで接続する(2)

前回の記事より、もう少し実践的なコードを紹介します。

今回は、クレジットカードで支払いをするたびに
メールで送られてくるクレジットカードの利用情報を、
perlで自動的に集計しようというものです。
家計簿をつける際に必要になってので書いてみました。

処理の流れとしては

  • IMAP接続
  • 全角スペースを半角スペースに変換
  • 該当レコード行抜き出し
  • ファイル出力

という流れになっています。
全角スペースのおかげでレコードがうまく抜き出せないので
半角スペースに置換しています。(これがけっこうひっかかったポイントでした...)

OS:Windows XP
Perl:ActivePerl 5.14.2
インストールフォルダ:C:\Perl

受信するメールの文字コードはJISコード(iso-2022-jp)とします。
メールのヘッダを見てあらかじめ確認しておいてください。

またppmを使って必要なパッケージをインストールしておいてください。
前回はMail::IMAPClientを使いましたが、今回はNet::IMAP::Clientを使用します。
ppmについては前回の記事参照。
http://d.hatena.ne.jp/taiyakisun/20120626

ファイルはUTF-8で保存してください。

#!/usr/bin/perl
use lib 'C:\Perl\site\lib';
use strict;
use warnings;
use Encode;
use IO::Socket::SSL;
use Net::IMAP::Client;

# 結果出力ファイル
my $manfile = 'C:\managefile.txt';

my $imap = Net::IMAP::Client->new(
   server   => 'imap.gmail.com',
   user     => 'sample@gmail.com',   # メールアドレス(書き換えてください)
   pass     => 'sample_pass',        # パスワード(書き換えてください)
   ssl      => 1,
   port     => 993,
) or die "Net::IMAP::Client new(): $@";

$imap->login or die('Login failed: ' . $imap->last_error);

# メールが格納されているのは受信ボックスとする
$imap->select('INBOX') or die "Could not select: $@ (Error:$!)\n";

# 特定の送信元だけ対象とする
my $msgs = $imap->search(
    { from => 'cardinfo@xxx.com' },  # 差出人メールアドレス(書き換えてください)
);
@$msgs or die "mail not found.\n";

open(MANFILE, ">$manfile") or die "cannot create $manfile(Error:$!).\n";

# ヘッダの出力
print MANFILE "date,content,amount\n";

foreach (@$msgs)
{
    my $data = $imap->get_rfc822_body( $_ );

    # ヘッダーとメール本文を取り出す
    my ( $header, $body ) = $$data =~ /(.*?)(?:\x0d\x0a){2}(.*)/s;

    # 全角スペースの排除
    $body = Encode::decode('iso-2022-jp', $body);
    my $spc = "\xa1\xa1";
    $spc = Encode::decode('euc-jp', $spc );
    $body =~ s/$spc/ /g;
    $body = Encode::encode('shift-jis', $body );

    # 1行単位で分割してほしい情報を抜き出す
    my @mail_lines = split("\x0d", $body);
    for( my $i=0; $i < int(@mail_lines); ++$i )
    {
        #                        date                        content    amount
        if ( $mail_lines[$i] =~ m#(\d{4}/\d{2}/\d{2})\s{2,}(.+?)\s{3,}(.+?)# )
        {
            print MANFILE $1 . "," . $2 . "," . $3 . "\n";
        }
    }
}

close(MANFILE);
$imap->logout;

あまり説明は必要ないと思いますが、全角スペース→半角スペース部分だけは少し補足します。

全角スペースを排除するにあたり、メール本文を一度UTF-8に変換します。それが以下のコードです。

$body = Encode::decode('iso-2022-jp', $body);

Perl5.8(もっと前から?)では、perl内部で所持している文字列に
UTF-8フラグ付の文字列、つまりUnicode文字列形式を使用しています。

一方、通常のファイル等から読み込まれた文字列はマルチバイト文字列と
呼ばれる形式で管理され、両者を比較する場合はどちらかに合わせる必要があります。
(VisualStudioでC/C++で開発している人は、コンパイルするときの文字セットと
同じと考えればいいかもしれません。マルチバイト文字セットと、Unicode文字セットです)

そこで使用するのがEncode::decodeおよびEncode::encodeです。
Encode::decodeでは、iso-2022-jpeuc-jp等のマルチバイト文字列を
perlの内部文字列であるUTF-8フラグ付の文字列に変換します。

Encode::encodeはその逆でUTF-8フラグ付の文字列をマルチバイト文字列に
変換します。

ここではeuc-jpの全角空白文字コード「0xa10xa1」をEncode::decodeでeuc-jpから
UTF-8フラグ付の文字列に変換し、全角スペース→半角スペース置換を行っています。

my $spc = "\xa1\xa1";
$spc = Encode::decode( 'euc-jp', $spc );
$body =~ s/$spc/ /g;

csvとしてExcelで閲覧するので最終的にはshift-jisのマルチバイト文字列に変換して出力しています。

$body = Encode::encode('shift-jis', $body );

説明のためスクリプトの一部を切り出しているので、
うまく動かないこと等あるかもしれません。その場合は連絡ください。

プライバシーポリシー お問い合わせ