Python小说推荐系统:协同过滤+TF-IDF双路建模实战
2026/9/15 1:44:26 网站建设 项目流程

简介:这是一套面向Python初学者与推荐系统入门学习者的实战项目源码,基于协同过滤与内容相似度原理实现轻量级小说推荐功能,适用于课程设计、毕设参考或算法实践。资源共16个文件,包含4个核心Python脚本(如interface.py主程序、recommend3.py推荐逻辑、爬虫.py数据获取、炫酷系统.py交互界面),4个CSV格式的小说元数据与用户行为样本,4个XML配置及IDE配置文件,以及README.md说明文档和novels.txt原始文本数据,整体包体仅125KB,结构紧凑、依赖简洁。已有362人学习下载,适合快速部署运行并理解推荐流程。读者可直接复现完整推荐链路:从数据采集、特征处理、相似度计算到结果展示;代码配有超详细中文注释,关键函数与算法步骤均有逐行解释;目录模块划分清晰,.idea与.gitignore等开发环境配置文件齐全,便于在PyCharm等IDE中无缝导入调试。

1. 小说推荐系统不是“猜你喜欢”,而是用协同过滤+内容特征双路建模的可调试Python工程

你打开一个小说站,首页弹出“您可能喜欢《诡秘之主》《道诡异仙》”,这背后不是玄学匹配,而是一套基于用户行为与文本特征联合建模的推荐流水线。这个源码包不是玩具 demo,它包含完整的数据加载(novels.csv/novels1.csv/novels2.csv)、用户-小说交互矩阵构建(history.csv)、三种推荐策略实现(recommend3.py中含基于物品的协同过滤、TF-IDF 文本相似度、混合加权逻辑),以及可直接运行的交互入口炫酷系统.py。所有核心模块均带逐行中文注释——比如interface.pyget_user_recommendations()函数,不仅标注了每行代码作用,还说明了k=10的含义是“取相似度 Top10 物品”,alpha=0.7是协同过滤结果与内容推荐结果的加权系数。适合刚学完 Pandas 和 Scikit-learn 的中级 Python 工程师,通过修改novels.txt添加新书、调整recommend3.py中的相似度阈值、替换爬虫.py的 XPath 规则来实操理解推荐系统从数据到服务的完整链路。


2. 推荐引擎底层:协同过滤与文本特征双路建模原理与代码实现

2.1 协同过滤路径:从用户行为日志构建稀疏评分矩阵

推荐系统的第一条路径依赖显式反馈数据。history.csv文件结构为user_id,item_id,rating,timestamp,其中rating是用户对小说的打分(1–5 分)。源码中recommend3.pybuild_user_item_matrix()函数负责将其转为 SciPy CSR 矩阵:

import pandas as pd import numpy as np from scipy.sparse import csr_matrix def build_user_item_matrix(history_path): df = pd.read_csv(history_path) # 确保 user_id 和 item_id 为连续整数索引,避免稀疏矩阵维度错位 user_ids = df['user_id'].astype('category').cat.codes item_ids = df['item_id'].astype('category').cat.codes ratings = df['rating'].values # 构建 CSR 矩阵:行=user_id,列=item_id,值=rating matrix = csr_matrix((ratings, (user_ids, item_ids)), shape=(user_ids.max()+1, item_ids.max()+1)) return matrix, df['user_id'].unique(), df['item_id'].unique()

注意astype('category').cat.codes是关键预处理步骤。若原始user_id为字符串(如"U1001")或存在跳号(如1,2,4,5),直接用作矩阵索引会导致维度膨胀或索引越界。该方法将类别映射为0,1,2,...连续整数,保证矩阵紧凑性。shape参数必须显式指定,否则csr_matrix可能因缺失 ID 而生成远超实际规模的稀疏结构,拖慢后续cosine_similarity计算。

2.2 内容特征路径:TF-IDF 提取小说标题与简介的语义向量

第二条路径不依赖用户行为,而是挖掘小说自身文本信息。novels.csv包含id,title,intro,genre,author字段,recommend3.pybuild_content_vector()使用TfidfVectorizer构建特征:

from sklearn.feature_extraction.text import TfidfVectorizer import jieba # 源码已内置中文分词支持 def chinese_tokenizer(text): return list(jieba.cut(text)) # 合并标题与简介作为文本特征源 df_novels = pd.read_csv('novels.csv') df_novels['text'] = df_novels['title'] + ' ' + df_novels['intro'].fillna('') vectorizer = TfidfVectorizer( tokenizer=chinese_tokenizer, stop_words=['的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个'], max_features=5000, # 控制特征维度,避免内存爆炸 ngram_range=(1, 2) # 启用 unigram + bigram,捕获“修真”“无敌流”等复合词 ) content_vectors = vectorizer.fit_transform(df_novels['text'])

提示max_features=5000是平衡效果与性能的关键参数。实测中若设为10000,在 2GB 内存机器上fit_transform可能 OOM;若低于2000,则“废柴逆袭”“高武世界”等关键短语易被截断。ngram_range=(1,2)显著提升对网文特有表达的捕捉能力——单靠title分词无法区分“重生之我是首富”与“重生之我在首富家当保姆”,而加入 bigram 后,“重生之我”“首富家当”等片段权重上升,相似度计算更准。

2.3 双路融合:加权混合推荐与冷启动兜底策略

两条路径输出后,recommend3.pyhybrid_recommend()函数执行融合:

from sklearn.metrics.pairwise import cosine_similarity def hybrid_recommend(user_id, user_item_matrix, content_vectors, user_to_idx, item_to_idx, alpha=0.7, k=10): # 路径1:协同过滤(基于用户相似度) user_vec = user_item_matrix[user_to_idx[user_id]] user_sim = cosine_similarity(user_vec, user_item_matrix).flatten() # 取相似用户Top5,聚合其评分(排除用户自己已评过的) similar_users = np.argsort(user_sim)[-6:-1][::-1] # top5相似用户索引 cf_scores = np.zeros(content_vectors.shape[0]) for su in similar_users: cf_scores += user_item_matrix[su].toarray().flatten() # 路径2:内容相似度(基于物品向量) # 获取该用户历史阅读的小说ID列表 user_history = user_item_matrix[user_to_idx[user_id]].nonzero()[1] if len(user_history) == 0: # 冷启动:无历史行为 # 直接返回内容相似度最高的Top10小说(基于所有小说平均向量) avg_vec = content_vectors.mean(axis=0) content_sim = cosine_similarity(avg_vec, content_vectors).flatten() return np.argsort(content_sim)[-k:][::-1] # 对用户读过的每本小说,计算其内容相似度并累加 content_scores = np.zeros(content_vectors.shape[0]) for item_id in user_history: item_vec = content_vectors[item_id] sim = cosine_similarity(item_vec, content_vectors).flatten() content_scores += sim # 加权融合:alpha * CF + (1-alpha) * Content final_scores = alpha * cf_scores + (1 - alpha) * content_scores # 过滤已读小说 already_read = user_item_matrix[user_to_idx[user_id]].nonzero()[1] final_scores[already_read] = -np.inf return np.argsort(final_scores)[-k:][::-1]
参数作用调试建议
alpha协同过滤权重新用户多时调低至0.3;老用户行为丰富时可升至0.8
k返回推荐数量前端展示通常5–10,API 接口建议20供下游排序
similar_users数量协同过滤邻居数5平衡精度与速度;10提升长尾覆盖但响应变慢

3. 工程化落地:从源码到可交互系统的关键配置与调试技巧

3.1炫酷系统.py的启动逻辑与接口封装

整个系统以炫酷系统.py为入口,它并非简单脚本,而是封装了 CLI 交互与轻量 HTTP 服务双模式:

# 炫酷系统.py 核心逻辑节选 if __name__ == '__main__': # 初始化推荐引擎(耗时操作,只执行一次) engine = RecommendationEngine( history_path='history.csv', novels_path='novels.csv', cache_dir='.cache' # 自动缓存TF-IDF向量与相似度矩阵 ) # CLI 模式:输入 user_id 直接输出推荐 if len(sys.argv) > 1 and sys.argv[1] == '--cli': user_id = int(sys.argv[2]) if len(sys.argv) > 2 else 1 recs = engine.get_recommendations(user_id, k=5) print(f"用户 {user_id} 的推荐:") for idx, novel_id in enumerate(recs, 1): title = engine.novel_df.loc[engine.novel_df['id'] == novel_id, 'title'].iloc[0] print(f"{idx}. {title}") # Web 模式:启动 Flask 服务(需 pip install flask) else: from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/recommend', methods=['GET']) def recommend_api(): user_id = int(request.args.get('user_id')) k = int(request.args.get('k', 5)) try: recs = engine.get_recommendations(user_id, k=k) # 返回小说完整信息,非仅ID result = [] for nid in recs: novel_info = engine.novel_df[engine.novel_df['id'] == nid].to_dict('records')[0] result.append(novel_info) return jsonify({'status': 'success', 'data': result}) except KeyError: return jsonify({'status': 'error', 'message': 'User not found'}), 404 app.run(host='0.0.0.0', port=5000, debug=False) # 生产环境务必关闭debug

提示cache_dir='.cache'是性能关键。首次运行会生成tfidf_matrix.npzuser_similarity.npz两个二进制文件,后续启动跳过耗时的fit_transformcosine_similarity计算。若修改了novels.csv,需手动删除.cache目录触发重建。

3.2爬虫.py的可定制化字段抽取与反爬适配

爬虫.py并非通用框架,而是针对某小说站点 HTML 结构硬编码的解析器,但设计了清晰的扩展点:

# 爬虫.py 关键配置区(位于文件顶部) SITE_CONFIG = { 'base_url': 'https://example-novel-site.com', 'book_list_selector': 'div.book-list > ul > li', # 小说列表容器 'title_selector': 'h3.title a', # 标题链接 'intro_selector': 'p.intro', # 简介文本 'genre_selector': 'span.genre', # 分类标签 'author_selector': 'span.author', # 作者名 'delay_range': (1, 3) # 请求间隔(秒),防封IP } def parse_novel_page(soup): """解析单本小说详情页,返回字典""" data = {} data['title'] = soup.select_one(SITE_CONFIG['title_selector']).get_text(strip=True) data['intro'] = soup.select_one(SITE_CONFIG['intro_selector']).get_text(strip=True) data['genre'] = [tag.get_text(strip=True) for tag in soup.select(SITE_CONFIG['genre_selector'])] data['author'] = soup.select_one(SITE_CONFIG['author_selector']).get_text(strip=True) return data

要适配新站点,只需修改SITE_CONFIG字典中的 CSS 选择器,无需重写解析逻辑。若目标站使用 JavaScript 渲染,需将requests.get()替换为seleniumplaywright,并在parse_novel_page()前添加等待逻辑:

# 替换原 requests.get() 调用 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get(url) WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, SITE_CONFIG['title_selector']))) soup = BeautifulSoup(driver.page_source, 'html.parser') driver.quit()

3.3.idea目录与 PyCharm 调试配置详解

项目包含完整 PyCharm 工程配置(.idea目录),新手可直接用 PyCharm 打开根目录,无需手动配置解释器与路径。关键配置点:

  • 运行配置Run/Debug Configurations中预设CLI ModeWeb Mode两个配置

    • CLI Mode:Script path 设为炫酷系统.py,Parameters 设为--cli 101(测试用户101)
    • Web Mode:Script path 同上,Parameters 留空,Environment variables 添加FLASK_ENV=production
  • 代码检查inspectionProfiles启用了PEP 8PyLint规则,但禁用了too-many-lines(因recommend3.py达 320 行属合理复杂度)

  • 注释模板inspectionProfilesPythonDocstring模板已预置,输入"""自动生成:

    """ Args: user_id (int): 用户唯一标识符 k (int): 返回推荐数量,默认5 Returns: List[int]: 小说ID列表,按推荐得分降序排列 """

注意:若 PyCharm 提示Unresolved reference 'jieba',需在File → Settings → Project → Python Interpreter中点击+安装jiebascikit-learnnumpypandas通常已预装,但版本需 ≥1.20(history.csv读取依赖pd.read_csvdtype推断优化)。


4. 排查高频问题:从 ImportError 到推荐结果为空的定位路径

4.1 模块导入失败的三类根源与修复命令

当执行python 炫酷系统.pyImportError: No module named 'sklearn',不要盲目pip install sklearn

错误现象根本原因修复命令验证方式
No module named 'jieba'中文分词库未安装pip install jiebapython -c "import jieba; print(jieba.lcut('测试'))"
No module named 'scipy.sparse'SciPy 安装不完整(常见于 Windows)pip uninstall scipy && pip install --only-binary=all scipypython -c "from scipy.sparse import csr_matrix"
ImportError: cannot import name 'cosine_similarity'scikit-learn 版本过低(<0.22)pip install --upgrade scikit-learnpython -c "from sklearn.metrics.pairwise import cosine_similarity"

提示pip install --only-binary=all scipy解决 Windows 下编译失败问题。scipy的 C 扩展在 Windows 缺少 Visual Studio Build Tools 时无法编译,--only-binary强制使用预编译 wheel 包。

4.2 推荐结果为空的五步诊断法

炫酷系统.py --cli 101输出空列表,按顺序排查:

  1. 检查history.csv中是否存在user_id=101

    awk -F, '$1==101 {print}' history.csv | head -5

    若无输出,说明该用户无行为记录,触发冷启动逻辑——此时应返回content_vectors最相似的 Top10,而非空。

  2. 验证user_item_matrix是否构建成功
    recommend3.pybuild_user_item_matrix()函数末尾添加:

    print(f"Matrix shape: {matrix.shape}, non-zero entries: {matrix.nnz}")

    正常应输出类似Matrix shape: (500, 2000), non-zero entries: 12450。若nnz=0,说明history.csv格式错误(如逗号分隔符被中文逗号替代)。

  3. 确认novels.csvid字段与history.csvitem_id类型一致

    # 在 hybrid_recommend() 开头添加 print("History item_id dtype:", type(user_history[0])) print("Novels id dtype:", type(engine.novel_df['id'].iloc[0]))

    若前者为numpy.int64后者为str,需在build_user_item_matrix()中统一转换:df['item_id'] = df['item_id'].astype(int)

  4. 检查content_vectors是否为空矩阵

    print("Content vectors shape:", content_vectors.shape) print("Content vectors nnz:", content_vectors.nnz)

    nnz=0,说明TfidfVectorizer未提取到有效 token——大概率是novels.csvintro列全为NaN或空字符串,需用fillna('')处理。

  5. 审查hybrid_recommend()中的过滤逻辑
    关键行final_scores[already_read] = -np.infalready_read为空数组,-np.inf赋值无效。应改为:

    if len(already_read) > 0: final_scores[already_read] = -np.inf

4.3 性能瓶颈定位:用 cProfile 快速识别慢函数

当推荐响应超过 2 秒,启用内置性能分析:

# 在炫酷系统.py 同级目录执行 python -m cProfile -o profile_stats.prof 炫酷系统.py --cli 101 # 生成分析报告 python -c " import pstats p = pstats.Stats('profile_stats.prof') p.sort_stats('cumulative').print_top(10) "

典型输出:

100000 function calls in 1.892 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 1.892 1.892 炫酷系统.py:15(<module>) 1 0.000 0.000 1.891 1.891 recommend3.py:123(hybrid_recommend) 1 0.002 0.002 1.520 1.520 recommend3.py:89(build_content_vector) 1 0.001 0.001 1.518 1.518 sklearn/feature_extraction/text.py:1570(fit_transform) 50000 1.515 0.000 1.515 0.000 {built-in method builtins.len}

可见fit_transform占用 1.5 秒,证实TfidfVectorizer是瓶颈。此时应检查max_features是否过大,或启用cache_dir避免重复计算。


5. 进阶技巧:用novels.txt快速注入新书与 A/B 测试推荐策略

5.1novels.txt的格式规范与增量更新流程

novels.txt是纯文本小说元数据注入通道,格式为|分隔的单行记录:

1001|《深海余烬》|蒸汽朋克与克苏鲁神话交织的航海史诗,主角在沉没的旧大陆遗迹中寻找文明火种。|科幻,奇幻|黑山老妖 1002|《灵境行者》|都市异能+副本闯关,主角觉醒“灵境”能力,在现实与幻境夹缝中求生。|都市,异能|卖报小郎君

要新增小说,只需追加一行并运行python 爬虫.py --update-from-txt(该命令在爬虫.py中已预留):

# 爬虫.py 中新增函数 def update_from_txt(txt_path='novels.txt'): """从novels.txt追加小说到novels.csv""" with open(txt_path, 'r', encoding='utf-8') as f: lines = [line.strip() for line in f if line.strip()] new_records = [] for line in lines: parts = line.split('|') if len(parts) != 5: print(f"跳过格式错误行: {line}") continue new_records.append({ 'id': int(parts[0]), 'title': parts[1], 'intro': parts[2], 'genre': parts[3], 'author': parts[4] }) df_existing = pd.read_csv('novels.csv') df_new = pd.DataFrame(new_records) # 去重:按id合并,新记录覆盖旧记录 df_merged = pd.concat([df_existing, df_new]).drop_duplicates(subset=['id'], keep='last') df_merged.to_csv('novels.csv', index=False, encoding='utf-8-sig') print(f"已更新 {len(new_records)} 条小说记录")

注意encoding='utf-8-sig'确保 Excel 能正确读取 CSV 中的中文。keep='last'实现“新数据覆盖旧数据”,便于修正小说简介错别字。

5.2 A/B 测试框架:在同一入口切换推荐算法版本

炫酷系统.py支持通过环境变量动态加载不同推荐策略,无需修改代码:

# 炫酷系统.py 中 get_recommendations() 方法 def get_recommendations(self, user_id, k=5): strategy = os.getenv('RECOMMEND_STRATEGY', 'hybrid') # 默认hybrid if strategy == 'cf_only': return self._collaborative_filtering(user_id, k) elif strategy == 'content_only': return self._content_based(user_id, k) else: # hybrid return self._hybrid_recommend(user_id, k) # 启动时指定策略 # Linux/Mac: RECOMMEND_STRATEGY=cf_only python 炫酷系统.py --cli 101 # Windows: set RECOMMEND_STRATEGY=content_only && python 炫酷系统.py --cli 101

结合 Nginx 日志,可统计不同策略下用户的点击率(CTR):

# 从 access.log 提取 /recommend 请求的策略参数与响应时间 awk '/\/recommend.*strategy=/ {print $9,$11}' access.log | \ awk '{strategy[$1]++; total[$1]+=$2} END {for (s in strategy) print s, total[s]/strategy[s]}' | \ sort -k2 -nr

输出示例:

cf_only 1245.3 hybrid 892.1 content_only 1678.9

说明content_only策略平均响应最慢(因需实时计算所有小说相似度),而cf_only最快——这验证了混合策略在效果与性能间的折中价值。

5.3 注释质量验证:用 pydocstyle 检查文档字符串合规性

源码宣称“超详细注释”,可用pydocstyle客观验证:

pip install pydocstyle pydocstyle recommend3.py --convention=google

正常应输出No violations found。若出现D102 Missing docstring in function,说明某函数缺少 Google 风格文档字符串。修复模板如下:

def get_user_recommendations(self, user_id, k=5): """获取指定用户的推荐小说列表。 Args: user_id (int): 用户唯一标识符,必须存在于history.csv中 k (int): 返回推荐数量,取值范围1-20,默认5 Returns: List[Dict]: 包含小说信息的字典列表,按推荐得分降序排列, 每个字典含'id','title','intro','genre','author'字段 Raises: KeyError: 当user_id不在用户索引中时抛出 """

此格式被 PyCharm、VS Code 的 IntelliSense 完全识别,悬停提示即显示完整参数说明,真正实现“注释即文档”。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询