前面我们已经计算得到tfidf的结果‘tfidf_keywords_values’和textrank的结果‘textrank_keywords_values’。
下面要计算基于tfidf
和textrank
计算离线文章画像
所谓文章画像,就是给文章定义一些主题词和关键词,用来代表这篇文章。
关键词和主题词最大的区别在于
这个项目中,我们采用tfidf和textrank两种方式得到了每篇文章两组权重前20的词组,对于如何确定主题词和关键词,我们处理的方式是:
1) 关键词:textrank处理的topN个词,其权重乘以对应词的逆文档频率(较少个别意义不大的词在多个文档中出现导致权重过高)
2) 主题词:textrank和tfidf计算结果的共同出现的词
因为我们是将每篇文章的textrank处理得到的topN个词乘以idf,消除在多篇文章出现的词带来的权重影响,所以要分别加载textrank_keywords_values和idf_keywords_values
# 加载idf逆文档频率
idf = ktt.spark.sql("select * from idf_keywords_values") #
# 加载textrank_keywords_values
textrank_keywords_df = ktt.spark.sql("select * from textrank_keywords_values")
# 重命名idf的keyword列为keyword1
idf = idf.withColumnRenamed("keyword", "keyword1")
# 按照keyword关键词作连接,相当于在textrank结果右边补充对应词的逆文档频率
result = textrank_keywords_df.join(idf,textrank_keywords_df.keyword==idf.keyword1)
# 利用withcolumn方法新增‘weights’列,数据=textrank词权重X该词的idf,最后只选择需要的列
keywords_res = result.withColumn("weights", result.textrank * result.idf).select(["article_id", "channel_id", "keyword", "weights"])
以上计算已经得到最终的结果,但是每篇文章的每个关键词和权重都是单独一列,因此利用文章id进行聚合,将关键词结果放在一起,让结果以字典形式展现
# spark sql的dataframe注册临时表temptable,方便用sql语句操作
keywords_res.registerTempTable("temptable")
# 根据文章id进行分组,一篇文章的所有textrank关键词用collect_list聚合成list,weights同理
merge_keywords = ktt.spark.sql("select article_id, min(channel_id) channel_id, collect_list(keyword) keywords, collect_list(weights) weights from temptable group by article_id")
# 定义函数,实现:单独聚合的keyword和weight如何成为keyword:weight的字典形式
def _func(row):
return row.article_id, row.channel_id, dict(zip(row.keywords, row.weights))
# 调用函数,合并关键词权重合并成字典
keywords_info = merge_keywords.rdd.map(_func).toDF(["article_id", "channel_id", "keywords"])
前面我们计算完了关键词,接下来我们将 TF-IDF 和 TextRank 的共现词作为主题词,将 TF-IDF 权重表 tfidf_keywords_values 和 TextRank 权重表 textrank_keywords_values 进行关联,并利用 collect_set() 对结果进行去重,即可得到 TF-IDF 和 TextRank 的共现词,即主题词
# 利用inner join取两个结果的keyword交集
topic_sql = select t.article_id article_id2, collect_set(t.keyword) topics
from tfidf_keywords_values t
inner join textrank_keywords_values r
where t.keyword=r.keyword
group by article_id2
article_topics = ktt.spark.sql(topic_sql)
article_topics 结果如下所示,topics 即为每篇文章的主题词列表
最后,将主题词结果和关键词结果合并,即为文章画像,保存到表 article_profile
# 通过文章id,将关键词和主题词的结果必定
article_profile = keywords_info.join(article_topics,keywords_info.article_id==article_topics.article_id2).select(["article_id", "channel_id", "keywords", "topics"])
# 结果写入hive数仓的表
articleProfile.write.insertInto("article_profile")
到hive中检查一下表
hive> select * from article_profile limit 1;
OK
26 17 {
"策略":0.3973770571351729,"jpg":0.9806348975390871,"用户":1.2794959063944176,"strong":1.6488457985625076,"文件":0.28144603583387057,"逻辑":0.45256526469610714,"形式":0.4123994242601279,"全自":0.9594604850547191,"h2":0.6244481634710125,"版本":0.44280276959510817,"Adobe":0.8553618185108718,"安装":0.8305037437573172,"检查更新":1.8088946300014435,"产品":0.774842382276899,"下载页":1.4256311032544344,"过程":0.19827163395829256,"json":0.6423301791599972,"方式":0.582762869780791,"退出应用":1.2338671268242603,"Setup":1.004399549339134} ["Electron","全自动","产品","版本号","安装包","检查更新","方案","版本","退出应用","逻辑","安装过程","方式","定性","新版本","Setup","静默","用户"]
Time taken: 0.322 seconds, Fetched: 1 row(s)
定义离线更新文章画像的方法,首先合并最近一个小时的文章信息,接着计算每个词的 TF-IDF 和 TextRank 权重,并根据 TF-IDF 和 TextRank 权重计算得出文章关键词和主题词,最后将文章画像信息保存到 Hive
def update_article_profile():
"""
定时更新文章画像
:return:
"""
ua = UpdateArticle()
# 合并文章信息
sentence_df = ua.merge_article_data()
if sentence_df.rdd.collect():
textrank_keywords_df, keywordsIndex = ua.generate_article_label()
ua.get_article_profile(textrank_keywords_df, keywordsIndex)
利用 Apscheduler 添加定时更新文章画像任务,设定每隔 1 个小时更新一次
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.executors.pool import ProcessPoolExecutor
# 创建scheduler,多进程执行
executors = {
'default': ProcessPoolExecutor(3)
}
scheduler = BlockingScheduler(executors=executors)
# 添加一个定时更新文章画像的任务,每隔1个小时运行一次
scheduler.add_job(update_article_profile, trigger='interval', hours=1)
scheduler.start()
利用 Supervisor 进行进程管理,配置文件如下
[program:offline]
environment=JAVA_HOME=/root/bigdata/jdk,SPARK_HOME=/root/bigdata/spark,HADOOP_HOME=/root/bigdata/hadoop,PYSPARK_PYTHON=/miniconda2/envs/reco_sys/bin/python,PYSPARK_DRIVER_PYTHON=/miniconda2/envs/reco_sys/bin/python
command=/miniconda2/envs/reco_sys/bin/python /root/toutiao_project/scheduler/main.py
directory=/root/toutiao_project/scheduler
user=root
autorestart=true
redirect_stderr=true
stdout_logfile=/root/logs/offlinesuper.log
loglevel=info
stopsignal=KILL
stopasgroup=true
killasgroup=true