学习是一个逐步发现自己无知的过程!

Python 分析业务日志 处理access日志并写入excel

分析日志结构

# 正常日志
162.221.197.226 - - [05/May/2022:22:37:51 +0800] "GET /robots.txt HTTP/1.1" 200 127 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36" "-"0.182 0.182 127.0.0.1:9000 2000.000 - blog.sirliu.com
# 访问IP:162.221.197.226
# 访问时间:[05/May/2022:22:37:51 +0800]
# 访问的URL: /robots.txt HTTP/1.1
# 访问设备:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36

# 异常日志
ee/2/ HTTP/1.1" 200 1055 "https://www.luffycity.com/study/degree" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36"
- - - [1/Dec/2021:11:53:42 +0800] "\x16\x03\x01\x00\x9A\x01\x00\x00\x96\x03\x03g\xAE%\xCF\xCA\xC8v\x191\x90\xAA\xAD9\xBC\xFE\x9AJ]\xFC\xB8\xB4\x83\xF6\xF7\xB3+\xA3<AG\xF3\xAE\x00\x00\x1A\xC0/\xC0+\xC0\x11\xC0\x07\xC0\x13\xC0\x09\xC0\x14\xC0" 400 166 "-" "-"
- - - [1/Dec/2021:12:36:00 +0800] "HEAD / HTTP/1.1" 499 0 "-" "-"

首先把异常日志排除掉。
然后再针对性实现每个需求。

  • 统计日志文件的总pv、uv
-- 循环每条正常的日志,使用正则查找当前行的 ip
      - pv = 每一个可以重复的 ip 的和
      - uv = 每一个不可重复的 ip 的和
  • 列出全天每小时的pv、uv
-- 循环每条正常的日志,使用正则查找当前行的 ip
-- 以小时为分割,然后统计每个小时的pv和uv
      - pv = 每一个可以重复的 ip 的和
      - uv = 每一个不可重复的 ip 的和
-- 难点:
      - 从日志中读取的日期时间,如何处理为可用的时间格式(格式化后的字符串/时间戳)
      - 如何组织数据结构
  • 列出top10 uv的IP地址,以及每个IP的pv点击数
-- 循环每条正常的日志,使用正则查找当前行的 ip
      - 循环读取所有ip,取出每一个ip
            - 统计每个不重复的ip,以及统计该ip重复出现的次数
      - 最后进行降序排序,然后取最高的top10
  • 列出top10访问量最多的页面及每个页面的访问量
-- 循环每条正常的日志,使用正则查找当前行的 page
      - 循环读取所有page,取出每一个page
            - 统计每个不重复的page,以及统计该page重复出现的次数
      - 最后进行降序排序,然后取最高的top10 的page
  • 列出访问来源设备列表及每个设备的访问量
-- 循环每条正常的日志,使用正则查找当前行的设备
      - 统计每个不重复的设备,以及统计该设备重复出现的次数
      - 虽然需求是列出所有的设备,但我的代码中只展示了前20条数据
-- 注意,设备有多种:
      - Mozilla
      - Mozilla/5.0  Mozilla/4.0  Mozilla/3.0
      - Python-urllib/2.7
      - Go-http-client/1.1
      - -                        # 是的,只有一个 - ,我认为是未知的设备,我代码中没有处理
      - curl/7.19.7
      - Sogou web
      - Xiaomi_MCT1_TD-LTE/V1

具体的脚本如下:


import datetime
import re
from prettytable import PrettyTable

LOG_INFO = 'files/access.log'

# ----- 正则规则 -----
MATCH_STARTSWITH = MATCH_IP = re.compile(
    "(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)")
MATCH_DATETIME = re.compile(
    "(?P<ip>((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d))).*?\[(?P<dt>.*?)\s\+0800\]")
MATCH_EQUIPMENT = re.compile("\"\s\"(?P<equipment>.*?)\"")
MATCH_PAGE = re.compile("\"[A-Z]{3,}\s(?P<page>.*?)\sHTTP/1.1")

FILE_LIST = []

def init():
    """ 过滤符合条件的日志,存储在列表中 """

    with open(LOG_INFO, 'r', encoding='UTF-8') as f:
        for line in f:
            res = MATCH_STARTSWITH.match(line)
            if res:
                FILE_LIST.append(line)

def total_pv_uv():
    """ 统计本日志文件的总pv、uv """
    tmp_dict = {"pv": [], "uv": set(), }
    print('waiting.....')
    for i in FILE_LIST:
        res = MATCH_IP.search(i)
        if res:
            res = res.group()
            tmp_dict['pv'].append(res)
            tmp_dict['uv'].add(res)
    table = PrettyTable(['总PV', '总UV'])
    table.add_row([len(tmp_dict['pv']), len(tmp_dict['uv'])])
    print(table)

def total_24hour_pv_uv():
    """ 列出全天每小时的pv、uv数 """
    tmp_dict = {}
    print("waiting.....")
    for i in FILE_LIST:
        res = MATCH_DATETIME.search(i)
        if res:
            ip, dt = res.group("ip"), res.group("dt")
            date_time_str = datetime.datetime.strptime(dt, '%d/%b/%Y:%H:%M:%S')
            date_time_day = date_time_str.strftime("%Y-%m-%d")
            date_time_hour = date_time_str.strftime("%Y-%m-%d %H")
            if not tmp_dict.get(date_time_day, False):
                tmp_dict[date_time_day] = {}
            # 创建当天的24小时
            for hour in range(0, 24):
                # 必须用zfill在个位数之前填充0,不然生成的 key 是这样的 2019-04-14 1,和 date_time_hour生成的key 2019-04-14 01 不一致,会出问题
                tmp_key = "{} ".format(date_time_day) + "{}".format(hour).zfill(2)
                if tmp_key not in tmp_dict[date_time_day]:
                    tmp_dict[date_time_day][tmp_key] = {"pv": [], "uv": set()}
            tmp_dict[date_time_day][date_time_hour]['uv'].add(ip)
            tmp_dict[date_time_day][date_time_hour]['pv'].append(ip)
    # print(tmp_dict)
    for k in tmp_dict:
        print('[{}]日的每小时的pv和uv数统计如下: '.format(k))
        table = PrettyTable(["时间", '每个小时的pv数', '每个小时的uv数'])
        for j, v in tmp_dict[k].items():
            table.add_row([j, len(v['pv']), len(v['uv'])])
        print(table)

def total_top10_uv():
    """ 列出top 10 uv的IP地址,以及每个ip的pv点击数 """
    tmp_dict = {}
    print("waiting.....")
    for i in FILE_LIST:
        res = MATCH_IP.search(i)
        if res:
            res = res.group()
            if res in tmp_dict:
                tmp_dict[res] += 1
            else:
                tmp_dict[res] = 1
    table = PrettyTable(['top10 uv的IP', 'top10 uv的ip的pv'])
    table.align['top10 uv的IP'] = 'l'
    tmp_list = sorted(tmp_dict.items(), key=lambda x: x[1], reverse=True)[
               0:10]  # tmp_dict.items()  --> ("113.89.97.191", 13)
    for n1, n2 in tmp_list:
        table.add_row([n1, n2])
    print(table)

def total_top10_page():
    """ 列出top 10 访问量最多的页面及每个页面的访问量 """
    print("waiting.....")
    tmp_dict = {}
    for i in FILE_LIST:
        res = MATCH_PAGE.search(i)
        if res:
            page = res.group("page")
            if page in tmp_dict:
                tmp_dict[page].append(page)
            else:
                tmp_dict[page] = [page]
    table = PrettyTable(['访问量是top10的页面URL', '访问量'])
    table.align['访问量是top10的页面URL'] = 'l'
    tmp_list = sorted(tmp_dict.items(), key=lambda x: len(x[1]), reverse=True)[0:10]
    for n1, n2 in tmp_list:
        table.add_row([n1, len(n2)])
    print(table)

def total_equipment_list():
    """ 列出访问来源的设备列表及每个设备的访问量 """
    print("waiting.....")
    tmp_dict = {}
    for i in FILE_LIST:
        res = MATCH_EQUIPMENT.search(i)
        if res:
            equipment = res.group("equipment")
            if equipment in tmp_dict:
                tmp_dict[equipment].append(equipment)
            else:
                tmp_dict[equipment] = [equipment]
    table = PrettyTable(['设备来源', '访问量'])
    table.align['设备来源'] = 'l'
    # 这里仅展示前20条,你可以取消分片,展示所有
    # tmp_list = sorted(tmp_dict.items(), key=lambda x: len(x[1]), reverse=True)
    tmp_list = sorted(tmp_dict.items(), key=lambda x: len(x[1]), reverse=True)[0:20]
    for n1, n2 in tmp_list:
        table.add_row([n1, len(n2)])
    print(table)

def q():
    """ 退出 """
    exit('再来呦')

def handler():
    tmp_dict = {
        "1": ["统计本日志文件的总pv、uv", total_pv_uv],
        "2": ["列出全天每小时的pv、uv数", total_24hour_pv_uv],
        "3": ["列出top 10 uv的IP地址,以及每个ip的pv点击数", total_top10_uv],
        "4": ["列出top 10 访问量最多的页面及每个页面的访问量", total_top10_page],
        "5": ["列出访问来源的设备列表及每个设备的访问量", total_equipment_list],
        "6": ["退出", q]
    }
    while True:
        print('欢迎使用网站访问数据分析系统'.center(40, '*'))
        for k, v in tmp_dict.items():
            print(k, v[0])
        cmd = input("输入序号选择对应的操作: ").strip()
        if cmd in tmp_dict:
            tmp_dict[cmd][-1]()
        else:
            print('输入不合法')

if __name__ == "__main__":
    init()
    handler()

写入到excel


import datetime
import re

from openpyxl import Workbook

LOG_INFO = 'files/access.log'
cmd = input("请输入分析的是什么终端日志:").strip()

# ----- 正则规则 -----
MATCH_STARTSWITH = MATCH_IP = re.compile(
    "(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)")
MATCH_DATETIME = re.compile(
    "(?P<ip>((25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d))).*?\[(?P<dt>.*?)\s\+0800\]")

FILE_LIST = []

def xlsx_file(files):
    works = Workbook()
    words = works.active
    words.title = f"{cmd}.xlsx"
    words.append(['时间', '每小时PV', '每小时UV'])
    for exl in files:
        words.append(exl)
    works.save('excel/pc.xlsx')

def init():
    """ 过滤符合条件的日志,存储在列表中 """
    with open(LOG_INFO, 'r', encoding='UTF-8') as f:
        for line in f:
            res = MATCH_STARTSWITH.match(line)
            if res:
                FILE_LIST.append(line) 

def total_24hour_pv_uv():
    """ 列出全天每小时的pv、uv数 """
    tmp_dict = {}
    for i in FILE_LIST:
        res = MATCH_DATETIME.search(i)  
        if res:
            ip, dt = res.group("ip"), res.group("dt")
            date_time_str = datetime.datetime.strptime(dt, '%d/%b/%Y:%H:%M:%S')
            date_time_day = date_time_str.strftime("%Y-%m-%d")
            date_time_hour = date_time_str.strftime("%Y-%m-%d %H")

            if not tmp_dict.get(date_time_day, False):
                tmp_dict[date_time_day] = {}

            # 创建当天的24小时
            for hour in range(0, 24):
                tmp_key = "{} ".format(date_time_day) + "{}".format(hour).zfill(2)
                if tmp_key not in tmp_dict[date_time_day]:
                    # 拼接字典结构,这里的set()是集合,应为集合中不能写入相同的数据
                    tmp_dict[date_time_day][tmp_key] = {"pv": [], "uv": set()}
            tmp_dict[date_time_day][date_time_hour]['uv'].add(ip)
            tmp_dict[date_time_day][date_time_hour]['pv'].append(ip)

    for k in tmp_dict: 
        tt = []
        for j, v in tmp_dict[k].items():
            tt.append([j, len(v['pv']), len(v['uv'])])
        xlsx_file(tt)

if __name__ == "__main__":
    init()
    total_24hour_pv_uv()
赞(0)
未经允许不得转载:劉大帥 » Python 分析业务日志

你的评论可能会一针见血! 抢沙发

登录

找回密码

注册