RDD综合应用
实验目的
深入理解和掌握RDD各种常见操作和编程方法;掌握使用RDD编程解决实际问题的方法;掌握RDD分区映射编程方法;掌握RDD算法优化方法。
实验要求
- 掌握基于Maven的Scala和Spark Core编程环境配置;
- 掌握Scala容器方法编程;
- 掌握RDD数据分区;
- 掌握RDD算法优化方法。
实验内容
- 配置Scala和Spark Core编程环境
- 将文件TestHTML.txt上传至HDFS
- 文件TestHTML.txt中每一行为一个HTML网页,原本的HTML中原本的\r\n被替换成了\t,每个网页爬取自同一个网站的同一个栏目,布局基本相同;设计正则表达式提取每个网页的URL、标题和正文。
- 编写代码,实现以下功能:
(1) 将TestHTML.txt按行读取至RDD;
val rdd = sc.textFile("hdfs://主机名或ip地址:端口号/文件路径")
(2) 使用正则表达式提取每篇网页的URL、标题和文章正文(训练正则表达式+5分),RDD类型RDD[String]各元素为:
RDD元素:标题+文章正文
或URL和去除了HTML标记的所有文本:
RDD元素:去除了HTML标记的所有文本
提示:去除HTML标记,用正则表达式<[^>]*>将HTML替换为空字符串即可
val textWithoutTag = "<[^>]*>".r replaceAllIn(html, "")
val url_pattern = "(https.*?shtml)".r
val title_pattern = "<h1 class=.*?>(.*?)</h1>".r
val content_pattern = "<p cms-style=.*?>(.*?)</p>".r
val url = rdd.map(x=>url_pattern.findFirstMatchIn(x).map(_.group(1).replaceAll("\t", "")).mkString(""))
val title = rdd.map(x=>title_pattern.findFirstMatchIn(x).map(_.group(1)).mkString(""))
val content = rdd.map(x=>content_pattern.findAllMatchIn(x).map(_.group(1).replaceAll(" ", "").replaceAll("[\\u3002\\uff1b\\uff0c\\uff1a\\u201c\\u201d\\uff08\\uff09\\u3001\\uff1f\\u300a\\u300b]","").replaceAll("<.*?>", "")).toList)
以上代码是分别提取链接,标题和正文内容,其中提取正文时,打算将标点符号,标签一节空格全部替换掉,但仍有福分表点符号保留
Regx:[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]
在软件中的演示结果
但不知为何保存的文件中仍能看到一些标点符号,尤其是“, ”为之后最终查询结果埋下了伏笔。最终,将提取到url,标题和正文保存为一个三元组
保存结果预览
(3) 对所有文本执行中文分词,统计各文章中的词汇次数,RDD类型RDD[List[(String, Int)]]
val wordsRdd = url_title_content.map(x=>{
val words = new JiebaSegmenter().sentenceProcess(x._2.toString()+x._3.toString()).toArray()
words
})
val wordsIntexts = wordsRdd.map(x=>({val wordcount = x.map(y=>(y, 1)).groupBy(_._1).map(y=>(y._1.toString, y._2.count(z=>true)))
wordcount.toList.sortBy(_._2)
}))
RDD元素:List((词汇, 词汇在文章中的次数))
(4) 将RDD元素转为词汇、词汇文章次数二元组,RDD类型RDD[(String, Int)]
RDD元素:(词汇, 词汇在文章中的次数)
提示:flatMap
val wordsIntexts_flatMap = wordsIntexts.flatMap(x=>x).sortBy(_._2)
(5) 计算词汇的总次数并保存为文件,RDD[(String, Int)]
RDD元素:(词汇, 词汇总次数)
提示:reduceByKey或groupByKey+Scala容器方法
val wordsIntexts_sum = wordsIntexts_flatMap.reduceByKey(+).sortBy(_._2)
wordsIntexts_sum.saveAsTextFile(“hdfs://主机名或ip地址:端口号/文件路径”)
- 代码优化
- shuffle优化
使用mapPartiton优化分词和词频统计(参考PPT第4章3.1.2例2)
提示:
//先将RDD类型转为RDD[(String. Int)], mapPartition不能修改元素类型
rdd.map(x => (x, 1))
//遍历4(2)步生成的RDD的各分区,将4(3)~4(5)步的shuffle算子在分区内实现
//得到各个分区的词汇和总数
//再用一个reduceByKey算子将各分区的词汇和总数汇总
.mapPartition(x => {
//实例化jieba类,一个分区实例化一次,而不是map中一个元素实例化一次
val jieba = new JiebaSegmenter()
//遍历各分区中的元素,按4(3)步要求执行分词
//x类型为Iterator,必须先转为List才能应用容器方法
val words = x.toList.map(y => jieba.sentenceProcess(y._1).toArray())
//使用scala容器方法计算后续操作
//在此(分区内)执行的所有操作均不会产生shuffle
……
//计算完毕后,再将数据构造成Iterator[(String, Int)]类型返回
//mapPartition不能修改元素类型,返回值类型必须与参数x的类型一致
. toIterator
})
- 存储结构优化
采用更为高效的Object文件保存RDD
- 编译代码输出jar包并上传并spark-submit运行
先使用小规模数据集在本地调试,再进入曙光大数据系统使用大数据集运行(参考《曙光大数据系统使用方法》)
spark-submit --class 项目路径.项目名 --master yarn /文件路径/jar包名
思考:分别执行优化前和优化后的代码,并记录其运行时间和保存文件的大小
- 进入spark-shell,将第4步保存的Object文件读取到RDD(参考PPT第4章1.5),使用filter算子查询词汇信息:
读取ObjectFile
val objFile = sc.objectFile[(String, Int)]("hdfs://HA/文件路径")
(1) 查询"量子"并输出其次数
val quantum = objFile.filter(x=>(x._1=="量子")).map(_._2).collect()
(2) 查询"核电站"并输出其次数
val nuclear = objFile.filter(x=>(x._1=="核电站")).map(_._2).collect()
(3) 查询次数最多的二字词汇并输出(直接collect)
val max_num = objFile.filter(x=>x._1.length==2).map(_._2).max()
val result = objFile.filter(x=>x._1.length==2 && x._2 == max_num).collect()
代码
package cn.edu.swpu.scs
import com.huaban.analysis.jieba.JiebaSegmenter
import org.apache.spark.{SparkConf, SparkContext}
object Jieba {
def main(args: Array[String]):Unit = {
val conf = new SparkConf().setAppName("名字")
val sc = new SparkContext(conf)
val rdd = sc.textFile("hdfs://HA/文件路径")
val url_pattern = "(https.*?shtml)".r
val title_pattern = "<h1 class=.*?>(.*?)</h1>".r
val content_pattern = "<p cms-style=.*?>(.*?)</p>".r
val url = rdd.map(x => url_pattern.findFirstMatchIn(x).map(_.group(1).replaceAll("\t", "")).mkString(""))
val title = rdd.map(x => title_pattern.findFirstMatchIn(x).map(_.group(1)).mkString(""))
val content = rdd.map(x => content_pattern.findAllMatchIn(x).map(_.group(1).replaceAll(" ", "").replaceAll("[\\u3002\\uff1b\\uff0c\\uff1a\\u201c\\u201d\\uff08\\uff09\\u3001\\uff1f\\u300a\\u300b]", "").replaceAll("<.*?>", "")).toList)
val url_title_content = rdd.map(x => ((url_pattern.findFirstMatchIn(x).map(_.group(1).replaceAll("\t", "")).mkString("")), (title_pattern.findFirstMatchIn(x).map(_.group(1)).mkString("")), (content_pattern.findAllMatchIn(x).map(_.group(1).replaceAll(" ", "").replaceAll("<.*?>", "")).toList))) //提取url,标题,正文内容构成三元组
val wordsRdd = url_title_content.map(x => {
val words = new JiebaSegmenter().sentenceProcess(x._2.toString() + x._3.toString()).toArray()
words
})
//词汇在各文章的次数
val wordsIntexts = wordsRdd.map(x => ({
val wordcount = x.map(y => (y, 1)).groupBy(_._1).map(y => (y._1.toString, y._2.count(z => true)))
wordcount.toList.sortBy(_._2)
}))
//词汇在各文章的次数的二元组
val wordsIntexts_flatMap = wordsIntexts.flatMap(x => x).sortBy(_._2)
//词汇在所有文章中的总次数
val wordsIntexts_sum = wordsIntexts_flatMap.reduceByKey(_ + _).sortBy(_._2)
wordsIntexts_sum.saveAsObjectFile("hdfs://HA/文件路径")
}
}
// //应用
// val objFile = sc.objectFile(String, Int) //读取ObjectFile
// val quantum = objFile.filter(x=>(x.1==“量子”)).map(._2).collect()
// val nuclear = objFile.filter(x=>(x.1==“核电站”)).map(._2).collect()
//
// val max_num = objFile.filter(x=>x.1.length==3).map(._2).max()
// val result = objFile.filter(x=>x._1.length==3 && x._2 == max_num).collect()
实验感悟
因为本次需要处理的过程比较复杂,无法用一个语句完成,所以开始尝试函数式编程。然后又熟悉了一下在大数据曙光平台上的操作。