いまさらですがPythonを使ってみた
Table of Contents
1 どうして?
- ログ解析
-
何を使うか
-
Java + Groovy
- 直接インストールしなければならない
-
Python
-
OSのパッケージで入っている。Windowsでも動く。バージョンの違いに注意
- CentOS 5 -> 2.4.3
- FreeBSD 7.2-RELEASE-p8 -> 2.5.2
- Cygwin 1.7.7 -> 2.6.5
- Google App Engineで採用
-
OSのパッケージで入っている。Windowsでも動く。バージョンの違いに注意
-
Java + Groovy
- 勉強も兼ねて
2 ゴール
- Apacheのログを読んで
-
ファイル
Hoge.zip
の一週間のダウンロード数を計測して - メールで送信する
3 書いてみる
3.1 Hello World
hello.py
print "Hello World"
実行結果
> python hello.py Hello World
3.2 if文
- ブロックを始める前に":"、次の行はインデント
http://docs.python.org/tutorial/controlflow.html#if-statements
x = int(raw_input("Please enter an integer: ")) if x < 0: x = 0 print 'Negative changed to zero' elif x == 0: print 'Zero' elif x == 1: print 'Single' else: print 'More'
- None や 0 は 偽と判定
>>> if None: ... print "true" ... else: ... print "false" ... false >>> if 0: ... print "true" ... else: ... print "false" ... false
3.3 ファイルの一覧を取る
-
/etc/httpd/logs/hoge_access.log*
-
APIマニュアルをみる
-
glob.glob(pathname)
- 指定したパスネームに一致するファイルのリストを返す
- http://docs.python.org/release/2.6.6/library/glob.html
- importして使う
-
import glob list = glob.glob("/etc/httpd/logs/hoge_access.log*")
3.4 ファイルの判別
-
読み込むファイルは
hoge_access.log
かhoge_access.log.gz
のどちらか -
リストからファイル名を取り出して、その種類によって分岐する
-
ファイル名の識別には
re
モジュールを使う
-
ファイル名の識別には
for i in list: if re.match(".*\.gz", i): ... else:
3.5 ファイルを読み込む
-
通常ファイルを読み込む場合
-
ビルドインの
open()
を使う
-
ビルドインの
-
gzファイルを読み込む場合
-
gzip
モジュールのopen()
を使う
-
-
IOErrorを返す可能性があるため、
try
、except
、else
ブロックを使う - ファイルのクローズも忘れずに
-
"
ret = []
"は空のリストを作成します
ret = [] try: f = open(i) except: print "failed to open ", i else: for line in f: # lineに1行分の文字列が入る m = chooseHoge(analyze(line)) if m: ret.append(m) f.close()
3.6 ログ1行の解析
-
その行が解析対象のファイルか確認する
- 仮にHoge.zipとします
対象行
XXX.XXX.XXX.XXX - - [15/Aug/2010:10:41:55 +0900] "GET /download_step3.jsp?filename=Hoge.zip HTTP/1.1" 200 140180790 "http://hoge.com/download.html" "Mozilla/4.0 (compatible; MSIE 7.0;Windows NT 5.1; GTB6.5; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"
-
日付とファイル名を取り出して、dictに保存する
-
dictはキーと値を格納するためのデータ構造。"
ret = {}
" が dict
-
dictはキーと値を格納するためのデータ構造。"
-
関数として用意し、呼び出す
-
"
def
関数名(引数リスト):"で始まり、次行はインデントする
-
"
-
re.match()
は 完全一致、re.search()
は部分一致-
結果はマッチオブジェクト。
m.group(0)
は全体、m.group(1)
は最初の括弧の内容を返す
-
結果はマッチオブジェクト。
-
time.strptime
は、フォーマットされた日付文字列をtime.struct_time
型にします
def analyze(line): ret = {} m = re.match("([0-9.]+) - - (\[.*\]) (\"[^\"]+\") ([0-9.]+) ([0-9.]+) (\"[^\"]+\") (\"[^\"]+\")", line) if m: ret["ipaddr"] = m.group(1) # [15/Aug/2010:10:41:55 +0900] t = time.strptime(m.group(2), "[%d/%b/%Y:%H:%M:%S +0900]") ret["datetime"] = datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) ret["file"] = m.group(3) mm = re.search("Hoge\.zip", ret["file"]) if mm: ret["file"] = mm.group(0) ret["browser"] = m.group(7) return ret
3.7 日付の扱い
-
datetime.datetime
クラス やdatetime.timedelta
クラス を使います-
datetime
日付と時刻を表します -
timedelta
2つのdate、datetime、timeの差分を表現します- datetimeの場合は日単位
-
from datetime import datetime, timedelta ... today = datetime.today() startday = today - timedelta (today.weekday()) - timedelta(3) lastday = startday + timedelta (7) start = datetime(startday.year, startday.month, startday.day,0,0,0) end = datetime(lastday.year, lastday.month, lastday.day,0,0,0)
- 関数chooseHogeにて、指定した期間内のログであることを確認します
def chooseHoge(m): if not "datetime" in m: return None if m["datetime"] < start: return None if m["datetime"] > end: return None if not re.search("Hoge.zip", m["file"]): return None for robot in robots: if re.search(robot, m["browser"]): return None return m
3.8 メールメッセージの作成
-
string.Template
クラス-
substitute()
は文字列内の"${key}"の部分を引数のdictからkeyに結び付く値を取り出して当てはめます
-
- """…""" は改行を含める事ができます
- u"""…"""はユニコードを意味します
def makeMessage(list, lendict): message = Template(u""" お疲れ様です。 ${start}から${end}までのダウンロード数は${num}です。 IPアドレス総数は${ipnum}です。 ${liststr} IPアドレス毎のダウンロード回数は以下の通りです。 (ダウンロード回数の多い順です。) ${ipliststr} 以上です。 """).substitute(start=str(start), end=str(end), num=str(len(list)),liststr=makeDownloadList(list),ipliststr=makeDownloadListParIP(lendict),ipnum=str(ipNum(lendict))) return message
- メール送信時は本文をエンコードする必要があります
-
メールの情報(本文、To、Fromなど)は
MIMEText
オブジェクトに格納します -
メールの送信は
smtplib
モジュールを使用します
def sendmail(message): enc = "ISO-2022-JP" msg = MIMEText(message.encode(enc),"plain",enc) msg["Subject"] = Header(u"Hoge ダウンロード数 " + str(start.date()) + " - " + str(end.date()), enc) msg["From"] = frm msg["To"] = to smtp = smtplib.SMTP("mail.xxxxxx.xx.jp") smtp.sendmail(frm, [to], msg.as_string()) smtp.quit()
4 感想
-
ライブラリの名称がバージョン毎に微妙に違う
-
2.4
email.MIMEText.MIMEText
-
2.6.5
email.mime.text.MIMEText
-
2.4
-
Unixを考慮したライブラリが多い
- gzファイルからの読込など
- ドキュメントが整備されているので調べやすい
Date: 2010-09-25 土
HTML generated by org-mode 6.33x in emacs 23