いまさらですが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日付と時刻を表します -
timedelta2つの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