zoukankan      html  css  js  c++  java
  • spark学习笔记8 一个例子_Apache日志分析

    需求

    • 统计每日 PV 和独立 IP
    • 统计每种不同的 HTTP 状态对应的访问数
    • 统计不同独立 IP 的访问量
    • 统计不同页面的访问量

    基础知识准备

    Apache日志位置

    • Windows下是: C:Program FilesApache Software FoundationApache2.2logsaccess.txt
    • Linux下是: /var/log/apache2/access.log

    Apache日志文件格式

    一条Apache记录,从左往右,大概是这个样子:

    1.远程主机IP:表明访问网站的是谁 
    2.空白(E-mail):为了避免用户的邮箱被垃圾邮件骚扰,第二项就用“-”取代了
    3.空白(登录名):用于记录浏览者进行身份验证时提供的名字。
    4.请求时间:用方括号包围,而且采用“公用日志格式”或者“标准英文格式”。 时间信息最后的“+0800”表示服务器所处时区位于UTC之后的8小时。
    5.方法+资源+协议:服务器收到的是一个什么样的请求。该项信息的典型格式是“METHOD RESOURCE PROTOCOL”,即“方法 资源 协议”。
     METHOD: GET、POST、HEAD、……
    RESOURCE: /、index.html、/default/index.php、……(请求的文件)
     PROTOCOL: HTTP+版本号
    6.状态代码:请求是否成功,或者遇到了什么样的错误。大多数时候,这项值是200,它表示服务器已经成功地响应浏览器的请求,一切正常。
    7.发送字节数:表示发送给客户端的总字节数。它告诉我们传输是否被打断(该数值是否和文件的大小相同)。把日志记录中的这些值加起来就可以得知服务器在一天、一周或者一月内发送了多少数据
    8、url以及客户端的详细信息,比如操作系统、浏览器等等
    

    另外,经过查找,也有说法说,第二项和第三项分别是客户端记录和浏览者记录。
    以及,第4项请求时间里面的“标准格式”是有:日期、时间、地区三种信息的。

    例如这条Apache日志记录

    180.76.15.161 - - [06/Dec/2014:06:49:26 +0800] "GET / HTTP/1.1" 200 10604 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
    

    就,非常明显,每一项都是能够对应得上的,哪个位置都是什么。

    正则表达式


    正则表达式手册
    菜鸟工具 正则表达式在线测试

    PV

    pv(Page View)单指访问量,每打开一次页面PV计数+1,刷新页面也是。和UV是不一样的
    UV是访问数(Unique Visitor)指独立访客访问数,一台电脑终端为一个访客。

    实现

    下载资料

    自己电脑上的Apache日志基本没有什么,就算做实验搭建过web服务器,也是仅限于局域网,数据量比较少。这个实验选取的数据来源是:

    http://labfile.oss.aliyuncs.com/courses/456/access.log
    

    可以用wget命令下载到当前目录下。

    嗯,一兆多一点的文本文件。

    解析记录生成新RDD

    这个正则表达式是对除了客户端详细信息以外的前7项内容做解析的,需求里面要每天的ip,所以需要分出来ip和时间。然后,还要求了不同http状态的访问数和页面访问量,所以还需要有HTTP状态码和URL。下面这个正则表达式,可以用来过滤掉不能够解析的日志记录,并解析有需要的信息的日志记录。下面这句scala让正则表达式的字符串调用了r这个方法从而创建了一个Regex对象也就是正则表达式对象

    val logPattern = """^(S+) (S+) (S+) [([w/]+)([w:/]+)s([+-]d{4})] "(S+) (S+) (S+)" (d{3}) (d+)""".r
    

    然后使用这个正则表达式来实现两个函数,分别用来实现上面的,过滤不能解析的日志记录和解析需要的信息的功能。
    这个是过滤不能够识别的信息的:

    def filterWithParse(s: String) = {
        logPattern.findFirstIn(s) match {
            case Some(logPattern(_*)) => true
            case _ => false
        }
    }
    

    根据我对scala的浅显了解,这个函数起作用的就是Some类,Some类是继承了Option类的一个类,主要就是用来判断这个是不是none的。
    能够匹配,或者说能用的记录就是true否则就是false
    下一个函数是用来解析日志,并且获取所需要的的信息的。

    def parseLog(s: String) = {
        val m = logPattern.findAllIn(s)
        if(m.hasNext){
            val clientIP = m.group(1).toString
            val requestDate = m.group(4).toString
            val requestURL = m.group(8).toString
            val status = m.group(10).toString
            (clientIP, requestDate, requestURL, status)
        } else {
            ("null", "null", "null", "null")
            // 为了便于分析数据,此处用一个内容为“null”的字符串来统计无效值
        }
    }
    

    然后,筛选的传给filter,filter的结果调用map,而map的参数就是parseLog这个分析函数,最后生成一个新的RDD

    val logRDDv1 = logRDD.filter(filterWithParse).map(parseLog)
    

    这个logRDDv1就是后面做分析,然后实现最开始的要求的基础。

    在新RDD基础上做统计

    pv

    新RDD里面一共有5122条记录

    题目要求是根据日期统计pv数量的,而刚才新生成RDD的时候,日期是放在了RDD的第二列或者说每一条记录的第二项。
    然后,我还需要知道的是,在scala的元组里面计数是从1开始的不是0。
    利用这些,生成一个新的键值对RDD: logRDDv2,里面的键是日期,每个键对应的值是加和加出来的数量。

    然后,按时间排序,保存

    每日ip

    生成一个,键是日期,值是ip的键值对rdd

    val logRDDv3 = logRDDv1.map(x => (x._2, x._1))
    

    调用distinct给这个rDD去重,再给去完重的RDD统计每日独立的ip访问数,因为distinct完成之后,每一行就是说这一天有这个ip访问,然后同一天的行数就是这一天访问服务器的独立ip数。
    所以就按值相加生成一个新的键值对RDD喽。

    val logRDDv4 = logRDDv3.distinct()
    val logRDDv5 = logRDDv4.map(x => (x._1, 1)).reduceByKey(_ + _)
    val DIP = logRDDv5.collect()
    

    每种HTTP的状态数

    下面就基本是和统计每日独立ip和pv差不多的操作了
    比如这个统计每种HTTP的状态数,就是按每一行的http状态的那个属性生成一个键值对rdd然后就按照相同的键相加,就得到了每种http的状态数。和Wordcount那个例子其实区别不大。

    val logRDDv6 = logRDDv1.map(x => (x._4, 1)).reduceByKey(_ + _)
    val StatusPV = logRDDv6.sortByKey().collect()
    

    不同的独立ip访问量

    嗯。还是一样的操作

    val logRDDv7 = logRDDv1.map(x => (x._1, 1)).reduceByKey(_ + _)
    val IPPV = logRDDv7.sortBy(x => x._2, false).collect()
    

    这里的话,我需要注意一下这个sortBy,因为以前我觉得在scala里面排序一般都是sortByKey,但是其实sortBy就可以做按键值对的“值”来排序。
    sortBy函数的第二个参数传入false的意思让它降序排序。其实还有第三个参数,用来决定排序完之后的RDD的分区个数。详见参考资料。

    不同页面的访问量

    这个如果没有教程的话,我应该会直接像上面那样做完了就拉到了
    就这样

    val logRDDv8 = logRDDv1.map(x => (x._3, 1)).reduceByKey(_ + _)
    val PagePV = logRDDv8.sortBy(x => x._2, false).collect()
    


    但是,就像教程说的:

    通过查看 PagePV 我们发现有大量的 js 文件的访问,这不是我们需要的内容,

    然后,教程提供了一个很巧妙地去除有问题的页面访问的办法

    一看,这不就是上次学的那个停词表么,只不过这个停的不是词,是文件的后缀名
    就,属于这个列表的就不要,不是这个列表里面的就统计,然后根据这个生成新的rdd,再用新的rdd做上面统计ip那些操作。

    参考资料

    Scala字符串中的查找模式
    正则表达式手册
    菜鸟工具 正则表达式在线测试
    class Some
    sortByKey和sortBy函数详解

  • 相关阅读:
    nVelocity .NET的模板引擎(template engine) 转载
    NewWords/700800
    美剧字幕杀手之王第1季 第1集
    美剧字幕傲骨贤妻第1季 第1集
    NewWords/9001000
    示例代码控制设备背景代码
    算法把图像压缩成指定大小的代码
    获取和设置 iphone 屏幕亮度
    美剧字幕傲骨贤妻第1季 第2集
    NewWords/600700
  • 原文地址:https://www.cnblogs.com/ltl0501/p/12182285.html
Copyright © 2011-2022 走看看