ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

知识图谱电影推荐系统毕设源码:从爬虫到NetworkX建图全链路实战

知识图谱电影推荐系统毕设源码:从爬虫到NetworkX建图全链路实战 简介这份资源是面向计算机相关专业学生与项目实战学习者的Python毕业设计完整源码主题为基于知识图谱的电影推荐系统适合用作毕设参考、课程设计或期末大作业。项目经导师指导并通过评审源码均经本地编译与严格调试可正常运行难度适中。压缩包共55个文件以43个py脚本为核心涵盖爬虫采集、电影信息解析与推荐逻辑等模块另含4个txt数据文件、4个cfg配置、3个md说明文档及1个sql建表脚本整体约890KB结构清晰便于按模块阅读。内容预览显示项目包含豆瓣、互动百科、百度百科等多源数据采集与电影评分处理等环节可帮助读者理解知识图谱构建与推荐流程的落地方式。目前已有155人学习适合需要完整项目方案与排错思路的学习者下载参考。1. 知识图谱电影推荐系统一份能跑通的毕设源码到底长什么样很多计算机专业的同学做毕设时都遇到过这种局面推荐算法方向听起来体面真动手却发现要么是协同过滤跑个 MovieLens 就交差要么是知识图谱概念一堆、代码一行跑不起来。这份 Python 毕业设计基于知识图谱的电影推荐系统源码走的是另一条路——它把「爬数据 → 建图谱 → 做推荐」整条链路都落成了可执行的 Python 脚本而不是只给一个算法片段。项目里能看到 craw、craw_all_baidu、craw_without_spider、hudong_baike、baidu_baike、craw_all_hudong、douban 这些目录配合 dou_tv.py、dou_movie.py、movieinfor.py、movie_grade.py 等脚本以及 movies.txt 数据文件和 README.md 说明基本覆盖了从数据采集到电影信息、评分处理的完整流程。它适合正在做计算机相关毕设、课程设计或期末大作业的学习者难度适中源码经过本地编译调试能直接作为项目实战练习的底稿。下面我按「这是什么 → 怎么用 → 坑在哪」的顺序把这份资源拆开讲清楚。2. 知识图谱推荐系统的技术选型为什么用爬虫加图谱而不是纯协同过滤2.1 推荐系统的三条常见路线与这份源码的定位做电影推荐业内常见三条路线。第一条是协同过滤靠用户-物品评分矩阵算相似度实现简单但冷启动严重新电影没评分就推不出去。第二条是内容推荐靠电影本身的类型、导演、演员做特征匹配能缓解冷启动但特征工程全靠人工维度一多就难维护。第三条是知识图谱推荐把电影、演员、导演、类型、评分等实体和它们之间的关系建成图结构推荐时沿着关系路径找相似实体既能解释「为什么推这部」又能利用实体间的多跳关联。这份源码选的是第三条路但并没有一上来就搞复杂的图神经网络而是用爬虫先把数据攒够再用图谱结构组织电影信息最后落到推荐逻辑上。这个定位对毕设很友好评审老师看得到数据来源、看得到图谱构建过程、也看得到推荐结果每一环都有东西可讲。纯协同过滤的毕设容易被问「你的创新点在哪」而知识图谱方向天然带一个「结构化语义关联」的叙事答辩时更好展开。从目录结构看craw 系列目录负责数据采集douban 相关脚本处理豆瓣电影数据hudong_baike 和 baidu_baike 处理百科类实体信息movies.txt 是落地的数据文件movieinfor.py 和 movie_grade.py 分别处理电影信息和评分。这套分工说明作者是把「数据层」和「处理层」分开的不是一锅乱炖。对做毕设的同学来说这种分层本身就是可以写进论文系统设计章节的内容。2.2 知识图谱构建的核心概念实体、关系、属性在动手之前得先把三个词对齐。实体是图谱里的节点比如一部电影、一个演员、一个导演、一个电影类型。关系是节点之间的边比如「主演」「执导」「属于类型」「评分」。属性是节点或边上的附加信息比如电影的上映年份、时长、评分值。这份源码里movies.txt 很可能就是实体和属性的落地载体而爬虫脚本负责把散落在网页上的实体关系抓回来。常见做法是先用爬虫抓电影列表和详情页解析出电影名、导演、主演、类型、评分等字段存成结构化文本再把这些字段映射成图谱的节点和边。比如「肖申克的救赎」是一个电影实体「弗兰克·德拉邦特」是一个导演实体两者之间有一条「执导」关系。推荐时如果用户喜欢某部电影系统就沿着「同导演」「同主演」「同类型」这些关系路径找相邻电影按路径权重排序输出。这里有个容易翻车的地方很多人以为知识图谱必须上 Neo4j 这类图数据库。其实对毕设规模的数据量用 Python 字典或 NetworkX 在内存里建图完全够用还省去装数据库的麻烦。这份源码没有在目录里暴露数据库配置文件大概率是轻量级实现这对本地跑通是好事。如果你论文里想写「基于 Neo4j 构建知识图谱」那是另一套方案需要额外装环境和写 Cypher 查询工作量会上去。选型时想清楚是要快速跑通拿分还是要堆技术栈撑篇幅。2.3 环境准备与依赖安装的可执行步骤拿到源码包后第一步不是急着跑主程序而是把 Python 环境理顺。这份项目是 Python 写的爬虫部分大概率用到 requests、BeautifulSoup 或 lxml数据处理可能用到 pandas。下面是我一般会走的准备流程。# 查看当前 Python 版本建议 3.8 及以上 python --version # 创建独立虚拟环境避免污染全局包 python -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 激活虚拟环境macOS / Linux source venv/bin/activate # 安装常见依赖具体以 README.md 为准 pip install requests beautifulsoup4 lxml pandas networkx这段命令的逻辑是先确认 Python 版本因为部分爬虫库对新版本支持有差异再用 venv 建隔离环境毕设项目依赖往往版本敏感隔离能避免和系统里其他项目打架最后装依赖。参数上requests 负责发 HTTP 请求beautifulsoup4 加 lxml 负责解析 HTMLpandas 处理表格数据networkx 用来建图。注意 README.md 里如果列了 requirements.txt优先用pip install -r requirements.txt那样版本更准。如果装 lxml 时报编译错误Windows 下可以换pip install lxml --only-binary :all:走预编译包。提示先读 README.md 再动手。这类毕设项目的说明文件里通常写了运行顺序和依赖清单跳过它直接跑脚本十有八九会卡在缺包或路径错误上。3. 爬虫脚本与数据落地从 douban 到 movies.txt 的完整链路3.1 爬虫目录结构拆解与运行顺序这份源码的爬虫部分目录名很有信息量。craw 可能是通用爬虫逻辑craw_all_baidu 和 craw_all_hudong 看名字是抓取百度百科和互动百科的全量数据craw_without_spider 暗示有一种不依赖爬虫框架的轻量抓取方式baidu_baike 和 hudong_baike 则是针对具体百科站点的解析脚本。douban 目录配合 dou_tv.py、dou_movie.py负责豆瓣的电视剧和电影数据。合理的运行顺序是先跑百科类爬虫补齐实体信息再跑豆瓣类爬虫抓电影详情和评分最后用 movieinfor.py 和 movie_grade.py 做信息整合与评分处理输出到 movies.txt。为什么这个顺序因为百科提供的是实体基础属性导演、演员、类型豆瓣提供的是用户评分和热度先有实体再有评分图谱的节点和边才能对齐。如果反过来先抓评分实体信息不全建图时会大量出现孤立节点。# 以 dou_movie.py 为例的典型爬虫结构示意具体以源码为准 import requests from bs4 import BeautifulSoup import time import random def fetch_movie_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } # 随机延时降低被目标站点限流的概率 time.sleep(random.uniform(1, 3)) resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding return resp.text def parse_movie(html): soup BeautifulSoup(html, lxml) # 解析电影名、导演、主演、类型、评分等字段 title soup.find(span, propertyv:itemreviewed) rating soup.find(strong, class_ll rating_num) return { title: title.get_text(stripTrue) if title else None, rating: rating.get_text(stripTrue) if rating else None, }这段代码的关键点有三个。第一headers 里带 User-Agent 是基本礼貌不带的话很多站点直接返回 403。第二time.sleep(random.uniform(1, 3))是随机延时固定延时容易被识别出机器节奏随机化更稳。第三resp.apparent_encoding让 requests 自动推断编码中文站点编码混乱是常态写死 utf-8 经常抓到乱码。参数上timeout 设 10 秒是防止某个请求卡死拖垮整个脚本实际跑的时候如果网络慢可以调到 15。3.2 数据清洗与 movies.txt 的字段设计爬下来的原始数据是脏的电影名带多余空格、评分是字符串、类型字段可能用斜杠或逗号分隔、有的条目缺导演。movieinfor.py 和 movie_grade.py 的职责大概率就是清洗和整合。movies.txt 作为最终落地文件字段设计直接决定后面建图和推荐的难度。常见做法是设计成每行一条记录字段用制表符或竖线分隔包含电影 ID、名称、导演、主演、类型、评分、年份。下面是一个清洗脚本的示意。import pandas as pd def clean_movies(raw_path, out_path): df pd.read_csv(raw_path, sep\t, encodingutf-8) # 去掉名称首尾空格 df[title] df[title].astype(str).str.strip() # 评分转数值无法转换的置空 df[rating] pd.to_numeric(df[rating], errorscoerce) # 类型字段统一分隔符 df[genre] df[genre].astype(str).str.replace(/, |) # 丢弃没有名称的记录 df df.dropna(subset[title]) df.to_csv(out_path, sep\t, indexFalse, encodingutf-8) return df clean_movies(movies_raw.txt, movies.txt)逻辑说明pd.to_numeric的errorscoerce参数把无法转成数字的评分变成 NaN而不是直接报错中断这对脏数据很关键。str.replace(/, |)统一类型分隔符后面建图时按|切分就行不用再判断多种分隔符。dropna(subset[title])保证没有名称的记录不进入最终数据因为名称是图谱里电影实体的主键缺了它整条记录没法用。参数上sep 用制表符是因为电影名里可能含逗号用逗号分隔会错位。注意清洗后的 movies.txt 建议先备份一份。后面建图和推荐调试时如果改坏了数据有备份能快速回滚不用重新爬。3.3 爬虫合规与数据量控制的实际边界做毕设绕不开一个问题爬虫抓多少数据合适。抓太少图谱稀疏推荐结果没说服力抓太多跑一次几小时调试周期被拖垮。我的经验是毕设规模控制在几百到两千条电影记录就够展示效果了。movies.txt 如果已经有几百条先拿这批跑通全流程别一上来就追求全量。另外爬虫要控制频率、遵守目标站点的 robots 规则这是基本的技术伦理也是答辩时可能被问到的地方。craw_without_spider 这个目录名暗示作者提供了不依赖爬虫框架的方案可能是用 requests 直接请求接口或静态页面这种方案更轻但也更容易触发限流。实际跑的时候如果遇到 403 或验证码先降频率、换请求头而不是硬刚。数据抓不下来时检查三件事请求头是否完整、延时是否太短、目标页面结构是否变了。页面结构变化是爬虫失效的头号原因解析不到字段时先打印一段 HTML 看看选择器还对不对。4. 知识图谱构建与推荐逻辑把 movies.txt 变成可查询的关系网络4.1 用 NetworkX 从结构化数据建图数据清洗完下一步是把 movies.txt 里的记录转成图。前面说过毕设规模用 NetworkX 足够不用上图数据库。建图的核心是把每部电影当成一个节点把导演、演员、类型也当成节点电影和它们之间的关系作为边。import networkx as nx import pandas as pd def build_graph(movies_path): df pd.read_csv(movies_path, sep\t, encodingutf-8) G nx.Graph() for _, row in df.iterrows(): movie row[title] G.add_node(movie, typemovie, ratingrow.get(rating)) # 导演关系 if pd.notna(row.get(director)): G.add_node(row[director], typedirector) G.add_edge(movie, row[director], relationdirected_by) # 类型关系按 | 切分 if pd.notna(row.get(genre)): for g in str(row[genre]).split(|): g g.strip() if g: G.add_node(g, typegenre) G.add_edge(movie, g, relationbelongs_to) return G G build_graph(movies.txt) print(节点数:, G.number_of_nodes(), 边数:, G.number_of_edges())逻辑说明add_node时带type属性是为了后面区分节点种类推荐时只沿特定类型的关系走。add_edge带relation属性是为了区分「执导」和「属于类型」这两种边避免推荐时把导演和类型混为一谈。参数上pd.notna用来跳过缺失字段防止把 NaN 当成实体加进图里。跑完打印节点数和边数能快速判断图建得对不对——如果边数远小于节点数说明关系抽取有问题得回去查清洗脚本。4.2 基于关系路径的推荐算法实现图建好后推荐逻辑就是「给定一部电影找和它关联最强的其他电影」。最简单的做法是沿共同邻居走两部电影如果共享同一个导演或同一个类型就认为它们相关共享的邻居越多、路径越短相关度越高。def recommend(G, movie, top_n5): if movie not in G: return [] scores {} # 找电影的直接邻居导演、类型等 neighbors set(G.neighbors(movie)) for other in G.nodes: if other movie or G.nodes[other].get(type) ! movie: continue other_neighbors set(G.neighbors(other)) # 共同邻居数量作为基础分 common neighbors other_neighbors if common: # 评分高的电影加权 rating G.nodes[other].get(rating) or 0 scores[other] len(common) float(rating) / 10 ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_n] print(recommend(G, 肖申克的救赎))逻辑说明neighbors other_neighbors求交集交集大小就是共同关联的实体数量这是最直观的相关度信号。float(rating) / 10把评分归一化后作为加权项避免只靠共同邻居导致推荐结果全是冷门片。参数上top_n控制返回条数毕设演示设 5 到 10 都行。G.nodes[other].get(type) ! movie这个判断保证只推荐电影不会把导演或类型推给用户。如果推荐结果为空先检查传入的电影名是否和 movies.txt 里的名称完全一致中文名称差一个空格都会导致movie not in G。4.3 推荐结果的解释性输出知识图谱推荐相比协同过滤的一大优势是可解释。协同过滤说「因为和你相似的用户也喜欢」知识图谱可以说「因为你喜欢的这部电影和它同属剧情片且都由同一个导演执导」。答辩时这种解释性输出是加分项。def explain(G, movie, target): common set(G.neighbors(movie)) set(G.neighbors(target)) reasons [] for c in common: ctype G.nodes[c].get(type) if ctype director: reasons.append(f同一导演{c}) elif ctype genre: reasons.append(f同属类型{c}) return reasons print(explain(G, 肖申克的救赎, 阿甘正传))这段代码把共同邻居按类型翻译成人话输出「同一导演某某」或「同属类型剧情」。逻辑上它复用了推荐算法里的交集计算只是把结果转成可读文本。参数上不需要额外配置但要注意如果共同邻居是演员得在判断里补上actor分支否则演员关系不会被解释出来。实际项目里解释性输出可以直接接到前端展示也可以写进论文的「推荐结果分析」章节。5. 避坑与常见问题排查跑不通时先看这几条5.1 爬虫抓不到数据返回空列表或 403现象运行 dou_movie.py 或百科爬虫后输出文件为空或者控制台报 403、418。原因通常是请求头不完整、请求频率过高被限流或者目标页面结构改版导致选择器失效。解决先补全 User-Agent 和 Referer再把延时从 1 秒调到 3 到 5 秒如果还不行手动打开目标页面用浏览器开发者工具确认字段的 HTML 结构更新 BeautifulSoup 的选择器。页面改版是常态爬虫脚本不是一劳永逸的。5.2 movies.txt 中文乱码现象打开 movies.txt 看到一堆问号或方块。原因爬虫保存时编码和读取时编码不一致常见于 Windows 默认 GBK 而脚本写 utf-8。解决保存和读取统一用encodingutf-8如果原始数据已经是乱码得重新爬因为乱码是不可逆的。预防办法是在爬虫里用resp.apparent_encoding自动推断编码别写死。5.3 建图后节点数正常但边数极少现象G.number_of_nodes()有几百G.number_of_edges()只有几十。原因清洗时导演或类型字段大量为空或者分隔符没统一导致split(|)切不出内容。解决先df[director].isna().sum()看缺失比例再打印几行 genre 字段确认分隔符。如果缺失太多得回爬虫阶段补抓如果只是分隔符问题在清洗脚本里统一替换即可。5.4 推荐结果全是同一部电影或为空现象调用 recommend 后返回空列表或者反复推荐同一部。原因电影名和图中节点名不完全匹配或者共同邻居计算时把非电影节点也算进去了。解决先print(movie in G)确认名称匹配中文名称注意全角半角空格再检查推荐循环里的type ! movie过滤是否生效。如果结果单一说明图太稀疏需要补充数据或放宽推荐条件比如把二跳关系也纳入计算。5.5 依赖版本冲突导致脚本报 ImportError现象装完依赖跑脚本报某个库没有某个属性或函数。原因pip 默认装最新版而源码是按旧版本 API 写的。解决优先用 README.md 里的 requirements.txt 安装没有的话根据报错信息降级对应库比如pip install networkx2.8。虚拟环境在这里很关键能让你放心降级而不影响其他项目。6. 进阶技巧把推荐结果做成可演示的查询接口跑通命令行版本后如果想让毕设演示更直观可以给这套图谱加一个轻量查询接口。不用上重型框架Python 自带的 http.server 或 Flask 就够。下面是一个最小可用的 Flask 接口示例。from flask import Flask, request, jsonify import networkx as nx import pandas as pd app Flask(__name__) G build_graph(movies.txt) # 复用前面的建图函数 app.route(/recommend) def api_recommend(): movie request.args.get(movie, ) top_n int(request.args.get(top_n, 5)) result recommend(G, movie, top_n) return jsonify({ movie: movie, recommendations: [{title: t, score: round(s, 2)} for t, s in result] }) if __name__ __main__: app.run(host127.0.0.1, port5000, debugTrue)逻辑说明request.args.get从 URL 查询参数里取电影名和返回条数jsonify把结果转成 JSON前端拿到后直接渲染。参数上host127.0.0.1只允许本机访问毕设演示够用debugTrue方便改代码后自动重载但正式演示前记得关掉避免报错信息暴露。启动后访问http://127.0.0.1:5000/recommend?movie肖申克的救赎top_n5就能看到推荐结果。这里有个血泪经验中文参数在 URL 里需要编码直接用浏览器地址栏输入中文有时会失败用 requests 调用时记得params{movie: 肖申克的救赎}让库自动处理编码。另外图谱在 Flask 启动时加载一次即可别每次请求都重新建图否则数据量大时响应会明显变慢。验证这套系统是否真的可用我一般会走三步先用一部热门电影查推荐看结果是否合理再查一部冷门电影看是否返回空或报错最后故意传一个不存在的电影名确认接口不会崩。这三步能覆盖大部分边界情况。从那以后我每次交付这类图谱项目都会强制走一遍「热门-冷门-不存在」的验证流程比事后补 bug 省心得多。希望这份拆解能帮你把这份源码真正跑起来而不是让它躺在压缩包里。本文还有配套的精品资源点击获取
返回列表