第六課 hive日志分析案例

1.1 項目來源

本次實踐的目的就在于通過對該技術論壇網站的tomcat access log日志進行分析,計算該論壇的一些關鍵指標,供運營者進行決策時參考。

PS:開發該系統的目的是為了獲取一些業務相關的指標,這些指標在第三方工具中無法獲得的;

1.2 數據情況

該論壇數據有兩部分:

(1)歷史數據約56GB,統計到2012-05-29。這也說明,在2012-05-29之前,日志文件都在一個文件里邊,采用了追加寫入的方式。

(2)自2013-05-30起,每天生成一個數據文件,約150MB左右。這也說明,從2013-05-30之后,日志文件不再是在一個文件里邊。

圖2展示了該日志數據的記錄格式,其中每行記錄有5部分組成:訪問者IP、訪問時間、訪問資源、訪問狀態(HTTP狀態碼)、本次訪問流量。

image.png

圖2 日志記錄數據格式

二、關鍵指標KPI

2.1 瀏覽量PV

image.png

(1)定義:頁面瀏覽量即為PV(Page View),是指所有用戶瀏覽頁面的總和,一個獨立用戶每打開一個頁面就被記錄1 次。

(2)分析:網站總瀏覽量,可以考核用戶對于網站的興趣,就像收視率對于電視劇一樣。

計算公式:記錄計數,從日志中獲取訪問次數。

2.2 注冊用戶數

image.png

該論壇的用戶注冊頁面為member.php,而當用戶點擊注冊時請求的又是member.php?mod=register的url。

計算公式:對訪問member.php?mod=register的url,計數。

2.3 IP數

image.png

(1)定義:一天之內,訪問網站的不同獨立 IP 個數加和。其中同一IP無論訪問了幾個頁面,獨立IP 數均為1。

(2)分析:這是我們最熟悉的一個概念,無論同一個IP上有多少電腦,或者其他用戶,從某種程度上來說,獨立IP的多少,是衡量網站推廣活動好壞最直接的數據。

計算公式:對不同的訪問者ip,計數

2.4 跳出率

image.png

(1)定義:只瀏覽了一個頁面便離開了網站的訪問次數占總的訪問次數的百分比,即只瀏覽了一個頁面的訪問次數 / 全部的訪問次數匯總。

(2)分析:跳出率是非常重要的訪客黏性指標,它顯示了訪客對網站的興趣程度:跳出率越低說明流量質量越好,訪客對網站的內容越感興趣,這些訪客越可能是網站的有效用戶、忠實用戶。

PS:該指標也可以衡量網絡營銷的效果,指出有多少訪客被網絡營銷吸引到宣傳產品頁或網站上之后,又流失掉了,可以說就是煮熟的鴨子飛了。比如,網站在某媒體上打廣告推廣,分析從這個推廣來源進入的訪客指標,其跳出率可以反映出選擇這個媒體是否合適,廣告語的撰寫是否優秀,以及網站入口頁的設計是否用戶體驗良好。

計算公式:①統計一天內只出現一條記錄的ip,稱為跳出數;②跳出數/PV;

處理步驟

1 數據清洗
  使用MapReduce對HDFS中的原始數據進行清洗,以便后續進行統計分析;

2 統計分析
  使用Hive對清洗后的數據進行統計分析;

數據清洗

一、數據情況分析

1.1 數據情況回顧

該論壇數據有兩部分:

(1)歷史數據約56GB,統計到2012-05-29。這也說明,在2012-05-29之前,日志文件都在一個文件里邊,采用了追加寫入的方式。

(2)自2013-05-30起,每天生成一個數據文件,約150MB左右。這也說明,從2013-05-30之后,日志文件不再是在一個文件里邊。

圖1展示了該日志數據的記錄格式,其中每行記錄有5部分組成:訪問者IP、訪問時間、訪問資源、訪問狀態(HTTP狀態碼)、本次訪問流量。

image.png

圖1 日志記錄數據格式

本次使用數據來自于兩個2013年的日志文件,分別為access_2013_05_30.log與access_2013_05_31.log,下載地址為:http://pan.baidu.com/s/1pJE7XR9

1.2 要清理的數據

(1)根據前一篇的關鍵指標的分析,我們所要統計分析的均不涉及到訪問狀態(HTTP狀態碼)以及本次訪問的流量,于是我們首先可以將這兩項記錄清理掉;

(2)根據日志記錄的數據格式,我們需要將日期格式轉換為平常所見的普通格式如20150426這種,于是我們可以寫一個類將日志記錄的日期進行轉換;

(3)由于靜態資源的訪問請求對我們的數據分析沒有意義,于是我們可以將"GET /staticsource/"開頭的訪問記錄過濾掉,又因為GET和POST字符串對我們也沒有意義,因此也可以將其省略掉;

二、數據清洗過程

2.1 定期上傳日志至HDFS

首先,把日志數據上傳到HDFS中進行處理,可以分為以下幾種情況:

(1)如果是日志服務器數據較小、壓力較小,可以直接使用shell命令把數據上傳到HDFS中;

(2)如果日志服務器非常多、數據量大,使用flume進行數據處理;

這里我們的實驗數據文件較小,因此直接采用第一種Shell命令方式。

清洗之前

27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/faq.gif HTTP/1.1" 200 1127
110.52.250.126 - - [30/May/2013:17:38:20 +0800] "GET /data/cache/style_1_widthauto.css?y7a HTTP/1.1" 200 1292
27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/hot_1.gif HTTP/1.1" 200 680
27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/hot_2.gif HTTP/1.1" 200 682

清洗之后

110.52.250.126  20130530173820  data/cache/style_1_widthauto.css?y7a
110.52.250.126  20130530173820  source/plugin/wsh_wx/img/wsh_zk.css
110.52.250.126  20130530173820  data/cache/style_1_forum_index.css?y7a
110.52.250.126  20130530173820  source/plugin/wsh_wx/img/wx_jqr.gif
27.19.74.143    20130530173820  data/attachment/common/c8/common_2_verify_icon.png
27.19.74.143    20130530173820  data/cache/common_smilies_var.js?y7a

2.2 編寫MapReduce程序清理日志

package com.neuedu;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class LogCleanJob {

        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            Configuration conf = new Configuration();

            if (args.length != 2) {
                System.err.println("Usage:Merge and duplicate removal <in> <out>");
                System.exit(2);
            }

            Job job = Job.getInstance(conf, "LogCleanJob");
            job.setJarByClass(LogCleanJob.class);
            job.setMapperClass(MyMapper.class);
            job.setReducerClass(MyReducer.class);
            job.setMapOutputKeyClass(LongWritable.class);
            job.setMapOutputValueClass(Text.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(NullWritable.class);
            FileInputFormat.addInputPath(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
            System.exit(job.waitForCompletion(true) ? 0 : 1);
        }

    static class MyMapper extends
            Mapper<LongWritable, Text, LongWritable, Text> {
        LogParser logParser = new LogParser();
        Text outputValue = new Text();

        protected void map(
                LongWritable key,
                Text value,
                Context context)
                throws java.io.IOException, InterruptedException {
            final String[] parsed = logParser.parse(value.toString());

            // step1.過濾掉靜態資源訪問請求
            if (parsed[2].startsWith("GET /static/")
                    || parsed[2].startsWith("GET /uc_server")) {
                return;
            }
            // step2.過濾掉開頭的指定字符串
            if (parsed[2].startsWith("GET /")) {
                parsed[2] = parsed[2].substring("GET /".length());
            } else if (parsed[2].startsWith("POST /")) {
                parsed[2] = parsed[2].substring("POST /".length());
            }
            // step3.過濾掉結尾的特定字符串
            if (parsed[2].endsWith(" HTTP/1.1")) {
                parsed[2] = parsed[2].substring(0, parsed[2].length()
                        - " HTTP/1.1".length());
            }
            // step4.只寫入前三個記錄類型項
            outputValue.set(parsed[0] + "\t" + parsed[1] + "\t" + parsed[2]);
            context.write(key, outputValue);
        }
    }

    static class MyReducer extends
            Reducer<LongWritable, Text, Text, NullWritable> {
        protected void reduce(
                LongWritable k2,
                Iterable<Text> values,
                Context context)
                throws java.io.IOException, InterruptedException {
                context.write(values.iterator().next(), NullWritable.get());
        }
    }

    /*
     * 日志解析類
     */
    static class LogParser {
        public static final SimpleDateFormat FORMAT = new SimpleDateFormat(
                "d/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);
        public static final SimpleDateFormat dateformat1 = new SimpleDateFormat(
                "yyyyMMddHHmmss");

        public static void main(String[] args) throws ParseException {
            final String S1 = "27.19.74.143 - - [30/May/2013:17:38:20 +0800] \"GET /static/image/common/faq.gif HTTP/1.1\" 200 1127";
            LogParser parser = new LogParser();
            final String[] array = parser.parse(S1);
            System.out.println("樣例數據: " + S1);
            System.out.format(
                    "解析結果:  ip=%s, time=%s, url=%s, status=%s, traffic=%s",
                    array[0], array[1], array[2], array[3], array[4]);
        }

        /**
         * 解析英文時間字符串
         *
         * @param string
         * @return
         * @throws ParseException
         */
        private Date parseDateFormat(String string) {
            Date parse = null;
            try {
                parse = FORMAT.parse(string);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return parse;
        }

        /**
         * 解析日志的行記錄
         *
         * @param line
         * @return 數組含有5個元素,分別是ip、時間、url、狀態、流量
         */
        public String[] parse(String line) {
            String ip = parseIP(line);
            String time = parseTime(line);
            String url = parseURL(line);
            String status = parseStatus(line);
            String traffic = parseTraffic(line);

            return new String[] { ip, time, url, status, traffic };
        }

        private String parseTraffic(String line) {
            final String trim = line.substring(line.lastIndexOf("\"") + 1)
                    .trim();
            String traffic = trim.split(" ")[1];
            return traffic;
        }

        private String parseStatus(String line) {
            final String trim = line.substring(line.lastIndexOf("\"") + 1)
                    .trim();
            String status = trim.split(" ")[0];
            return status;
        }

        private String parseURL(String line) {
            final int first = line.indexOf("\"");
            final int last = line.lastIndexOf("\"");
            String url = line.substring(first + 1, last);
            return url;
        }

        private String parseTime(String line) {
            final int first = line.indexOf("[");
            final int last = line.indexOf("+0800]");
            String time = line.substring(first + 1, last).trim();
            Date date = parseDateFormat(time);
            return dateformat1.format(date);
        }

        private String parseIP(String line) {
            String ip = line.split("- -")[0].trim();
            return ip;
        }
    }
}

一、借助Hive進行統計

1.1 準備工作:建立分區表

image.png

為了能夠借助Hive進行統計分析,首先我們需要將清洗后的數據存入Hive中,那么我們需要先建立一張表。這里我們選擇分區表,以日期作為分區的指標,建表語句如下:(這里關鍵之處就在于確定映射的HDFS位置,我這里是/project/techbbs/cleaned即清洗后的數據存放的位置)

hive> dfs -mkdir -p /project/techbbs/cleaned

hive>CREATE EXTERNAL TABLE techbbs(ip string, atime string, url string) PARTITIONED BY (logdate string) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/project/techbbs/cleaned';

建立了分區表之后,就需要增加一個分區,增加分區的語句如下:(這里主要針對20150425這一天的日志進行分區)

hive>ALTER TABLE techbbs ADD PARTITION(logdate='2015_04_25') LOCATION '/project/techbbs/cleaned/2015_04_25';

hive> load data local inpath '/root/cleaned' into table techbbs3 partition(logdate='2015_04_25');

1.2 使用HQL統計關鍵指標

(1)關鍵指標之一:PV量

頁面瀏覽量即為PV(Page View),是指所有用戶瀏覽頁面的總和,一個獨立用戶每打開一個頁面就被記錄1 次。這里,我們只需要統計日志中的記錄個數即可,HQL代碼如下:

hive>CREATE TABLE techbbs_pv_2015_04_25 AS SELECT COUNT(1) AS PV FROM techbbs WHERE logdate='2015_04_25';

image.png

(2)關鍵指標之二:注冊用戶數

該論壇的用戶注冊頁面為member.php,而當用戶點擊注冊時請求的又是member.php?mod=register的url。因此,這里我們只需要統計出日志中訪問的URL是member.php?mod=register的即可,HQL代碼如下:

hive>CREATE TABLE techbbs_reguser_2015_04_25 AS SELECT COUNT(1) AS REGUSER FROM techbbs WHERE logdate='2015_04_25' AND INSTR(url,'member.php?mod=register')>0;

image.png

(3)關鍵指標之三:獨立IP數

一天之內,訪問網站的不同獨立 IP 個數加和。其中同一IP無論訪問了幾個頁面,獨立IP 數均為1。因此,這里我們只需要統計日志中處理的獨立IP數即可,在SQL中我們可以通過DISTINCT關鍵字,在HQL中也是通過這個關鍵字:

hive>CREATE TABLE techbbs_ip_2015_04_25 AS SELECT COUNT(DISTINCT ip) AS IP FROM techbbs WHERE logdate='2015_04_25';

image.png

(4)關鍵指標之四:跳出用戶數

只瀏覽了一個頁面便離開了網站的訪問次數,即只瀏覽了一個頁面便不再訪問的訪問次數。這里,我們可以通過用戶的IP進行分組,如果分組后的記錄數只有一條,那么即為跳出用戶。將這些用戶的數量相加,就得出了跳出用戶數,HQL代碼如下:

hive>CREATE TABLE techbbs_jumper_2015_04_25 AS SELECT COUNT(1) AS jumper FROM (SELECT COUNT(ip) AS times FROM techbbs WHERE logdate='2015_04_25' GROUP BY ip HAVING times=1) e;

image.png

PS:跳出率是指只瀏覽了一個頁面便離開了網站的訪問次數占總的訪問次數的百分比,即只瀏覽了一個頁面的訪問次數 / 全部的訪問次數匯總。這里,我們可以將這里得出的跳出用戶數/PV數即可得到跳出率。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容