Python 读取非结构化log文本数据

背景:我们在这个网站上部署了Google Analytics的代码用来监测用户的行为,由于该工具是SAAS工作模式,数据采集之后直接发送到Google云端服务器。我们通过一定的方式将每次发送给谷歌的数据同时“备份”了一条保存到本地服务器日志。其中日志请求内容的部分以“GET /__ua.gif?”开头便是日志记录。我们的目标是找到这些日志,然后将日志做初步的解析,并存放到本地文件便于后期做进一步数据和应用。
要实现这一目标的基本思路是:先从日志文件夹中读取日志文件列表(本示例中仅有1个文件因此省略该步骤),然后依次读取每个日志文件中的数据,接着对日志文件中的每条数据进行判断,符合条件才能成为我们要的目标数据,最后将数据做初步解析并写到文件里面。
本节我们依然以函数的方式来撰写各个功能模块,如下是各模块代码。

  1. # 读取非结构化文本数据
  2. def get_raw_log(file):
  3.     fn_read = open(file,'r') # 打开要读取的日志文件对象
  4.     content = fn_read.readlines() # 以列表形式读取日志数据
  5.     fn_read.close() # 关闭文件对象
  6.     for single_log in content: # 循环判断每天记录
  7.         rule1 = single_log.find('GET /__ua.gif?') != -1  # 定义日志规则:含ua.gif的请求
  8.         if rule1 == True# 如果符合规则,则执行
  9.             fn_write = open('ua_data.txt','a+') # 打开要保存的ua日志文件对象
  10.             fn_write.writelines(single_log) # 写入符合规则的日志
  11.             fn_write.close() # 关闭文件对象

代码中,我们先读取原始日志文件并获得日志内容列表;然后定义了一个规则用于识别符合规则的数据记录,并把记录写到另一个文件中。
通过定义file为附件的日志文件格式,并执行在Python程序当前工作目录下产生一个名为ua_data.txt的新文件,执行代码如下:

  1. file = 'traffic_log_for_dataivy'
  2. get_raw_log(file

打开生成的数据文件预览下,似乎符合预期,但在日志中我们发现在user agent信息中有类似下面的记录,并且数据不少:

  1. 66.249.77.27 - - [13/Mar/2017:23:37:00 +0800] "GET /__ua.gif?v=1&_v=j49&a=1866784098&t=timing&_s=2&dl=http%3A%2F%2Fwww.dataivy.cn%2Fblog%2F%25E5%259B%25A0%25E5%25AD%2590%25E5%2588%2586%25E6%259E%2590factor-analysis%2F&ul=en-us&de=UTF-8&dt=%E5%9B%A0%E5%AD%90%E5%88%86%E6%9E%90(Factor%20Analysis)%20%7C%20%E6%95%B0%E6%8D%AE%E5%B8%B8%E9%9D%92%E8%97%A4&sd=24-bit&sr=1024x1024&vp=1024x1024&je=0&fl=13.0%20r0&plt=56221209&pdt=0&dns=0&rrt=56221003&srt=0&tcp=0&dit=56221064&clt=56221064&_u=SGAAgEAj~&jid=&cid=2098553546.1487808000&tid=UA-50808718-1&sf=90&z=1264569826 HTTP/1.1" 200 52 "http://www.dataivy.cn/blog/%E5%9B%A0%E5%AD%90%E5%88%86%E6%9E%90factor-analysis/" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

这些记录大多来源于搜索引擎公司的网络爬虫在抓取页面时产生的数据,除了谷歌外,还百度、搜狗、雅虎、有道、必应等都有类似的爬虫(或者称为机器人),这些数据需要过滤掉,我们通过定义一个函数模块来实现这个功能。

  1. # 去除爬虫功能
  2. def remove_spider_data(single_log):
  3.     # 定义排除爬虫规则集
  4.     exclude_set =[
  5.         'AhrefsBot',
  6.         'archive.org_bot',
  7.         'baiduspider',
  8.         'Baiduspider',
  9.         'bingbot',
  10.         'DeuSu',
  11.         'DotBot',
  12.         'Googlebot',
  13.         'iaskspider',
  14.         'MJ12bot',
  15.         'msnbot',
  16.         'Slurp',
  17.         'Sogou web spider',
  18.         'Sogou Push Spider',
  19.         'SputnikBot',
  20.         'Yahoo! Slurp China',
  21.         'Yahoo! Slurp',
  22.         'YisouSpider',
  23.         'YodaoBot',
  24.         'bot.html'
  25.     ]
  26.     count = 0 # 初始计数用于计算日志中是否包含爬虫
  27.     for spider in exclude_set: # 循环读取每个爬虫
  28.         if single_log.find(spider) != -1: # 如果爬虫出现在日志里面
  29.             count += 1 # count + 1
  30.     if count > 0: # 如果结果不为0,意味着日志中有爬虫
  31.         return 0 #返回 0
  32.     else# 否则
  33.         return 1 # 返回1

代码中我们先定义了一个爬虫列表,所有我们怀疑为爬虫的关键字字段都可加到列表里面,用来做爬虫记录过滤;然后我们循环读取爬虫列表的每个字段,并在每行日志里面查找是否存在,如果存在则在计算器(count)上加1,一旦count大于0,我们就认为在日志记录里面至少包含1个爬虫信息。因此,在返回结果为0代表有爬虫;1代表没有爬虫。
增加了新的过滤功能模块,相应的第一段get_raw_log模块代码也要做下修改:

  1. # 读取非结构化文本数据
  2. def get_raw_log(file):
  3.     fn_read = open(file,'r') # 打开要读取的日志文件对象
  4.     content = fn_read.readlines() # 以列表形式读取日志数据
  5.     fn_read.close() # 关闭文件对象
  6.     for single_log in content: # 循环判断每天记录
  7.         rule1 = single_log.find('GET /__ua.gif?') != -1  # 定义日志规则:含ua.gif的请求
  8.         rule2 = remove_spider_data(single_log)
  9.         if rule1 == True and rule2 == True# 如果同时符合2条规则,则执行
  10.             fn_write = open('ua_data.txt','a+') # 打开要保存的ua日志文件对象
  11.             fn_write.writelines(single_log) # 写入符合规则的日志
  12.             fn_write.close() # 关闭文件对象

代码中仅在rule1下面新增了一个rule2,然后在if条件语句中增加了同时包含的条件判断。我们再执行get_raw_log(file),返回的数据文件中的数据就是正确的。
下面开始解析每条日志,将日志的主要字段进行分割。

  1. # 解析每条日志数据
  2. def split_ua(line):
  3.     import re
  4.     #定义不同日志分割的正则表达式
  5.     ip_rule = r'[\d.]*' # 定义IP规则,例如203.208.60.230
  6.     time_rule = r'\[[^\[\]]*\]'  # 定义时间规则,例如[02/Mar/2016:14:00:23 +0800]
  7.     request_rule = r'\"[^\"]*\"' # 定义请求规则
  8.     status_rule = r'\d+' # 定义返回的状态码规则,例如200
  9.     bytes_rule = r'\d+' # 返回的字节数,例如326
  10.     refer_rule = r'\"[^\"]*\"' # 定义refer规则
  11.     user_agent_rule = r'?\"[^\"]*\"' # 定义user agnet规则
  12.     # 原理:主要通过空格和-来区分各不同项目,各项目内部写各自的匹配表达式
  13.     log_re_pattern = re.compile(r'(%s)\ -\ -\ (%s)\ (%s)\ (%s)\ (%s)\ (%s)\ (%s)' % (
  14.     ip_rule, time_rule, request_rule, status_rule, bytes_rule, refer_rule, user_agent_rule), re.VERBOSE) # 完整表达式模式
  15.     matchs = log_re_pattern.match(line) # 匹配
  16.     if matchs != None: # 如果不为空
  17.         allGroups = matchs.groups() # 获得所有匹配的列表
  18.         ip_info = allGroups[0] # IP信息
  19.         time_info = allGroups[1] # 时间信息
  20.         request_info = allGroups[2] # 请求信息
  21.         status_info = allGroups[3] # 状态信息
  22.         bytes_info = allGroups[4] #字节数信息
  23.         refer_info = allGroups[5] # refer信息
  24.         user_agent_info = allGroups[6] # user agent信息
  25.         return ip_info,time_info,request_info,status_info,bytes_info,refer_info,user_agent_info

该模块定义了日志下所有字段的分割规则,首先我们定义了一个正则表达式构成的规则集,分别表示不同字段的过滤规则;接着将分散的规则合成一个匹配模式;然后通过匹配模式匹配每条数据记录,并由此返回匹配的字段值列表,并返回。

  1. # 主程序
  2. def get_final_data():
  3.     file = 'traffic_log_for_dataivy' # 定义原始日志
  4.     get_raw_log(file# 读取非结构化文本数据
  5.     fn_r = open('ua_data.txt','r') # 打开要读取的ua日志文件
  6.     content = fn_r.readlines() # 读取ua所有日志记录
  7.     fn_r.close() # 关闭ua文件对象
  8.     fn_w = open('final_data.txt','a+') # 打开要写入的格式化文件
  9.     for line in content: # 按行循环
  10.         ip_info, time_info, request_info, status_info, bytes_info, refer_info, user_agent_info = split_ua(line) # 获得分割后的数据
  11.         log_line =ip_info + '!'+ time_info + '!'+ request_info + '!'+ status_info + '!'+ bytes_info + '!'+ refer_info\
  12.                   + '!'+ user_agent_info # 按指定规则组合用于写入文件
  13.         fn_w.writelines(log_line) # 写入文件
  14.         fn_w.writelines('\n') # 写入换行符用于换行
  15.     fn_w.close() # 关闭写入的文件对象
  16. # 执行程序
  17. get_final_data() # 执行所有程序

代码中,我们将前文提到的功能整合到该模块中。先是定义了一个日志文件,并获取符合要求的日志数据;再读取该日志数据并按行循环,在循环中,我们将每条日志进行分割并以!作为最终分隔符写入目标文件;最后执行程序。
总结:上述过程看似略显复杂,原因是为了提高每个模块的可读性和可理解性,我们按照功能单独定义,因此“忽略”了总体考虑和最优化规则,此代码有很多地方可以优化:
 import re应该放到全局里面导入
 从get_raw_log获得的数据,其实不必先写入文件再从文件读取,该过程可以省略(本节之所以先存后读是为了便于读者理解过程),所有程序最终可以只有1个结果文件
 日志被分割后又进行组合并再次写入文件,通常实际中不会这样执行,因为已经格式化的数据可以放到数据库里面。
 分割后的request_info模块的数据还没有解析,原因是没有应用场景需求做支撑,所以暂时不做处理。该字段中每个请求的数据主体(URL中?之后的部分)都是以key-value形式存在的键值对,解析起来非常容易。但是,由于不同的请求里面包含的key(即参数)可能不同,而且系统预定义的key的值域数量非常大(例如ilpicm的最大笛卡尔为200*200*200=8000000),因此这些数据无法直接分列存储到一张关系型数据库表里面。解决思路有两种:一是将字段按主题拆分,形成数据仓库或数据集市;二是使用非关系型数据仓库,直接支持海量数据(行和列)的扩展。目前两种方式都有公司在实行。
总结:对于非结构化的文本处理,通常更多的侧重于特定场景,通用性较差,原因就在于非结构化的形式本身变化多样。自然语言理解、文本处理和挖掘、用户日志和机器日志解析等都是围绕该领域的主要工作主题。


====================【好书推荐,我为自己代言】====================

《Python数据分析与数据化运营》上市啦!

50+数据流工作知识点
14个数据分析与挖掘主题
8个综合性运营分析案例
涵盖会员、商品、流量、内容4大主题
360°把脉运营问题并贴合数据场景落地


本书主要基于Python实现,其中主要用到的计算库是numpy、pandas和sklearn,其他相关库还包括:
  • 标准库:re、time、datetime、json、 base64、os、sys、cPickle、tarfile
  • 统计分析:Statsmodels
  • 中文处理:结巴分词
  • 文本挖掘:Gensim
  • 爬虫和解析:requests、Beautiful Soup、xml
  • 图像处理:OpenCV和PIL
  • 数据读取:xlrd、pymongo、mysql.connector
  • 数据预处理:imblearn
  • 展示美化类:Matplotlib、graphviz、prettytable、wordcloud、mpl_toolkits、pydotplus
如果你对以下内容感兴趣,那么本书将值得一看:
  • KMeans聚类的自动K均值的确立方法
  • 基于软方法的多分类模型组合评估模型的应用
  • 基于自动下探(下钻、细分)的应用
  • 基于增量学习的多项式贝叶斯分类
  • pipeline管道技术的应用
  • 基于超参数的自动参数值的优化方法
  • 特征自动选择
  • 文本分类、文本主题挖掘
  • 基于自动时间序列ARIMA的P、D、Q的调整
  • python决策树规则输出
  • 基于自定义图像的文本标签云
  • 非结构化数据,例如图像、音频、文本等处理
  • 对象持久化处理
有关这本书的写作感受、详细内容介绍、附件(含数据和代码源文件-源代码可更改数据源直接使用)下载、关键知识和方法以及完整书稿目录,请访问《Python数据分析与数据化运营》新书上线,要购买此书请直接点击图片或扫描二维码去京东购买

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>