环境
  hadoop-2.6.5

  首先要知道为什么要做数据清洗?通过各个渠道收集到的数据并不能直接用于下一步的分析,所以需要对这些数据进行缺失值清洗、格式内容清洗、逻辑错误清洗、非需求数据清洗、关联性验证等处理操作,转换成可用的数据。
具体要做的工作可以参考文章:数据清洗的一些梳理

当了解ETL之后,有一些工具,比如开源kettle可以做这个工作。但是也可以完全自己开发,ETL无非就是三个阶段:数据抽取、数据清洗、清洗后数据存储。比如可借助hadoop、spark、kafka都可以做这个工作,清洗的规则可以按需开发。
这里借助hadoop编写MR来完成ETL工作。

根据架构图设计,ETL之后的数据要存到HBase,所以ETL阶段整个工作分四块:
1、过滤脏数据
2、解析IP-IPSeeker
3、浏览器信息解析-UASparser
4、设计rowkey

Runner:

package com.sxt.etl.mr.ald;

import java.io.IOException;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import org.apache.log4j.Logger; import com.sxt.common.EventLogConstants;
import com.sxt.common.GlobalConstants;
import com.sxt.util.TimeUtil; /**
* 编写mapreduce的runner类
*
*/
public class AnalyserLogDataRunner implements Tool
{
private static final Logger logger = Logger.getLogger(AnalyserLogDataRunner.class);
private Configuration conf = null; public static void main(String[] args)
{
try
{
ToolRunner.run(new Configuration(), new AnalyserLogDataRunner(), args);
}
catch (Exception e)
{
logger.error("执行日志解析job异常", e);
throw new RuntimeException(e);
}
} @Override
public void setConf(Configuration conf) {
conf.set("fs.defaultFS", "hdfs://node1:8020");
//conf.set("yarn.resourcemanager.hostname", "node3");
//conf.set("hbase.zookeeper.quorum", "node1,node2,node3");//用来连接HBase
conf.set("hbase.zookeeper.quorum", "node104");//用来连接HBase
this.conf = HBaseConfiguration.create(conf);
} @Override
public Configuration getConf() {
return this.conf;
} @Override
public int run(String[] args) throws Exception {
Configuration conf = this.getConf();
this.processArgs(conf, args); Job job = Job.getInstance(conf, "analyser_logdata"); // 设置本地提交job,集群运行,需要代码
// File jarFile = EJob.createTempJar("target/classes");
// ((JobConf) job.getConfiguration()).setJar(jarFile.toString());
// 设置本地提交job,集群运行,需要代码结束 job.setJarByClass(AnalyserLogDataRunner.class);
job.setMapperClass(AnalyserLogDataMapper.class);
job.setMapOutputKeyClass(NullWritable.class);
job.setMapOutputValueClass(Put.class);
// 设置reducer配置
// 1. 集群上运行,打成jar运行(要求addDependencyJars参数为true,默认就是true)
// TableMapReduceUtil.initTableReducerJob(EventLogConstants.HBASE_NAME_EVENT_LOGS,null, job);
// 2. 本地运行,要求参数addDependencyJars为false
TableMapReduceUtil.initTableReducerJob(
EventLogConstants.HBASE_NAME_EVENT_LOGS,
null,
job,
null,
null,
null,
null,
false);
job.setNumReduceTasks(0); // 设置输入路径
this.setJobInputPaths(job);
return job.waitForCompletion(true) ? 0 : -1;
} /**
* 处理参数
*
* @param conf
* @param args
*/
private void processArgs(Configuration conf, String[] args) {
String date = null;
for (int i = 0; i < args.length; i++) {
if ("-d".equals(args[i])) {
if (i + 1 < args.length) {
date = args[++i];
break;
}
}
} System.out.println("-----" + date); // 要求date格式为: yyyy-MM-dd
if (StringUtils.isBlank(date) || !TimeUtil.isValidateRunningDate(date)) {
// date是一个无效时间数据
date = TimeUtil.getYesterday(); // 默认时间是昨天
System.out.println(date);
}
conf.set(GlobalConstants.RUNNING_DATE_PARAMES, date);
} /**
* 设置job的输入路径
*
* @param job
*/
private void setJobInputPaths(Job job)
{
Configuration conf = job.getConfiguration();
FileSystem fs = null;
try
{
fs = FileSystem.get(conf);
String date = conf.get(GlobalConstants.RUNNING_DATE_PARAMES);
// Path inputPath = new Path("/flume/" +
// TimeUtil.parseLong2String(TimeUtil.parseString2Long(date),
// "MM/dd/"));
Path inputPath = new Path("/log/"
+ TimeUtil.parseLong2String(
TimeUtil.parseString2Long(date), "yyyyMMdd")
+ "/"); if (fs.exists(inputPath))
{
FileInputFormat.addInputPath(job, inputPath);
}
else
{
throw new RuntimeException("文件不存在:" + inputPath);
}
}
catch (IOException e)
{
throw new RuntimeException("设置job的mapreduce输入路径出现异常", e);
}
finally
{
if (fs != null)
{
try
{
fs.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
} }

Mapper:

package com.sxt.etl.mr.ald;

import java.io.IOException;
import java.util.Map;
import java.util.zip.CRC32; import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.log4j.Logger; import com.sxt.common.EventLogConstants;
import com.sxt.common.EventLogConstants.EventEnum;
import com.sxt.etl.util.LoggerUtil; /**
* 自定义数据解析map类
*
* @author root
*
*/
public class AnalyserLogDataMapper extends Mapper<LongWritable, Text, NullWritable, Put>
{
private final Logger logger = Logger.getLogger(AnalyserLogDataMapper.class);
private int inputRecords, filterRecords, outputRecords; // 主要用于标志,方便查看过滤数据
private byte[] family = Bytes.toBytes(EventLogConstants.EVENT_LOGS_FAMILY_NAME);
private CRC32 crc32 = new CRC32(); @Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException
{
this.inputRecords++;
this.logger.debug("Analyse data of :" + value); try
{
// 解析日志
Map<String, String> clientInfo = LoggerUtil.handleLog(value.toString()); // 过滤解析失败的数据
if (clientInfo.isEmpty()) {
this.filterRecords++;
return;
} // 获取事件名称
String eventAliasName = clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_EVENT_NAME);
EventEnum event = EventEnum.valueOfAlias(eventAliasName);
switch (event) {
case LAUNCH:
case PAGEVIEW:
case CHARGEREQUEST:
case CHARGEREFUND:
case CHARGESUCCESS:
case EVENT:
// 处理数据
this.handleData(clientInfo, event, context);
break;
default:
this.filterRecords++;
this.logger.warn("该事件没法进行解析,事件名称为:" + eventAliasName);
}
}
catch (Exception e)
{
this.filterRecords++;
this.logger.error("处理数据发出异常,数据:" + value, e);
}
} @Override
protected void cleanup(Context context) throws IOException, InterruptedException {
super.cleanup(context);
logger.info("输入数据:" + this.inputRecords + ";输出数据:" + this.outputRecords + ";过滤数据:" + this.filterRecords);
} /**
* 具体处理数据的方法
*
* @param clientInfo
* @param context
* @param event
* @throws InterruptedException
* @throws IOException
*/
private void handleData(Map<String, String> clientInfo, EventEnum event,Context context) throws IOException, InterruptedException
{
String uuid = clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_UUID);
String memberId = clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_MEMBER_ID);
String serverTime = clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_SERVER_TIME);
if (StringUtils.isNotBlank(serverTime))
{
// 要求服务器时间不为空
clientInfo.remove(EventLogConstants.LOG_COLUMN_NAME_USER_AGENT); // 浏览器信息去掉
String rowkey = this.generateRowKey(uuid, memberId, event.alias, serverTime); // timestamp
Put put = new Put(Bytes.toBytes(rowkey));
for (Map.Entry<String, String> entry : clientInfo.entrySet())
{
if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue()))
{
put.add(family, Bytes.toBytes(entry.getKey()), Bytes.toBytes(entry.getValue()));
}
}
context.write(NullWritable.get(), put);
this.outputRecords++;
}
else
{
this.filterRecords++;
}
} /**
* 根据uuid memberid servertime创建rowkey
*
* @param uuid
* @param memberId
* @param eventAliasName
* @param serverTime
* @return
*/
private String generateRowKey(String uuid, String memberId, String eventAliasName, String serverTime)
{
StringBuilder sb = new StringBuilder();
sb.append(serverTime).append("_");
this.crc32.reset();
if (StringUtils.isNotBlank(uuid)) {
this.crc32.update(uuid.getBytes());
}
if (StringUtils.isNotBlank(memberId)) {
this.crc32.update(memberId.getBytes());
}
this.crc32.update(eventAliasName.getBytes());
sb.append(this.crc32.getValue() % 100000000L);
return sb.toString();
}
}

处理日志数据的具体工作类:

package com.sxt.etl.util;

import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map; import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import com.sxt.common.EventLogConstants;
import com.sxt.etl.util.IPSeekerExt.RegionInfo;
import com.sxt.etl.util.UserAgentUtil.UserAgentInfo;
import com.sxt.util.TimeUtil; /**
* 处理日志数据的具体工作类
*
* @author root
*
*/
public class LoggerUtil {
private static final Logger logger = Logger.getLogger(LoggerUtil.class);
private static IPSeekerExt ipSeekerExt = new IPSeekerExt(); /**
* 处理日志数据logText,返回处理结果map集合<br/>
* 如果logText没有指定数据格式,那么直接返回empty的集合
*
* @param logText
* @return
*/
public static Map<String, String> handleLog(String logText)
{
Map<String, String> clientInfo = new HashMap<String, String>();
if (StringUtils.isNotBlank(logText))
{
//192.168.118.1^A1561656575.201^Anode101^A/log.gif?en=e_l&ver=1&pl=website&sdk=js&u_ud=E5631595-EDC2-4B3B-A306-B19576D74DC3&u_sd=C7C0D4E3-7E60-479B-AC1C-2F5305EC20D4&c_time=1561627763553&l=zh-CN&b_iev=Mozilla%2F5.0%20(Windows%20NT%206.1%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F75.0.3770.100%20Safari%2F537.36&b_rst=1920*1080
String[] splits = logText.trim().split(EventLogConstants.LOG_SEPARTIOR);
if (splits.length == 4)
{
// 日志格式为: ip^A服务器时间^Ahost^A请求参数
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_IP, splits[0].trim()); // 设置ip
// 设置服务器时间
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_SERVER_TIME, String.valueOf(TimeUtil.parseNginxServerTime2Long(splits[1].trim())));
int index = splits[3].indexOf("?");
if (index > -1) {
String requestBody = splits[3].substring(index + 1); // 获取请求参数,也就是我们的收集数据
// 处理请求参数
handleRequestBody(requestBody, clientInfo);
// 处理userAgent
handleUserAgent(clientInfo);
// 处理ip地址
handleIp(clientInfo);
} else {
// 数据格式异常
clientInfo.clear();
}
}
}
return clientInfo;
} /**
* 处理ip地址
*
* @param clientInfo
*/
private static void handleIp(Map<String,String> clientInfo) {
if (clientInfo.containsKey(EventLogConstants.LOG_COLUMN_NAME_IP)) {
String ip = clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_IP);
RegionInfo info = ipSeekerExt.analyticIp(ip);
if (info != null) {
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_COUNTRY, info.getCountry());
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_PROVINCE, info.getProvince());
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_CITY, info.getCity());
}
}
} /**
* 处理浏览器的userAgent信息
*
* @param clientInfo
*/
private static void handleUserAgent(Map<String, String> clientInfo) {
if (clientInfo.containsKey(EventLogConstants.LOG_COLUMN_NAME_USER_AGENT)) {
UserAgentInfo info = UserAgentUtil.analyticUserAgent(clientInfo.get(EventLogConstants.LOG_COLUMN_NAME_USER_AGENT));
if (info != null) {
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_OS_NAME, info.getOsName());
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_OS_VERSION, info.getOsVersion());
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_BROWSER_NAME, info.getBrowserName());
clientInfo.put(EventLogConstants.LOG_COLUMN_NAME_BROWSER_VERSION, info.getBrowserVersion());
}
}
} /**
* 处理请求参数
*
* @param requestBody
* @param clientInfo
*/
private static void handleRequestBody(String requestBody, Map<String, String> clientInfo)
{
if (StringUtils.isNotBlank(requestBody))
{
String[] requestParams = requestBody.split("&");
for (String param : requestParams)
{
if (StringUtils.isNotBlank(param))
{
int index = param.indexOf("=");
if (index < 0)
{
logger.warn("没法进行解析参数:" + param + ", 请求参数为:" + requestBody);
continue;
} String key = null, value = null;
try
{
key = param.substring(0, index);
value = URLDecoder.decode(param.substring(index + 1), "utf-8");
}
catch (Exception e)
{
logger.warn("解码操作出现异常", e);
continue;
}
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value))
{
clientInfo.put(key, value);
}
}
}
}
}
}

HBase创建接收清洗后的数据表:

hbase(main)::> create 'eventlog','log'

代码参考:wjy.rar

参考:
开源ETL工具kettle

【电商日志项目之四】数据清洗-ETL的更多相关文章

  1. 【电商日志项目之五】数据分析-MR方式

    环境 hadoop-2.6.5 hbase-0.98.12.1-hadoop2 新增用户指标分析(1)用户分析模块 (2)浏览器分析模块 根据分析效果图,找出分析的维度:用户分析是指某个时间段内的数量 ...

  2. 【电商日志项目之六】数据分析-Hive方式

    环境 hadoop-2.6.5 hive-1.2.1 一.Hive和Hbase整合如果使用Hive进行分析,Hive要从Hbase取数据(当然可以直接将数据存到Hive),那么就需要将Hive和HBa ...

  3. 01-Flutter移动电商实战-项目学习记录

    一直想系统性的学习一下 Flutter,正好看到该课程<Flutter移动电商实战>的百度云资源,共 69 课时,由于怕自己坚持不下去(经常学着学着就不学了),故采用博客监督以记之. 1. ...

  4. 基于spark和flink的电商数据分析项目

    目录 业务需求 业务数据源 用户访问Session分析 Session聚合统计 Session分层抽样 Top10热门品类 Top10活跃Session 页面单跳转化率分析 各区域热门商品统计分析 广 ...

  5. Mall电商实战项目发布重大更新,全面支持SpringBoot 2.3.0

    1. 前言 前面近一个月去写自己的mybatis框架了,对mybatis源码分析止步不前,此文继续前面的文章.开始分析mybatis一,二级缓存的实现. 附上自己的项目github地址:https:/ ...

  6. 电商网站项目Angular+Bootstrap+Node+Express+Mysql

    1.登陆 2.注册 3.主页 4.购物车 5.管理中心 6.文件上传 代码: https://github.com/Carol0311/min_Shop.git 后期会持续进行功能更新以及开发阶段遇到 ...

  7. Cloudera Hadoop 4 实战课程(Hadoop 2.0、集群界面化管理、电商在线查询+日志离线分析)

    课程大纲及内容简介: 每节课约35分钟,共不下40讲 第一章(11讲) ·分布式和传统单机模式 ·Hadoop背景和工作原理 ·Mapreduce工作原理剖析 ·第二代MR--YARN原理剖析 ·Cl ...

  8. 16套java架构师,高并发,高可用,高性能,集群,大型分布式电商项目实战视频教程

    16套Java架构师,集群,高可用,高可扩展,高性能,高并发,性能优化,设计模式,数据结构,虚拟机,微服务架构,日志分析,工作流,Jvm,Dubbo ,Spring boot,Spring cloud ...

  9. 微服务电商项目发布重大更新,打造Spring Cloud最佳实践!

    Spring Cloud实战电商项目mall-swarm地址:转发+关注 私信我获取地址 系统架构图   系统架构图 项目组织结构 mall├── mall-common-- 工具类及通用代码模块├─ ...

随机推荐

  1. 微信小程序~TabBar底部导航切换栏

    底部导航栏这个功能是非常常见的一个功能,基本上一个完成的app,都会存在一个导航栏,那么微信小程序的导航栏该怎么实现呢?经过无数的踩坑,终于实现了,好了,先看看效果图. 对于底部导航栏,小程序上给出的 ...

  2. 树莓派安装C#运行环境

    一. 安装mono ARMv6(一代 Raspberry Pi B+) : http://yunpan.cn/cw6NYzXkD9kHq 访问密码 63ae ARMv7(二代 Raspberry Pi ...

  3. Python学习进阶

    阅读目录 一.python基础 二.python高级 三.python网络 四.python算法与数据结构 一.python基础 人生苦短,我用Python(1) 工欲善其事,必先利其器(2) pyt ...

  4. UVA11424 GCD - Extreme (I)[数论]

    其实这题我也没太明白... 我们要求 \[ \sum_{i=1}^{N-1}\sum_{j=i+1}^Ngcd(i,j) \] 引理: 我们要求\(gcd(i,j)=k\)的个数,可转化为求\(gcd ...

  5. 项目Alpha冲刺 7

    作业描述 课程: 软件工程1916|W(福州大学) 作业要求: 项目Alpha冲刺(团队) 团队名称: 火鸡堂 作业目标: 介绍第7天冲刺的项目进展.问题困难和心得体会 1.团队信息 队名:火鸡堂 队 ...

  6. LeetCode 1049. Last Stone Weight II

    原题链接在这里:https://leetcode.com/problems/last-stone-weight-ii/ 题目: We have a collection of rocks, each ...

  7. 权限管理(chown、chgrp、umask)

    对于文件或目录的权限的修改,只能管理员和文件的所有者拥有此权限,但是对于文件或目录的的所有者的更改,只有管理员拥有此权限(虽然普通用户创建的文件或目录,用户也不能修改文件或目录的所有者). 1.cho ...

  8. zabbix值显示的问题

    虽然在创建监控项的时候,是可以选值类型的,目前有的是整型,浮点型,日志,文本,字符串.但是不要误认为zabbix采集数据的时候就是按照这个格式采集的. zabbix各种接口采集到的数据都是字符串类型, ...

  9. [转]查看 docker 容器使用的资源

    作者:sparkdev 出处:http://www.cnblogs.com/sparkdev/     在容器的使用过程中,如果能及时的掌握容器使用的系统资源,无论对开发还是运维工作都是非常有益的.幸 ...

  10. 虚拟变量陷阱(Dummy Variable Trap)

    虚拟变量陷阱(Dummy Variable Trap):指当原特征有m个类别时,如果将其转换成m个虚拟变量,就会导致变量间出现完全共线性的情况. 假设我们有一个特征“性别”,包含男性和女性两个类别,如 ...