Web log sessions (HubSpot) - アクセスログ解析
どんな問題?
Web log sessions
http://www.checkio.org/mission/web-log-sessions/
Webサイトのアクセスログから、セッション(Session)データを作成せよ。
引数のアクセスログは、以下の形式。タイムスタンプのフォーマットは、YYYY-MM-DD-hh-mm-ss となる。
タイムスタンプ;;ユーザー名;;URL
ログファイルを解析して、ユーザー名とURLのセカンドレベルドメインが同じで、かつ30分以内のアクセスは同じセッションとしてまとめる。
セッションデータを以下の形式で作成して、戻り値として返す。
ユーザー名;;サイト(セカンドレベルドメイン);;セッション時間(秒数);;リクエスト数
ユーザー名、サイト、セッション時間、リクエスト数の順でソートすること。
例題:
checkio("""2013-01-01-01-00-00;;Name;;http://checkio.org/task 2013-01-01-01-02-00;;name;;http://checkio.org/task2 2013-01-01-01-31-00;;Name;;https://admin.checkio.org 2013-01-01-03-00-00;;Name;;http://www.checkio.org/profile 2013-01-01-03-00-01;;Name;;http://example.com 2013-02-03-04-00-00;;user2;;http://checkio.org/task 2013-01-01-03-11-00;;Name;;http://checkio.org/task""") == """name;;checkio.org;;661;;2 name;;checkio.org;;1861;;3 name;;example.com;;1;;1 user2;;checkio.org;;1;;1"""
どうやって解く?
めずらしく実用的な問題だなぁ。
まず、URLからセカンドレベルドメインを取得するために、正規表現を使おうと思ったのだが、うまくできなかった。しかたないので、まずURLからhostを取得して、そこからセカンドレベルまでを取得することにした。
host = re.match('.+?://(.+?)(?:[:/].*)?$', url).group(1) # URLからhostを取得 domain = '.'.join(host.split('.')[-2:]) # セカンドレベルドメインを取得
今回は、ログとセッションのクラスを作成した。CheckiOでクラスを使うのは初めてかも。
import re from datetime import datetime, timedelta class LogItem(): def __init__(self, log_line): timestamp, name, url = log_line.split(';;') host = re.match('.+?://(.+?)(?:[:/].*)?$', url).group(1) self.name = name.lower() self.domain = '.'.join(host.split('.')[-2:]) self.time = datetime.strptime(timestamp, '%Y-%m-%d-%H-%M-%S') def __lt__(self, other): get_key = lambda i: (i.name, i.domain) return get_key(self) < get_key(other) class Session(): def __init__(self, item): self.name = item.name self.domain = item.domain self.first = item.time self.last = item.time self.count = 1 def add(self, item): self.last = item.time self.count += 1 def is_same_session(self, item): return (item.name, item.domain) == (self.name, self.domain) \ and item.time - self.last < timedelta(minutes=30) @property def seconds(self): return round((self.last - self.first).total_seconds()) + 1 def __lt__(self, other): get_key = lambda i: (i.name, i.domain, i.seconds, i.count) return get_key(self) < get_key(other) def __str__(self): return '{};;{};;{};;{}'.format(self.name, self.domain, self.seconds, self.count) def checkio(log_text): # LogItemのリストを作成してソート log_items = sorted([LogItem(line) for line in log_text.splitlines()]) # Sessionのリストを作成していく sessions = [] for item in log_items: if sessions and sessions[-1].is_same_session(item): # 最後のSessionと同じセッションなら、更新 sessions[-1].add(item) else: # 初回や異なるセッションなら、新しいSessionを作成 sessions.append(Session(item)) # sessionsをソートして文字列化 return '\n'.join(str(session) for session in sorted(sessions))
http://www.checkio.org/mission/web-log-sessions/publications/natsuki/python-3/first/
LogItem、Sessionクラスともに、sortedに対応するために __lt__ を定義している。
いま見直すと、アクセスログの並びが時間順でなくてもいいように、LogItemの__lt__で、get_keyにi.timeも追加した方がよかったかも。
def __lt__(self, other): get_key = lambda i: (i.name, i.domain, i.time) return get_key(self) < get_key(other)