20200608数仓网站流量日志分析项目一

数仓网站流量日志分析项目一

黑马的项目,过了一遍总结就是mr做数据清洗,hive做sql,sqoop导入导出,azkaban做调度,最后Echarts做数据可视化

总结:重点还是hive


1.网站分析意义:
为了赚钱。

网站分析,可以帮助网站管理员、运营人员、推广人员等实时获取网站流量 信息,并从流量来源、网站内容、网站访客特性等多方面提供网站分析的数据依据。
从而帮助提高网站流量,提升网站用户体验,让访客更多的沉淀下来变成会 员或客户,通过更少的投入获取最大化的收入。

首先,网站分析是网站的眼睛。是从网站的营销角度看到的网站分析。在这部分中,网站分析的主要对象是访问者,访问者在网站中的行为以及不同流量之 间的关系。
其次,网站分析是整个网站的神经系统。这是从产品和架构的角度看到的网站分析。在这部分中,网站分析的主要对象是网站的逻辑和结构,网站的导航结 构是否合理,注册购买流程的逻辑是否顺畅。
最后,网站分析是网站的大脑,在这部门中,网站分析的主要分析对象是投资回报率(ROI)。也就是说在现有的情况下,如何合理的分配预算和资源以完成 网站的目标。
终极意义:改善网站的运营,获取更高投资回报率(ROI)。赚更多的钱。

不是访问量越高越好,毕竟多数访问量都是要投钱打广告
所以要分析访问来源-时间粒度,地理位置        访问媒介-目标页面, 新老访客


指标是访问量,就是我们常说的流量。在来源维度、媒介维度、时间维 度、位置维度等维度下,我们可以对访问量进行单独或者重叠的多维度细分

2.3. 网站内容及导航分析(内容分析)
  对于所有网站来说,页面都可以被划分为三个类别:
导航页、功能页、内容页
首页和列表页都是典型的导航页; 站内搜索页面、注册表单页面和购物车页面都是典型的功能页, 而产品详情页、新闻和文章页都是典型的内容页。 导航页的目的是引导访问者找到信息,功能页的目的是帮助访问者完成特定
任务,内容页的目的是向访问者展示信息并帮助访问者进行决策。 比如从内容导航分析中,以下两类行为就是网站运营者不希望看到的行为:


第一个问题:访问者从导航类页面(首页)进入,还没有看到内容类页面(详 情页)之前就从导航类页面(列表页)离开网站。在这次访问中,访问者并没有 完成任务,导航类页面也没有将访问者带入到内容类页面(详情页)中。因此, 需要分析导航类页面(列表页)造成访问者中途离开的原因。
第二个问题:访问者从导航类页面(首页或列表页)进入网站,从内容类页 面(详情页)又返回到导航类页面(首页)。看似访问者在这次访问中完成了任 务(如果浏览内容页就是这个网站的最终目标的话),但其实访问者返回首页是 在开始一次新的导航或任务。说明需要分析内容页的最初设计,并考虑中内容页 提供交叉的信息推荐。


2.4. 网站转化以及漏斗分析(转化分析)
转化,指网站业务流程中的一个封闭渠道,引导用户按照流程最终实现业务 目标(比如商品成交);在这个渠道中,我们希望访问者一路向前,不要回头也 不要离开,直到完成转化目标。


对于转化渠道,主要进行两部分的分析:访问者的流失和迷失。
 转化中的阻力的流失 转化的阻力是造成访问者流失的主要原因之一。这里的阻力包括: 错误的设计、错误的引导 错误的设计包括访问者在转化过程中找不到下一步操作的按钮,无法确认订
单信息,或无法完成支付等。 错误的引导包括在支付过程中提供很多离开的渠道链接,如不恰当的商品或
者活动推荐、对支付环节中专业名称的解释、帮助信息等内容。 造成流失的原因很多,如:
不恰当的商品或活动推荐 对支付环节中专业名词的解释、帮助信息等内容不当
 访问者的迷失 造成迷失的主要原因是转化流量设计不合理,访问者在特定阶段得不到需要 的信息,并且不能根据现有的信息作出决策,比如在线购买演唱会门票,直 到支付也没看到在线选座的提示,这时候就很可能会产生迷失,返回查看。


二:
数据处理过程:
如同我上面说的,数据采集->数据预处理->数据入库(ETL)->数据分析->数据可视化


1.1. 数据采集
  数据采集概念,目前行业会有两种解释:
一是数据从无到有产生的过程(服务器打印的 log、自定义采集的日志等) 叫做数据采集;
另一方面也有把通过使用 Flume 等工具把数据采集搬运到指定位置的这个 过程叫做数据采集。
关于具体含义要结合语境具体分析,明白语境中具体含义即可。

1.2. 数据预处理
数据预处理(data preprocessing)是指在正式处理以前对数据进行的一些
处理。现实世界中数据大体上都是不完整,不一致的脏数据,无法直接进行数据 分析,或者说不利于分析。为了提高数据分析的质量和便捷性产生了数据预处理 技术。
数据预处理有多种方法:数据清理,数据集成,数据变换等。这些数据处理 技术在正式数据分析之前使用,大大提高了后续数据分析的质量与便捷,降低实 际分析所需要的时间。
技术上原则来说,任何可以接受数据经过处理输出数据的语言技术都可以用 来进行数据预处理。比如 java、Python、shell 等。
本项目中通过 MapReduce 程序对采集到的原始日志数据进行预处理,比如 数据清洗,日期格式整理,滤除不合法数据等,并且梳理成点击流模型数据。
使用 MapReduce 的好处在于:一是 java 语言熟悉度高,有很多开源的工具 库便于数据处理,二是 MR 可以进行分布式的计算,并发处理效率高。


1.3. 数据入库
预处理完的结构化数据通常会导入到 Hive 数据仓库中,建立相应的库和表 与之映射关联。这样后续就可以使用 Hive SQL 针对数据进行分析。
因此这里所说的入库是把数据加进面向分析的数据仓库,而不是数据库。因 项目中数据格式比较清晰简明,可以直接 load 进入数据仓库。
实际中,入库过程有个更加专业的叫法—ETL。ETL 是将业务系统的数据经 过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、 标准不统一的数据整合到一起,为企业的决策提供分析依据。


ETL 的设计分三部分:数据抽取、数据的清洗转换、数据的加载。在设计 ETL 的时候我们 也是从这三部分出发。数据的抽取是从各个不同的数据源抽取到 ODS(Operational Data Store,操作型数据存储)中——这个过程也可以做一些数据的清洗和转换),在抽取的过程中 需要挑选不同的抽取方法,尽可能的提高 ETL 的运行效率。ETL 三个部分中,花费时间最长的 是“T”(Transform,清洗、转换)的部分,一般情况下这部分工作量是整个 ETL 的 2/3。数据 的加载一般在数据清洗完了之后直接写入 DW(Data Warehousing,数据仓库)中去。


1.4. 数据分析
本阶段是项目的核心内容,即根据需求使用Hive SQL分析语句,得出指标
各种统计结果。


1.5. 数据可视化 将分析所得数据结果进行数据可视化,一般通过图表进行展示。
数据可视化可以帮你更容易的解释趋势和统计数据。


2. 系统的架构
相对于传统的 BI 数据处理,流程几乎差不多,但是因为是处理大数据,所 以流程中各环节所使用的技术则跟传统 BI 完全不同:
数据采集:页面埋点 JavaScript 采集;开源框架 Apache Flume 数据预处理: Hadoop MapReduce 程序
数据仓库技术:基于 hadoop 的数据仓库 Hive
数据导出:基于 hadoop 的 sqoop 数据导入导出工具 数据可视化:定制开发 web 程序(echarts) 整个过程的流程调度:hadoop 生态圈中的 azkaban 工具

js的埋点代码略过--应该有其他部门实现的吧

通过flume采集数据

58.215.204.118 - - [18/Sep/2018:06:51:35 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.10.2 HTTP/1.1" 304 0 "http://blog.fens.me/nodejs-socketio-chat/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"


note:字段解析:
1、访客 ip 地址: 58.215.204.118
2、访客用户信息: - -
3、请求时间:[18/Sep/2018:06:51:35 +0000]
4、请求方式:GET
5、请求的 url:/wp-includes/js/jquery/jquery.js?ver=1.10.2 6、请求所用协议:HTTP/1.1
7、响应码:304
8、返回的数据流量:0
9、访客的来源 url:http://blog.fens.me/nodejs-socketio-chat/
10 、 访 客 所 用 浏 览 器 : Mozilla/5.0 (Windows NT 5.1; rv:23.0) Firefox/23.0

3 点击流模型数据

3.1 点击流概念

点击流(Click Stream)是指用户在网站上持续访问的轨迹。注重用户浏览 网站的整个流程。用户对网站的每次访问包含了一系列的点击动作行为,这些点 击行为数据就构成了点击流数据(Click Stream Data),它代表了用户浏览网站 的整个流程。
点击流和网站日志是两个不同的概念,点击流是从用户的角度出发,注重用 户浏览网站的整个流程;而网站日志是面向整个站点,它包含了用户行为数据、 服务器响应数据等众多日志信息,我们通过对网站日志的分析可以获得用户的点 击流数据。

点击流模型完全是业务模型,相关概念由业务指定而来。由于大量的指标统 计从点击流模型中更容易得出,所以在预处理阶段,可以使用 MapReduce 程序来 生成点击流模型的数据。
在点击流模型中,存在着两种模型数据:PageViews、Visits。

3.2. 点击流模型 pageviews
Pageviews 模型数据专注于用户每次会话(session)的识别,以及每次 session 内访问了几步和每一步的停留时间。
在网站分析中,通常把前后两条访问记录时间差在 30 分钟以内算成一次会 话。如果超过 30 分钟,则把下次访问算成新的会话开始。
大致步骤如下:
 在所有访问日志中找出该用户的所有访问记录
 把该用户所有访问记录按照时间正序排序
 计算前后两条记录时间差是否为 30 分钟
 如果小于 30 分钟,则是同一会话 session 的延续
 如果大于 30 分钟,则是下一会话 session 的开始
 用前后两条记录时间差算出上一步停留时间
 最后一步和只有一步的 业务默认指定页面停留时间 60s
部分代码如下:


3.3. 点击流模型 visit
Visit 模型专注于每次会话 session 内起始、结束的访问情况信息。比如用 户在某一个会话 session 内,进入会话的起始页面和起始时间,会话结束是从哪 个页面离开的,离开时间,本次 session 总共访问了几个页面等信息。
大致步骤如下:
 在 pageviews 模型上进行梳理
 在每一次回收 session 内所有访问记录按照时间正序排序  第一天的时间页面就是起始时间页面
 业务指定最后一条记录的时间页面作为离开时间和离开页面

数据预处理:

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;


/**
 * 对接外部数据的层,表结构定义最好跟外部数据源保持一致
 * 术语: 贴源表
 * @author
 *
 */
public class WebLogBean implements Writable {

    private boolean valid = true;// 判断数据是否合法
    private String remote_addr;// 记录客户端的ip地址
    private String remote_user;// 记录客户端用户名称,忽略属性"-"
    private String time_local;// 记录访问时间与时区
    private String request;// 记录请求的url与http协议
    private String status;// 记录请求状态;成功是200
    private String body_bytes_sent;// 记录发送给客户端文件主体内容大小
    private String http_referer;// 用来记录从那个页面链接访问过来的
    private String http_user_agent;// 记录客户浏览器的相关信息

    public void set(boolean valid,String remote_addr, String remote_user, String time_local, String request, String status, String body_bytes_sent, String http_referer, String http_user_agent) {
        this.valid = valid;
        this.remote_addr = remote_addr;
        this.remote_user = remote_user;
        this.time_local = time_local;
        this.request = request;
        this.status = status;
        this.body_bytes_sent = body_bytes_sent;
        this.http_referer = http_referer;
        this.http_user_agent = http_user_agent;
    }

    public String getRemote_addr() {
        return remote_addr;
    }

    public void setRemote_addr(String remote_addr) {
        this.remote_addr = remote_addr;
    }

    public String getRemote_user() {
        return remote_user;
    }

    public void setRemote_user(String remote_user) {
        this.remote_user = remote_user;
    }

    public String getTime_local() {
        return this.time_local;
    }

    public void setTime_local(String time_local) {
        this.time_local = time_local;
    }

    public String getRequest() {
        return request;
    }

    public void setRequest(String request) {
        this.request = request;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getBody_bytes_sent() {
        return body_bytes_sent;
    }

    public void setBody_bytes_sent(String body_bytes_sent) {
        this.body_bytes_sent = body_bytes_sent;
    }

    public String getHttp_referer() {
        return http_referer;
    }

    public void setHttp_referer(String http_referer) {
        this.http_referer = http_referer;
    }

    public String getHttp_user_agent() {
        return http_user_agent;
    }

    public void setHttp_user_agent(String http_user_agent) {
        this.http_user_agent = http_user_agent;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.valid);
        sb.append("\001").append(this.getRemote_addr());
        sb.append("\001").append(this.getRemote_user());
        sb.append("\001").append(this.getTime_local());
        sb.append("\001").append(this.getRequest());
        sb.append("\001").append(this.getStatus());
        sb.append("\001").append(this.getBody_bytes_sent());
        sb.append("\001").append(this.getHttp_referer());
        sb.append("\001").append(this.getHttp_user_agent());
        return sb.toString();
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.valid = in.readBoolean();
        this.remote_addr = in.readUTF();
        this.remote_user = in.readUTF();
        this.time_local = in.readUTF();
        this.request = in.readUTF();
        this.status = in.readUTF();
        this.body_bytes_sent = in.readUTF();
        this.http_referer = in.readUTF();
        this.http_user_agent = in.readUTF();

    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeBoolean(this.valid);
        out.writeUTF(null==remote_addr?"":remote_addr);
        out.writeUTF(null==remote_user?"":remote_user);
        out.writeUTF(null==time_local?"":time_local);
        out.writeUTF(null==request?"":request);
        out.writeUTF(null==status?"":status);
        out.writeUTF(null==body_bytes_sent?"":body_bytes_sent);
        out.writeUTF(null==http_referer?"":http_referer);
        out.writeUTF(null==http_user_agent?"":http_user_agent);

    }
}

public class WebLogParser {
    public static SimpleDateFormat df1 = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.US);
    public static SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);

    public static WebLogBean parser(String line) {
        WebLogBean webLogBean = new WebLogBean();
        String[] arr = line.split(" ");
        if (arr.length > 11) {
            webLogBean.setRemote_addr(arr[0]);
            webLogBean.setRemote_user(arr[1]);
            String time_local = formatDate(arr[3].substring(1));
            if(null==time_local || "".equals(time_local)) time_local="-invalid_time-";
            webLogBean.setTime_local(time_local);
            webLogBean.setRequest(arr[6]);
            webLogBean.setStatus(arr[8]);
            webLogBean.setBody_bytes_sent(arr[9]);
            webLogBean.setHttp_referer(arr[10]);

            //如果useragent元素较多,拼接useragent
            if (arr.length > 12) {
                StringBuilder sb = new StringBuilder();
                for(int i=11;i<arr.length;i++){
                    sb.append(arr[i]);
                }
                webLogBean.setHttp_user_agent(sb.toString());
            } else {
                webLogBean.setHttp_user_agent(arr[11]);
            }

            if (Integer.parseInt(webLogBean.getStatus()) >= 400) {// 大于400,HTTP错误
                webLogBean.setValid(false);
            }

            if("-invalid_time-".equals(webLogBean.getTime_local())){
                webLogBean.setValid(false);
            }
        } else {
            webLogBean=null;
        }

        return webLogBean;
    }

    public static void filtStaticResource(WebLogBean bean, Set<String> pages) {
        if (!pages.contains(bean.getRequest())) {
            bean.setValid(false);
        }
    }
    //格式化时间方法
    public static String formatDate(String time_local) {
        try {
            return df2.format(df1.parse(time_local));
        } catch (ParseException e) {
            return null;
        }

    }
}


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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * 处理原始日志,过滤出真实pv请求 转换时间格式 对缺失字段填充默认值 对记录标记valid和invalid
 *
 */
public class WeblogPreProcess {
    static class WeblogPreProcessMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
        // 用来存储网站url分类数据
        Set<String> pages = new HashSet<String>();
        Text k = new Text();
        NullWritable v = NullWritable.get();

        /**
         * 从外部配置文件中加载网站的有用url分类数据 存储到maptask的内存中,用来对日志数据进行过滤
         */
        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
            pages.add("/about");
            pages.add("/black-ip-list/");
            pages.add("/cassandra-clustor/");
            pages.add("/finance-rhive-repurchase/");
            pages.add("/hadoop-family-roadmap/");
            pages.add("/hadoop-hive-intro/");
            pages.add("/hadoop-zookeeper-intro/");
            pages.add("/hadoop-mahout-roadmap/");

        }

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

            String line = value.toString();
            WebLogBean webLogBean = WebLogParser.parser(line);
            if (webLogBean != null) {
                // 过滤js/图片/css等静态资源
                WebLogParser.filtStaticResource(webLogBean, pages);
                /* if (!webLogBean.isValid()) return; */
                k.set(webLogBean.toString());
                context.write(k, v);
            }
        }

    }

    public static void main(String[] args) throws Exception {

        String inPath = "idea/input/project";
        String outpath ="idea/output/project";

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        job.setJarByClass(WeblogPreProcess.class);

        job.setMapperClass(WeblogPreProcessMapper.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

//         FileInputFormat.setInputPaths(job, new Path(args[0]));
//         FileOutputFormat.setOutputPath(job, new Path(args[1]));
        FileInputFormat.setInputPaths(job, new Path(inPath));
        FileOutputFormat.setOutputPath(job, new Path(outpath));

        job.setNumReduceTasks(0);

        boolean res = job.waitForCompletion(true);
        System.exit(res?0:1);

    }
}


然后得到一份清洗了的数据


接下来再做pageviews跟visits的mr

pageviews:

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class PageViewsBean implements Writable {
    private String session;
    private String remote_addr;
    private String timestr;
    private String request;
    private int step;
    private String staylong;
    private String referal;
    private String useragent;
    private String bytes_send;
    private String status;

    public void set(String session, String remote_addr, String useragent, String timestr, String request, int step, String staylong, String referal, String bytes_send, String status) {
        this.session = session;
        this.remote_addr = remote_addr;
        this.useragent = useragent;
        this.timestr = timestr;
        this.request = request;
        this.step = step;
        this.staylong = staylong;
        this.referal = referal;
        this.bytes_send = bytes_send;
        this.status = status;
    }

    public String getSession() {
        return session;
    }

    public void setSession(String session) {
        this.session = session;
    }

    public String getRemote_addr() {
        return remote_addr;
    }

    public void setRemote_addr(String remote_addr) {
        this.remote_addr = remote_addr;
    }

    public String getTimestr() {
        return timestr;
    }

    public void setTimestr(String timestr) {
        this.timestr = timestr;
    }

    public String getRequest() {
        return request;
    }

    public void setRequest(String request) {
        this.request = request;
    }

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public String getStaylong() {
        return staylong;
    }

    public void setStaylong(String staylong) {
        this.staylong = staylong;
    }

    public String getReferal() {
        return referal;
    }

    public void setReferal(String referal) {
        this.referal = referal;
    }

    public String getUseragent() {
        return useragent;
    }

    public void setUseragent(String useragent) {
        this.useragent = useragent;
    }

    public String getBytes_send() {
        return bytes_send;
    }

    public void setBytes_send(String bytes_send) {
        this.bytes_send = bytes_send;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.session = in.readUTF();
        this.remote_addr = in.readUTF();
        this.timestr = in.readUTF();
        this.request = in.readUTF();
        this.step = in.readInt();
        this.staylong = in.readUTF();
        this.referal = in.readUTF();
        this.useragent = in.readUTF();
        this.bytes_send = in.readUTF();
        this.status = in.readUTF();

    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(session);
        out.writeUTF(remote_addr);
        out.writeUTF(timestr);
        out.writeUTF(request);
        out.writeInt(step);
        out.writeUTF(staylong);
        out.writeUTF(referal);
        out.writeUTF(useragent);
        out.writeUTF(bytes_send);
        out.writeUTF(status);

    }
}

import dwproject.WebLogBean;
import org.apache.commons.beanutils.BeanUtils;
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;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 *
 * 将清洗之后的日志梳理出点击流pageviews模型数据
 *
 * 输入数据是清洗过后的结果数据
 *
 * 区分出每一次会话,给每一次visit(session)增加了session-id(随机uuid)
 * 梳理出每一次会话中所访问的每个页面(请求时间,url,停留时长,以及该页面在这次session中的序号)
 * 保留referral_url,body_bytes_send,useragent
 *
 *
 * @author
 *
 */
public class ClickStreamPageView {
    static class ClickStreamMapper extends Mapper<LongWritable, Text, Text, WebLogBean> {

        Text k = new Text();
        WebLogBean v = new WebLogBean();

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

            String line = value.toString();

            String[] fields = line.split("\001");
            if (fields.length < 9) return;
            //将切分出来的各字段set到weblogbean中
            //fields[0].equals("true")
            v.set("true".equals(fields[0]) ? true : false, fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], fields[7], fields[8]);
            //只有有效记录才进入后续处理
            if (v.isValid()) {
                //此处用ip地址来标识用户
                k.set(v.getRemote_addr());
                context.write(k, v);
            }
        }
    }

    static class ClickStreamReducer extends Reducer<Text, WebLogBean, NullWritable, Text> {

        Text v = new Text();

        @Override
        protected void reduce(Text key, Iterable<WebLogBean> values, Context context) throws IOException, InterruptedException {
            ArrayList<WebLogBean> beans = new ArrayList<WebLogBean>();

//            for (WebLogBean b : values) {
//                beans.add(b);
//            }

            // 先将一个用户的所有访问记录中的时间拿出来排序
            try {
                for (WebLogBean bean : values) {
                    WebLogBean webLogBean = new WebLogBean();
                    try {
                        BeanUtils.copyProperties(webLogBean, bean);
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                    beans.add(webLogBean);
                }

                //将bean按时间先后顺序排序
                Collections.sort(beans, new Comparator<WebLogBean>() {

                    @Override
                    public int compare(WebLogBean o1, WebLogBean o2) {
                        try {
                            Date d1 = toDate(o1.getTime_local());
                            Date d2 = toDate(o2.getTime_local());
                            if (d1 == null || d2 == null)
                                return 0;
                            return d1.compareTo(d2);
                        } catch (Exception e) {
                            e.printStackTrace();
                            return 0;
                        }
                    }

                });

                /**
                 * 以下逻辑为:从有序bean中分辨出各次visit,并对一次visit中所访问的page按顺序标号step
                 * 核心思想:
                 * 就是比较相邻两条记录中的时间差,如果时间差<30分钟,则该两条记录属于同一个session
                 * 否则,就属于不同的session
                 *
                 */

                int step = 1;
                String session = UUID.randomUUID().toString();
                for (int i = 0; i < beans.size(); i++) {
                    WebLogBean bean = beans.get(i);
                    // 如果仅有1条数据,则直接输出
                    if (1 == beans.size()) {

                        // 设置默认停留时长为60s
                        v.set(session+"\001"+key.toString()+"\001"+bean.getRemote_user() + "\001" + bean.getTime_local() + "\001" + bean.getRequest() + "\001" + step + "\001" + (60) + "\001" + bean.getHttp_referer() + "\001" + bean.getHttp_user_agent() + "\001" + bean.getBody_bytes_sent() + "\001"
                                + bean.getStatus());
                        context.write(NullWritable.get(), v);
                        session = UUID.randomUUID().toString();
                        break;
                    }

                    // 如果不止1条数据,则将第一条跳过不输出,遍历第二条时再输出
                    if (i == 0) {
                        continue;
                    }

                    // 求近两次时间差
                    long timeDiff = timeDiff(toDate(bean.getTime_local()), toDate(beans.get(i - 1).getTime_local()));
                    // 如果本次-上次时间差<30分钟,则输出前一次的页面访问信息

                    if (timeDiff < 30 * 60 * 1000) {

                        v.set(session+"\001"+key.toString()+"\001"+beans.get(i - 1).getRemote_user() + "\001" + beans.get(i - 1).getTime_local() + "\001" + beans.get(i - 1).getRequest() + "\001" + step + "\001" + (timeDiff / 1000) + "\001" + beans.get(i - 1).getHttp_referer() + "\001"
                                + beans.get(i - 1).getHttp_user_agent() + "\001" + beans.get(i - 1).getBody_bytes_sent() + "\001" + beans.get(i - 1).getStatus());
                        context.write(NullWritable.get(), v);
                        step++;
                    } else {

                        // 如果本次-上次时间差>30分钟,则输出前一次的页面访问信息且将step重置,以分隔为新的visit
                        v.set(session+"\001"+key.toString()+"\001"+beans.get(i - 1).getRemote_user() + "\001" + beans.get(i - 1).getTime_local() + "\001" + beans.get(i - 1).getRequest() + "\001" + (step) + "\001" + (60) + "\001" + beans.get(i - 1).getHttp_referer() + "\001"
                                + beans.get(i - 1).getHttp_user_agent() + "\001" + beans.get(i - 1).getBody_bytes_sent() + "\001" + beans.get(i - 1).getStatus());
                        context.write(NullWritable.get(), v);
                        // 输出完上一条之后,重置step编号
                        step = 1;
                        session = UUID.randomUUID().toString();
                    }

                    // 如果此次遍历的是最后一条,则将本条直接输出
                    if (i == beans.size() - 1) {
                        // 设置默认停留市场为60s
                        v.set(session+"\001"+key.toString()+"\001"+bean.getRemote_user() + "\001" + bean.getTime_local() + "\001" + bean.getRequest() + "\001" + step + "\001" + (60) + "\001" + bean.getHttp_referer() + "\001" + bean.getHttp_user_agent() + "\001" + bean.getBody_bytes_sent() + "\001" + bean.getStatus());
                        context.write(NullWritable.get(), v);
                    }
                }

            } catch (ParseException e) {
                e.printStackTrace();

            }

        }

        private String toStr(Date date) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
            return df.format(date);
        }

        private Date toDate(String timeStr) throws ParseException {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
            return df.parse(timeStr);
        }

        private long timeDiff(String time1, String time2) throws ParseException {
            Date d1 = toDate(time1);
            Date d2 = toDate(time2);
            return d1.getTime() - d2.getTime();

        }

        private long timeDiff(Date time1, Date time2) throws ParseException {

            return time1.getTime() - time2.getTime();

        }

    }

    public static void main(String[] args) throws Exception {

        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        job.setJarByClass(ClickStreamPageView.class);

        job.setMapperClass(ClickStreamMapper.class);
        job.setReducerClass(ClickStreamReducer.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(WebLogBean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

//        FileInputFormat.setInputPaths(job, new Path(args[0]));
//        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        FileInputFormat.setInputPaths(job, new Path("idea/output/project"));
        FileOutputFormat.setOutputPath(job, new Path("idea/output/pageview"));

        job.waitForCompletion(true);

    }
}


VisitBean:


import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class VisitBean implements Writable {
    private String session;
    private String remote_addr;
    private String inTime;
    private String outTime;
    private String inPage;
    private String outPage;
    private String referal;
    private int pageVisits;

    public void set(String session, String remote_addr, String inTime, String outTime, String inPage, String outPage, String referal, int pageVisits) {
        this.session = session;
        this.remote_addr = remote_addr;
        this.inTime = inTime;
        this.outTime = outTime;
        this.inPage = inPage;
        this.outPage = outPage;
        this.referal = referal;
        this.pageVisits = pageVisits;
    }

    public String getSession() {
        return session;
    }

    public void setSession(String session) {
        this.session = session;
    }

    public String getRemote_addr() {
        return remote_addr;
    }

    public void setRemote_addr(String remote_addr) {
        this.remote_addr = remote_addr;
    }

    public String getInTime() {
        return inTime;
    }

    public void setInTime(String inTime) {
        this.inTime = inTime;
    }

    public String getOutTime() {
        return outTime;
    }

    public void setOutTime(String outTime) {
        this.outTime = outTime;
    }

    public String getInPage() {
        return inPage;
    }

    public void setInPage(String inPage) {
        this.inPage = inPage;
    }

    public String getOutPage() {
        return outPage;
    }

    public void setOutPage(String outPage) {
        this.outPage = outPage;
    }

    public String getReferal() {
        return referal;
    }

    public void setReferal(String referal) {
        this.referal = referal;
    }

    public int getPageVisits() {
        return pageVisits;
    }

    public void setPageVisits(int pageVisits) {
        this.pageVisits = pageVisits;
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.session = in.readUTF();
        this.remote_addr = in.readUTF();
        this.inTime = in.readUTF();
        this.outTime = in.readUTF();
        this.inPage = in.readUTF();
        this.outPage = in.readUTF();
        this.referal = in.readUTF();
        this.pageVisits = in.readInt();

    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(session);
        out.writeUTF(remote_addr);
        out.writeUTF(inTime);
        out.writeUTF(outTime);
        out.writeUTF(inPage);
        out.writeUTF(outPage);
        out.writeUTF(referal);
        out.writeInt(pageVisits);

    }

    @Override
    public String toString() {
        return session + "\001" + remote_addr + "\001" + inTime + "\001" + outTime + "\001" + inPage + "\001" + outPage + "\001" + referal + "\001" + pageVisits;
    }
}

import dwproject.pageviews.PageViewsBean;
import org.apache.commons.beanutils.BeanUtils;
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;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;


/**
 * 输入数据:pageviews模型结果数据
 * 从pageviews模型结果数据中进一步梳理出visit模型
 * sessionid  start-time   out-time   start-page   out-page   pagecounts  ......
 *
 * @author
 *
 */
public class ClickStreamVisit {
    // 以session作为key,发送数据到reducer
    static class ClickStreamVisitMapper extends Mapper<LongWritable, Text, Text, PageViewsBean> {

        PageViewsBean pvBean = new PageViewsBean();
        Text k = new Text();

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

            String line = value.toString();
            String[] fields = line.split("\001");
            int step = Integer.parseInt(fields[5]);
            //(String session, String remote_addr, String timestr, String request, int step, String staylong, String referal, String useragent, String bytes_send, String status)
            //299d6b78-9571-4fa9-bcc2-f2567c46df3472.46.128.140-2013-09-18 07:58:50/hadoop-zookeeper-intro/160"https://www.google.com/""Mozilla/5.0"14722200
            pvBean.set(fields[0], fields[1], fields[2], fields[3],fields[4], step, fields[6], fields[7], fields[8], fields[9]);
            k.set(pvBean.getSession());
            context.write(k, pvBean);

        }

    }

    static class ClickStreamVisitReducer extends Reducer<Text, PageViewsBean, NullWritable, VisitBean> {

        @Override
        protected void reduce(Text session, Iterable<PageViewsBean> pvBeans, Context context) throws IOException, InterruptedException {

            // 将pvBeans按照step排序
            ArrayList<PageViewsBean> pvBeansList = new ArrayList<PageViewsBean>();
            for (PageViewsBean pvBean : pvBeans) {
                PageViewsBean bean = new PageViewsBean();
                try {
                    BeanUtils.copyProperties(bean, pvBean);
                    pvBeansList.add(bean);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            Collections.sort(pvBeansList, new Comparator<PageViewsBean>() {

                @Override
                public int compare(PageViewsBean o1, PageViewsBean o2) {

                    return o1.getStep() > o2.getStep() ? 1 : -1;
                }
            });

            // 取这次visit的首尾pageview记录,将数据放入VisitBean中
            VisitBean visitBean = new VisitBean();
            // 取visit的首记录
            visitBean.setInPage(pvBeansList.get(0).getRequest());
            visitBean.setInTime(pvBeansList.get(0).getTimestr());
            // 取visit的尾记录
            visitBean.setOutPage(pvBeansList.get(pvBeansList.size() - 1).getRequest());
            visitBean.setOutTime(pvBeansList.get(pvBeansList.size() - 1).getTimestr());
            // visit访问的页面数
            visitBean.setPageVisits(pvBeansList.size());
            // 来访者的ip
            visitBean.setRemote_addr(pvBeansList.get(0).getRemote_addr());
            // 本次visit的referal
            visitBean.setReferal(pvBeansList.get(0).getReferal());
            visitBean.setSession(session.toString());

            context.write(NullWritable.get(), visitBean);

        }

    }

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        job.setJarByClass(ClickStreamVisit.class);

        job.setMapperClass(ClickStreamVisitMapper.class);
        job.setReducerClass(ClickStreamVisitReducer.class);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(PageViewsBean.class);

        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(VisitBean.class);


//        FileInputFormat.setInputPaths(job, new Path(args[0]));
//        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        FileInputFormat.setInputPaths(job, new Path("idea/output/pageview"));
        FileOutputFormat.setOutputPath(job, new Path("idea/output/visitout"));

        boolean res = job.waitForCompletion(true);
        System.exit(res?0:1);

    }
}

有点考验mr功力,而且ip地址方面其实还可以根据ip库再做一下,mr实在是太麻烦了
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值