1. 背景
近日项目要求基于爬取的影视评论信息,抽取影视的关键字信息。考虑到影视评论数据量较大,因此采用Spark处理框架。关键词提取的处理主要包含分词+算法抽取两部分。目前分词工具包较为主流的,包括哈工大的LTP以及HanLP,而关键词的抽取算法较多,包括TF-IDF、TextRank、互信息等。本次任务主要基于LTP、HanLP、Ac双数组进行分词,采用TextRank、互信息以及TF-IDF结合的方式进行关键词抽取。
说明:本项目刚开始接触,因此效果层面需迭代调优。
2. 技术选型
(1) 词典
1) 基于HanLP项目提供的词典数据,具体可参见HanLP的github。
2) 考虑到影视的垂直领域特性,引入腾讯的嵌入的汉语词,参考该地址。
(2) 分词
1) LTP分词服务:基于Docker Swarm部署多副本集服务,通过HTTP协议请求,获取分词结果(部署方法可百度); 也可以直接在本地加载,放在内存中调用,效率更高(未尝试)
2) AC双数组:基于AC双数组,采用最长匹配串,采用HanLP中的AC双数组分词器
(3) 抽取
1) 经典的TF-IDF:基于词频统计实现
2) TextRank:借鉴于PageRank算法,基于HanLP提供的接口
3) 互信息:基于HanLP提供的接口
3. 实现代码
(1) 代码结构
1) 代码将分词服务进行函数封装,基于不同的名称,执行名称指定的分词
2) TextRank、互信息、LTP、AC双数组等提取出分词或短语,最后均通过TF-IDF进行统计计算
(2) 整体代码
1) 主体代码:细节层面与下载的原始评论数据结构有关,因此无需过多关注,只需关注下主体流程即可
1
2 def extractFilmKeyWords(algorithm: String): Unit ={
3 // 测试
4 println(HanLPSpliter.getInstance.seg("如何看待《战狼2》中的爱国情怀?"))
5
6 val sc = new SparkContext(new SparkConf().setAppName("extractFileKeyWords").set("spark.driver.maxResultSize", "3g"))
7
8 val baseDir = "/work/ws/video/parse/key_word"
9
10 import scala.collection.JavaConversions._
11 def extractComments(sc: SparkContext, inputInfo: (String, String)): RDD[(String, List[String])] = {
12 sc.textFile(s"$baseDir/data/${inputInfo._2}")
13 .map(data => {
14 val json = JSONObjectEx.fromObject(data.trim)
15 if(null == json) ("", List())
16 else{
17 val id = json.getStringByKeys("_id")
18 val comments: List[String] = json.getArrayInfo("comments", "review").toList
19 val reviews: List[String] = json.getArrayInfo("reviews", "review").toList
20 val titles: List[String] = json.getArrayInfo("reviews", "title").toList
21 val texts = (comments ::: reviews ::: titles).filter(f => !CleanUtils.isEmpty(f))
22 (IdBuilder.getSourceKey(inputInfo._1, id), texts)
23 }
24 })
25 }
26
27 // 广播停用词
28 val filterWordRdd = sc.broadcast(sc.textFile(s"$baseDir/data/stopwords.txt").map(_.trim).distinct().collect().toList)
29
30 def formatOutput(infos: List[(Int, String)]): String ={
31 infos.map(info => {
32 val json = new JSONObject()
33 json.put("status", info._1)
34 try{
35 json.put("res", info._2)
36 } catch {
37 case _ => json.put("res", "[]")
38 }