我做了一个基于知识图谱的图书推荐系统,踩了不少坑
起因
去年做毕设的时候,导师给了个课题:做一个图书推荐系统。一开始想的很简单,不就是协同过滤嘛,sklearn 调个包就完事了。结果导师说:"你这推荐出来的书,用户问你为什么推荐,你怎么解释?"
这一问把我问住了。确实,传统的协同过滤、矩阵分解这些方法,推荐结果就是个黑盒,说不清楚为什么推荐这本书。
于是开始调研,看了一堆论文,最后决定用知识图谱 + 评论关键词的方案。现在系统已经上线了,分享一下整个过程。
在线体验地址:http://47.110.250.188:5000/
GitHub 地址:github.com/yangqunfeng… (欢迎 Star)
数据准备
爬虫部分
数据是用的 Scrapy 爬了大概一个月,最后拿到:
- 68 万本图书的基本信息(书名、作者、出版社、评分等)
- 367 万条用户评论
这里有个坑:网站的反爬很严格,IP 封得很快。最后是买了代理池 + 设置随机延迟才搞定的。
数据清洗
原始数据质量参差不齐,主要问题:
- 作者名字格式不统一(有的带国籍,有的不带)
- 出版社名字有各种变体
- 评论里有大量无意义的短评("好看"、"不错"之类的)
清洗代码写了好几版,最后用正则 + 人工规则搞定。
技术方案
知识图谱构建
用 NetworkX 构建了一个异构图,包含 5 种实体:
- 图书
- 作者
- 出版社
- 译者
- 系列
关系有:
- 图书-作者(写作关系)
- 图书-出版社(出版关系)
- 图书-译者(翻译关系)
- 图书-系列(系列关系)
最后构建出来的图谱有 70 万+ 实体,100 万+ 关系。
评论关键词提取
这部分是核心创新点。传统的推荐系统只看图书的结构化信息,但评论里其实包含了很多有价值的特征。
比如《三体》的评论里,高频词有:科幻、宇宙、文明、物理、黑暗森林等。这些词能很好地描述这本书的特点。
关键词提取用了 TF-IDF + TextRank 双算法:
# TF-IDF 提取
tfidf_keywords = jieba.analyse.extract_tags(
comment_text,
topK=50,
withWeight=True
)
# TextRank 提取
textrank_keywords = jieba.analyse.textrank(
comment_text,
topK=40,
withWeight=True
)
# 合并权重
for word, weight in tfidf_keywords:
keyword_dict[word] = weight
for word, weight in textrank_keywords:
keyword_dict[word] = keyword_dict.get(word, 0) + weight * 0.8
但这样提取出来的关键词质量不高,有很多无意义的词("作者"、"小说"、"故事"之类的)。
后来加了智能过滤,只保留真正能描述图书特征的词:
- 主题词(科幻、历史、爱情等)
- 情节元素(战斗、阴谋、复仇等)
- 人物特征(主角、英雄、反派等)
- 风格特征(幽默、深刻、细腻等)
这部分调了很久,最后效果还不错。
推荐算法
提供了三种推荐策略:
1. 知识图谱推荐
基于图结构,找相似的书。比如用户喜欢《三体》,那就推荐:
- 同作者的书(刘慈欣的其他作品)
- 同系列的书(三体 2、三体 3)
- 同出版社的科幻书
这种推荐的好处是可解释性强,能明确告诉用户为什么推荐。
2. 关键词推荐
基于评论关键词的语义相似度。用户喜欢《三体》,系统提取出关键词:科幻、宇宙、文明、物理等,然后找其他书的评论里也有这些关键词的。
这种推荐能发现一些跨作者、跨系列的相似书籍。
3. 混合推荐
结合上面两种策略:
最终得分 = 0.5 × 知识图谱得分 + 0.5 × 关键词相似度
实测效果最好。
性能优化
多进程加速
评论关键词提取很慢,367 万条评论,单进程要跑好几个小时。
后来改成多进程并行:
from multiprocessing import Pool, cpu_count
num_processes = cpu_count() - 1
with Pool(processes=num_processes) as pool:
results = pool.imap_unordered(process_book_comments, tasks)
速度提升了 6 倍,半小时就跑完了。
缓存机制
知识图谱和关键词数据都做了缓存,用 pickle 序列化。首次运行需要 30-60 分钟构建,之后启动只要几秒。
前端界面
前端用的原生 JavaScript,没用框架(主要是懒得学 React/Vue)。
做了几个功能:
- 搜索框自动补全
- 三种推荐策略切换
- 关键词可视化选择
- 中英文双语切换
界面还算简洁,主要精力都在后端算法上了。
踩过的坑
坑 1:内存爆炸
一开始把所有数据都加载到内存,结果程序跑着跑着就 OOM 了。后来改成分批处理 + 及时释放内存才解决。
坑 2:中文分词不准
jieba 默认的分词效果不太好,"三体世界"会被分成"三体"和"世界"。后来加了自定义词典,把书名都加进去了。
坑 3:推荐结果太单一
最开始只用知识图谱推荐,结果推荐出来的都是同一个作者的书。后来加了关键词推荐,多样性才上来。
效果展示
随便测试几个:
输入:三体 推荐:
- 球状闪电(同作者)
- 银河帝国(关键词匹配:科幻、宇宙、文明)
- 2001太空漫游(关键词匹配:科幻、太空)
输入:活着 推荐:
- 许三观卖血记(同作者)
- 平凡的世界(关键词匹配:苦难、人性、时代)
- 白鹿原(关键词匹配:历史、家族、命运)
效果还可以,至少比纯协同过滤要好。
开源
技术栈:
- 后端:Flask + NetworkX + Pandas + Jieba + scikit-learn
- 前端:原生 JavaScript + CSS3
- 算法:知识图谱 + TF-IDF + TextRank
后续计划
- 加入用户行为数据,做个性化推荐
- 优化关键词提取算法,提高准确率
- 加入图书封面展示
- 做个移动端适配
总结
整个项目做下来,最大的感受是:推荐系统不只是算法,数据质量和工程实现同样重要。
算法再好,数据质量差也白搭。工程实现不好,性能上不去也没用。
另外,可解释性真的很重要。用户不会因为你的算法多先进就信任你的推荐,但如果你能告诉他"推荐这本书是因为和你喜欢的《三体》作者相同",他就更容易接受。
最后,欢迎大家体验和提意见!