思路
nginx 访问日志,记录了每次客户端请求,其中包括 ip、时间、使用的客户端等信息
通过解析每行数据,提取这些信息,然后对信息进行整理,并做一些必要的统计
最后将统计数据展示出来,可以直观地感知数据中蕴含的问题
基本思路就是这样,不过知道和做到之间地距离还有很远,为了达到目标,需要一些工具做支持
由于数据是 nginx 访问日志,所有不需要爬取,从服务器上下载就好
整理处理过程,除了 python 本身一些功能外,还离不开 pandas 的支持
最后数据展示部分,用的是 Flask + echarts,从头写,确实很有挑战,不过今天我们利用 TurboWay 同学的框架 bigdata_practice,就能轻松搞定
闲话少叙,开始吧
数据处理
下载到 nginx 访问日志,从 nginx 配置文件中可以查看日志存放地址,另外,本文源码中有附带示例日志文件,可下载使用
日志文件为文本文件,每行记录一条访问情况,例如:
124.64.19.27 - - [04/Sep/2020:03:21:12 +0800] "POST /api/hb.asp HTTP/1.1" 200 132 "http://erp.example.com/mainframe/main.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36" "-"
读取文本文件的行,实现比较简单,这里只对提取字段和通过 ip 确定省份做下说明
提取
提取字段的方法如下:
import re obj = re.compile(r'(?P<ip>.*?)- - \[(?P<time>.*?)\] "(?P<request>.*?)" (?P<status>.*?) (?P<bytes>.*?) "(?P<referer>.*?)" "(?P<ua>.*?)"') result = obj.match(line) # print(result.group("time")) # ip处理 ip = result.group("ip").split(",")[0].strip() # 如果有两个ip,取第一个ip # 状态码处理 status = result.group("status") # 状态码 # 时间处理 time = result.group("time").replace(" +0800", "") # 提取时间,并去掉时区信息 t = datetime.datetime.strptime(time, "%d/%b/%Y:%H:%M:%S") # 格式化 # request处理 request = result.group("request") a = request.split()[1].split("?")[0] # 提取请求 url,去掉查询参数 # user_agent处理 ua = result.group("ua") if "Windows NT" in ua: u = "windows" elif "iPad" in ua: u = "ipad" elif "Android" in ua: u = "android" elif "Macintosh" in ua: u = "mac" elif "iPhone" in ua: u = "iphone" else: u = "其他设备" # refer处理 referer = result.group("referer")
代码看着长,其实逻辑很简单,核心是提取信息的正则表达式,利用了命名分组的方式,匹配后,可以通过命名来提取数据
对提取出的数据,需要处理一下,比如请求时间,采用的是类似 UTC 时间格式,需要去掉时区,并转换为 datatime
类型
另外就是的客户端的处理,根据关键字,判断客户端类型
将提取的信息,存入一个 词典
对象中,即每行对于一个 词典
对象,最后将一个个对象追加到一个 列表
对象中,带进一步处理
获取用户省份
为了后面对访问者所在区域进行分析,需要对一些字段做处理,例如将 ip 转换为省份信息
转换主要利用的是百度的 ip 定位服务
百度的 ip 定位服务,通过认证,可以获得每日 3 万次的免费配额
通过提供的 api 可以获取 ip 地址所在的省名称
考虑到查询效率和配额限制问题,最好对 ip 定位的结果做个缓存:
import requests import os ak = "444ddf895 ... a5ad334ee" # 百度 ak 需申请 # ip 定位方法 def ip2province(ip): province = ipCache.get(ip, None) if province is None: url = f"https://api.map.baidu.com/location/ip?ak={ak}&ip={ip}&coor=bd09ll" try: province = json.loads(requests.get(url).text)['address'].split('|')[1] ipCache[ip] = province # 这里就需要写入 with open("ip_cache.txt","a") as f: f.write(ip + "\t" + province + "\n") return province except Exception as e: return "未知" else: return province # 初始化缓存 ipCache = {} if os.path.exists("ip_cache.txt"): with open("ip_cache.txt", "r") as f: data = f.readline() while data: ip, province = data.strip().split("\t") ipCache[ip] = province data = f.readline()
- 首先需要申请一个百度 app key
- 合成请求,通过 requests get,得到响应,从中提取到 ip 对应的省份信息
- 对应地址缓存,将没有缓存的结果存入 ipCache 词典对象,并写入 ip_cache.txt 文件,下次启动时,用缓存文件中的内容初始化 ipCache 词典对象
- 在每次需要获取 ip 对应地址时,先检查缓存,如果没有才通过 api 获取
数据分析
数据分析,就是对提取到的特征数据做统计加工,利用的是强大的 pandas
通过数据处理过程,我们可以得到处理好的 列表
对象,列表对象很容易创建为 pandas 的 DataFrame
接着,利用 pandas 的统计功能,将原始数据转换为可以展示用的分析数据
最后将数据存入 Excel 文件
def analyse(lst): df = pd.DataFrame(lst) # 创建 DataFrame # 统计省份 province_count_df = pd.value_counts(df['province']).reset_index().rename(columns={"index": "province", "province": "count"}) # 统计时段 hour_count_df = pd.value_counts(df['hour']).reset_index().rename(columns={"index": "hour", "hour": "count"}).sort_values(by='hour') # 统计客户端 ua_count_df = pd.value_counts(df['ua']).reset_index().rename(columns={"index": "ua", "ua": "count"}) # 数据存储 to_excel(province_count_df, 'data.xlsx', sheet_name='省份') to_excel(hour_count_df, 'data.xlsx', sheet_name='按时') to_excel(ua_count_df, 'data.xlsx', sheet_name='客户端') def to_excel(dataframe, filepath, sheet_name): if os.path.exists(filepath):j excelWriter = pd.ExcelWriter(filepath, engine='openpyxl') book = load_workbook(excelWriter.path) excelWriter.book = book dataframe.to_excel(excel_writer=excelWriter,sheet_name=sheet_name,index=None, header=None) excelWriter.close() else: dataframe.to_excel(filepath, sheet_name=sheet_name, index=None, header=None)
analyse
方法,接受一个列表
对象,即在数据整理部分得到的数据- 将数据创建为 DataFrame,利用 pandas 的
value_counts
方法对对应字段数据进行统计,注意,value_counts
会做去重处理,从而统计出每个值出现的个数 - 因为
value_counts
处理的结果,是一个 Series 对象,索引为不重复的值,所以在用 reset_index 方法处理一下,将索引转换为一个正常列,并对列名做了替换,以便后续处理更方便 - 由于 value_counts 后的结果是按统计数量从多到少排列的,对应按时间的统计有些奇怪,所以利用
sort_values
方法,按时间列做了重新排序 to_excel
方法是为了将数据导出为 excel,可以支持导入不同 sheet,以便做数据展示
数据分析部分,可以从不同的角度对数据进行统计分析,最终将需要展示的数据存入 Excel,当然根据需要也可以存入其他数据库