分享

Spark应用——网络服务器日志解析

 株野 2017-12-18

2017年06月22日

来源:CSDN



这里我们展示如何使用Spark来分析网络服务器日志。

服务器日志是富含信息的通用大数据。Spark提供了能快速简便执行这类数据分析的工具。日志数据可以来自多个源,如网络、文件、计算机服务器、应用日志、用户产生的内容,并可以用于监视服务器、改善商业和客户信息、构建推荐系统、欺诈侦测以及更多的应用。

首先我们需要载入一些会用到的库

  1. import re  
  2. import datetime  
  3. from databricks_test_helper import Test  

载入日志文件并快速浏览一下数据

  1. import sys  
  2. import os  
  3.   
  4. log_file_path = “...”  
  5.   
  6. base_df = sqlContext.read.text(log_file_path)  
  7.   
  8. base_df.printSchema()  
  9. base_df.show(truncate=False)  

尝试性的进行数据分析

网络日志一般含有以下信息:

remotehost rfc931 authuser [date] "request" status bytes


field meaning
remotehost Remote hostname (or IP number if DNS hostname is not available).
rfc931 The remote logname of the user. We don't really care about this field.
authuser The username of the remote user, as authenticated by the HTTP server.
[date] The date and time of the request.
"request" The request, exactly as it came from the browser or client.
status The HTTP status code the server sent back to the client.
bytes The number of bytes (Content-Length) transferred to the client.

接下来,我们要将这些信息解析到单独的栏,我们使用内置的regexp_extract()函数。

  1. from pyspark.sql.functions import split, regexp_extract  
  2. split_df = base_df.select(regexp_extract('value', r'^([^\s]+\s)', 1).alias('host'),  
  3.                           regexp_extract('value', r'^.*\[(\d\d/\w{3}/\d{4}:\d{2}:\d{2}:\d{2} -\d{4})]', 1).alias('timestamp'),  
  4.                           regexp_extract('value', r'^.*"\w+\s+([^\s]+)\s+HTTP.*"', 1).alias('path'),  
  5.                           regexp_extract('value', r'^.*"\s+([^\s]+)', 1).cast('integer').alias('status'),  
  6.                           regexp_extract('value', r'^.*\s+(\d+)$', 1).cast('integer').alias('content_size'))  
  7. split_df.show(truncate=False)  

数据清洗

首先我们要将原始数据集中的所有null行去除

  1. base_df.filter(base_df['value'].isNull()).count()  

进行一个快速的检查

  1. bad_rows_df = split_df.filter(split_df['host'].isNull() |  
  2.                               split_df['timestamp'].isNull() |  
  3.                               split_df['path'].isNull() |  
  4.                               split_df['status'].isNull() |  
  5.                              split_df['content_size'].isNull())  
  6. bad_rows_df.count()  

你会发现我们依然还有一些null值,让我们看一下哪些列有问题。

  1. from pyspark.sql.functions import col, sum  
  2.   
  3. def count_null(col_name):  
  4.   return sum(col(col_name).isNull().cast('integer')).alias(col_name)  
  5.   
  6. exprs = []  
  7. for col_name in split_df.columns:  
  8.   exprs.append(count_null(col_name))  
  9.   
  10. split_df.agg(*exprs).show()  

我们此前用了\d+来选择每行输入最后的一个以上的数字,是否可能这一栏没有合法的内容规模?

  1. bad_content_size_df = base_df.filter(~ base_df['value'].rlike(r'\d+$'))  
  2. bad_content_size_df.count()  

我们看一下一些含错误值的行

  1. from pyspark.sql.functions import lit, concat  
  2. bad_content_size_df.select(concat(bad_content_size_df['value'], lit('*'))).show(truncate=False)  

我们简单的使用fillna()来处理含有null的行

  1. cleaned_df = split_df.na.fill({'content_size': 0})  

检查一下所有的null值都被进行了处理

  1. exprs = []  
  2. for col_name in cleaned_df.columns:  
  3.   exprs.append(count_null(col_name))  
  4.   
  5. cleaned_df.agg(*exprs).show()  

现在我们要来处理时间戳,由于时间往往不是统一标准因此最简单的方式使用一个用户定义的函数(UDF)来解析它。

  1. month_map = {  
  2.   'Jan': 1, 'Feb': 2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7,  
  3.   'Aug':8,  'Sep': 9, 'Oct':10, 'Nov': 11, 'Dec': 12  
  4. }  
  5.   
  6. def parse_clf_time(s):  
  7.     """ Convert Common Log time format into a Python datetime object 
  8.     Args: 
  9.         s (str): date and time in Apache time format [dd/mmm/yyyy:hh:mm:ss (+/-)zzzz] 
  10.     Returns: 
  11.         a string suitable for passing to CAST('timestamp') 
  12.     """  
  13.   
  14.     return "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}".format(  
  15.       int(s[7:11]),  
  16.       month_map[s[3:6]],  
  17.       int(s[0:2]),  
  18.       int(s[12:14]),  
  19.       int(s[15:17]),  
  20.       int(s[18:20])  
  21.     )  
  22.   
  23. u_parse_time = udf(parse_clf_time)  
  24.   
  25. logs_df = cleaned_df.select('*', u_parse_time(cleaned_df['timestamp']).cast('timestamp').alias('time')).drop('timestamp')  
  26. total_log_entries = logs_df.count()  

现在我们来看一下网络服务器返回内容的一些统计指标。

  1. content_size_summary_df = logs_df.describe(['content_size'])  
  2. content_size_summary_df.show()  

此外,我们也可以使用SQL直接计算这些数值。

  1. from pyspark.sql import functions as sqlFunctions  
  2. content_size_stats =  (logs_df  
  3.                        .agg(sqlFunctions.min(logs_df['content_size']),  
  4.                             sqlFunctions.avg(logs_df['content_size']),  
  5.                             sqlFunctions.max(logs_df['content_size']))  
  6.                        .first())  
  7.   
  8. print 'Using SQL functions:'  
  9. print 'Content Size Avg: {1:,.2f}; Min: {0:.2f}; Max: {2:,.0f}'.format(*content_size_stats)  

我们看一下日志中HTTP的状态值

  1. status_to_count_df =(logs_df  
  2.                      .groupBy('status')  
  3.                      .count()  
  4.                      .sort('status')  
  5.                      .cache())  
  6.   
  7. status_to_count_length = status_to_count_df.count()  
  8. print 'Found %d response codes' % status_to_count_length  
  9. status_to_count_df.show()  
  10.   
  11. assert status_to_count_length == 7  
  12. assert status_to_count_df.take(100) == [(200, 940847), (302, 16244), (304, 79824), (403, 58), (404, 6185), (500, 2), (501, 17)]  

可以使用内置函数display()来对它进行图形化

  1. display(status_to_count_df)  

由于数据间差异太大,我们可以取它们的对数值以更好的进行比较。

  1. log_status_to_count_df = status_to_count_df.withColumn('log(count)', sqlFunctions.log(status_to_count_df['count']))  
  2.   
  3. display(log_status_to_count_df)  

我们可以进一步的使用例如matplotlib之类库进行更好的图形化

  1. from spark_notebook_helpers import prepareSubplot, np, plt, cm  

  1. data = log_status_to_count_df.drop('count').collect()  
  2. x, y = zip(*data)  
  3. index = np.arange(len(x))  
  4. bar_width = 0.7  
  5. colorMap = 'Set1'  
  6. cmap = cm.get_cmap(colorMap)  
  7.   
  8. fig, ax = prepareSubplot(np.arange(0, 6, 1), np.arange(0, 14, 2))  
  9. plt.bar(index, y, width=bar_width, color=cmap(0))  
  10. plt.xticks(index + bar_width/2.0, x)  
  11. display(fig)  

我们现在看一下访问服务器最频繁的那些hosts

  1. host_sum_df =(logs_df  
  2.               .groupBy('host')  
  3.               .count())  
  4.   
  5. host_more_than_10_df = (host_sum_df  
  6.                         .filter(host_sum_df['count'] > 10)  
  7.                         .select(host_sum_df['host']))  
  8.   
  9. print 'Any 20 hosts that have accessed more then 10 times:\n'  
  10. host_more_than_10_df.show(truncate=False)  

现在我们图形化日志中对于路径(URIs)的点击量。

  1. paths_df = (logs_df  
  2.             .groupBy('path')  
  3.             .count()  
  4.             .sort('count', ascending=False))  
  5.   
  6. paths_counts = (paths_df  
  7.                 .select('path', 'count')  
  8.                 .map(lambda r: (r[0], r[1]))  
  9.                 .collect())  
  10.   
  11. paths, counts = zip(*paths_counts)  
  12.   
  13. colorMap = 'Accent'  
  14. cmap = cm.get_cmap(colorMap)  
  15. index = np.arange(1000)  
  16.   
  17. fig, ax = prepareSubplot(np.arange(0, 1000, 100), np.arange(0, 70000, 10000))  
  18. plt.xlabel('Paths')  
  19. plt.ylabel('Number of Hits')  
  20. plt.plot(index, counts[:1000], color=cmap(0), linewidth=3)  
  21. plt.axhline(linewidth=2, color='#999999')  
  22. display(fig)  

找到错误路径的前十位

  1. from pyspark.sql.functions import desc  
  2. not200DF = logs_df.filter('status != 200')  
  3. not200DF.show(10)  
  4.   
  5. logs_sum_df = not200DF.groupBy('path').count().sort('count', ascending=False)  
  6.   
  7. print 'Top Ten failed URLs:'  
  8. logs_sum_df.show(10, False)  
  9.   
  10. top_10_err_urls = [(row[0], row[1]) for row in logs_sum_df.take(10)]  

独立hosts的数量

  1. unique_host_count = logs_df.select('host').distinct().count()  
  2. print 'Unique hosts: {0}'.format(unique_host_count)  

每日独立hosts的数量

  1. from pyspark.sql.functions import dayofmonth  
  2.   
  3. day_to_host_pair_df = logs_df.select('host', dayofmonth('time').alias('day'))  
  4. day_group_hosts_df = day_to_host_pair_df.drop_duplicates()  
  5. daily_hosts_df = day_group_hosts_df.groupBy('day').count().cache()  
  6.   
  7. print 'Unique hosts per day:'  
  8. daily_hosts_df.show(30, False)  

每个host每日请求的平均数

  1. total_req_per_day_df = logs_df.groupBy(dayofmonth('time').alias('day')).count()  
  2.   
  3. avg_daily_req_per_host_df = (  
  4.   total_req_per_day_df.join(daily_hosts_df, 'day').select(total_req_per_day_df.day, (total_req_per_day_df['count'] / daily_hosts_df['count']).alias('avg_reqs_per_host_per_day')).cache()  
  5. )  
  6.   
  7. print 'Average number of daily requests per Hosts is:\n'  
  8. avg_daily_req_per_host_df.show()  

探究404状态

计算有多少404反馈

  1. not_found_df = logs_df.filter('status = 404').cache()  
  2. print('Found {0} 404 URLs').format(not_found_df.count())  

列举404状态的记录

  1. not_found_paths_df = not_found_df.select('path')  
  2. unique_not_found_paths_df = not_found_paths_df.distinct()  
  3.   
  4. print '404 URLS:\n'  
  5. unique_not_found_paths_df.show(n=40, truncate=False)  

列举404反馈的路径

  1. top_20_not_found_df = not_found_df.groupBy('path').count().sort('count', ascending=False)  
  2.   
  3. print 'Top Twenty 404 URLs:\n'  
  4. top_20_not_found_df.show(n=20, truncate=False)  

列举404反馈的hosts

  1. hosts_404_count_df = not_found_df.groupBy('host').count().sort('count', ascending=False)  
  2.   
  3. print 'Top 25 hosts that generated errors:\n'  
  4. hosts_404_count_df.show(n=25, truncate=False)  

列举每天404错误

  1. errors_by_date_sorted_df = not_found_df.groupBy(dayofmonth('time').alias('day')).count().cache()  
  2.   
  3. print '404 Errors by day:\n'  
  4. errors_by_date_sorted_df.show()  

404错误最频繁的天数

  1. top_err_date_df = errors_by_date_sorted_df.sort('count', ascending=False)  
  2.   
  3. print 'Top Five Dates for 404 Requests:\n'  
  4. top_err_date_df.show(5)  

每小时的404错误

  1. from pyspark.sql.functions import hour  
  2. hour_records_sorted_df = not_found_df.groupBy(hour('time').alias('hour')).count().cache()  
  3.   
  4. print 'Top hours for 404 requests:\n'  
  5. hour_records_sorted_df.show(24)  

我们可以看到通过使用Spark我们可以快速的对网络服务器日志的各个方面进行分析,了解我们所需要的信息,尤其是对404的详细分析使我们能发现运行中的一些问题,更好的改善用户体验,尤为重要的是我们可以借助Spark分布式处理系统对大量的数据在短时间内做出处理,并能通过图形化直观的进行展示。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约