资讯动态

Python猫眼电影数据分析可视化系统:从爬虫到展示全流程实践

发布时间:2026/9/24 7:13:18 来源:尧图企业网站定制
简介一份基于Python的猫眼电影数据分析可视化系统的毕业设计论文面向计算机、数据分析方向的在校学生与爬虫/可视化爱好者适合作为毕业设计、课程设计或项目实战参考资料。论文完整阐述了系统从数据采集到可视化展示的全流程基于requests库爬取猫眼电影数据利用Pandas进行清洗与预处理通过Matplotlib和Echarts实现电影评分、票房趋势、类型分布等多维度的可视化分析并采用Flask框架搭建Web端分析系统。文中对各模块的设计思路、实现方法和结果呈现均有细致说明能够帮助读者快速建立起数据分析与Web应用结合的项目认知。压缩包内仅含1个docx格式文档大小3.31MB包含摘要、目录、正文等完整章节结构规范清晰。目前已有381人浏览学习对于需要撰写毕业设计论文或构建同类电影数据分析系统的读者具有很高的参考价值。1. 直接说结论这套猫眼电影系统爬虫到可视化一条链都能跑数据分析这行最怕三件事没数据、数据脏、图表丑。基于 Python 的猫眼电影数据分析可视化系统恰好把这三件事串成了一条完整链路——requests 爬取猫眼电影数据Pandas 清洗整理MySQL 落库Flask 做后端接口Echarts 把评分、票房、类型分布画成直观图表。适合谁毕业设计需要一套“从数据采集到可视化展示”完整作品的在校生或者想练手数据分析全流程、又不想从零造轮子的从业者。这套系统的价值不在于某个算法多高级而在于它把爬虫、清洗、存储、可视化、推荐五个环节全部打通了。接下来我按数据采集、数据清洗与入库、Web 可视化、推荐算法四个核心模块拆开讲重点落在每一步的代码写法和容易翻车的位置。2. 数据采集与清洗链路requests 拿到手、Pandas 擦干净2.1 选型理由为什么 requests BeautifulSoup 就够用做电影数据采集第一反应是上 Scrapy但对着这份项目正文看作者用的是 Python 的 requests 库配合 BeautifulSoup 做页面解析。这个选型是合理的。Scrapy 强在调度、去重、并发下载和中间件机制适合大规模分布式采集但对于猫眼电影这种单站、单列表、数据量在万级左右的项目requests 写起来更直白调试也更方便一个文件就能跑通出问题了 print 一下就能定位。另一个问题是采集对象到底是什么。项目里说“通过调用猫眼电影网站的 API”实际开发中你面对的往往是两种响应一种是浏览器在页面渲染时发出的异步接口返回 JSON另一种是直接请求 HTML 页面。前者用resp.json()解析后者用 BeautifulSoup 配合选择器提取。两种方式我都建议你掌握因为猫眼页面改版是常态接口路径和页面结构都变过只抱死一种写法容易被牵着走。无论走哪条路采集之前有几个底线要守住遵守目标网站的 robots 协议控制请求频率数据只用于学习研究不要拿去商用。爬虫本身是中性技术但用的时候要带着边界感。后面避坑章节我会再强调一次。2.2 发送请求的第一步拿到响应先看状态码先写请求函数。这里我一般会做一个带重试的版本因为网络抖动和服务器限流在爬虫里太常见了单次请求失败不代表目标挂了。import requests import time def fetch_data(url, paramsNone, retry3): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Referer: https://maoyan.com/, Accept: application/json, text/plain, */* } for attempt in range(retry): try: resp requests.get(url, paramsparams, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp except requests.RequestException as e: print(f第 {attempt 1} 次请求失败: {e}) time.sleep(2) return None这段代码里有几个参数值得说明。headers里的 User-Agent 用来标识客户端类型不带的请求很容易被识别为脚本Referer 表示请求来源页面部分接口会校验这个字段Accept 告诉服务器我们期望的响应格式。timeout10是必须写的否则请求可能一直挂在那里不返回。resp.encoding resp.apparent_encoding是处理中文乱码的关键让 requests 根据响应内容自动猜测编码比写死 utf-8 更稳妥。retry参数控制重试次数配合time.sleep(2)让每次失败之间留出间隔避免频繁重试把请求打得像攻击。拿到响应之后下一步就是判断返回的是 JSON 还是 HTML。直接打印resp.text[:500]看前 500 个字符比任何猜测都可靠。这个习惯我建议你保留因为接口变了、页面结构变了肉眼一看就知道往哪个方向排查。2.3 字段提取JSON 和 HTML 两种解析方式都给你猫眼电影的数据接口返回的结构通常是嵌套 JSON。电影名称、评分、上映时间、票房这些字段可能分布在不同的层级里。常见做法是先resp.json()拿到整个字典再用.get()一层层往下取。.get()的好处是键不存在时不报错返回 None 或者你指定的默认值对爬虫这种数据质量参差不齐的场景非常友好。data resp.json() movie_list data.get(data, {}).get(movies, []) rows [] for item in movie_list: row { title: item.get(title, ), types: ,.join(item.get(types, [])), score: item.get(score, 0), box_office: item.get(box_office, 0), release_date: item.get(release_date, ) } rows.append(row) print(f本次获取 {len(rows)} 条电影数据)这里data.get(data, {}).get(movies, [])是双重防御外层没有 data 字段时返回空字典内层没有 movies 字段时返回空列表不会因为结构变化直接抛 KeyError。types字段在接口里通常是一个列表比如[剧情, 爱情]入库前用,.join()拼成字符串方便存进 MySQL 的 VARCHAR 字段。score和box_office都给了默认值 0因为有些电影票房字段可能为空特别是刚上映还没出票房数据的。如果接口结构变了或者你决定直接爬 HTML 页面那就换成 BeautifulSoupfrom bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) items soup.select(.movie-item) # 选择器以实际页面结构为准 for movie in items: title_tag movie.select_one(.movie-title) score_tag movie.select_one(.movie-score) if title_tag: rows.append({ title: title_tag.get_text(stripTrue), score: score_tag.get_text(stripTrue) if score_tag else })选择器是最容易碎的部分。猫眼页面的 class 命名改过不止一次select_one(.movie-title)拿不到内容时优先去浏览器开发者工具里重新确认 class 名而不是怀疑代码写错了。get_text(stripTrue)会把标签里的空白字符清掉这是一个很小的细节但能省掉后面很多清洗功夫。2.4 Pandas 清洗重复值、缺失值、异常值一次处理干净数据采集回来直接入库一定会出问题。猫眼的接口数据里重复、缺字段、评分异常是家常便饭。项目正文里明确写了用 Pandas 做清洗去重、处理缺失值和异常值。我常用的清洗流程是固定四步去重、删空、类型转换、范围过滤。import pandas as pd df pd.DataFrame(rows) df.drop_duplicates(subset[title, release_date], keepfirst, inplaceTrue) df.dropna(subset[title], inplaceTrue) df[score] pd.to_numeric(df[score], errorscoerce) df df[df[score].between(0, 10)] df[release_date] pd.to_datetime(df[release_date], errorscoerce) df[box_office] pd.to_numeric(df[box_office], errorscoerce).fillna(0) df.to_csv(movie_clean.csv, indexFalse, encodingutf-8-sig) print(f清洗完成剩余 {len(df)} 条数据)逐行解释一下参数。drop_duplicates(subset[title, release_date])按电影名和上映日期两个字段判断重复因为不同年份可能有同名电影只看标题会误删。keepfirst表示保留第一条出现的记录。dropna(subset[title])只删除标题为空的记录因为标题是主键级别的字段其他字段可以为空但标题不能丢。pd.to_numeric(errorscoerce)把无法转成数字的值变成 NaN比如字符串 暂无评分 会被转成 NaN 而不是报错。between(0, 10)过滤掉评分越界的异常值。fillna(0)把票房空值补成 0。最后存 CSV 时用encodingutf-8-sig这是给 Excel 留的后路不然用 Excel 打开 CSV 中文会乱码。这套清洗流程跑完数据质量和入库时的报错率都能控制住。清洗是数据分析里最不性感但最不能跳的一步后面所有图表和推荐的准确性都建立在这份干净数据上。3. MySQL 落库与 Flask 接口后端把数据送到浏览器3.1 表结构设计字段怎么定直接决定后续查询顺不顺数据清洗完下一步是入库。项目正文里明确选择 MySQL 存储这个决策没毛病。电影数据是结构化数据字段固定、关系清晰MySQL 足够胜任而且毕业设计答辩时MySQL 比 SQLite 更像一个“完整系统”的存储层。表结构我一般这样设计字段名类型说明idINT AUTO_INCREMENT主键titleVARCHAR(200)电影名称typesVARCHAR(100)类型逗号分隔scoreDECIMAL(3,1)评分范围 0.0 ~ 10.0box_officeDECIMAL(12,1)累计票房元release_dateDATE上映日期created_atTIMESTAMP入库时间默认当前时间几个字段值得说清楚。score用 DECIMAL(3,1) 而不是 FLOAT是因为评分只需要一位小数DECIMAL 是精确类型不会出现浮点误差。box_office用 DECIMAL(12,1) 而不是 INT是因为票房数字可能上亿INT 上限 21 亿看着够用但累计票房趋势分析时如果做单位换算DECIMAL 更灵活。release_date用 DATE 类型这样 SQL 里可以直接用YEAR()、MONTH()做时间维度聚合后面做上映时间趋势分析会非常方便。types用逗号分隔的字段而不是单独建关联表是因为这个项目的分析维度是“类型分布”拆关联表反而让查询变复杂。创建表的 SQL 和建索引一并写上CREATE TABLE IF NOT EXISTS film ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, types VARCHAR(100), score DECIMAL(3,1), box_office DECIMAL(12,1), release_date DATE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_title_date (title, release_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;UNIQUE KEY uk_title_date (title, release_date)非常重要。它给 title 和 release_date 的组合加了唯一约束后面入库时配合ON DUPLICATE KEY UPDATE就能实现幂等写入——同样一部电影重复采集时不会插入重复行而是更新已有记录的评分和票房。CHARSETutf8mb4必须写utf8mb4 支持完整 Unicode 字符集电影名称里可能出现的特殊字符、emoji 都能存住比 utf8 更能防乱码。3.2 pymysql 批量写入用 executemany 而不是一条条 insert连数据库和批量插入是固定套路。先建连接再写 SQL再用executemany批量执行最后提交事务。这里有个血泪经验一定要写ON DUPLICATE KEY UPDATE否则第二次跑采集任务就会因为唯一键冲突直接报错。import pymysql conn pymysql.connect( hostlocalhost, userroot, passwordyour_password, databasemovie_db, charsetutf8mb4 ) sql INSERT INTO film (title, types, score, box_office, release_date) VALUES (%(title)s, %(types)s, %(score)s, %(box_office)s, %(release_date)s) ON DUPLICATE KEY UPDATE score VALUES(score), box_office VALUES(box_office), types VALUES(types) cursor conn.cursor() cursor.executemany(sql, df.to_dict(records)) conn.commit() cursor.close() conn.close()df.to_dict(records)会把 DataFrame 转成字典列表每个字典对应一行字典的键就是 SQL 里的%(title)s占位符这个对应关系很直观。executemany是批量执行比用 for 循环逐条execute快得多。VALUES(score)是 MySQL 的语法表示“用本次插入的值覆盖已存在记录的值”这样重复采集时数据会自动更新。conn.commit()必须显式调用pymysql 默认不会自动提交事务忘记 commit 的话数据会一直停留在内存里这个坑我踩过不止一次。数据入库后顺手验证一下SELECT COUNT(*) FROM film看总行数SELECT COUNT(DISTINCT title) FROM film看去重后的电影数。两个数字对得上说明清洗和入库都没问题。3.3 Flask 路由与 JSON 接口前后端的衔接点Flask 在这个系统里的职责很清晰提供 Web 页面和 JSON 数据接口。项目正文明确用了 Flask它的轻量特性刚好匹配这个场景——不需要 Django 那样的全家桶只需把一个页面和几个数据接口跑起来。我一般会把路由分成两类页面路由和数据接口。页面路由负责渲染 HTML 模板数据接口负责返回 JSON前端用 fetch 去取。核心代码长这样from flask import Flask, jsonify, render_template import pymysql app Flask(__name__) def get_db(): return pymysql.connect( hostlocalhost, userroot, passwordyour_password, databasemovie_db, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) app.route(/) def index(): return render_template(index.html) app.route(/api/score_dist) def score_dist(): conn get_db() cursor conn.cursor() cursor.execute( SELECT FLOOR(score / 2) * 2 AS score_range, COUNT(*) AS cnt FROM film WHERE score IS NOT NULL GROUP BY score_range ORDER BY score_range ) rows cursor.fetchall() conn.close() return jsonify({code: 0, data: rows}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)这个/api/score_dist接口做了评分区间的聚合统计。FLOOR(score / 2) * 2把 0 到 10 的评分分成 0-2、2-4、4-6、6-8、8-10 五个区间GROUP BY 后直接拿到每个区间的电影数量。前端拿到这个 JSON 后丢给 Echarts 画柱状图就行。cursorclasspymysql.cursors.DictCursor很关键它让查询结果变成字典列表而不是默认的元组列表前端取row[cnt]比取row[0]可读性强得多。app.run(host0.0.0.0, port5000, debugTrue)在开发阶段用debugTrue方便热加载但部署时建议关掉避免暴露调试信息。接口设计的原则是“前端要什么后端就给什么”。评分分布、票房趋势、类型占比、上映时间分布一个维度一个接口前端各取所需。这样调试时可以直接在浏览器访问/api/score_dist看到 JSON不用每次都在页面上排查效率高很多。4. Echarts 可视化与协同过滤推荐图表怎么出、推荐怎么算4.1 可视化选型Matplotlib 和 Echarts 各管哪一段项目正文里同时提到了 Matplotlib 和 Echarts很多人会疑惑这两个到底什么关系。我理解的定位是Matplotlib 跑在 Python 后端负责服务端快速出图比如生成静态图片嵌入报告、验证数据分布Echarts 跑在浏览器前端负责 Web 页面里的交互式图表鼠标悬停有提示、点击可以联动、数据更新时平滑过渡。实际开发时Flask 页面里的图表基本只用 Echarts。原因很直接Web 页面需要交互性Matplotlib 生成的 PNG 图片是静态的没法做 tooltip、缩放、图例切换这些操作。Echarts 是纯前端库用 JavaScript 初始化图表绑定数据后渲染中文文档全、社区案例多遇到不会的图直接搜“Echarts 折线图”就能找到配置项。项目正文说“在数据分析模块中Matplotlib 和 Echarts 进行数据可视化”实操中你就理解为验证数据用 Matplotlib展示系统用 Echarts。这里有一个需要注意的点Echarts 是前端库不是 Python 库。后端 Python 负责从数据库取数、聚合、返回 JSON前端 JavaScript 负责把 JSON 变成图表。数据链路是“MySQL → Flask 接口 → 前端 fetch → Echarts 渲染”每一步的边界要清楚否则会陷入“Python 里怎么 import echarts”这种方向性错误。4.2 从 MySQL 到 Echarts一个完整的图表数据链路跑通以类型分布饼图为例我演示全链路是怎么走的。先看 Flask 后端接口app.route(/api/type_dist) def type_dist(): conn get_db() cursor conn.cursor() cursor.execute(SELECT types, COUNT(*) AS cnt FROM film GROUP BY types) rows cursor.fetchall() conn.close() return jsonify({code: 0, data: rows})这个接口把每种电影类型的数量统计出来。GROUP BY types直接按类型分组计数返回的数据形如[{types: 剧情, cnt: 42}, {types: 喜剧, cnt: 35}]。如果一条电影记录有多个类型比如“剧情,爱情”这里会整个作为一组后续你可以用FIND_IN_SET或拆表处理但版本一先用简单分组没问题。前端页面的核心代码div idtypeChart styleheight: 400px;/div script src/static/echarts.min.js/script script fetch(/api/type_dist) .then(response response.json()) .then(res { const chart echarts.init(document.getElementById(typeChart)); chart.setOption({ title: { text: 电影类型分布 }, tooltip: { trigger: item }, series: [{ type: pie, data: res.data.map(item ({ name: item.types, value: item.cnt })) }] }); }); /scriptecharts.init的入参是 DOM 元素所以必须先有一个设置了高度和 id 的 div图表才能渲染。setOption是 Echarts 的核心方法第一次调用时初始化图表配置后续数据更新时再次调用会自动 merge。res.data.map(...)是把后端返回的字典列表转换成 Echarts 需要的{name, value}格式这一步是前后端数据结构对齐的关键。tooltip: { trigger: item }启用鼠标悬停提示饼图悬停时显示对应类型的数量和占比。这套写法可以复用到所有图表上。折线图票房趋势把type: pie换成type: linedata换成{name: 2024-01, value: 12345}的列表柱状图评分分布换成type: bar横轴用xAxis.data、纵轴用series.data。核心思想是后端只负责给干净的数据前端负责把数据映射成图两者通过 JSON 解耦互不干扰。4.3 协同过滤推荐从评分矩阵到 TopN 推荐项目里有一个电影推荐模块用到了协同过滤算法。论文里写的“用户基于”和“物品基于”是两类思路实际落地时我建议用物品协同过滤Item-based计算电影之间的相似度用户看过某部电影后给他推荐相似度最高的其他电影。实现物料协同过滤需要先构建一个“用户-电影评分矩阵”。行是用户列是电影格子是评分。真实场景下这个矩阵来自用户历史行为但这个系统里没有真实注册用户和评分行为常见的做法是用猫眼观众评分或演示数据来构建矩阵。下面是核心计算代码import pandas as pd from sklearn.metrics.pairwise import cosine_similarity # 演示矩阵3 个用户对 4 部电影的评分0 表示未评分 df pd.DataFrame({ movie_a: [5, 4, 0], movie_b: [3, 0, 1], movie_c: [0, 0, 5], movie_d: [1, 1, 4] }, index[user1, user2, user3]) # 计算电影之间的余弦相似度 movie_sim cosine_similarity(df.T) # 转置后行电影列用户 sim_df pd.DataFrame(movie_sim, indexdf.columns, columnsdf.columns) def recommend(movie_name, top_n2): sim_scores sim_df[movie_name].sort_values(ascendingFalse) # 去掉自己取前 N 个 return sim_scores.iloc[1:top_n 1].index.tolist() print(recommend(movie_a))cosine_similarity(df.T)是整个计算的核心。原矩阵的每个用户是行、每部电影是列转置后每部电影是行、每个用户是列然后计算两两电影之间的余弦相似度。余弦相似度衡量的是两个向量方向的接近程度数值越接近 1 表示两部电影的评分模式越相似越接近 -1 表示越相反。推荐函数里sort_values(ascendingFalse)把相似度从高到低排序iloc[1:top_n 1]跳过自己第一行是电影自身相似度恒为 1取接下来的 TopN。这个模块的定位要摆正它证明了“推荐功能在这个系统里是可行的”但受限于数据量演示矩阵只有 3×4推荐效果本身不说明问题。如果你想把它做得更扎实一个方向是从数据库里把真实电影数据构造成矩阵——把评分字段作为推荐依据让系统给用户推荐评分趋势相近的电影。协同过滤的三个经典问题——冷启动、稀疏性、可扩展性——在答辩时一定要能说出来尤其是冷启动新电影没有评分记录相似度计算不出来怎么兜底常见做法是热度推荐新电影先按票房和评分做一轮基础排序等有了足够评分再去进协同过滤。5. 猫眼项目避坑实录反爬、乱码、入库冲突的常见问题排查5.1 请求被限制返回的不是电影数据而是验证页现象代码没变前两天还能正常抓数据某天突然返回空列表或者拿到的内容根本不是电影信息而是验证码页面、异常页面。原因请求频率过高或者 User-Agent 等 headers 没有配齐被服务端的反爬策略拦了。猫眼对异常请求的识别比较敏感同一 IP 短时间内高频访问触发的概率会明显提升。解决先检查返回内容确认是不是被拦了。然后做三件事把 User-Agent、Referer、Accept 三个 headers 配齐在每次请求之间加time.sleep(random.uniform(1, 3))随机延时避免固定间隔被识别采集任务控制总量不要一次性抓取几万条分批次跑。如果临时被限制停止任务等几分钟再继续不要立刻加重试次数。5.2 控制台和数据库里中文全部乱码现象Python 里打印数据正常但写入 MySQL 后用命令行查询看到的是???或者乱码字符或者一开始从接口拿到的数据就是乱码。原因两种情况。一是响应编码识别错误requests 默认用 ISO-8859-1 解析响应体猫眼返回的是 UTF-8不重新设置编码就会乱码二是 MySQL 连接时字符集没设置为 utf8mb4表和库的字符集不匹配。解决在代码里显式设置resp.encoding resp.apparent_encoding或者直接resp.encoding utf-8MySQL 连接字符串里加上charsetutf8mb4建表时也指定DEFAULT CHARSETutf8mb4。做完这两步乱码问题基本消灭。写代码的时候就把这三处字符集统一不要等数据入库了再回去看。5.3 入库时报错字段缺失、NaN 写入失败现象cursor.executemany()执行时报TypeError: must be real number, not str或者nan写入数据库失败。原因清洗环节漏掉了。接口返回的数据里某些字段可能不存在用.get()拿到默认值还好但如果用item[score]直接取值键不存在就抛 KeyError。另一个常见来源是 Pandas 清洗后某些单元格是 NaNNaN 是浮点数直接传给 pymysql 写 DECIMAL 字段时会报类型错误。解决采集时统一用.get(key, default)形式取值所有字段都配默认值清洗时对数值字段做pd.to_numeric(errorscoerce).fillna(0)把 NaN 全部转成明确的值入库前打印一下df.dtypes和df.isnull().sum()确认没有漏网之鱼。做完这套检查再入库基本上不会在类型上翻车。5.4 日期解析报错pd.to_datetime直接抛异常现象清洗时执行pd.to_datetime(df[release_date])报错说无法解析某个日期字符串比如“2024年1月”“未上映”或者格式完全不统一的日期。原因猫眼接口里日期格式并不统一有的带年月日有的只有年份有的字段干脆是空字符串或提示文案。to_datetime遇到无法识别的字符串会抛异常。解决加errorscoerce参数解析失败的自动转成 NaTPandas 的缺失时间值后面再用dropna(subset[release_date])删除没有有效日期的行。如果日期格式相对统一但带中文可以先用字符串替换把“年”“月”“日”替换成“-”再解析。核心是一句pd.to_datetime(df[release_date], errorscoerce)把异常消化在转换这一步而不是让它炸出来。5.5 重复爬取时主键冲突程序中断现象第一次采集成功入库第二次跑采集任务执行到插入语句时报IntegrityError: Duplicate entry xxx for key uk_title_date。原因没有做幂等处理。同一部电影第二次被采集唯一键值相同直接 INSERT 就撞了唯一约束。解决在 INSERT 语句末尾加上ON DUPLICATE KEY UPDATE scoreVALUES(score), box_officeVALUES(box_office)让重复插入变成更新操作。代码里 sql 写一遍后面不管跑多少次都不会因重复而中断。这是爬虫项目里最常见的量产级坑一开始就把幂等写进去后面能少掉一大半烦恼。6. 把系统跑得更远定时增量采集与词云图扩展系统跑通之后最有价值的一件事不是立刻增加功能而是让数据自己更新起来。电影市场每天都在变化票房在涨、评分在动一次性的数据快照很快就会过时。我一般会给这类系统加一个定时采集任务每天固定时间跑一次增量更新。增量采集中间件不用太复杂APScheduler 就够用。在 Flask 应用里初始化一个后台调度器每天凌晨执行一次采集和清洗入库。需要注意的一点是增量更新必须依赖前面说的幂等写入否则第二天就会因为主键冲突挂掉。from apscheduler.schedulers.background import BackgroundScheduler scheduler BackgroundScheduler() def daily_update(): # 1. 采集当天票房榜数据 # 2. 走同一套 Pandas 清洗流程 # 3. 执行 INSERT ... ON DUPLICATE KEY UPDATE print(每日数据更新完成) scheduler.add_job(daily_update, cron, hour2, minute30) scheduler.start()cron触发器指定任务按 Cron 表达式运行hour2, minute30表示每天凌晨 2 点 30 分执行。这个时间点选得讲究凌晨访问量低对目标站点影响小也避开白天高峰。调度器启动后Flask 主进程会继续处理页面请求后台任务安静地跑。验证调度是否生效可以在任务里写日志第二天看到每日数据更新完成的日志就说明链路通了。另一个值得加的功能是词云图。项目正文里提到过词云图可视化实现常见做法是用 jieba 分词加 wordcloud 生成词云图片。从数据库里取出电影类型字段或者评论内容用 jieba 分词把文本切成词再统计词频渲染成词云。代码大概是这样import jieba from wordcloud import WordCloud def generate_wordcloud(text_data): text .join(jieba.cut(text_data)) wc WordCloud(font_pathmsyh.ttc, width800, height600).generate(text) wc.to_file(static/wordcloud.png)font_pathmsyh.ttc必须指定中文字体否则词云图里的中文会显示成豆腐块。jieba.cut会把中文句子切成词列表用空格 join 成字符串后交给 WordCloud 统计词频。生成的图片直接丢进 Flask 的 static 目录页面里用img标签显示即可。最后说个我自己的习惯从那以后我每次写爬虫项目都会在代码里强制走一遍“合规检查”——确认请求间隔、确认数据用途限定在学习研究、确认入库前走完清洗流程。这不是矫情而是爬虫写多了之后你会发现翻车最多的从来不是算法难而是这些看起来不起眼的细节。这套系统的完整源码和论文文档已经打包好了下载后按第 2 章到第 4 章的流程把环境配好数据一跑、页面一开整条链路就通了。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价