Ruby - UNIX MBOXデータ読み込み!
Updated:
Windows でメールを扱う場合、メールの保存形式は UNIX MBOX 形式にすることがあると思います。 当方も Thunderbird で UNIX MBOX 形式を使用しています。
この UNIX MBOX形式のデータを MySQL に保存してみたくて、まずは Rubyで 読み込んでみようと考えました。
Ruby1.8系であれば “mailread” や “tmail” ライブラリを使用するみたいですが、Ruby1.9系では標準では使用できないみたいです。
そこで “mail” ライブラリはどうかと思い調べてみましたが、メールの送受信系はこれで出来ますが、UNIX MBOX 形式データの解析については README を読んでも記述がなかったため出来ないと判断。
そこで “mailread” のソースを眺めてみたところ、数十行のソースだったし簡単に埋め込めそうだと思い、直接該当のRubyスクリプトに埋め込んでみました。
参考までに以下にRubyスクリプトを掲載します。 ※試験的に作成したものなので、細部で不具合は出るかと思います。なんとなく流れがわかればと・・・
UNIX MBOX データ読み込みRubyスクリプト
処理の流れ
- 存在するメールボックスの一覧を取得。
- 各メールボックス中のMBOXファイルの一覧を取得。
-
各MBOXファイルを読み込む。
- “From “で始まる行を1メールの先頭と判断。
- 最初の空行でメールヘッダの終了を判断。
- 各メールヘッダはハッシュに格納。
- 以降、1メールの最後までをメールボディと判断。
※メールヘッダの属性値は1つずつしか保存できないので、”Received”のように複数あるものは最後の1件分が保存されます。(当方、今のところ”Received”は保存対象にしていないので問題はない) ※multipart や 添付ファイル のことは今のところ考慮していません。全部ボディになります。 ※”From “で始まる行を各メールの先頭みなすため、ボディに”From “で始まる行があると予期しない動作をすると思います。
Rubyスクリプトサンプル
- Thunderbird のメールボックスを想定しています。UNIX MBOX ファイルには拡張子は付与されていないと思います。
- アカウントが3つ、つまりメールボックス(フォルダ)が3つある環境で動作確認。
- メールボックスはグループ分けされていてもかまいません。
- このサンプルではファイル一覧を取得するために “find” というGemsパッケージを使用しています。事前にインストールしてください。
- このサンプルでは取得したデータの内 “Date”、”Subject”、本文を出力しています。
- データの量にもよりますが、処理には時間がかかります。適宜スクリプトを修正してください。
●ファイル名:ana_mbox.rb
# -*- coding: utf-8 -*-
#---------------------------------------
# MBOXデータを読み込み表示する
#---------------------------------------
#
require 'find'
require 'kconv'
class AnaMbox
# メールボックス格納フォルダ
DIR_MBOX = "D:\01_Mail\Thunderbird"
# [CLASS] 解析
class Analyze
# INITIALIZE
def initialize
# 読み込み件数初期化
@cnt = 0
end
# Mailbox名一覧取得
def get_mailbox_list
begin
# 指定ディレクトリ配下のディレクトリ一覧
res = Array.new
Dir::entries( DIR_MBOX ).each do |d|
if File::ftype( DIR_MBOX + "/" + d ) == "directory"
res << d unless ( d.to_s == "." || d.to_s == ".." || d.to_s == "local" )
end
end
return res
rescue => e
# エラーメッセージ
str_msg = "[EXCEPTION][" + self.class.name + ".get_mailbox_list] " + e.to_s
STDERR.puts( str_msg )
exit 1
end
end
# MBOXファイル一覧取得
def get_mbox_list( mailbox )
begin
# 指定ディレクトリ配下のファイル一覧
res = Array.new
Find.find( DIR_MBOX + "/" + mailbox ) do |f|
# ファイルのみ抽出
unless File::ftype( f ) == "directory"
# ファイルを読み込み1行目の先頭がFromならMBOXと判定
open( f ) do |file|
l = file.gets
if l =~ /^From/
res << f
end
end
end
end
return res
rescue => e
# エラーメッセージ
str_msg = "[EXCEPTION][" + self.class.name + ".get_mbox_list] " + e.to_s
STDERR.puts( str_msg )
exit 1
end
end
# MBOXファイル解析
def ana_mbox( mbox )
begin
# 1つのMBOXファイルを開く
open( mbox ) do |f|
f.slice_before do |line|
# "From "を1グループの先頭とみなす。
line.start_with? "From "
end.each do |mail|
flg_body = 0
header = {}
body = []
# HEADER読み込み
mail.each do |line|
# 以下の"kconv"は通常不要ですが
# 何らかの原因でメールヘッダーに非ASCII文字が存在してしまう場合の
# 対処のために使用しています。
line = line.kconv( Kconv::SJIS, Kconv::AUTO )
# 行最後の改行文字を削除
line.chop!
# "From "で始まる行を読み飛ばし
next if /^From / =~ line
# HEADERの終了
if flg_body == 0 && /^$/ =~ line
flg_body = 1
next # BODY部分読み込まないなら next を break に変更
end
# HEADER
unless flg_body == 1
# 属性と値を取得
if /^(\S+?):\s*(.*)/ =~ line
( @attr = $1 ).capitalize!
header[@attr] = $2
elsif @attr
# 複数行にわたる場合は結合
line.sub!( /^\s*/, '' )
if @attr == "Subject"
# Subjectの場合は改行せずに結合
header[@attr] += line
else
header[@attr] += "\n" + line
end
end
# BODY
else
body.push(line) # BODY部分読み込まないなら、この行は削除
end
end
@cnt += 1
puts "Date: #{header['Date']}"
header['Subject'] ||= "" # nilなら""を設定
puts Kconv.tosjis( "Subject: #{header['Subject']}" )
puts "#" * 40
body.each do |row|
puts Kconv.tosjis( row )
end
puts "######## COUNT = #{@cnt}"
end
end
rescue => e
# エラーメッセージ
str_msg = "[EXCEPTION][" + self.class.name + ".ana_mbox] " + e.to_s
STDERR.puts( str_msg )
exit 1
end
end
end
##############
#### MAIN ####
##############
begin
puts "====< START >===="
# 解析クラスインスタンス化
obj_ana = Analyze.new
# Mailbox名一覧取得
mailbox_list = obj_ana.get_mailbox_list
# Mailbox分ループ
mailbox_list.each do |mailbox|
# MBOXファイル一覧取得
mbox_list = obj_ana.get_mbox_list( mailbox )
# MBOXファイル分ループ
mbox_list.each do |mbox|
# MBOXファイル解析
obj_ana.ana_mbox( mbox )
end
end
puts "====< E N D >===="
rescue => e
# エラーメッセージ
str_msg = "[EXCEPTION] " + e.to_s
STDERR.puts( str_msg )
exit 1
end
end
当方は、上記のスクリプトを更に改良してMySQLに保存することを考えています。。 ※ちなみに、当方の3ドメイン・約7年分のMBOXデータで試したところ約6万件(メルマガ・サーバ管理関連が多数)ありました。 特にボディ部分の “multipart” 関連が難しく、どうしようか思案中。
以上。
Comments