大数据-数据清洗加工技术架构方案

目录

数据清洗加工技术架构方案 3

一. 数据清洗意义、要求、内容 4

1.1 数据清洗的意义与要求 4

1.1.1解决数据完整性问题 4

1.1.2解决数据的唯一性问题 4

1.1.3解决数据的合法性问题 4

1.1.4让数据更适合做挖掘或是展示 5

1.1.5解决高维度问题 5

1.1.6解决维度低或缺少维度问题 5

1.1.7解决无关信息和字段冗余 5

1.1.8解决多指标数值、单位不同问题 5

1.2 数据清洗的内容 5

1.2.1 纠正错误 6

1.2.2 删除重复项 6

1.2.3 统一规格 8

1.2.4 修正逻辑 8

1.2.5  转换构造 9

1.2.6  数据压缩 11

1.2.7  补足残缺/空值 11

1.2.8  丢弃数据/变量 12

二. 数据的获取 13

2.1网络爬虫 13

2.2 Python和Scrapy 框架的安装 13

2.2.1Python爬虫框架安装 13

2.2.2 使用Python爬虫相关说明 14

开发工具 - pycharm 14

爬虫框架 – scrapy 15

项目代码结构 17

1、debug.py 是结合pycharm进行测试的文件 17

2、Item.py 定义的抓取的字段 18

3、middlewares.py 18

4、pipeline.py 19

5、settings.py 19

6、spiders目录中文件 20

代理池修改部分说明 23

2.3 云采集操作相关说明 24

2.3.1基本信息设置 24

2.3.2采集网址规则 24

采集网址规则一: 24

采集网址规则二 25

(1)(下栏点击添加按钮)设置请求方式(Post请求方式需要设置参数) 25

(2)列表网址提取(上下级采集) 26

(3)当没有1级网址时,创建伪链接来抓取数据。 26

(4)Post请求方式 27

配置找不到1级网址的请求方式: 29

1、 找到并打开隐藏的地址 29

2、 找到1级网址链接 30

3、 配置链接 30

2.4列表页全局数据提取 31

2.4.1选择添加按钮,自定义表单 31

2.4.2在定义好的表单中点击添加按钮: 31

(一)、前后标签提取值 31

(二)、正则提取 32

三.数据的处理 32

3.1在项目中使用Hive建模及步骤 33

3.3.1 逻辑模型的创建 33

3.3.2 物理模型的创建 33

3.3.3 将爬虫数据导入表 33

3.4 使用Hive进行数据清洗转换 35

3.4.1 使用HQL清洗数据 35

3.4.2 提取维度信息 35

3.6.1使用HQL清洗数据 36

3.6.2 提取维度信息 36

3.7.1 Hive UDF的开发、部署和调用 36

3.7.2 Python版本的UDF 37

3.8 使用左外连接构造聚合表 37

3.9 让数据处理自动调度 37

3.9.1 HQL的几种执行方式 37

3.9.2 Hive Thrift服务 38

3.9.3 使用JDBC连接Hive 39

3.10 查看任务的运行状态和详细信息 40

3.9.4 Python调用HiveServer服务 41

3.9.5 用crontab实现的任务调度 41

四.数据的存储 44

4.1NoSQL及HBase简介 44

4.2 HBase中的主要概念 45

4.3 HBase客户端及JavaAPI 45

4.4 Hive数据导入HBase的两种方 47

4.4.1 利用既有的JAR包实现整合 47

4.4.2 手动编写MapReduce程序 48

4.5 使用Java API查询HBase中的信息 48

4.3.1 为什么是HBase而非Hive 48

4.3.2 多条件组合查询HBase中的信息 49

五.数据的展示 50

5.1 概述 50

5.2 数据分析的一般步骤 50

5.3 用R来做数据分析展示 50

5.3.1 在Ubuntu上安装R 50

5.3.2 R的基本使用方式 51

5.4 用Hive充当R的数据来源 51

5.4.1 RHive组件 51

5.4.2 把R图表整合到Web页面中 53

六.数据的分析挖掘 57

6.1 基于Spark的数据挖掘技术 57

6.2 Spark和Hadoop的关系 57

6.3 在Ubuntu上安装Spark集群 58

6.3.1 JDK和Hadoop的安装 58

6.3.2 安装Scala 58

6.3.3 安装Spark 58

6.4 Spark的运行方式 59

1. spark-shell命令行 59

2. pyspark命令行 59

3. sparkR命令行 59

6.5 使用Spark替代Hadoop Yarn 60

6.5.1 使用spark-sql查看Hive表 60

6.5.2 在beeline客户端使用Spark引擎 61

6.5.3 在Java代码中引用Spark的 61

6.6 对招聘公司名称做全文检索 61

6.6.1 从HDFS数据源构造JavaRDD 62

6.6.2 把RDD运行结果展现在前端 62

6.6.3 SparkR的安装及启动 63

6.6.4 运行自带的Sample例子 64

数据清洗加工技术架构方案

  • 数据清洗意义、要求、内容

1.1 数据清洗的意义与要求

数据清洗一是为了解决数据质量问题,二是让数据更适合做挖掘。不同的目的下分不同的情况,也都有相应的解决方式和方法。

解决数据质量问题

解决数据质量问题,包括但不限于:

  1. 数据的完整性--例如人的属性中缺少性别、籍贯、年龄等
  2. 数据的唯一性--例如不同来源的数据出现重复的情况
  3. 数据的权威性--例如同一个指标出现多个来源的数据,且数值不一样
  4. 数据的合法性--例如获取的数据与常识不符,年龄大于150岁
  5. 数据的一致性--例如不同来源的不同指标,实际内涵是一样的,或是同一指标内涵不一致

数据清洗的结果是对各种脏数据进行对应方式的处理,得到标准的。干净的。连续的数据,提供给数据统计、数据挖掘等使用。

1.1.1解决数据完整性问题

解决思路:数据缺失,补数据。

  1. 通过其他信息补全,例如使用身份证号码推算性别、籍贯、出生日期、年龄等
  2. 通过前后数据补全,例如时间序列缺数据了,可以使用前后的均值,缺的多了,可以使用平滑处理,如Matlab等。
1.1.2解决数据的唯一性问题
  1. 按主键去重,用sql或是excel“去除重复记录”即可
  2. 按规则去重,编写一系列的规则,对重复情况复杂的数据进行去重。例如不同渠道来的客户数据,可以通过相同的关键信息进行匹配,合并去重。

解决数据的权威性问题

1.1.3解决数据的合法性问题

设定强制合法规则,凡是不在此规则范围内的,强制设为最大值,或者判为无效,剔除

字段类型合法规则:日期字段格式为“2010-10-10”

字段内容合法规则:性别In(男、女、未知);出生日期<=今天

  1. 设定警告规则,凡是不在此规则范围内的,进行警告,然后人工处理

警告规则:年龄》110

  1. 离群值人工特殊处理,使用分箱、聚类、回归等方式发现离群值

解决数据一致性问题

  1. 指标体系(重量)
  2. 维度(分组、统计口径)
  3. 单位
  4. 频度
  5. 数据
1.1.4让数据更适合做挖掘或是展示
  1. 高维度——不适合挖掘
  2. 维度太低——不适合挖掘
  3. 无关信息——减少存储
  4. 字段冗余——一个字段或是其他字段计算出来的,会造成相关系数为1或者主成分分析异常
  5. 多指标数值、单位不同——如GDP与城镇居民人均收入数值相差过大
1.1.5解决高维度问题
  1. 主成分分析
  2. 随机森林
1.1.6解决维度低或缺少维度问题

1各种汇总、平均、加总、最大、最小等

  1. 各种离散化、聚类、自定义分组等
1.1.7解决无关信息和字段冗余
1.1.8解决多指标数值、单位不同问题
  1. 最下-最大
  2. 零-均值
  3. 3.小数定标

1.2数据清洗的内容

数据清洗主要包括:纠正错误、删除重复项、统一规格、修正逻辑、转换构造、数据压缩、补足残缺/空值、丢弃数据/变量。

1.2.1 纠正错误

错误数据是数据源环境中经常出现的一类问题。数据错误的形式包括:

q 数据值错误:数据直接是错误的,例如超过固定域集、超过极值、拼写错误、属性错误、源错误等。

q 数据类型错误:数据的存储类型不符合实际情况,如日期类型的以数值型存储,时间戳存为字符串等。

q 数据编码错误:数据存储的编码错误,例如将UTF-8写成UTF-80。

q 数据格式错误:数据的存储格式问题,如半角全角字符、中英文字符等。

q 数据异常错误:如数值数据输成全角数字字符、字符串数据后面有一个回车操作、日期越界、数据前后有不可见字符等。

q 依赖冲突:某些数据字段间存储依赖关系,例如城市与邮政编码应该满足对应关系,但可能存在二者不匹配的问题。

q 多值错误:大多数情况下,每个字段存储的是单个值,但也存在一个字段存储多个值的情况,其中有些可能是不符合实际业务规则的。

这类错误产生的原因是业务系统不够健全,尤其是在数据产生之初的校验和入库规则不规范,导致在接收输入后没有进行判断或无法检测而直接写入后台数据库造成的。

1.2.2 删除重复项

由于各种原因,数据中可能存在重复记录或重复字段(列),对于这些重复项目(行和列)需要做去重处理。

对于重复项的判断,基本思想是“排序和合并”,先将数据库中的记录按一定规则排序,然后通过比较邻近记录是否相似来检测记录是否重复。这里面其实包含了两个操作,一是排序,二是计算相似度。

常见的排序算法:

q 插入排序

q 冒泡排序

q 选择排序

q 快速排序

q 堆排序

q 归并排序

q 基数排序

q 希尔排序

常见的判断相似度的算法:

q 基本的字段匹配算法

q 标准化欧氏距离

q 汉明距离

q 夹角余弦

q 杰卡德距离

q 马氏距离

q 曼哈顿距离

q 闵可夫斯基距离

q 欧氏距离

q 切比雪夫距离

q 相关系数

q 信息熵

对于重复的数据项,尽量需要经过业务确认并进行整理提取出规则。在清洗转换阶段,对于重复数据项尽量不要轻易做出删除决策,尤其不能将重要的或有业务意义的数据过滤掉,校验和重复确认的工作必不可少。

1.2.3 统一规格

由于数据源系统分散在各个业务线,不同业务线对于数据的要求、理解和规格不同,导致对于同一数据对象描述规格完全不同,因此在清洗过程中需要统一数据规格并将一致性的内容抽象出来。

数据字段的规则大致可以从以下几个方面进行统一:

q 名称,对于同一个数据对象的名称首先应该是一致的。例如对于访问深度这个字段,可能的名称包括访问深度、人均页面浏览量、每访问PV数。

q 类型:同一个数据对象的数据类型必须统一,且表示方法一致。例如普通日期的类型和时间戳的类型需要区分。

q 单位:对于数值型字段,单位需要统一。例如万、十万、百万等单位度量。

q 格式:在同一类型下,不同的表示格式也会产生差异。例如日期中的长日期、短日期、英文、中文、年月日制式和缩写等格式均不一样。

q 长度:同一字段长度必须一致。

q 小数位数:小数位数对于数值型字段尤为重要,尤其当数据量累积较大时会因为位数的不同而产生巨大偏差。

q 计数方法:对于数值型等的千分位、科学计数法等的计数方法的统一。

q 缩写规则:对于常用字段的缩写,例如单位、姓名、日期、月份等的统一。例如将周一表示为Monday还是Mon还是M。

q 值域:对于离散型和连续型的变量都应该根据业务规则进行统一的值域约束。

q 约束:是否允许控制、唯一性、外键约束、主键等的统一。

统一数据规格的过程中,需要重要的一点是确认不同业务线带来数据的规格一致性,这需要业务部门的参与、讨论和确认,以明确不同体系数据的统一标准。

1.2.4 修正逻辑

在多数据源的环境下,很可能存在数据异常或冲突的问题。

例如不同的数据源对于订单数量的数据统计冲突问题,结果出现矛盾的记录。通常,这是由于不同系统对于同一个数据对象的统计逻辑不同而造成的,逻辑的不一致会直接导致结果的差异性;除了统计逻辑和口径的差异,也有因为源数据系统基于性能的考虑,放弃了外键约束,从而导致数据不一致的结果;另外,也存在极小的数据丢失的可能性,通常由于并发量和负载过高、服务器延迟甚至宕机等原因导致的数据采集的差异。

对于这类的数据矛盾,首先需要明确各个源系统的逻辑、条件、口径,然后定义一套符合各个系统采集逻辑的规则,并对异常源系统的采集逻辑进行修正。

某些情况下,也可能存在业务规则的错误导致的数据采集的错误,此时需要从源头纠正错误的采集逻辑,然后再进行数据清洗和转换。

1.2.5  转换构造

数据变换是数据清理过程的重要步骤,是对数据的一个的标准的处理,几乎所有的数据处理过程都会涉及该步骤。数据转换常见的内容包括:数据类型转换、数据语义转换、数据值域转换、数据粒度转换、表/数据拆分、行列转换、数据离散化、数据离散化、提炼新字段、属性构造、数据压缩等。

数据类型转换

当数据来自不同数据源时,不同类型的数据源数据类型不兼容可能导致系统报错。这时需要将不同数据源的数据类型进行统一转换为一种兼容的数据类型。

数据语义转换

传统数据仓库中基于第三范式可能存在维度表、事实表等,此时在事实表中会有很多字段需要结合维度表才能进行语义上的解析。例如,假如字段M的业务含义是浏览器类型,其取值分为是1/2/3/4/5,这5个数字如果不加转换则很难理解为业务语言,更无法在后期被解读和应用。

数据粒度转换

业务系统一般存储的是明细数据,有些系统甚至存储的是基于时间戳的数据,而数据仓库中的数据是用来分析的,不需要非常明细的数据,一般情况下,会将业务系统数据按照数据仓库中不同的粒度需求进行聚合。

表/数据拆分

某些字段可能存储多中数据信息,例如时间戳中包含了年、月、日、小时、分、秒等信息,有些规则中需要将其中部分或者全部时间属性进行拆分,以此来满足多粒度下的数据聚合需求。同样的,一个表内的多个字段,也可能存在表字段拆分的情况。

行列转换

某些情况下,表内的行列数据会需要进行转换(又称为转置),例如协同过滤的计算之前,user和term之间的关系即互为行列并且可相互转换,可用来满足基于项目和基于用户的相似度推荐计算。

数据离散化

将连续取值的属性离散化成若干区间,来帮助消减一个连续属性的取值个数。例如对于收入这个字段,为了便于做统计,根据业务经验可能分为几个不同的区间:0~3000、3001~5000、5001~10000、10001~30000、大于30000,或者在此基础上分别用1、2、3、4、5来表示。

数据标准化

不同字段间由于字段本身的业务含义不同,有些时间需要消除变量之间不同数量级造成的数值之间的悬殊差异。例如将销售额进行离散化处理,以消除不同销售额之间由于量级关系导致的无法进行多列的复合计算。数据标准化过程还可以用来解决个别数值较高的属性对聚类结果的影响。

提炼新字段

很多情况下,需要基于业务规则提取新的字段,这些字段也称为复合字段。这些字段通常都是基于单一字段产生,但需要进行复合运算甚至复杂算法模型才能得到新的指标。

属性构造

有些建模过程中,也会需要根据已有的属性集构造新的属性。例如,几乎所有的机器学习都会讲样本分为训练集、测试集、验证集三类,那么数据集的分类(或者叫分区)就属于需要新构建的属性,用户做机器学习不同阶段的样本使用。

提示 在某些场景中,也存在一些特殊转换方法。例如在机器学习中,有些值是离散型的数据但存在一定意义,例如最高学历这个字段中包含博士、研究生、大

学、高中这4个值,某些算法不支持直接对文本进行计算,此时需要将学历这个字段进行转换。常见的方法是将值域集中的每个值拆解为一个字段,每个字段取值为0或1(布尔型或数值型)。这时,就会出现4个新的字段,对于一条记录来看(通常是一个人),其最高学历只能满足一个,例如字段博士为1,那么其余的字段(研究生、大学、高中)则为0。因此这个过程实际上是将1个字段根据值域(4个值的集合)拆解为4个字段。

1.2.6  数据压缩

数据压缩是指在保持原有数据集的完整性和准确性,不丢失有用信息的前提下,按照一定的算法和方式对数据进行重新组织的一种技术方法。

对大规模的数据进行复杂的数据分析与数据计算通常需要耗费大量时间,所以在这之前需要进行数据的约减和压缩,减小数据规模,而且还可能面临交互式的数据挖掘,根据数据挖掘前后对比对数据进行信息反馈。这样在精简数据集上进行数据挖掘显然效率更高,并且挖掘出来的结果与使用原有数据集所获得结果基本相同。

数据压缩的意义不止体现在数据计算过程中,还有利于减少存储空间,提高其传输、存储和处理效率,减少数据的冗余和存储的空间,这对于底层大数据平台具有非常重要的意义。

数据压缩有多种方式可供选择:

q 数据聚合:将数据聚合后使用,例如如果汇总全部数据,那么基于更粗粒度的数据更加便利。

q 维度约减:通过相关分析手动消除多余属性,使得参与计算的维度(字段)减少;也可以使用主成分分析、因子分析等进行维度聚合,得到的同样是更少的参与计算的数据维度。

q 数据块消减:利用聚类或参数模型替代原有数据,这种方式常见于多个模型综合进行机器学习和数据挖掘。

q 数据压缩:数据压缩包括无损压缩和有损压缩两种类型。数据压缩常用于磁盘文件、视频、音频、图像等。

1.2.7  补足残缺/空值

由于各种主客观原因,很多系统存在残缺数据,残缺数据包含行缺失、列缺失、字段缺失三种情况。行缺失指的是丢失了一整条数据记录,列缺失指的是丢失一整列数据,字段缺失指的是字段中的值为空值。其中空值也分两种情况:

q 缺失值。缺失值指的是的数据原本是必须存在的,但实际上没有数据。例如年龄这个字段每个人都会有,所以如果系统强制验证是不应该为空。

q 空值。空值指的是实际存在可能为空的情况,所以空值不一定是数据问题。例如身份证号这个字段,只有成人之后才有这个字符串,因此也可能存在非成人的用户,所以可能为空。

对于缺失值和空值的填充处理主要包含两种方式:

一是手工填入可能的值;

二是利用规则填充可能的值:某些缺失值可以从本数据源或其它数据源推导出来,这就可以用数据分布的状态和特征,使用众数、中位数、平均值、最大值、最小值填充,或者使用近邻分析甚至更为复杂的概率估计代替缺失的值,从而达到填充的目的,某些情况下也可以直接以未知或unknown填充,这是一种先期不处理而等到后期业务在处理数据的时候再处理的方法。

提示 对缺失数据进行填补后,填入的值可能不正确,数据可能会存在偏置,导致数据并不是十分可靠的。除了明显的可以确定的规则来填充值以外,基于已有属性来预测缺失值是一种流行的方法。假如性别字段部分记录为空,可以将性别字段作为目标变量进行建模分析,对完整样本建模后得出缺失数据性别为男、女的概率,然后进行填充。对于更多的不确定值的数据样本,如果不影响整体计算逻辑的,建议先保持原样;如果会成为计算和建模噪音的数据,则可以采取上述方法进行处理,尽量使其在建模过程的作用消减。

1.2.8  丢弃数据/变量

对于数据中的异常数据,包括缺失值、空值、错误值、不完整的数据记录等,除了使用上面讲的方法进行清洗、转换、提升外,还有另外一种方法——丢弃。丢弃也是提升数据质量的一种方法。丢弃数据的类型包含两种:

q 整条删除,指的是删除含有缺失值的样本。某些情况下,由于各种原因可能存在大量的有某些字段缺失的数据记录,这会导致完整的数据很少,此时需要慎重使用。因此,这只适合关键变量缺失,或者含有无效值或缺失值的样本比重很小的情况。

q 变量删除。如果某一变量的无效值和缺失值很多,而且该变量对于所研究的问题不是特别重要,则可以考虑将该变量删除。这种做法减少了供分析用的变量数目,但没有改变样本量。

提示 数据丢弃或删除操作要慎重执行。一方面,被丢弃的数据很可能存在业务实际意义,而这些意义作为开发人员是不清楚的;另一方面,后期可能会需要针对异常数据进行处理,并成为重要的研究课题。例如,营销领域存在流量欺诈,电商领域存在黄牛订单,银行保险领域存在高风险业务,这些课题对应的底层数据可能都是异常数据。

2.1网络爬虫

是通过一段程序代码来访问某个URL地址,解析其返回的HTML文本字符串的技术。实现简单、获取信息廉价、数据时效性强等,被广泛应用在搜索引擎网页采集和数据挖掘、监测及自动化测试中。例如比价网和12306抢票插件,均使用了爬虫技术。最简单的网络爬虫就是Linux系统中的wget命令,通过代码来实现爬虫,方法更多。根据车间开发团队的技术储备和维护成本,主要选择python、flum、Scrapy。

2.2 Python和Scrapy 框架的安装

2.2.1Python爬虫框架安装
  1. 在Windows安装虚拟机Vmware,并在虚拟机安装操作系统 Ubuntu 14.04,这个系统自带python2.7.6 就是我们要用的。
  2. 在Windows安装工具 SecureCRT, python2.7 Windows版【方便直接学习用,也可以不装】
  3. 在Ubuntu14.04 安装必要软件 

sudo apt-get install openssh-server

sudo apt-get install lrzsz

  1. 安装了openssh-server之后,可以用SecureCRT连接Ubuntu了
  2. 安装mysql  sudo apt-get install

sudo apt-get install mysql-server

  1. 安装MongoDB,具体百度一下MongoDB的官网,上边有Ubuntu14.04 的安装步骤
  2. 安装python相关环境

语言环境和库:

         sudo apt-get install python-dev

         sudo apt-get install python-setuptools

         sudo apt-get install python-pip

         

MySQL的库

         sudo apt-get install libmysqlclient-dev

         sudo pip install mysql-python

         sudo pip install sqlalchemy

Mongo的库

         sudo pip install pymongo

         sudo pip install mongokit

Scrapy相关的库

         sudo pip install greenlet

         sudo pip install gevent

         sudo pip install Scrapy

         sudo pip install Scrapyd

其它库

         sudo pip install bs4

  1. python语言学习

http://docs.pythontab.com/python/python2.7/

Windows上安装的Python2.7的手册

廖雪峰教程

Python教程 - 廖雪峰的官方网站

简明 Python 教程

http://www.kuqin.com/abyteofpython_cn/

  1. Scrapy架构学习

  初窥Scrapy — Scrapy 0.24.6 文档

  1. 下载开发工具pycharm-community-2017.2.3.tar,解压

11、打开

--------------------------

2.2.2 使用Python爬虫相关说明

开发工具 - pycharm

Pycharm windows/linux/mac 三平台都有。PyCharm: the Python IDE for data science and web development

按照自己的需求下载相关的对于的平台的。

左边的专业版需要收费 –免费30天,后续激活可以网上找激活

右边的社区版是免费

爬虫框架 – scrapy

目前版本 1.4

中文教程 Scrapy入门教程 — Scrapy 0.24.6 文档 

·  引擎(Scrapy Engine),用来处理整个系统的数据流处理,触发事务。

·  调度器(Scheduler),用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。

·  下载器(Downloader),用于下载网页内容,并将网页内容返回给蜘蛛。

·  蜘蛛(Spiders),蜘蛛是主要干活的,用它来制订特定域名或网页的解析规则。编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。

·  项目管道(Item Pipeline),负责处理有蜘蛛从网页中抽取的项目,他的主要任务是清晰、验证和存储数据。当页面被蜘蛛解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

·  下载器中间件(Downloader Middlewares),位于Scrapy引擎和下载器之间的钩子框架,主要是处理Scrapy引擎与下载器之间的请求及响应。

·  蜘蛛中间件(Spider Middlewares),介于Scrapy引擎和蜘蛛之间的钩子框架,主要工作是处理蜘蛛的响应输入和请求输出。

·  调度中间件(Scheduler Middlewares),介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应

Scrapy中的数据流由执行引擎控制,其过程如下:

  1. 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
  2. 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
  3. 引擎向调度器请求下一个要爬取的URL。
  4. 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
  5. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
  6. 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
  7. Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
  8. 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
  9. (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。

项目代码结构

所有的pyc文件是python的编译文件忽略

1、debug.py 是结合pycharm进行测试的文件

针对不同的爬虫需要修改的是 scrapy crawl xxxx

2、Item.py 定义的抓取的字段

这个按照自己的需求进行修改即可

3、middlewares.py

这个文件中存在三个类

JdMobileSpiderMiddleware  – 生成项目时自带的。并没有启用,忽略不用管

JdProxyDownloaderMiddleware – 代理下载中间件

主要是针对获取价格时候存在为空时,会获取一个代理IP并完成设置

后续需要修改的是 将url 值换成你们本地部署的

RandomUserAgentMiddleware – 随机请求头下载中间件  这部分不需要修改

4、pipeline.py 

该文件中两个类

JdMobilePipeline – 主要是对抓取的数据进行存储到MySQL数据库中

DownloadImagesPipeline – 图片下载的pipeline

这两个类如果不对item中字段进行修改(数据库对应字段类型长度主键等),该文件中两个类是不需要修改

5、settings.py

该文件在项目创建时自动生成的,主要是对spider的一些配置管理

IMAGES_STORE - 图片的存储的地址(此处写的是项目的根目录下面)

MySQL数据库项目配置

MYSQL_HOST – MySQL数据库地址

MYSQL_PORT – 端口号

MYSQL_USER – 用户名

MYSQL_PASSWD – 密码

MYSQL_DATABASE – 存储的数据库

6、spiders目录中文件

该目录下存在三个py文件

  • jd_mobilephone.py – 老版本的爬虫可以运行,不参考修改

  • jd_mobiles.py – 单独的某个具体的详情页面的测试爬虫

需要对进行的测试的页面的URL添加到start_urls中即可

  • jd_notebook.py 笔记本的爬虫,后期参考这个修改

主要修改的部分如下

去京东的网站,针对某一种产品,点击进入列表页面后,查看筛选条件,如男装

一级页面

二级页面

此时要考虑具体针对某一个商品,此时可以选择 夹克

三级页面

在这个页面针对京东给出的筛选条件,得到相应的url

如上面的页面,可以人群得到六个分类,如下是六个连接

http://search.jd.com/search?keyword=%E5%A4%B9%E5%85%8B%E7%94%B72017&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=T%E6%81%A4%E7%94%B72017&ev=2287_37399%5E&uc=0#J_searchWrap

http://search.jd.com/search?keyword=%E5%A4%B9%E5%85%8B%E7%94%B72017&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=T%E6%81%A4%E7%94%B72017&ev=2287_37401%5E&uc=0#J_searchWrap

http://search.jd.com/search?keyword=%E5%A4%B9%E5%85%8B%E7%94%B72017&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=T%E6%81%A4%E7%94%B72017&ev=2287_37403%5E&uc=0#J_searchWrap

测试以上连接,得到该页面的每个商品的url

通过 scrapy shell ‘xxx.html’ 打开一个scrapy的shell 

response.xpath('//ul[@class="gl-warp clearfix"]//div[@class="p-name"]/a/@href').extract()

测试是否存在结果,如果不存在,请查看页面,相应的修改紫 的xpath语法

XPath 教程 - 学习xpath 语法(够用)

对单独爬虫的设置

主要设置了两项 ,两个pipeline,图片下载和数据库存储

DownloadMiddleWare  - 请求头的随机替换,以及代理IP切换。

该爬虫是继承类 scrapy.Spider.

重写parse函数,后续根据自己的需求进行深度抓取。

yield 后面的值,只能是scrapy.Request对象或者item.

代理池修改部分说明

  1. 结构如图

  1. settings.py – 主配置文件

redis数据库相关配置

  1. getter.py

将以上函数中地址,换成你们购买的IP请求地址,然后验证结果,yield返回正常的数据即可。格式为192.168.120.32

其余地方不需要做修改.

  1. 运行方式

python run.py

2.3 云采集操作相关说明

2.3.1基本信息设置
  1. 引擎名称首页地址栏目名称必填 。 如下图所示
  2. 其它根据实际需要选填

2.3.2采集网址规则

采集网址规则一:

(1)(上栏点击添加按钮)填写要采集的链接地址

(2)网页有分页选择批量网址,使用分页索引替代分页值,没有分页选择普通网址即可。如下图所示

采集网址规则二

(1)(下栏点击添加按钮)设置请求方式(Post请求方式需要设置参数)

(2)列表网址提取(上下级采集)

     如:在品牌首页查看源码,采集价格,找出它们相同的标签,(如果数据写在JavaScript里,请看步骤三的正则提取)。如下图所示

      (3)当没有1级网址时,创建伪链接来抓取数据。

              如:http://data.miit.gov.cn/resultSearch?categoryTreeId=1064&pagenow=2(工信网)

(4)Post请求方式

配置找不到1级网址的请求方式:

  1. 找到并打开隐藏的地址

  1.  找到1级网址链接

  1. 配置链接

2.4列表页全局数据提取

2.4.1选择添加按钮,自定义表单
2.4.2在定义好的表单中点击添加按钮:

  (1)为空值再次提取

(2)过滤或保留标签

(3)内容替换(一般用于标签过滤不掉)

(4)Http请求(正则表达式)

(5)内容截取范围

………….

(一)、前后标签提取值

(二)、正则提取

一般用于页面上隐藏的链接再次Http请求截取。

方法:找出隐藏在JavaScript里的链接,在页面上找到与它相对应的唯一标识(如ID)链接,引入设置的参数,添加Http请求。如下图所示

三.数据的处理

Hive是Hadoop生态系统中重要的组成部分,主要充当数据仓库建模和ETL工具的角色。利用Hive工具,将爬虫抓取下来的数据(如招聘职位信息)放入HDFS后,做数据转换、汇总和检索。

  Apache Hive是基于Hadoop的一个数据仓库工具,可以将结构化的HDFS数据文件映为一个数据库表,并使用SQL查询功能来操作其中的数据。用户提交给Hive的每个SQL语句,都会转换为一个MapReduce任务,放在Hadoop平台上进行运算,最后把结果集展示在Hive中。其优点是学习成本低,可以通过类SQL语快速实现简单的MapReduce统计,不必发专门的MapReduce应用,十分适合数据仓库的统计分析。此外,由于后端有MapReduce计算框架做支撑,即使处理TB级的数据量,操作的可靠性及性能的扩展性也都有保障。

Hive提供了命令行方式来查看HDFS中的数据,并能对数据进行检索、按列求和、求平均值或统计条数等功能。Hive提供了HiveQL语法,可以用来创建数据库、创建表、导入数据、检索数据和删除,语法结构类似于MySQL SQL,它向非编程人员开放了大数据Hadoop生态系统。它常被描述为一个构建于Hadoop之上的数据仓库基础架构。利用Hive工具,数据仓库开发人员在建模和ETL数据处理时,更关注于如何设计而更少关心技术实现。

3.1在项目中使用Hive建模及步骤

在数据仓库系统中,数据库建模是指按照应用主题,对用户业务数据进行抽象,设计合理、关联、统一的数据结构的过程。数据库建模可分为三个阶段:概念模型、逻辑模型和物理模型。数据库建模是数据仓库系统开发的基础性工作,建模结构的优劣将会直接或间接影响到数据的处理效率、性能及扩展性等诸多方面。虽然使用Hive可以很方便地处理HDFS中的数据,但是Hive本身并不是数据库,它并不存储数据。数据是存放在Hadoop HDFS中的,我们用Hive可以定义表,让一个表和HDFS中的某个文件或目录建立对应关系。数据库中表的定义及表字段描述(元数据)是存放在Hive的metaDB中的。正如Hive的中文意思“蜂巢”那样,使用Hive可以非常致密、规则地构造数据仓库系统中的表及其之间的关联关系。具体到飞谷项目中,我们希望把爬虫抓取的数据最终展示在页面上,针对学历要求、工作地点、薪资待遇及工作年限等维度实现检索和统计,并能够对职位名称、公司名称和招聘日期进行检索,

3.3.1 逻辑模型的创建

数据建模的一般方法是针对事实表、中间计算表、维度表依次建

模。

3.3.2 物理模型的创建

数据仓库中物理模型的创建,是指根据逻辑模型定义出的表结构,结合具体的数据库类型,转换成符合语法的DDL语句的过程。在创建表之前,通常会先创建数据库。Hive中数据库的概念,本质上仅仅是表存放的目录或命名空间。使用数据库名称的好处是可以避免表命名冲突,如果用户没有指明具体的数据库,Hive会使用默认的数据库default。本项目中数据库名称是feigu3。创建数据库语法如下:

CREATE DATABASE feigu3;

数据库创建后,Hive会在HDFS文件系统中建立一个对应的目录,数据库中的表将会以这个数据库目录的子目录形式存储。数据库目录的默认位置,在hive-site.conf文件中hive.metastore.warehouse.dir属性所对应的目录下。如果想查看Hive中都有哪些数据库目录,可以使用SHOWDATABASES命令:如果想使用哪个数据库,通过use命令切换数据库。

3.3.3 将爬虫数据导入表

要执行创建表脚本,可以登录Hive客户端beeline,或者使用Hive CLI界面,把每条DDL语句贴进去执行。这样做不具备自动执行的能力。那么,有没有一种简单的方法呢?在Hive中可以使用“-f <文件名>”参数方式从文件中执行Hive查询语句。依照惯例,一般把这些查询语句保存为*.q或*.hql(hive  querylanguage)后缀的文件。我们可以把上面的DDL语句合并在一个文本文件中,命名为create-feigu.hql。或者,在Hive shell中使用source命令执行脚本文件:脚本执行完成后,可以输入show tables命令查看表名:前面提到过,Hive中的每个表在HDFS中都对应一个文件目录,可以在Hive中输入Hadoop的dfs命令查看这些目录的位置:我们看到,目前****表还只是一个文件夹,里面没有任何数据。要把爬虫抓取的数据导入表中,读者可能会很自然想到INSERT语句。在Hive中没有提供行级别的数据插入、数据更新和数据删除功能,往表中“装载”大量的数据Hive提供了两种办法,分别是从外部文件或HQL结果集中将数据批量插入至Hive表。

LOAD DATA命令是从文件将数据加载至Hive表的。如下命令将爬虫抓取的数据加载至表:

LOAD DATA LOCAL INPATH '/home/feigu/scrapy_output/20150501'

OVERWRITE INTO TABLE ****

PARTITION (pt='20150501')

(1)LOCAL INPATH

要加载的数据文件可以有两种不同的来源:LOCAL  INPATH是从执行LOAD命令的本机中的某个目录下加载数据的;没有LOACL参数,只有INPATH是从Hadoop分布式文件系统中的某个目录下加载数据的。INPATH后面引号中的字符串,既可以是一个目录名,也可以是一个文件名。如果指定目录名,Hive会尝试加载该目录下的每个文件。INPATH后面的字符串,通常都会指明一个目录,这样处理起来更加方便,但是不允许再包含子目录。另外,如果LOCAL参数存在,Hive只会复制文件到对应的Hive表目录下;如果LOCAL参数不存在,

则是把HDFS中的一个目录移动到Hive表对应的目录下,原有目录和文件会被删除。

(2)OVERWRITE

如果命令中包含OVERWRITE关键字,Hive表中对应目录下已有的数据会被先删除,然后再加载新的数据;如果没有这个关键字,仅会把INPATH后面字符串中对应的数据追加到Hive表目录下。这就好比是写文件的两种方式:一种是覆盖原有文件内容;一种是在原有文件末尾接着写。

在实际应用场景中,通常OVERWRITE和PARTITION配合使用,实现对某个分区数据的重复写入。比如爬虫抓取当天数据,由于某个招聘网站出现访问故障,数据没有抓取下来,导致最终导入到Hive表中的数据不完整,这时可以通过人工干预的方式,重新抓取完整的数据,再次导入到Hive表中相同日期的分区下。

(3)PARTITION(分区)

在创建****表时,我们用pt(职位发布日期)作为该表的分区字段。在使用LOAD DATA命令导入数据时,也必须指定要把数据文件放在哪个分区下。(pt='20150501')的含义是告诉Hive表,把5月1日爬虫抓取生成的职位文件放在20150501这个目录下。该目录是隶属于****父目录的。由于Python爬虫设定的是每天晚上抓取当日的职位,抓取完成后再导入到相同日期的Hive分区下,这样就使得职位发布时间和目录对应起来。命令执行完成后,可以在HDFS文件系统中查看到所创建的这个目录:在Hive CLI中,可以通过SHOW PARTITIONS命令查看****表中的所有分区:LOAD命令的运行通常是很快的,这是因为Hive并不会验证用户加载的数据和表的模式是否匹配。Hive会验证文件格式是否和表结构定义的一致。比如,定义的存储格式是SEQUENCEFILE,那么加载的文件也必须是相同的格式。数据加载完成后,就可以通过SELECT语句查看特定分区中的数据了。

3.4 使用Hive进行数据清洗转换

3.4.1 使用HQL清洗数据

逻辑模型中的****表和s_job表分别用来存放爬虫抓取的原始数据和针对维度信息去空后的数据。数据从****表到s_job表,使用了HQL语句直接导入,在导入的同时,对维度为空的字段进行了转换。

3.4.2 提取维度信息

在数据仓库中,维度表和事实表是密不可分的。事实表是用户/开发人员所要关注的基本内容;维度表则是观察基本内容的角度。比如在“5月份商品销售量”中,“销售量”就是事实,“5月份”则是维度。

LOAD DATA命令解决了数据导入问题,数据进入到Hive中后,工作才刚刚开始。爬虫抓取下来的招聘数据,可能会存在诸多问题,比如:数据项为空。抓取下来的薪资待遇、工作年限等字段可能为空,空数据会干扰指标的统计结果,必须剔除。检索标准不一致。在设计的Web页面中,“月薪”检索项预先设定了几个标准:一至三万、三至五万、五万及以上。由于不同来源的招聘网站,薪资待遇的设定是不一样的,有的是“8000~10000/月”,有的是“年薪15万”,那么如何与所设定的三个标准进行匹配,得到正确的检索结果呢?

为了解决上述问题,就需要用到ETL技术。ETL是数据抽取(Extract)、转换(Transform)、加载(Load)英文的缩写,ETL技术是构建数据仓库的关键环节,占到整个数据仓库构

建工作量的60%以上;而且,ETL是数据仓库日常维护中问题最多、最烦琐的一部分。所以,选择一个合适的ETL工具就显得尤为重要了。ETL处理流程如3.4所示。

3.4 ETL处理流程Hive作为一个可靠的ETL工具,在高效性、扩展性、容错性等方面

的表现非常突出。Hive的每条操作指令几乎都会转换成MapReduce任务,利用分布式架构来完成运算,这在效率和扩展性上,都比使用传统的数据库存储过程(Stored Procedure)或某种高级语言更为优越;并且Hive面对由于错误数据而导致的程序异常,应对起来更加“从容”,某个节点的程序或硬件发生故障,不会影响整个任务的结果输出。此外,相

对于其他收费的ETL工具(比如Informatic ETL),Hive的开源和免费,一方面,使得用户经验得以在全球范围内积累和传播;另一方面,企业也可以根据需要修改源码来满足更多的应用场景,非常适合于“BAT”类型的公司使用。

3.5 数据清洗转换的必要性

在大多数情况下,原始数据采集完成进入HDFS后,都要进行预处理,将原始的输入数据转换成适合分析的数据形式。但是数据并不完美,缺点表现为:数据缺失。比如缺少属性值,缺少重要的统计属性,或者仅包含加总数据。噪声。包含错误或异常值,例如Salary="-100"。不一致。编码或命名差异,例如品牌=耐克,商品品牌=Nike。数据质量是数据挖掘质量的前提条件,重复或缺失数据会导致错误的统计结果。数据预处理分为数据清理、数据变换和数据集成。

3.6 使用HiveQL清洗数据、提取

3.6.1使用HQL清洗数据
  1. INSERT OVERWRITE TABLE …PARTITION ( …) SELECT
  2. CASE…WHEN…THEN…句式
  3. 表存储格式的转换
  4. 语句运行效率
  5. 关于动态分区
3.6.2 提取维度信息

通过提取维度信息,为同一维度下的不同数据取值做汇总。

方便在生成报表时提供数据来源3.7 定义Hive UDF封装处理逻辑

使用自定义函数(User Defined Function,UDF)的目的是为了封装HQL中整块的处理逻辑,以方便调用。被封装的代码要处理的可能是比较复杂的业务逻辑,如果全部放在HQL中,则既不便于阅读,也不便于维护,Hive支持把这些代码块放在一个函数中,在HQL中直接调用得到结果值的做法。在RDBMS中也有自定义函数的概念,比如在Oracle中就有函数、过程等数据库对象,方便在数据库开发中重复使用。Oracle中的函数代码是用PL/SQL编写的,创建后便可直接调用;但在Hive自定义函数中出现的通常是Java或其他高级语言代码,创建后首先要部署到Hadoop分布式环境中才可以在HQL中调用。Hive安装完成后自带了一些内置函数,使用SHOW FUNCTIONS命令可以查看当前Hive连接中所有的函数列表如下列出了该命令运行的部分结果:如果想使用某个函数,只需要在HQL中调用函数名,传入所需的参数列表即可。比如使用CONCAT函数实现字符串拼接:

3.7.1 Hive UDF的开发、部署和调用

开发Hive自定义函数通常使用Java语言,这是因为Hive本身是用Java编写的,用户只需要简单地继承UDF类并重载evaluate方法即可。

下面描述如何开发UDF实现“薪资待遇”自动归类的功能。

① 在Eclipse中创建一个Java项目。

②  把${hive_HOME}\lib\hive-exec-0.13.0.jar文件和

${HADOOP_HOME}\contrbute\hadoop- common-2.2.0.jar文件添加到所创

建项目的编译路径(build path)中。

③  创建ParseSalary类,继承org.apache.hadoop.hive.ql.exec.UDF父

类,并重载evaluate方法。

3.7.2 Python版本的UDF

倘若不熟悉Java语言,是不是就无法开发Hive自定义函数了呢?利用Hadoop Streaming技术可以解决这个问题。Hadoop的计算框架MapReduce本身是用Java语言开发的,用户要想在这个计算框架上实现自己的应用,就必须使用Java开发相应的代码,这无疑使得操作者的学习曲线变陡了。Hadoop在设计之初就考虑到了这一点,使用Streaming技术来替代Java编程,允许用户使用其他语言实现业务逻辑处理。Streaming采用UNIX标准输入输出机制(stdin/stdout)作为应用程序和Hadoop计算框架之间的数据接口标准,这样就很好地实现了语言无关性,只要符合标准I/O接口,开发人员便可以选择任意语言编写Map/Reduce模块。Streaming模式的流程。

Streaming模式流程

使用非Java语言开发Hive UDF的步骤和上一节类似,也分为开发、部署、调用几个步骤。

3.8 使用左外连接构造聚合表

3.9 让数据处理自动调度

前面我们讨论了如何利用HQL实现数据加载、清洗、转换,ETL的每一个步骤最终都会表现为一个或一组HQL语句。对于一个数据来源复杂、处理逻辑烦琐的生产场景而言,每一次的ETL过程都会用到很多HQL,并且这些HQL之间的数据有依赖关系,必须按照先后次序运行。比如本项目中的数据来源,有各种不同的招聘网站。如果要汇总分析的是各大电商网站的商品信息,那么数据来源和每天的增量数据都会非常多。如何来保证ETL中的所有HQL一个不漏地顺序执行呢?对于创建物理模型DDL来讲,只运行一次也就够了。但爬虫数据是每天都要增量获取的,ETL部分的HQL是每天都要运行的,只有通过程序自动调度,才是值得信赖的解决办法。

3.9.1 HQL的几种执行方式

在前面章节中展示过两种ETL运行方式,分别是使用HiveCLI(Command  Line Interface,命令行接口)和beeline客户端。安装Hive后,在bin目录下有两个可执行脚本,分别是写的hive和beeline文件名,如下所示:Hive命令行界面,也就是CLI,是和Hive交互的最常见方式。使用CLI,用户可以创建表、检查模式以及运行HQL语句等。使用如下命令可以查看CLI所提供的选项列表:beeline工具是Hive  0.11.0版本中增加的新的交互式CLI,它基于SQLLine,可以作为Hive  JDBC客户端访问Hive中的表和数据,执行HQL语句。运行beeline的前提是要先启动HiveServer服务。beeline在使用前要先连接上HiveServer服务,有点像PL/SQL客户端工具的角色,如下所示:使用“!connect  jdbc:hive://hiveServerIP地址:端口号”的格式连接HiveServer。目前Hive提供两种JDBC连接方式,分别是HiveServer和HiveServer2,两者的区别下面会讲到。beeline在连接时要识别用户身份,可以通过配置Kerberos认证来实现;而Hive CLI连接时是不需要用户登录的。HQL在beeline中均可以正常使用,和Hive  CLI的区别是不再输出MapReduce任务执行的日志信息,并且连接HiveServer后,所有的HQL语句执行均使用同一个数据库连接。

下面展示了HQL运行内容:

退出Hive CLI使用exit命令,退出beeline使用!q命令。

另外,还有一种在生产场景中比较实用的运行方式,是把HQL嵌入

shell脚本中执行。下面是一个简单的例子(printJobName.sh)。

#!/bin/sh

#######

# 从控制台接收日期参数

# 打印该日的职位名称

#######

function printJobNameByDate() {

jobdate=$1

echo "USE feigu3; SELECT job_name FROM s_job WHERE pt='$jobdate';" | hive

}

printJobNameByDate $1

执行方式是在控制台输入“sh printJobName.sh 20150501<回车>”,

即可在shell脚本中运行HQL并打印结果。

上面的代码是把参数和HQL拼接成字符串,打印到管道“|”中,hive

命令从管道中取得命令行,得以执行。在echo后可以拼接多条HQL语

句,中间用分号分隔。使用这种方式要注意的是:HQL不宜太复杂;输

出到控制台的内容混合了数据和日志信息;在运行脚本前,还要保证

hive命令已经加入到系统PATH环境变量中。

3.9.2 Hive Thrift服务

在使用beeline连接Hive数据仓库前,要在服务器上开启Hive  Thrift服务。Hive Thrift(HiveServer)服务是Hive中的组件之一,设计目的是为了实现跨语言轻量级访问Hive数仓库。通过Hive CLI访问Hive是胖客户端方式,需要客户机安装JRE环境和Hive程序,就像客户端要连接Oracle数据库服务器,必须安装Oracle客户端一样。而采用JDBC方式访问Hive数据仓库,则是瘦客户端方式,前提是服务器必须先启动Hive  Thrift服务。和使用JDBC方式访问Oracle数据库,要求服务器要启动LISTENER监听服务,道理是一样的。目前,HiveThrift组件包含两个版本,分别是HiveServer(ThriftServer)和HiveServer2,后者是在Hive  0.11.0版本中增加的,原因是HiveServer不能处理多客户端的并发请求。需要注意的

是,HiveServer2不会向下兼容ThriftServer,编码实现时必须分别处理。

两者的差别有三:

(1)启动方式

HiveServer

hive

--service hiveserver

-p <端口号>

HiveServer2

hive

--service hiveserver2

-hiveconf hive.server2.thrift.port=<端口号>

或者,在${HIVE_HOME}\bin\下运行HiveServer2脚

本,端口等配置信息默认从hive-site.xml中读取

(2)beeline/JDBC连接字符串

HiveServer jdbc:hive://hive服务器IP地址:端口号

HiveServer2 jdbc:hive2://hive服务器IP地址:端口号

(3)JDBC驱动名称

HiveServer org.apache.hadoop.hive.service.HiveServer

HiveServer2 org.apache.hive.service.server.HiveServer2

需要注意的是,两个组件启动后都是独立的Java进程,只要端口号

不重复,在一台机器上就可以同时启动两个服务。此外,HiveService服

务可以选择在Hadoop集群环境中的任何一个安装有Hive的节点上启动,

不必一定要放在NameNode上。

3.9.3 使用JDBC连接Hive

使用JDBC接口操作Hive数据库,和操作其他数据库的方式大体相

同,步骤分为查找驱动程序、得到数据源连接、创建

Statement/PreparedStatement语句、执行HQL、关闭数据源连接。下面代

码演示了从daily_dim_sum表读取某个分区中数据的功能。

public void prepStatement() throws Exception, SQLException{

// 加载HiveServer驱动

Class.forName("org.apache.hadoop.hive.jdbc.HiveDriver");

String url = "jdbc:hive://slave02:10001/default";

Connection dbConn =DriverManager.getConnection(url);

// 使用预编译Statement执行HQL

String hql = "SELECT dim_type, cnt_val FROM feigu3.daily_dim_sum WHERE pt=?";

PreparedStatement pstmt = dbConn.prepareStatement(hql);

pstmt.setString(1, "20150501");

ResultSet rs = pstmt.executeQuery();

// 循环结果集

while (rs.next()) {

System.out.println(rs.getString(1) + "," + rs.getString(2));

}

// 关闭连接

rs.close();

dbConn.close();

}

需要注意的是,用于数据清洗转换的ETL  HQL,都是“LOAD DATA”、“SELECT…OVERWRITE”数据写入类的,不是查询类的。在JDBC代码中,要使用Statement的executeUpdate()方法,而不是executeQuery()方法。另外,由于要执行的HQL语句较多,为了便于维护查看,可这些HQL单独放入资源文件中,在JDBC代码中依次调用执行。通常Hive程序开发是在Windows的Eclipse等IDE环境中完成的,而测试/生产环境又都是Linux平台。由于MapReduce任务是分布在多台机器上运行的,使用IDE工具无法进行断点调试,遭遇运行期错误时常常会感到无从下手。下面对Hive调试常见问题做一个总结。

(1)HQL及Java代码

此类问题初学者可能会遇到,Java编码方面的语法问题可以用IDE

工具解决,HQL运行出错多半是语法问题。可以先把HQL在beeline客户

端里面运行一下,保证语法正确了再放入代码中。HQL语法是不区分大

小写的,但是规范的写法是保留字全部使用大写。

(2)Hive环境

这类错误都是SQLException类型的,比如java.sql.SQLException:

Query returned non-zero code: 2, cause: FAILED: Execution Error, return

code 2 from org.apache.hadoop.hive.ql.exec.MapRedTask这种类型的错

误,JDBC直接抛出Hive异常,从异常信息中又读不出错误原因。毫无

疑问,遇到这种问题时要查看日志信息。Hive中的日志分为两种。

Hive中的两种日志系统日志记录了Hive的运行情况、错误状况 记录了Hive 中Job执行的历史过程。

采用Log4j日志方式在hive/conf/hive-log4j.properties文件中记录了Hive 日志的存储位置,默认配置项如下:

hive.root.logger=WARN,DRFA

采用Log4j日志方式

在 hive/conf/hive-sites.xml中有hive.querylog.location属

性,比如:

<property>

<name>hive.querylog.location</name>

<value>/home/hadoop/opt/bigdata/logs/hive/logs</value>

置 hive.log.dir=${java.io.tmpdir}/${user.name}

#

默认的存储位置

hive.log.file=hive.log # 默认的文件名

<description>

Location of Hive run time structured log file

</description>

</property>

(2)Hadoop环境相关

由于Hive HQL语句运行基本上都要转换成MapReduce任务,因此直接查看Hadoop Job运行日志也是最快捷的方法。在大多数应用场景中,直接查看生产环境的运行日志调错是不允许的,可以通过Hadoop提供的Web监控界面来实现。在浏览器中访问集群master节点的默认9005端口可以查看所有任务的运行状态和详细信息,当然,其中也会包含Hive提交的HQL的运行情况。

3.10 查看任务的运行状态和详细信息

此外,如果已经安装了CDH  HUE或Zeus等集群任务管理工具,在其提供的界面中查看日志信息也非常方便。

3.9.4 Python调用HiveServer服务

使用Java JDBC方式操作Hive表中的数据,java文件要编译成class文件才可以使用,并且还需要Hive的JAR包支持。HiveThriftSever也支持用其他语言连接的方式,比如Python语法简单,并且以脚本方式运行,免去了编译的环节。

下面例子展示了如何使用Python调用HiveServer。

(1)找到${HIVE_HOME}/lib/py目录,该目录下的文件是Hive提

供的使用Python语言连接HiveServer的第三方类库,都是以.py结尾的

Python文件。

(2)在py目录下创建showHiveTable.py文件,文件代码如下:

import sys

from hive_service import ThriftHive

from hive_service.ttypes import HiveServerException

from thrift import Thrift

from thrift.transport import TSocket

from thrift.transport import TTransport

from thrift.protocol import TBinaryProtocol

def hiveExe(sql):

try:

transport = TSocket.TSocket('125.0.0.1', 10000)

transport = TTransport.TBufferedTransport(transport)

protocol = TBinaryProtocol.TBinaryProtocol(transport)

client = ThriftHive.Client(protocol)

sport.open()

client.execute('use feigu3')

client.execute(sql)

print "Tables in feigu3 : "

print client.fetchAll()

transport.close()

except Thrift.TException, tx:

print '%s' % (tx.message)

if __name__ == '__main__':

hiveExe("show tables")

(3)由于代码中连接的HiveServer是本机(125.0.0.1),因此首先要在本机上开启HiveThrift服务,然后在控制台上直接运行该文件。python showHiveTable.py如果运行正常,则可以看到feigu3数据库中所有的表名。Python和HiveServer通信使用Socket方式,代码中以“from”开头的内容都是引用Hive库函数,这些库函数类和showHiveTable.py文件位于同级目录下,以便能被该文件引用到。也可以把py目录下的东西加入到Python的第三方包路径中,这样showHiveTable.py就可以在任何一个目录位置运行了。

3.9.5 用SpringBoot实现的任务调度

虽然爬虫实现了根据日期抓取招聘信息功能,但是何时运行爬虫程序,是代码本身无法解决的。启动爬虫的操作和进行HiveETL数据清洗的流程,是由操作人员手工触发的。在实际生产环境中,通常人工操作只会出现在系统异常时必须手动干预的条件下。一般而言,程序都是通过任务调度的方式自动执行的,当执行出错无法继续时,会发出报警信息,以短信或邮件方式通知运维人员。一个可靠的调度系统对于整个系统的稳健运行是至关重要的。调度系统的实现方式也有多种,可以由公司自行研发,或者采购现有的成熟产品。操作系统本身也提供了任务调度的功能,比如Windows系统的“计划任务”和Linux系统的crontab等。但是我们选择更为方便快捷的springboot来写这样的定时任务。

Spingboot实现定时任务的几种方式:

  • 1、Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
  • 2、ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
  • 3、Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
  • 4、Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。

我们选择其中比较好用的Springtask方式:

1.添加依赖<dependencies>

  <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

  </dependency>

  <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter</artifactId>

  </dependency>

  <dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <optional>true</optional>

  </dependency>

  <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-test</artifactId>

    <scope>test</scope>

  </dependency></dependencies>


 

2.创建任务类
@Component@EnableScheduling//@Asyncpublic class AlarmTask {

    /**默认是fixedDelay 上一次执行完毕时间后执行下一轮*/

    @Scheduled(cron = "0/5 * * * * *")

    public void run() throws InterruptedException {

        Thread.sleep(6000);

        System.out.println(Thread.currentThread().getName()+"=====>>>>>使用cron  {}"+(System.currentTimeMillis()/1000));

    }

    /**fixedRate:上一次开始执行时间点之后5秒再执行*/

    @Scheduled(fixedRate = 5000)

    public void run1() throws InterruptedException {

        Thread.sleep(6000);

        System.out.println(Thread.currentThread().getName()+"=====>>>>>使用fixedRate  {}"+(System.currentTimeMillis()/1000));

    }

    /**fixedDelay:上一次执行完毕时间点之后5秒再执行*/

    @Scheduled(fixedDelay = 5000)

    public void run2() throws InterruptedException {

        Thread.sleep(7000);

        System.out.println(Thread.currentThread().getName()+"=====>>>>>使用fixedDelay  {}"+(System.currentTimeMillis()/1000));

    }

    /**第一次延迟2秒后执行,之后按fixedDelay的规则每5秒执行一次*/

    @Scheduled(initialDelay = 2000, fixedDelay = 5000)

    public void run3(){

        System.out.println(Thread.currentThread().getName()+"=====>>>>>使用initialDelay  {}"+(System.currentTimeMillis()/1000));

    }

这样操作可以使同一个线程中串行执行,如果只有一个定时任务,这样做肯定没问题,当定时任务增多,如果一个任务卡死,会导致其他任务也无法执行

对于多线程,在传统的Spring项目中,我们可以在xml配置文件添加task的配置,而在SpringBoot项目中一般使用config配置类的方式添加配置,所以新建一个AsyncConfig类

@Configuration@EnableAsyncpublic class AsyncConfig {

     /*

    此处成员变量应该使用@Value从配置中读取

     */

    private int corePoolSize = 10;

    private int maxPoolSize = 200;

    private int queueCapacity = 10;

    @Bean

    public Executor taskExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(corePoolSize);

        executor.setMaxPoolSize(maxPoolSize);

        executor.setQueueCapacity(queueCapacity);

        executor.initialize();

        return executor;

    }}

@Configuration:表明该类是一个配置类
@EnableAsync:开启异步事件的支持

然后在定时任务的类或者方法上添加@Async 。最后重启项目,每一个任务都是在不同的线程中

四.数据的存储

Hive的时效性不是很强,比如一个简单的HQL调用,实现了后台利用MapReduce计算框架对大规模数据的处理,易用性和可靠性是其主要特点。但时效性不是Hive的强项,比如一个简单的带WHERE条件的SELECT语句,相比其他的RDBMS,执行速度比较慢。另外,Hive表中的数据也不支持单行数据删除和更新。在大数据环境下实现低延迟数据读写,就需要用到HBase。

4.1NoSQL及HBase简介

NoSQL,泛指非关系数据库(Not only SQL)。和关系数据库管理系统(RDBMS)相比,NoSQL不使用SQL作为查询语言。其存储可以不需要固定的表模式,通常也会避免使用RDBMS的JOIN操作,一般都具备水平可扩展的特性。NoSQL的实现具有两个特征:使用硬盘和把随机存储器作为存储载体。按照存储格式来分,NoSQL可以分为4类:键值(Key-Value)存储数据库、列存储数据库、文档型数据库和图形(Graph)数据库。目前比较流行的NoSQL数据库有Casssandra、Lucene、Neo4J、MongoDB和HBase。RDBMS和NoSQL的优缺点比较如表4.1所示。

表4.1 RDBMS和NoSQL的优缺点比较

RDBMS缺点 NoSQL优点高并发读写的瓶颈。Web2.0模式下要实时生成动态页面而无法使用静态化技术,对于每秒上万次的写入DB操作,硬盘I/O存在明显瓶颈扩展性强。每种NoSQL产品都去掉关系型数据库的关系特性,弱关联的数据更容易扩展,使得很容易实现支撑数据从TB级别到PB级别的过度。可扩展性的限制。DB无法像WebServer或App Server那样依靠简单增加节点来平滑扩展性能,往往要停机维护和数据迁移。并发性能好。NoSQL数据库具有良好的读写性能,其得益于它的弱关系性特点,数据库的结构简单。事务一致性的负面影响。保证数据完整性的唯一方法是使用事务,这会消耗数据库资源,而很多Web系统并不需要严格的数据一致性。数据模型灵活。NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。NoSQL允许用户随时添加字段。而对传统RDBMS,增删字段是非常麻烦的事情,尤其是对数据量非常大的表。HBase(Hadoop Database)是一个高可靠、高性能、面向列、可伸缩的分布式数据库系统,它使用类似于GFS的HDFS作为底文件存储系统,在其上运行MapReduce批量处理数据,使用ZooKeeper作为协同服务组件。HBase项目使用Java语言实现,最初是由Google Bigtable原型演化而来的,2007年第一简单可用的HBase版本发布,2008年1月,Hadoop升级为Apache的顶级项目时,HBase作为Hadoop的子项目而存在。后来随着Hadoop版本的提升而不断更新,2010年5月HBase成为Apache的顶级项目。截至2014年年底,HBase的稳定版本是0.96。HBase的运行严重依赖于Hadoop,且二者的版本存在协调关系。在HBase的概念中,HMaster主服务器主要负责利用ZooKeeper为Region服务器分配或移除Region,起负载均衡的作用。RegionServer对应于集群中的一个节点,而一个RegionServer负责管理多个Region。一个Region代表一个表的一部分数据,所以HBase中的一个表可能会需要很多个Region来存储其数据,但是每个Region中的数据并不是杂乱无章的,HBase在管理Region时会给每个Region定义一个rowkey的范围,落在特定范围内的数据将交给特定的Region,从而将负载分摊到多个节点上,充分利用分布式的优点。HBase数据存储示意图如图4.1所示。

图4.1 HBase数据存储示意图另外,为了防止集群中发生master单点故障,HBase中启动多个HMaster实例,通过ZooKeeper的投票机制来保证总有一个HMaster在运行。

4.2 HBase中的主要概念

HBase是一种列式存储的分布式数据库,其核心概念是表(Table)。和传统的数据库一样,表由行和列组成,但HBase把不同的列组成一个列族(Column  Family),同一列的值又可以有多个版本(version)。这种组织形式主要是出于存取性能的考虑。表:在HBas中数据以表的形式存储。表名使用Java  String类型或byte[]字节数组来表示。每个表名对应HDFS中的一个目录结构。创建表时需要指定表名和至少一个列族。列族影响表的物理存储结构。列族:一些列的集合。列族创建好后不能频繁修改,数量不能太多。列族名由可见的字符组成。列族中包含列的数量没有限制,可以有数百万个列。列值没有类型和长度的限定。一个列族中的所有列存储在同一个底层文件(HFile)中。常见的引用列的格式family:qualifier,

其中qualifier是任意的字节数组。行键:HBase中最重要的概念之一,它在表中用来唯一地标示行,其值保存为二进制字节数组。表中的行是按照行键的字典序排列存储的。行键存储示例如图4.2所示,其中第一列以“row-”开头的是表中每一行的行键。

图4.2 行键存储示例

在HBase中行键是默认索引,所以在设计行键时必须考虑检索数据的效率。比如把常用的检索字段组合作为rowkey,并且要保证其唯一性,但rowkey长度又不能太长,太长的rowkey会增加存储开销,降低内存利用率,从而降低索引命中率。另外,rowkey的设计同时要考虑如何使其值能够尽量散列,这样会保证所有的数据不都在一个Region上,避免进行读写时负载集中在个别Region上。单元:行键、列族和列名一起确定了一个单元。存储在单元里的数据称为单元值(Value)。单元值没有数据类型,存储为字节数组byte[]。时间版本:单元值有时间版本,时间版本用时间戳标识,是一个long型数字,默认使用当前时间戳。HBase保留单元值时间版本的数量基于列族进行配置,默认数量是3个。图4.3展示了如何保存两个不同版本的数据。

4.3 HBase客户端及JavaAPI

使用HBase客户端可以方便地操作HBase表中的数据。其中,HBaseshell工具、原生JavaAPI客户端和Thrift客户端是三种最常见的工具。此外,还有REST(Representational State Transfer,表述性状态转移)和Avro。HBase shell是安装HBase时就已经自带的客户端工具,它可以接收用户输入的DDL和DML语句来操作HBase表中的数据,其具体实现是用Ruby语言编写的,并使用JRuby解释器。它有两种使用方式:交互模式和批量模式。和Hive  CLI界面相似,HBase也提供了控制台客户端,可以操作HBase中的数据。客户端工具${HBASE_HOME}\bin目录下,通过输入hbase shell命令进入:

查看HBase集群状态使用status命令,可以看到有两个HMaster:查看HBase版本使用version命令:查看HBase的所有表使用list命令,目前有两个表:在shell工具中操作表的命令包括DDL和DML。DDL是数据定义语言,包括创建表、修改表、上线和下线表、删除表等。创建表使用create命令:上面命令在HBase中创建了表“test1”,同时创建了一个列

族“cf”,“VERSION=>3”的含义是每个单元格都保存3个版本的值,表的HFile文件采用“GZ”压缩方式。在创建表时,并不需要指定列族下的列名。列名是在插入数据时才创建DML是数据操作语言,包括数据写入、删除、查询和清空,不包括更新操作。

put命令按行写入数据,如下所示:上面命令在test1表中插入了两条数据,行键分别

是“rowkey1”和“rowkey2”,第一行包含两列,第二行包含三列。scan命令用于检索表数据,可以指定列族中的特定列、显示某个列的所有时间戳版本或限定返回的记录行数。最简单的语法格式是“scan表名”,如下所示:关于scan命令更多形式的语法组合,可以参考相关手册。退出HBase shell客户端,使用exit命令。HBase官方代码中包含了原生访问客户端,是由Java语言实现的,这也是最直接、最高效的客户端。相关类都在org.apache.hadoop.hbase.client包中,涵盖增、删、改、查等API。主要类包括HTable、HBaseAdmin、Put、Get、Scan、Delete等。在使用原生客户端之前,首先要配置客户端,在

HBaseConfiguration类中创建一个配置实例,让该实例依次加载所依赖的Hadoop和HBas中的多个配置文件。示例代码如下:Configuration conf = HBaseConfiguration.create();

conf.set("dfs.permissions", "false"// 从本地加载配置文件

String hadoop_home = System.getenv("HADOOP_HOME");

String hhase_home = System.getenv("HBASE_HOME");

conf.addResource(new Path(hadoop_home + "/conf/mapred-site.xml"));

conf.addResource(new Path(hadoop_home + "/conf/core-site.xml"));

conf.addResource(new Path(hhase_home + "/conf/hbase-site.xml"));

conf.addResource(new Path(hadoop_home + "/conf/yarn-site.xml"));

HTable类中的方法分为4类:增删查方法、获取表元数据方法、获

取状态信息方法和设置属性方法。HTable中的put方法可以向表中逐条

或批量插入数据。下面代码演示了如何在刚才创建的test1表中插入3行

数据。

public static void insertTableTest(Configuration conf ) {

try {

String tableName = "test1";

HTable table = new HTable(conf, tableName);

for(int i=1; i<=3; i++) {

Put put = new Put(Bytes.toBytes("rowkey"+i));

put.add(Bytes.toBytes("cf"),Bytes.toBytes("q1"), Bytes.toBytes("r"+i+"v1"));

put.add(Bytes.toBytes("cf"),Bytes.toBytes("q2"), Bytes.toBytes("r"+i+"v2"));

table.put(put);

}

table.close();

}

catch… …

}

使用HBaseAdmin类中的getTableDescriptor方法可以获取表的元数据

信息。使用HTable类中的get方法和scan方法可以检索表中的数据。get

方法实现指定rowkey检索,scan方法实现组合条件检索,scan和

FilterList结合可以实现各种组合条件检索。有关HBase Java API的更多

功能,可以参考《HBase权威指南》(人民邮电出版社出版)。

4.4 Hive数据导入HBase的两种方

Hive作为一个数据仓库的客户端工具,本身是不保存数据的,它所

操作的表数据都存放在HDFS中。而在HBase中创建表、插入的数据是

存放在HBase数据库内部的,这种底层文件叫作HFile,它被看作是

HDFS的子集,使用hadoop命令可以看到这个文件。执行如下命令,可

以看到目前HBase中有两个表,表名即目录名:

Hive和HBase整合有两种方案,分别是利用hive_hbase-handler.jar工

具类和编写MapReduce算法把Hive中的数据导入HBase中。

4.4.1 利用既有的JAR包实现整合

在${HIVE_HOME}\lib目录下有hive_hbase-handler.jar文件,利用该

JAR包提供的功能可以实现Hive和HBase之间的数据互通。数据是保存

在HBase中的,而Hive作为一个连接客户端可以读写HBase表中的数

据。首先要在hive-site.xml中配置hive.aux.jars.path属性,值指定为hive-

hbase-handler.jar。代码如下:

<property>

<name>hive.aux.jars.path</name>

<value>file:///home/hadoop/bigdata/hive/lib/hive-hbase-handler-0.13.0.jar,

file:///home/hadoop/bigdata/hive/lib/zookeeper-3.4.3.jar,file:///home/hadoop/bigdat

a/hbase/lib/protobuf-java-2.3.0.jar,file:///home/hadoop/bigdata/hbase/lib/hbase-com

mon-0.94.2-hadoop2.jar</value>

<description>add by jars for java connect hive or hbase</description>

</property>

在Hive中创建城市表hbase_table_city,它包含两个字段,分别是城

市等级和名称。建表语句如下:

CREATE TABLE hbase_table_city(level int, city string)

STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'

WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val")

TBLPROPERTIES ("hbase.table.name" = "t_city");

HBase中的表名叫t_city, Hive表中的level作为HBase中的rowkey, city

的值对应HBase表中的列族“cf1:val”。

在Hive中运行脚本,如下所示:

然后查看HBase数据库,发现也创建了表t_city,如下所示:

在HBase的t_city表中插入3条测试数:

在Hive表中也会看到相应的3条数据。

4.4.2 手动编写MapReduce程序

使用hive_hbase-handler.jar工具类虽然很方便,但不是很灵活。如果想实现把Hive的rpt_job表中每个日期分区下的数据导入HBase,还是采用代码方式实现更加灵活。首先在HBase中创建表rpt_job,DDL语句只有一行,创建一个列族“cf”。hbase(main):006:0> create 'rpt_job', 'cf'将数据从Hive导入到HBase中还有一种方法,就是利用HBase所提供的工具类TableMapReduceUtil来实现。该类在hadoop.hbase.mapreduce包下,使用其中包含TableMapper和TableReducer类来读写表中的数据。TableMapReduceUtil类把从Hive表中读取数据作为Mapper计算模型,写入数据到HBase作为Reducer模型。首先指定要读取的Hive表中的列名和HBase表中的rowkey,作为Mapper的输出。在TableReducer类中重写方法reduce(),Text类型的key作为HBase表的行键rowkey,values枚举列表中存放的是每个列族中的值,把这两者构造成一个Put对象实例,放入reduce方法的输出value中,而reduce方法的输出key是HBase中的表名。

将数据从Hive导入到HBase中,关键点在于如何从Hive表中选出合适的列,组合成HBase表的行键,并把Hive表中的其他字段作为列族插入到HBase表中。HBase表中的行键,和RDBMS中的主键有类似之处。行键的作用主要有二:其一,rowkey是HBase的KeyValue存储结构中的key,作为一条记录的唯一标识;其二,由于行键是以字典顺序从小到大排列存储的,并且HBase要在rowkey上建立索引,按照rowkey对数据进行检索效率是非常高的,因此在设计行键时,要把Hive表中经常用到的检索列组合起来作为行键。

使用TableMapReduceUtil类把数据导入HBase可以分为三步,分别

由Hive2HbaseRptJob、MapFromHiveSeqfile和ReduceToHbase三个类来完

成。

4.5 使用Java API查询HBase中的信息

    利用TableMapReduceUtil工具类把Hive表中的数据导入了HBase。通过爬虫抓取的职位信息,最终是要在检索页面中可以由用户任意查询的。既然是检索,只要有一个数据来源即可,那么为什么不从Hive的rpt_job表中检索,而要利用HBase呢?在HBase中又是如何实现对上面我们抽取的4个维度进行检索的?本节就回答了这些问题。

4.3.1 为什么是HBase而非Hive

Hive在Hadoop生态系统中的定位是数据仓库工具,处理数据而不存储数据。由于HQL在运行时会转换成MapReduce任务,利用Hadoop分布式计算框架,得以可靠地完成大规模数据运算。但Hive自身也有不擅长的地方,比如无法实现单条数据的更新;HQL的SELECT语句运行是相当慢的。传统的RDBMS,比如Oracle,在服务器接收到客户端提交的SQL后,会依次进行语法解析、创建执行计划、执行SQL、返回结果集到客户端等工作。而在Hive CLI界面中执行一条包含WHERE子句的SELECT语句时,在非“本地模式”下,Hive会把HQL转换成MapReduce  Job,提交到Hadoop的Master(JobTracker)节点,由该节点向各个Slave节点分发任务,每个Slave节点又会启动数量不等的TaskTracker进程,每个TaskTracker进程都会启动一个JVM。运算结束后,每个节点上的数据又要进行汇总,最后返回到客户端。即使返回的结果集只有一行数据,这种任务分配的模式也是不变的。Hive查询适用的场合是在不需要实时响应的后台任务模式下,比如每天晚上从Hive表中抽取数据,生成汇总报表的定时任务。而如果是在提供检索服务,需要及时把查询结果呈现给用户的场合下,使用Hive就不合适了。HBase可以解决检索效率问题。在4.4.2节中我们把Hive表中的数据原封不动地导入到了HBase中,目的就是为了利用HBase实现实时数据检索,增强用户体验;而把Hive作为资料库完成对实时性要求不高的数据统计、报表展示功能。

4.3.2 多条件组合查询HBase中的信息

Hbase的数据检索常规使用的是JAVA API中的Htable.get()方法,scan类和filter类,但是实际过程中如果使用JAVA API的话多条件查询过程极度缓慢。所以一般选用apache phoenix进行辅助工作

4.3.2.1 apache phoenix的安装

    1.下载地址:http://mirror.bit.edu.cn/apache/phoenix/

注意habse的版本与phoenix的版本要保持一致,否则运行会报错 

2.上传压缩包:将apache-phoenix-4.10.0-HBase-0.98-bin.tar.gz上传到habse集群中的master节点上

3.解压缩文件 tar -zxvf apche-phoenix-4.10.0-Hbase-0.98.bin.Tar.gz

  1. 配置phoenix

   将 Phoenix 目录下的 phoenix-core-4.10.0-HBase-0.98.jar、phoenix-4.10.0-HBase-0.98-client.jar 拷贝到 hbase 集群各个节点 hbase 安装目录 lib 中。

 比如,你是有master、slave1和slave2以及client,你是把这个phoenix安装在client里,则此刻。你就需拷贝到master、slave1和slave2上。

 然后将 hbase 集群中的配置文件 hbase-site.xml 拷贝到 Phoenix 的 bin 目录下,覆盖原有的配置文件

将 hdfs 集群中的配置文件 core-site.xml、 hdfs-site.xml 拷贝到 Phoenix 的 bin 目录下

最后再配置环境变量

4.3.2.2 使用apache phoenix进行多条件组合查询Hbase中的信息

安装好phoenix之后就可以使用简单的sql语句对hbase中的数据进行快速的查询了

# 查看帮助

0: jdbc:phoenix:thin:url=http://localhost:876> !?

...

# 查看连接

0: jdbc:phoenix:thin:url=http://localhost:876> !list

1 active connection:

 #0  open     jdbc:phoenix:thin:url=http://localhost:8765;serialization=PROTOBUF

# 查询表

0: jdbc:phoenix:thin:url=http://localhost:876> !table

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

| TABLE_CAT  | TABLE_SCHEM  | TABLE_NAME  |  TABLE_TYPE   | REMARKS  | TYPE_NAME  | SELF_REFERENCING_COL_NAME  | REF_GENERATION  | INDEX_STATE  | IMM |

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

|            | SYSTEM       | CATALOG     | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | FUNCTION    | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | LOG         | SYSTEM TABLE  |          |            |                            |                 |              | tru |

|            | SYSTEM       | SEQUENCE    | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | STATS       | SYSTEM TABLE  |          |            |                            |                 |              | fal |

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

# 创建一个数据表 users

0: jdbc:phoenix:thin:url=http://localhost:876> CREATE TABLE users (id INTEGER PRIMARY KEY, username VARCHAR, password VARCHAR);

No rows affected (1.573 seconds)

0: jdbc:phoenix:thin:url=http://localhost:876> !table

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

| TABLE_CAT  | TABLE_SCHEM  | TABLE_NAME  |  TABLE_TYPE   | REMARKS  | TYPE_NAME  | SELF_REFERENCING_COL_NAME  | REF_GENERATION  | INDEX_STATE  | IMM |

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

|            | SYSTEM       | CATALOG     | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | FUNCTION    | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | LOG         | SYSTEM TABLE  |          |            |                            |                 |              | tru |

|            | SYSTEM       | SEQUENCE    | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            | SYSTEM       | STATS       | SYSTEM TABLE  |          |            |                            |                 |              | fal |

|            |              | USERS       | TABLE         |          |            |                            |                 |              | fal |

+------------+--------------+-------------+---------------+----------+------------+----------------------------+-----------------+--------------+-----+

# 写入数据

0: jdbc:phoenix:thin:url=http://localhost:876> UPSERT INTO users (id, username, password) VALUES (1, 'admin', 'Letmein');

1 row affected (0.119 seconds)

0: jdbc:phoenix:thin:url=http://localhost:876>  UPSERT INTO users (id, username, password) VALUES (1, 'kongxx', 'Letmein');

1 row affected (0.033 seconds)

# 查询数据

0: jdbc:phoenix:thin:url=http://localhost:876> select * from users;

+-----+-----------+-----------+

| ID  | USERNAME  | PASSWORD  |

+-----+-----------+-----------+

| 1   | kongxx    | Letmein   |

+-----+-----------+-----------+

1 row selected (0.107 seconds)

五.数据的展示

5.1 概述

存放在Hadoop系统中的数据,如何展示给终端用户?展示时是使用文字描述还是其他方式?

5.2 数据分析的一般步骤

虽然分析工具有很多,但进行数据分析的步骤大体上是相同的,即都经过数据获取(整理)、分析、呈现三个环节。比如以大家都很熟悉的Excel电子表格为例,我们想实现一年内每月销售量的占比图,首先就要把一年的所有订单都录入到Excel里面,这叫作数据获取;然后利用Excel的函数功能进行按月分组、对销量求和,完成数据分析;最后选择柱状图或饼图进行数据呈现。一个好用的数据展示工具,在这三个方面都是很优秀的。除此之外,能适用多种场合,且学习起来容易上手,也是很重要的衡量标准。接下来通过一个实际的例子,讲解在数据清洗加工车间项目中如何分步骤进行数据分析展示。

5.3 用R来做数据分析展示

在数据清洗加工车间项目中,使用R制图系统来完成数据展示环节。R是一套用于统计分析、制图的软件系统和开发环境,是属于GNU范畴的免费、开源软件,官网地址是www.r-project.org。R以其突出的数据分析能力和强大的工具包支持,得到了很多专业统计人员的青睐。由于R具有免费、开源的特点,因此始终有很多的追随者。数据清洗加工车间项目之所以采用R工具,也是看中了其免费、跨平台的优势。并且,Hadoop大数据加上R语言展现,这种组合无论从技术实现还是成本上,都很适合科研院校及初创企业做架构选型使用。例子展示如何以Hadoop HDFS中的数据作为数据源,采用R语言生成统计图表。

5.3.1 在Ubuntu上安装R

在Linux系列操作系统上安装R环境,通常有两种方式:一是在线安装,使用“sudo apt-get install r-base”或“yum”命令,在本机上直接安装;二是下载某个特定版本的R源码包,手动编译安装。本节采用手动编译方式,选择R的3.1.3版本(使用此版本是为了能和Spark整合在一起使用)。下面是在Ubuntu上安装的关键步骤。从CRAN官网(cran.r-project.org)下载R源码包,选择3.1.3版本,存放到本地后,解压缩tar文件。

在编译R源码前,要先保证Java环境已经正确安装,并能访问到环境变量“JAVA_HOME”,如下所示。此处略去JDK安装步骤。由于R编译时要依赖很多系统包,可以使用“sudo apt-get install <包名>”命令预先安装好这些系统包,否则R编译过程可能会出错。下面列出了需要安装的系统包:build-essentialfort77

xorg-dev

liblzma-dev、libblas-dev、gfortran

gcc-multilib

gobjc++

aptitude

libreadline6-dev

tcl-dev、tk-dev

libicu-dev

libpng12-dev、libjpeg-dev、libtiff4-dev、libcairo2-dev

上面最后一行的4个lib包,是为了使R支持图形和生成PDF文件。

将tar文件解压缩到/home/open目录下,使用“./configure”命令配置编

译环境,其中“/home/open/bigdata/R-3.1.1”是指定编译后R文件的安装目

录;“enable-R-shlib”参数可以保证系统lib目录下的动态库能够被R共

享。

“./configure”命令运行完毕后,可能会出现的R安装环境清单内容如

下:

然后,使用“make && make install”命令编译、安装R环境。

安装完毕后,需要设定系统(或用户)的环境变量,新

增“R_HOME”以及在PATH变量中增加R/bin目录。

最后,确认R的安装版本号。

5.3.2 R的基本使用方式

从控制台进入R环境,只需要输入“R <回车>”即可。由于接下来要使用R生成统计图形,所以要确保R环境有生成JPEG/PNG文件的功能。进入R界面,输入“capabilities()”命令,可以看到在当前环境下jpeg和png显示的均为TRUE。如果使用的R环境在编译前未安装libpng/libjpeg等包,要想使R支持JPEG和X11图形方式,可以直接安装Cairo组件

(http://cairographics.org/),它是一个专门实现图形绘制渲染的组件,提供了一套API,也可以在R环境中使用。在安装Cairo组件前,首先要使用“sudo  apt-get  install  libcairo2-dev”命令安装cairo2开发库,然后在R控制台安装Cairo。选择一个镜像网站,正确安装完毕后,会出现如下提示信息:安装完毕后,可以加载Cairo库,并且可以查看到在Cairo中均支持创建png/tiff/pdf等文件类型。使用Cairo组件具有生成散点图等功能。

 5.4 用Hive充当R的数据来源

使用R作为数据挖掘和展示的工具,其支持的数据来源是多种多样的。在R中可以使用爬虫技术从网上抓取数据来进行分析,也可以从平面文件txt/csv中读取数据,还可以从RDBMS中读取数据表信息。车间项目从HDFS中读取数据,并最终把统计图表展在Java Web查询页面中。

5.4.1 RHive组件

对于R来讲,Hive只是一种数据源类型。就像Java使用JDBC封装的接口来读取不同的数据源一样,RHive封装了把数据从Hive读取到R中的具体实现逻辑,而R开发人员不必心数据加载过程,只需要把RHive包引入到library中,然后通过HQL读取Hive中的数据,换成R中的集合对象来调用即可。

  1. RHive组件的安装

在R界面操作Hive中的数据,主要是通过向HiveServer发送HQL语句的方式来执行的。操作Hive表中的数据可以通过Hive CLI方式或beeline方式。RHive的作用和beeline类似,它把一个操作Hive表数据的客户端工具嵌在了R中。RHive组件的运行需要Java环境,所以要先安装rJava组件,再安装RHive,然后启动HiveServer/HiveServer2,最后在rhive对象中通过执行HQL语句操作Hive表中的数据。

(1)安装rJava组件

选择一个镜像网站,下载并安装rJava组件。

测试rJava模块是否可以正常使用。

(2)安装RHive组件

选择一个镜像网站,安装RHive组件。

安装完毕后,测试RHive模块是否正常加载。

在使用RHive组件功能前,必须先启动HiveServer服务端。

安装RHive组件,除了使用函数install.packages("RHive")之外,还可

以通过命令行方式下载安装RHive开发包。用户可以先到http://cran.at.r-project.org/src/contrib/Archive/RHive地址,下载RHive_2.0-0.2.tar.gz压缩包,然后利用R命令行进行安装。默认RHive安装包的位置在~/R/x86_64-unknown-linux-gnu-library/3.1/RHive下,如上面安装信息中所示。然后需要把该目录下的java/rhive_udf.jar文件复制到HDFS文件系统中的/rhive/lib/2.0-0.2目录下,在HDFS中目录可以先手动创建好。

(3)启动HiveServer服务端

该步骤可参考“Hive Thrift服务”内容。

(4)在R中使用RHive连接Hive数据仓库

在本机上启动Hive  Thrift服务,因此连接地址是“localhost”。connect命令中的参数,还有一种语法形式是rhive.connect('localhost',11000,hiveServer2=TRUE,

defaultFS="hdfs://master:9000"),其中第一个参数是要连接的服务器地址;第二个参数是服务端口号,第三个参数是是否默认使用HiveServer2服务类型;第四个参数是指定默认的HDFS文件系统位置。

(5)利用HQL语句操作Hive表中的数据

(6)退出RHive时,要关闭数据库连接

2. 从Hive职位表中生成饼图

如在“逻辑模型的创建”中, 用户可以选择任意一个维度(学历、工作年限、职位地域、薪资),以饼图方式查看在一定时间段内该维度中的各个取值占比。

统计图形

对于以上图形,数据是从Hive取得的,图形是用R生成的,生成图

形的过程封装在一个R自定义函数genpic中,示例函数代码如下:

genpic<-function(picpath,dat1,dat,dimtype){

library(rJava)

library(RHive)

rhive.init()

rhive.connect("slave02",10001,hiveServer2=TRUE,defaultFS="hdfs://master:9000")

try(rhive.query('use feigu3'),silent=TRUE)

a<-rhive.query(paste('select * from daily_dim_sum where daily_dim_sum.pt between ',dat1,

'and',dat,sep=" "))

b<-aggregate(a$daily_dim_sum.cnt_val,by=list(a$daily_dim_sum.dim_type),FUN=sum)

b<-b[order(b[,1]),];

xx<-data.frame(x=c(paste('A',c(1:4,9),sep=""),paste('B',c(1:3,9),sep=""),paste('C',

c(1:3,9),sep=""),paste('D',c(1:3,8:9),sep="")), y=c('北京','上海','广州','深圳','其他

','大专','本科','研究生','其他','3 年以下','3 至 5 年','5 年及以上','其他','一至三万','三至五万

','五万以上','面议','其他'));

xx$y<-as.character(xx$y);

b$region<-xx$y[xx$x %in% b[,1]];

##output.filepath 图形输出路径, title 图片标题行

colnames(b)[2]<-'amount'; ##设置列名

b$dim1<-substr(as.character(b[,1]),1,1);

b$ratio<-round(with(b,b$amount*4/sum(b$amount)),3) ##计算百分比

title<-c('地域','学历','工作经验','月薪')

na<-paste(picpath,1:4,'.jpeg',sep="")

i<-dimtype;

jpeg(na[i],family='GB1');

pie(b$ratio[b$dim1==unique(b$dim1)[i]],labels=paste(b$region[b$dim1==unique(b$dim1)

[i]],":",b$ratio[b$dim1==unique(b$dim1)[i]]*100,"%",sep=""),family='STKaiti')##画饼图

title(main=paste(dat1,'-',dat,title[i],'分布图',sep=""),family='STXihei')##设置图片title

dev.off() ##关闭画图

}

5.4.2 把R图表整合到Web页面中

前面所生成的统计图形,是通过在R控制台输入命令行实现的。在Web检索页面中要实现的功能是,用户点击“检索”后,在页面下方直接显示所生成的图形,统计Pie图Web部分的功能都是用Java/JSP技术实现的,采用一种整合R和Java的方案。

1. 用Java调用R脚本

Java语言和R语言的运行环境及方式完全不同,如何实现相互调用

呢?

异构语言之间的相互调用,即进程间通信,要解决两个关键问题:一是如何通知对方;二是如何把参数或返回值传递给对方。不同语言的语法结构和变量类型定义均不相同,那么如何把参数做到标准化呢?最简单的方式是使用字符串。无论任何语言,都有基本的字符类型,把数字也转换成字符串格式,将参数变量保存到文件、数据库或共享存储中。两种不同语言的内存进程,采用“轮询”方式,一方写数据,一方读数据。这是最简单的实现方式。考虑到“轮询”方式耗费资源,且实时性不强,很多高级语言都支持Socket通信,因此使用Socket Server/Client方式,也是非常通用的方法。把需要传递的参数,以Socket报文方式发送到服务器端,服务器端执行完成后,再把返回值或结果集以报文方式发送到调用端。由于Socket通信报文长度会有限制,无法适用于更加复杂的场合,因此各种语言又发挥各所长,提供了Web Service方式实现RPC调用,这也是跨语言的通信。

此处要实现由Java端发起调用R的服务,我们采取一种和上述方案都不同的方法,该方既直观又容易实现。在R中生成饼图的genpic函数,可以封装成一个R脚本。所谓R脚本(R Script)是指包含了多行R命令的一个文本文件,每行R命令在R终端都可以直接运行。把R命令封装成R脚本文件是为了方便在不同地方重复使用,就像把很多JavaScript代码放到一个*.js文件中,在HTML页面中直接引用一样。执行R脚本文件不需要登录到R控制台,直接在操作系统终端运行即可。

在机器上安装好R环境后,在${R_HOME}\bin目录下会有

Rscript.sh(或Rscript.exe)文件,通过执行Rscript命令可以在OS终端直

接运行R脚本文件。运行方式为:

Rscript <文件路径/R脚本文件名> [R脚本中参数列表] <回车>

首先把genpic函数放在R脚本文件中,命名为genpicScript.R。扩展

名一般为.R,文件格式是文本文件,字符集采用UTF-8。文件内容大致如下:#从操作系统命令行得到参数列表,放入Args数组中

Args<-commandArgs()

#定义genpic函数

genpic<-function(picpath,dat1,dat,dimtype){

#以下省略函数内容

}

#调用genpic函数,参数是从控制台获取的

genpic(Args[1],Args[2],Args[3],Args[4])

把R脚本文件保存在/home/feigu/rscript/genpicScript.R路径下,在

Windows或Linux终端输入命令行调用。

Rscript /home/feigu/rscript/genpicScript.R

'/home/feigu/rpic/' '20150601' '20150630' 2

脚本文件名后的参数之间用空格分隔,参数值与在R客户端调用的含义相同。通过运行R脚本文件,同样可以生成图形文件。下面轮到Java出场了。在Java的Runtime类中,可以模拟在操作系统终端执行一个系统命令或外部命令的功能。也就是说,刚才我们用

Rscript运行R脚本文件的操作,可以移到Java代码中实现。代码如下:

try {

String cmd = "Rscript /home/feigu/rscript/genpicScript.R "+file_path+" "+date;

Runtime.getRuntime().exec(cmd);

System.out.println("Done");

} catch (IOException e) {

e.printStackTrace();

}

把要执行的命令行作为一个字符串,放入Runtime类的exec方法参数中,就可以实现执行命令行的效果,也就间接地实现了Java调用R脚本生成图形的功能。调用R脚本后,在Java代码中判断指定目录下的JPEG文件是否生成,如果生成了,就将文件路径返回给前端Web页面显示。

使用Rscript命令行执行R脚本,可以把复杂的实现逻辑封装在脚本文件中,先在R终端调试脚本内容,确保语法无误后再使用Java代码来调用,这样就大大降低了系统的耦合度,也方便进行错误调试。使用Runtime.exec()方法执行外部命令的方式,可以实现Java和其他各种语言进行交互的功能。比如PHP、Python、Wscript等脚本语言,均提供了在命令行执行的功能:“php  listFile.php”、“pythonmyFile.py”、“wscript myScript.vbs”(vbs文件是用VBScript语言编写的宏指令集合)。但是,使用Runtime.exec()也有一些限制条件。最大的缺点是,运行exec()参数里面的命令行,要求本机必须安装了执行命令行的软件环

境。比如用户在A机器上运行Java代码Runtime.getRuntime.exec("RscriptdrawPie.R"),要求A机器上必须已经安装了R环境,否则会报错“找不到Rscript命令”,其他命令行同理。此外,使用Runtime.exec("系统命令行")方式,在运行命令行时,操作系统另外启动了一个进程来执行“系统命令行”,Java进程本身还是继续向下运行,两个进程是异步方式。如果系统命令需要运行一段时间,而此时Java进程已经结束或返回,这也不是我们所期望的。

2. 用Rserve实现实时通信

用Java调用R脚本更高效的方法是使用Rserve通信方式。Rserve组件是R提供的采用TCP/IP协议、以监听方式执行R命令的服务端组件。客户端调用时可以采用其他语言,比如C/C++、PHP、Java、Python、C#等,客户端机器并不需要安装R环境,类似于Hive的ThriftServer。Java在调用Rserve服务时需要两个JAR包支持:REngine.jar和RserveEngine.jar,可以从http://www.rforge.net/Rserve/files/下载得到。

Java和Rserve之间的调用步骤如下。

(1)安装Rserve模块

进入R终端,输入install命令安装,如下所示,选择一个镜像地址。

(2)在R终端启动Rserve

安装过程很快,安装完毕后可以直接启动Rserve。

也可以在控制台输入“R CMD Rserve”来启动Rserve。Rserve启动后,可以输入“R CMD Rserve --help”命令来查看如何修改默认端口号。

Rserve启动后,默认只允许从本机发起连接。如果想将其他主机作为客户端连接Rserve,则需要打开远程连接模式,启动命令是“R CMDRserve--RS-enable-remote”。关于Rserve的详细配置信息,可以查看http://www.rforge.net/Rserve/doc.html。

(3)在Java代码中通过端口号连接Rserve

Rserve服务启动后,可以编写测试代码连接Rserve做测试。

import org.rosuda.REngine.REXP;

import org.rosuda.REngine.Rserve.RConnection;

……

……

public static void callRScript()throws RserveException, REXPMismatchException {

String rserver_ip = "125.0.0.1";

int rserver_port = 6311;

RConnection conn = new RConnection(rserver_ip, rserver_port);

System.out.println("connection ok..");

conn.close();

}

此处是在本机上启动Rserve守护进程的,Java代码连接的是本机IP

地址125.0.0.1。IP地址也可以换成其他启动Rserve服务的机器地址。

(4)以字符串方式发送R命令并执行

RConnection conn = new RConnection(rserver,rserver_port);

REXP rexp = conn.eval("R.version.string");

System.out.println(rexp.asString());// 打印R版本号

double[] arr = conn.eval("rnorm(10)").asDoubles();

for (double a : arr) {// 循环打印变量arr中的每个随机数

System.out.print(a + ",");

}

conn.close();

RConnection类使用eval("R命令行")方法来解析执行参数中的R命令,并把返回值放入对象REXP实例中。此处执行的R命令行只是生成图形,并不需要解析复杂的返回值。有关eval方法的详细使用说明,可以参考http://www.rforge.net/doc/packages/Rserve/Rserve.eval.html。

(5)引入genpicScript.R脚本文件,并执行其中的genpic函数生成图

public static boolean callRScript() {

String rserver_ip = "125.0.0.1";

int rserver_port = 6311;

String rscript_file = "/home/feigu/genpicScript.R";

String output_filepath = "/home/feigu/rimg/";

String start_dt = "20150601";

String end_dt = "20150630";

String dim_type = "2";

RConnection conn = null;

try {

// 创建RServer连接

conn = new RConnection(rserver_ip rserver_port);

System.out.println("connection ok..");

// 加载R脚本文件,里面定义了genpic函数

conn.eval("source('"+rscript_filelocation+"')");

System.out.println("load source Rfile ok..");

// 调用genpic函数生成饼图

REXP x = conn.eval(String.format("genpic(\"%s\", \"%s\", \"%s\",%s)", output_

filepath,start_dt,end_dt,dim_type));

System.out.println("genpic ok..");

// 判断如果图形已经存在,就返回true

String exists_file = output_filepath + "/" start_dt + "-" + end_dt + "-" + dimtype

+ ".jpeg";

File picfile = new File(exists_file);

return picfile.exists();

}

用户在前端检索页面中点击“生成统计图”按钮,就调用了callRScript方法,把起止时间作为参数传给R脚本,最终生成图形。由于前端是以Web页面方式调用的,生成的图形必须放在Web容器的webapp目录下,才能被查看到。整个交互过程可以采用AJAX异步方式。

3. 使用定时任务生成R图

使用R语言做数据分析,图表展示是其中的核心功能。爬虫每天从招聘网站抓取的职位信息,如果能够按照特定的维度,每天、每周、每月定期生成R图,并自动发布到网上,就会方便用户使用,也增加了数据展示途径,提高了数据利用率。Hive职位表中的数据是每天晚上自动导入的,导入后的数据是不会修改的。数据入库后,就可以自动开始执行R图的生成功能。统计时间窗可以是当日,也可以是从月初截至当日,由业务人员自己决定。上面提到的callRScript方法可以照搬过来直接使用,只是起止时间参数会有所调整。生成的图形同样放在webapp下的某个目录中,用户可以直接查看而不需要再指定检索条件。自动生成R图的功能模块也可以放在crontab中进行调度。

六.数据的分析挖掘

6.1 基于Spark的数据挖掘技术

Spark是当下大数据领域非常“耀眼”的技术之一,它是在进行大规模数据处理时的高效引擎。Spark数据计算在内存中完成,有效地解决了实时性问题;采用并行的分布式架构,使得用户可以采用大量廉价的设备来提升性能;可伸缩性和健壮性使得它得以稳定运行在不同的生产系统中。

Spark于2009年诞生于AMPLab,2013年成为Apache的孵化项目,2014年年初升级为Apache的顶级项目,与此同时,Spark和Hadoop的结合,使得它吸引了越来越多的目光。2015年10月,Spark版本更新至1.5.1,2016年7月推出2.0版本。主要具有4个特点:一是速度快,它通过使用有向无环图(DAG)执行计划,把Map-Reduce的计算过程放在内存中完成,比Hadoop使用硬盘数据交换方式至少快10倍以上;二是上手快,在Spark中编写代码,可以选择Java、Scala、Python、R语言其中任意一种;三是模块丰富,Spark提供了Spark  SQL和DataFrame、SparkStreaming(流式计算)、SparkGraphX(图计算)以及Spark机器学习库(Machine  LearningLibrary)等多种模块,用来满足各种场景下的数据计算需求;四是Spark还可以很好地和不同的数据源进行整合,比如HDFS、HBase、Cassandra、S3等,充分利用Spark计算引擎的特性。

Spark 1.5包括4个功能模块:Spark SQL、Spark Streaming、MLlib、GraphX。

Spark  SQL是Spark提供的用来操作内存中数据对象(DataFrame)的一种类SQL语言。Spark处理数据的来源可以是TXT文本文件、格式化JSON文件、关系数据库表或HDFS文件类型,这些异构的文件系统被Spark统一转换成DataFrame格式,然后使用开发人员都很熟悉的SQL语法规则进行数据投影、选择和连接等操作。Spark Streaming(流式计算)广泛应用在服务器日志收集、网络中节点状态监控及物联网领域从硬件中采集实时信息等各种场合。SparkStreaming就是Spark中用来处理实时数据的模块,它提供了一组用于和Spark核心的RDD(Resilient Distributed Dataset,弹性分布式数据集)进行交互的API来完成数据处理。Streaming模块同时也具有高容错和高吞吐能力。

GraphX是Spark提供的一组基于RDD的图形处理API,利用这组API可以实现大规模同步全局的图计算,还可以使得同一套数据既能以集合方式展现,也能以图形方式展现。利用它提供的Pregel API可以实现自定义迭代图形算法。MLlib(Machine  Learning)是Spark对常用的机器学习算法的实现库,同时包括相关的测试和数据生成器。MLlib支持各种常见的机器学习问题,如二元分类、回归、聚类以及协同过滤等。由于算法的实现过程使用了Spark核心的RDD,使得在性能方面具有显著的优势。

6.2 Spark和Hadoop的关系

通常来讲,Hadoop主要包括两大部分,即分布式文件系统HDFS和MapReduce(YARN)计算框架。而Spark的核心是一个基于RDD数据结构的分布式内存运算模型,它提供了在大数据环境下高效、可靠地完成数据运算的解决方案。另外,从模块划分上来讲,Hadoop本身是一个大数据生态系统,包含Sqoop、Pig、Hive、HBase等多个组成部分,这些模块分别实现了数据导入导出、数据运算及数据查询等各个方面的功能;而Spark所包含的模块,只限定于流式计算、图运算及机器学习等有限的几个方面,并且在这几个应用领域中,也有其他的解决方案对Spark构成了有力的挑战。然而,Spark受人瞩目的焦点就在于,它对Hadoop的数据运算性能有了大幅度的提升(关于这一点,我们在接下来的章节中会做一个性能对比);并且Spark可以和Hadoop中的HDFS、Hive、HBase等多个模块进行整合,读取其中的数据,从而利用上它的RDD内存运算模型。此外要注意,Spark并不依赖于Hadoop,Spark的核心是一个数据处理模型,它是一个“通用目的”下的集群计算框架。它支持的数据来源是多样的。Hadoop的HDFS只是其中一种数据格式,Spark是Hadoop中计算框架的“升级版本”,二者之间并不是前者要取代后者的关系。

6.3 在Ubuntu上安装Spark集群

安装Spark可以从Apache官网下载安装介质,安装方式有两种:一种是直接使用编译好的Spark版本;一种是下载Spark源码进行编译安装。由于Spark主要使用HDFS作为数据存储层,所以在安装Spark前,要先安装与之对应的Hadoop版本。本书使用的Spark版本是1.4.1,安装文件从(http://www.apache.org/dyn/closer.lua/spark/  spark-1.4.1/spark-1.4.1-bin-hadoop2.4.tgz)下载。该tgz包中是已经编译好的运行在Hadoop 2.4版本上的Spark。如果用户使用的是其他版本的Hadoop,也可以下载Spark源码后,直接在已经安装好Hadoop的环境下手动编译JAR包。Spark的启动方式有单机环境(本地模式)和集群方式,其中集群方式是真正的生产环境,本节主要描述在集群方式下Spark的安装启动步骤。

6.3.1 JDK和Hadoop的安装

这部分内容可以参考“ Hadoop基础环境安装及配置”章节。此外,调试Spark程序还可以使用Python语言,所以在安装Spark前,要保证已经安装好Python环境。在“Python和Scrapy框架的安装”章节中,使用的Python版本是2.7.3,这个版本可以和Spark 1.4.1兼容使用。

6.3.2 安装Scala

Scala(scaleable language)是一种语法类似于Java语言、以面向对象方式或面向过程编程方式运行的可伸缩语言,它可以和Java实现无缝对接。由于Spark中使用到了Scala语言来操作RDD,所以要先安装和Spark版本对应的Scala工具,下载地址为http://www.scala-lang.org/download/。此处车间使用的版本是Scala 2.11.6。安装Scala的先决条件是要安装JDK 1.5或以上版本,并已经在环境变量中设定好JAVA_HOME和将java\bin目录添加至系统PATH中,然后使用tar命令解压缩scala-2.11.6.tgz到某个目录下即可。在/etc/profile文件中,指定${SCALA_HOME}为刚才解压缩后的文件夹位置,并将scala\bin目录添加到系统PATH环境变量中,如下所示:配置完成后,在命令行中输入“scala<回车>”,即可进入交互式操作终端,查看当前安装版本号。退出终端使用“:quit”命令。另外,需要在集群的各个子节点上都安装相同版本的Scala。

6.3.3 安装Spark

安装完Hadoop和Scala后,就可以安装Spark了。通常Spark运行在集群环境下,需要依次在Master节点和所有的Slave节点上都安装Spark。首先解压缩Master机器上下载得到的spark-1.4.1-bin-hadoop2.4.tgz文件。进入解压缩后的目录,把conf目录下的spark-env.sh.template文件复制为spark-env.sh文件,并修改该文件的内容。以下是一台Spark主机上的配置文件部分内容,安装目录位置仅作为参考。其中,SPARK_WORKER_MEMORY是每个Worker节点上Spark实例可以使用的系统内存大小,SPARK_MASTER_IP是Master节点的主机名。关于spark-env.sh文件中更多配置项的内容释义,可以参考http://spark.apache.org/docs/latest/configuration.html文档。修改完spark-env.sh文件后,还要在conf目录下创建slaves文件,把所有Worker节点上的主机名加入slaves文件中。示例如下:然后可以通过运行Spark的测试程序来验证Master机器是否安装成功。在spark\bin目录下运行run-example脚本,如下所示:如果运行正常,则可以在日志中找到程序输出的结果信息,打印出Pi值。run-example脚本中的第一参数SparkPi,是spark/lib/spark-examples-1.4.1-hadoop2.4.0.jar文件中的一个Java类名,其作用是利用蒙特卡洛算法计算Pi值的示例程序,和Hadoop的计算Pi值类似。后面的参数“10”是指定首次Map的个数,数字越大,计算出的Pi值越精确。不同版本的Spark,调用示例程序所使用的参数有所不同。此外,在运行示例程序时,可能会抛出Java异常,错误信息如下:提示4040端口已经被占用。由于每个Spark计算任务启动时,都会自动启动SparkUI服务,SparkUI服务是以Web方式查看Spark任务运行状态的HTTP服务。SparkUI服务默认使用4040端口,如果被占用,则会抛出异常,然后尝试使用4041(4042、4043等)端口,依此类推。这个异常不影响程序运行。Master节点安装完成后,使用“scp”命令把Master上spark安装目录的所有内容,复制到每个Slave节点相同的目录下,然后就可以测试在集群方式下启动Spark服务了。找到Master节点上的spark\sbin目录,执行“./start-all.sh”命令,即可启动Spark集群,如下所示:日志中分别打印出每个Slave节点的输出日志文件路径,便于对节

点状态进行监控。如果该脚本没有抛出异常,在Master机器上执行“jps”命令,便可以看到Spark对应的Java进程名字为“8017 Master”,如下所示:选择一个Slave节点,执行“jps”命令,会看到一个“9196 Worker”进程Worker进程是在Spark集群模式下,在Slave子节点上运行的进程名。关闭Spark集群,只需要在Master机器上运行stop-all.sh脚本即可。既然Spark可以运行在集群环境下,那么如何在集群上测试Spark任务呢?前面我们运行SparkPi测试程序,其实是在本地模式(Local)下运行Spark任务的。运行本地模式,还可以使用下命令行语法:末尾的“spark://master:7077”参数,代表Spark集群的Master机器URL。而在集群方式下运行SparkPi,使用如下命令行:

6.4 Spark的运行方式

Spark在设计之初,就考虑到易用性特点,可以和其他语言集成使用。Spark的运行方式分为命令行交互(shell)方式和脚本(script)方式。在Spark中可以使用的语言包括Scala、Java、Python,在Spark 1.4版本中还支持R语言。下面分别列出了三种命令行方式运行Spark的方法。为了能在任意目录下直接运行这三种命令,需要把${SPARK_HOME}/bin目录添加到系统PATH环境变量中。

1. spark-shell命令行

2. pyspark命令行

3. sparkR命令行

以命令行交互方式操作Spark,又叫作Spark shell方式,shell方式在

启动时,还可以在后面加参数列表,比如“./bin/spark-shell  --masterlocal[3]”。关于参数可参见(http://spark.apache.org/docs/1.4.1/programming-guide.html#using-the-shell)。

除了使用命令行交互方式外,我们也可以把命令行合并在一个脚本文件中,直接交给Spark去执行,就好比把一连串的Linux命令行合并在一个shell脚本中去执行一样。例如下面两种命令行:

$ ./bin/spark-shell --master local[4] --jars code.jar

$ ./bin/pyspark --master local[4] --py-files code.py

就是把业务逻辑封装在了code.jar和code.py中,以文件的方式提交给Spark去执行的。

在Spark集群上做SparkPi测试时,使用的命令行是“bin$ ./spark-submit”格式,后面加了很多参数。这个脚本是向

Spark集群提交任务的标准模式,上述的“spark-shell”、“pyspark”和“sparkR”命令执行时,最终都会转换成格式统一的spark-submit命令提交。spark-submit提供了参数列表,用来配置任务执行时的各种环境变量。参数列表如下:

./bin/spark-submit \

--class <main-class>

--master <master-url> \

--deploy-mode <deploy-mode> \

--conf <key>=<value> \

... # other options

<application-jar> \

[application-arguments]

各参数取值的详细说明,请参考官网文档(http://spark.apache.org/docs/1.4.1/submitting- applications.html)。

最后,向Spark集群提交任务,还可以集成在应用程序代码中实现。比如在Java代码中通过引入Spark JAR包,调用Spark API方法,在集群中执行特定的任务。这种方式的灵活性最强。

6.5 使用Spark替代Hadoop Yarn

引擎在5.1节介绍Hive功能时,大家已经了解到,Hive是用来以SQL方式操作HDFS数据的一种工具,在HQL进行运算的背后,实际上运行的是MapReduce任务。而运行MapReduce任务使用的是Yarn引擎。由于在Yarn计算模式中,无法避免把运算的中间结果写入磁盘,从而会降低效率。Spark的基于内存的RDD运算模型,很好地解决了这个问题。因此,把这两者结合起来,扬长避短,从Spark推出之初,就在努力实现这个目标。在Spark 1.0之前,整合Apache Hive和Spark使用的是Shark项目,即SQL-on-Spark。在Spark 1.0之后,Shark项目被Spark SQL替代,原因是Shark代码严重耦合Hive中的类,给优化和维护带来了很多不利因素;而Spark  SQL则完全放弃了MapReduce引擎,采用新的查询优化引擎和RDD运算,因而使得同样的数据检索动作,使用Spark SQL比使用Hiv速度要快很多倍。下面就来介绍如何使用spark-sql,以及在车间项目中如何使用Spark引擎替代Hive。

6.5.1 使用spark-sql查看Hive表

为了能在spark-sql交互命令下操作Hive表数据,要求本机上同时安装了Spark和Hive工具,此处用来做演示的Hive版本是0.13.1。首先把hive-site.xml文件复制到spark的conf目录下。修改spark/conf/spark-env.sh脚本文件,设定本机Hive的安装目录。完成以上简单配置后,就可以在本机上启动spark-sql了。由于此处Hive的metaDB元数据信息是保存在MySQL数据库中的,而spark-sql启动时会去自动连接metaDB,所以在命令行参数中,要告诉spark-sql脚本mysql-jdbc驱动JAR包的位置在哪里,使用“--driver-class-path”参数来指

定,如下所示:在进入spark-sql窗口时,最后日志信息会显示出它加载的Hive的版进入spark-sql交互界面后,就可以直接输入HQL语句了。下面是一些示例,最后使用“exit”命令退出。此外,在启动spark-sql时,如果不指定“--driver-class-path”参数,也可以在spark/conf/spark-env.sh中把mysql-jdbc驱动JAR包加入类路径中,

如下所示:

6.5.2 在beeline客户端使用Spark引擎

除了使用spark-sql客户端以外,Spark中也封装了Hive beeline客户端的连接方式。beeline客户端的使用方法在5.9.1节中做过介绍,使用该客户端首先要启动HiveThriftServer服务。在Spark中也集成了使用Spark引擎的HiveThriftServer服务,即sbin目录下的“start-thriftserver.sh”脚本。在启动服务时,同样需要指定在哪个端口上提供该服务,使用“--hiveconf  hive.server2.thrift.port”参数。启动命令行如下所示:该服务以后台进程方式启动,日志输出在spark/logs目录下。后台进程就是一个SparkSubmit  Java进程,可以使用“jps”命令查看,其中“10778”是进程编号。关闭该服务,使用“spark/sbin/stop-thriftserver.sh”脚本。如果服务启动正常,就可以使用Spark beeline工具连接Hive数据仓库了。同样要在!connect命令后指明要连接的IP地址(主机名)和端口号,在连接成功后的日志信息中,会看到使用的驱动引擎是“SparkProject Core(version 1.4.1)”,在beeline客户端,可以使用HQL语句查询和操作HDFS中的数据,使用方法与在Hive beeline中完全一样。读者可以分别使用Spark beeline和Hive beeline运行同样的HQL select语句,比较两者的速度差别。

6.5.3 在Java代码中引用Spark的

ThriftServer既然说Spark引擎比传统的MapReduce迭代算法优秀,内存运算比写入硬盘效率高很多,那么有没有实际的例子来做比较呢?以实现“招聘职位和求职简历自动匹配”功能作为例子,比较两种实现方式到底有哪些差别。我们使用爬虫抓取了很多大数据相关的职位信息,然后使用Hive数据仓库做ETL,最后分析出一些统计指标。对于发布招聘信息的公司HR来讲,接下来就是从源源不断的简历中初步筛选出合适的面试人选了。技术类岗位的首要筛选条件,就是专业技能要尽可能匹配,其次是工作年限和期望工资待遇。我们想帮助HR实现的就是把招聘岗位中的技术关键字,和求职简历中的专业技能做自动匹配,找出符合技术要求的应聘人员。此处主要是为了描述技术实现过程,求职简历数据同样使用爬虫。

6.6 对招聘公司名称做全文检索

Spark作为一个基于内存的计算引擎,和Hadoop之间既有联系又有区别。Spark既可以和Hive配合使用,也可以单独对HDFS文件中的数据做运算。直接使用Scala、Java、Python语言在Spark中进行数据处理,是更加普遍的应用场景。在Spark核心中用来完成迭代算法而构建的数据模型叫作RDD。RDD(Resilient Distributed Dataset,弹性分布式数据集)是Spark主要用来操作的数据对象,充当在多个计算节点上并行运行的分布式数据集合。Spark核心中的任务调度、内存管理、故障恢复以及和存储介质的交互等功能,都与RDD数据结构密不可分。

6.6.1 从HDFS数据源构造JavaRDD

如何构造RDD的数据源。使用Sqoop工具把新闻帖子导入到HDFS中后,还是按照之前的思路,进入Hive的表中,表中部分数据如下所示:Hive数据仓库中的每个表,都对应于HDFS文件系统中的一个文件夹。同样,表在HDFS中也是一个文件目录,目录下每个日期分区又分别是一个子目录,如下所示:表数据分散在每个子目录下以“part-m-00000”为文件名的文件中。该文件中的一行数据存储一条新闻信息,列之间用“\001”做分割。知道文件的存储位置和文件格式后,就可以把它构造成Spark RDD了。Spark本身提供了丰富的Java API来实现在Java代码中调用Spark的功能。创建JavaRDD的代码片段如下:

String hdfsFileRoot= "hdfs://master:49100/user/hive/warehouse/feigu3.db/";

// 得到目录下的所有文件名

List<String> hdfsFiles = HDFSUtil.getFileListByRoot(hdfsFileRoot);

// 构造Spark配置信息实例及Spark SQL实例

SparkConf sparkConf = new SparkConf().setAppName("newsContentLikeSearch");

sparkConf.setMaster("local");

JavaSparkContext ctx = new JavaSparkContext(sparkConf);

org.apache.spark.sql.SQLContext sqlContext=new org.apache.spark.sql.SQLContext(ctx);

for(int i=0; i<hdfsFiles.size();i++){

// 把文本文件中的每一行构造成News对象,最终形成Java RDD

JavaRDD<News> newsSet = ctx.textFile(hdfsFiles.get(i)).map(

new Function<String, News>() {

private static final long serialVersionUID = 1L;

public News call(String line) throws Exception {

char sep = 01;

String[] parts = line.split(new Character(sep).toString());

News news = new News();

if (parts.length==4){

news.setNewsId(parts[0]);

news.setNewsTitle(parts[1]);

news.setContent(parts[2]);

news.setCreateTime(parts[3]);

return news;

}

});

6.6.2 把RDD运行结果展现在前端

完成SQL的模糊检索后,剩下的就是如何返回结果集了。请看下面的代码:

DataFrame relatedNews = sqlContext.sql("… …");

// 把检索结果转换成JSON字符串返回

List<String> relatedNewsId = relatedNews.javaRDD().map(new Function<Row, String>() {

private static final long serialVersionUID = 1L;

public String call(Row row) {

String newsId = row.getString(0);

String title = row.getString(1);

String ctime = row.getString(2);

News data = new News();

data.setCreateTime(ctime);

data.setNewsId(newsId);

data.setNewsTitle(title);

return CommonUtils.produceJson(data);

}

}).collect();

for(String nid: relatedNewsId){

resultStr += nid + ",";

}

对以上代码分析如下:

“DataFrame.javaRDD().map(…)”语法是把SQLContext运行后得到的结果集——也是一个新的DataFrame,转换成一个JavaRDD的过程。在map(…).collect()方法内部,同样定义了一个匿名内部类,该类也实现了Function接口,并重写了call()方法。call()方法的参数是Row类型,也就是RDD中存储的数据行。“row.getString(N)”是取得数据行中第N+1列数据的值。例如SparkSQL中的“SELECT newsId,newsTitle,createTime FROM …”,则N的取值范围是(0,1,2)。把RDD中的数据列依次取出后,放在News对象实例中。

“CommonUtils.produceJson(News)”是笔者自定义的一个方法,用来把News实例转换成JSON格式字符串,便于在Web前端展示。具体代码不再提供。

“DataFrame.javaRDD().map(…).collect()”语法最后的collect()方法是对结果集进行收集。Spark计算框架是分布式的,比如代码中查询出来的RDD结果集应该有10条记录,这10条记录是由集群中的多个节点“分块”运行得到并存储的。而collect()方法的作用就是把分散的10条记录合并到一个节点(即运行collect()方法的机器)上,合并过程在内存中完成。合并后才可以拿到最终的完整结果集,然后再做后续处理。通过以上三个步骤,即“取数据→筛选数据→返回结果”,我们完成了一次Spark RDD运算的基本过程。

6.6.3 SparkR的安装及启动

 安装了Spark1.4.1,该版本的Spark已经自带了SparkR模块。在Spark的安装目录下,可以看到子目录“<SPAKR_INSTALL_PATH>\R\lib”,里面的内容就是在Spark上运行R所依赖的包和配置文件。此外,在“<SPAKR_INSTALL_PATH>\bin”目录下,也可以看到可执行脚本文件“sparkR”,运行该命令,就可以进入R的交互式命令行环境。需要注意的是,Spark  1.4.1中的SparkR环境,并不是把整个R的安装包都放在了里面,如果读者机器之前没有安装过任何版本的R,而是直接安装的Spark 1.4.1,那么运行其中的sparkR命令是会出错的,因为SparkR只是两者之间的桥梁,并不是替代的关系。和Spark 1.4.1版本相对应的R的版本是3.1.3,更低版本的R是无法和Spark兼容使用的。另外,Spark是运行在集群环境中的,集群上每个节点也都要安装R环境。在运行sparkR命令前,还要在系统的环境变量中配置两个地方:在PATH中要有${R_HOME}\bin目录;同时要增加环境变量R_HOME,指向安装R的本机路径。这两个变量缺一不可,否则运行sparkR命令会提示如错误:此外,如果是在单机环境下运行sparkR,要保证机器名(localhost)能够正确映射到本机的IP地址,即127.0.0.1。用户可以修改/etc/hosts文件,确保“locahost 127.0.0.1”这一行出现在该文件中。如果没有正确映射localhost,在运行sparkR时,会出现“InsocketConnection(port = monitorPort) : localhost:xxxxx cannot be opened”此类的错误信息。另外,sparkR配置好集群模式后,以单机模式启动,会对配置项中的hostname和IP地址比较敏感。在单机模式下运行时,可能会出现“Could  not  connect  to  akka.tcp://sparkMaster@127.0.0.1:  7077:akka.remote.InvalidAssociation:  Invalid  address:akka.tcp://sparkMaster@127.0.0.1:7077”这样的提示错误。此处,可编辑${SPARK_HOME}/conf/spark-env.xml文件,注释掉“export  MASTER=

spark://127.0.0.1:7070”这一行。

如果一切配置正常,在“<SPAKR_INSTALL_PATH>/bin”目录下运

行sparkR命令,会看到交互界面的启动画面,

首先启动的是R的控制台界面,然后自动加载了sparkR插件。在该

示例中,Spark已经帮用户创建好了sparkR的上下文环境变量“sc”,用户在退出该界面可以手动关闭环境变量,输入命令行“sparkR.stop()”,退出R的控制台,输入“q()”命令。

6.6.4 运行自带的Sample例子

如果我们打开sparkR脚本查看一下,就会发现它其实执行的是“${SPARK_HOME}/bin/ spark-submit”命令,只是后面跟的参数是R环境的,这和之前我们演示的计算PI值的步骤是殊途同归的。换言之,在Spark中运行R命令,有两种方式:一种是交互式命令行界面;另一种就是指定R脚本的运行方式,比如“spark-submit R-script-file.R”。在安装完成Spark  1.4后,在${SPARK_HOME}/examples/src/main/r目录下,有测试R脚本文件dataframe.R,用来测试sparkR环境。脚本功能是从HDFS中读取JSON文件,构造出sparkRDD,并利用sparkSQL来筛选其中符合条件的记录。我们可以对该示例文件做出一些调整。首先把用来测试的数据件,即${SPARK_HOME}/ examples/src/main/resources/people.json,复制到HDFS目录中,比如:然后修改dataframe.R,注释掉之前的文件引用路径,更换成HDFS目录下的文件:

在${SPARK_HOME}/bin目录下运行sparkR命令,执行该脚本:最后可以在控制台看到输出的结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值