
简介面向计算机专业学生与Python实战学习者的猫眼电影数据综合项目覆盖网络爬虫、数据清洗、数据库存储、Web可视化等完整流程适合作为毕业设计、课程设计或期末大作业。压缩包共86个文件体积仅1.05MB内部包含9个Python源码文件爬虫脚本、词云生成、Flask服务、14个HTML页面、21个JavaScript与14个CSS样式文件另有SQLite数据库、环境配置、启动脚本和Markdown说明文档目录组织规范便于快速定位和理解。项目基于Flask框架搭建Web应用spiderTest.py负责抓取猫眼电影信息并写入movie.dbwordCloud.py对影评进行词云分析templates下多个可视化页面展示电影评分、票房排行、类型分布等结果。资源目前已获123人学习下载代码完整可运行附详细文档支撑从爬虫到展示层层递进既能帮助初学者建立项目全局观也可为毕业设计提供高分范本。1. 为什么选猫眼电影做 Python 数据可视化练手项目第一次写爬虫时最怕的不是 requests 不会用而是抓下来的数据根本没法分析。猫眼电影榜单页就是很合适的练手对象字段固定在dd标签里评分、上映时间、主演都规整抓下来可以直接做数据分析。相比其他平台猫眼不需要登录反爬主要靠请求头和访问频率适合把爬虫、清洗、可视化整个链路打通。这个标题要解决的事情很清晰用 Python 把猫眼电影数据爬取下来交给 Pandas 做数据分析再用 Matplotlib 和 Pyecharts 输出可视化图表最后把代码、数据、文档整理成可以直接跑起来的项目资料。适合刚学完 Python 基础、准备做第一个数据项目的同学也适合想在简历里放一个数据分析可视化项目的在职工程师。整套方案不需要额外环境常见做法是 requests 抓页面、正则解析字段、CSV 落盘、Pandas 清洗、Pyecharts 出 HTML 图表。下面按我实际交付项目的习惯来写先把 Top100 完整抓下来再做分析再出图最后把文件结构和文档要求说清楚。2. Python 猫眼电影数据爬取请求构造、正则解析和断点续抓猫眼电影榜单的爬取难点不在 JavaScript而在请求头校验和访问频率。直接用 requests 默认参数访问大概率拿到 403。先解决请求头再解决解析规则最后把翻页和异常重试串起来。2.1 猫眼榜单请求头和限频参数怎么设用浏览器打开猫眼电影榜单页按 F12 看网络面板页面仍然返回完整 HTML不需要额外执行 JS。真正拦人的是 User-Agent 校验以及短时间大量请求后的 IP 限制。最小请求头里至少要有下面这些字段。请求头/参数示例取值作用User-Agent带 Chrome 版本号的完整浏览器 UA绕过最基本的 UA 校验Accepttext/html,application/xhtmlxml,...让服务器返回页面而不是 JSONRefererhttps://maoyan.com/在部分环境下满足防盗链校验timeout10避免请求卡住整个爬虫sleep1-3秒控制访问频率降低被限制概率我一般先构造一个requests.Session把请求头写进 session翻页时能复用连接和 Cookie行为更接近浏览器。import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Referer: https://maoyan.com/ } session requests.Session() session.headers.update(headers)Session.headers.update之后所有由该 session 发起的请求都会带上这套请求头不需要在每次get里重复写。timeout是单次请求的超时时间不是爬取间隔间隔要放在翻页循环里单独控制。2.2 用正则从 HTML 中解析标题、主演和上映时间猫眼榜单每一部电影都在一个dd标签里电影名在a的title属性主演、上映时间、评分分别在对应的 class 里。正则匹配时可以一次把整个dd块抓下来能比 BeautifulSoup 少一层循环。页面改版后正则需要同步调整所以我会把解析函数单独抽出来。import re def parse_page(html: str) - list[dict]: pattern re.compile( rdd.*?a href/films/.*? rtitle(.*?).*? rp classstar(.*?)/p.*? rp classreleasetime(.*?)/p.*? ri classinteger(.*?)/i ri classfraction(.*?)/i, re.S, ) rows [] for item in pattern.findall(html): rows.append({ title: item[0].strip(), star: item[1].strip().replace(\n, ).replace(主演, ), releasetime: item[2].strip().replace(上映时间, ), score: item[3].strip() item[4].strip(), }) return rows第一次执行前先把抓到的 HTML 存一份样本用正则调试工具测试不要直接跑全量。正则里re.S让.能匹配换行因为 HTML 源码在标签之间经常有换行和空格。integer是9.fraction是7拼接后得到9.7到分析阶段再统一转 float。如果不想维护正则也可以用BeautifulSoup加select解析代码更容易读懂。两种方式的核心都是把字段从 HTML 结构里稳定抽出来选一种固定写进文档即可。2.3 翻页采集、去重和异常重试猫眼榜单通过offset参数翻页每页 10 条Top100 就是 0 到 90 共 10 页。真实环境里网络抖动和反爬会同时出现所以采集函数不能只写requests.get要加上重试、异常捕获和失败跳过。import time def fetch_page(session, url, retries3): for attempt in range(retries): try: with session.get(url, timeout10) as resp: if resp.status_code 200: return resp.text if resp.status_code 403: print(访问被限制等待 30 秒) time.sleep(30) except requests.RequestException as exc: print(f第 {attempt 1} 次请求失败{exc}) time.sleep(2 * (attempt 1)) return None def crawl_all(session, pages10): result [] seen set() for page in range(pages): offset page * 10 url fhttps://maoyan.com/board/4?offset{offset} html fetch_page(session, url) if html is None: print(f第 {page 1} 页抓取失败稍后手动补抓) continue for row in parse_page(html): if row[title] in seen: continue seen.add(row[title]) result.append(row) time.sleep(1) return resultretries默认 3 次每次失败后等待时间按2 * (attempt 1)递增。遇到 403 说明已经触发限流再重试没有意义等 30 秒更好。seen集合负责去重翻页后如果页面内容重复不会把相同电影再塞进列表。保存 CSV 时编码建议用utf-8-sig否则 Windows 下用 Excel 打开会乱码。import csv def save_csv(rows): with open(maoyan_top100.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter( f, fieldnames[title, star, releasetime, score] ) writer.writeheader() writer.writerows(rows)抓下来的 CSV 属于原始数据不要手工改单元格。后续所有数据清洗都在 Pandas 里做方便重复执行。3. Pandas 清洗与电影数据指标提取拿到 CSV 后不要急着排序。此时score是字符串直接排序会出现10.0排在9.7前面之类的问题。第一步是让 Pandas 把每一列转成正确类型再做缺失值和重复值处理最后才能算出有意义的指标。3.1 用 Pandas 完成字段类型转换和日期清洗用read_csv读入时所有值默认都是object类型。中文文本里的换行、前缀都要在这一步清理干净。import pandas as pd df pd.read_csv(maoyan_top100.csv, encodingutf-8-sig) df[title] df[title].str.strip() df[star] df[star].str.replace(\n, , regexFalse).str.strip() df[releasetime] df[releasetime].str.replace(上映时间, , regexFalse) df[score] pd.to_numeric(df[score], errorscoerce) df[release_date] pd.to_datetime(df[releasetime], format%Y-%m-%d, errorscoerce) df[year] df[release_date].dt.yearstr.replace里加regexFalse意思是把第一个参数当作普通字符串处理避免遇到[]或.这类正则符号时误伤。pd.to_numeric会把不能转为数字的评分变成 NaN后面直接过滤。pd.to_datetime负责把2024-01-01转成时间类型提取年份后就能按年度分组。字段原始示例清洗处理清洗后类型title霸王别姬去首尾空格strstar\n主演张国荣张丰毅去换行、去“主演”前缀strreleasetime上映时间1993-01-01去前缀datetime64score9.7to_numericfloat64如果日期格式里带月份中文比如1993年01月01日先统一替换成年月日分隔符再交给to_datetime。把清洗逻辑写成一个函数方便下次爬完新数据直接调用。3.2 重复值和缺失值怎么处理才算数清洗后的数据不一定完整。Top100 榜单本身不应该有重复电影但翻页逻辑写错时会出现同一部电影出现在两页评分字段也可能因为页面结构变化而解析失败。检查方法如下。before len(df) df df.drop_duplicates(subset[title], keepfirst) print(f去重前 {before} 条去重后 {len(df)} 条) df df.dropna(subset[score, release_date]) print(df.isna().sum())drop_duplicates按电影名去重保留第一次出现的那一行。评分和上映时间如果有缺失我倾向直接删除因为缺失比例低时对整体分析影响很小。不要用fillna(0)补评分补零会让分布图出现一个虚假的极端峰值。还要检查异常值如果评分小于 0 或大于 10大概率是解析拼接错误应该过滤掉。这一步经常被忽略但写进文档后对方复现你的代码时能省很多排查时间。3.3 从数据里提取评分、年代和主演次数清洗完成后可以做几个直接能写进数据分析报告的统计。猫眼 Top100 的评分是浮点型按年代分组能看出高分电影集中在上世纪还是近十年主演字段则是逗号分隔的长文本需要拆分后再统计频次。df[year] df[year].astype(int) df[decade] ((df[year] // 10) * 10).astype(int).astype(str) 年代 print(df[score].describe()) print(df.groupby(decade)[title].count()) star_series ( df[star] .str.split() .explode() .str.strip() ) print(star_series.value_counts().head(10)).explode()会把每个主演拆成单独一行value_counts()统计出现次数。这里注意分隔符是中文逗号还是英文逗号以爬下来的实际数据为准。年份在astype(int)前必须保证没有缺失否则转换会报错所以在 3.2 的dropna里要把release_date一并删掉。4. 可视化代码评分分布、Top10 排行和可视化大屏数据分析结果最终要靠图表表达。我通常先用 Matplotlib 快速看分布再用 Pyecharts 做交互式 HTML后者可以放浏览器里查看适合做演示和可视化大屏。4.1 Matplotlib 输出评分直方图和年度平均分Matplotlib 最大的坑是中文字体。Windows 默认SimHeimacOS 用Arial Unicode MSLinux 常见的有Noto Sans CJK SC。写一个字体回退列表比只写一个字体更省事。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [ SimHei, Noto Sans CJK SC, Arial Unicode MS ] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4)) df[score].plot.hist(axaxes[0], bins15, edgecolorwhite) axes[0].set_title(猫眼Top100评分分布) axes[0].set_xlabel(评分) trend df.groupby(year)[score].mean() trend.plot(axaxes[1], markero) axes[1].set_title(年度平均评分) axes[1].set_xlabel(年份) plt.tight_layout() plt.savefig(output/movie_analysis.png, dpi150)dpi150保证导出图片在文档里不模糊。tight_layout()自动调整子图间距不然标题和坐标轴容易重叠。先把两张图放到同一个 Figure 里减少文件数量。4.2 用 Pyecharts 制作可交互的 Top10 条形图Pyecharts 生成的是 HTML 文件打开后鼠标悬停可以看到具体评分比静态 PNG 更适合放进数据分析项目。横向条形图能完整显示长电影名。from pyecharts.charts import Bar from pyecharts import options as opts top10 df.nlargest(10, score)[[title, score]].sort_values(score) bar ( Bar() .add_xaxis(top10[title].tolist()) .add_yaxis(评分, top10[score].round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title猫眼Top100电影评分Top10), ) .reversal_axis() ) bar.render(output/top10_bar.html)nlargest(10, score)按照评分从大到小取 10 条再按升序排序让条形图最高的在上面。reversal_axis()把直角坐标系翻转成横向条形图。如果同一部电影出现多次说明去重没做好回到第 3 章检查。4.3 把多个图组合成可视化大屏做可视化大屏时不需要写前端框架Pyecharts 的Grid就能把多个图放到同一个 HTML 里。常用的图表选择可以按下面的对应关系来。图表类型适用场景Pyecharts 类条形图排行榜对比Bar折线图年度趋势Line饼图年代占比Pie词云主演高频词WordCloud组合图之前先把年代占比算成一个列表再放进Pie。decade_count df.groupby(decade).size().reset_index() decade_count.columns [decade, count] from pyecharts.charts import Grid, Pie pie ( Pie() .add( series_name年代占比, data_pairlist(zip(decade_count[decade], decade_count[count])), ) .set_global_opts(title_optsopts.TitleOpts(title年代占比)) ) grid Grid() grid.add(pie, grid_optsopts.GridOpts(pos_left55%)) grid.add(bar, grid_optsopts.GridOpts(pos_left10%, pos_right50%)) grid.render(output/dashboard.html)Grid只负责布局不负责重新渲染数据。bar是 4.2 里已经生成好的图表对象必须先运行那段代码。这里要注意pos_right和pos_left加在一起不能超过 100%否则图表会重叠。5. 全套资料整理成可交接的 Python 数据项目代码写完直接打包发给别人往往跑不起来。完整的交付不只是.py文件还要有依赖文件、数据目录和详细文档。这个项目我一般按下面的结构归档。5.1 项目目录与依赖配置maoyan_top100/ ├── crawler/ │ └── maoyan_spider.py ├── analysis/ │ └── clean_analyze.py ├── visualization/ │ └── charts.py ├── output/ │ ├── maoyan_top100.csv │ ├── score_dist.png │ └── dashboard.html ├── docs/ │ └── README.md └── requirements.txtrequirements.txt只写直接依赖不写pip freeze导出的全部传递依赖否则别人安装时容易版本冲突。requests pandas matplotlib pyecharts lxml安装命令和运行顺序写进 README对方拿到资料后照着执行就能复现。pip install -r requirements.txt python crawler/maoyan_spider.py python analysis/clean_analyze.py python visualization/charts.py5.2 详细文档说明里的数据字典README 里除了运行步骤还要有一张数据字典表。别人只看代码很难猜出releasetime是字符串还是时间类型。字段含义示例类型title电影名霸王别姬strstar主演列表张国荣张丰毅strreleasetime上映时间1993-01-01strscore猫眼评分9.7float64year上映年份1993int64decade所属年代1990年代str数据字典放在文档最前面后面再写“常见问题”。常见问题里至少包含两条中文乱码怎么解决爬虫中途断了怎么办。5.3 断点续抓的小技巧全量爬取时如果第 5 页中断重跑会从头开始很浪费。给爬虫加一个已抓标题记录下次启动时读入seen集合就能跳过已经抓过的电影。数据量变大后可以把seen集合从本地文件迁移到 Redis再用 Redis 可视化客户端查看集合大小。try: with open(seen.txt, encodingutf-8) as f: seen set(f.read().splitlines()) except FileNotFoundError: seen set() # 抓到新电影后追加写入 with open(seen.txt, a, encodingutf-8) as f: f.write(row[title] \n)这个技巧不改变解析逻辑只给crawl_all加一层缓存。最后再提一个文档里必须写的内容Windows 控制台运行爬虫时如果报UnicodeEncodeError先执行set PYTHONIOENCODINGutf-8Linux 和 macOS 用 export 设置同名环境变量这个值应该写进详细文档的“常见问题”部分。本文还有配套的精品资源点击获取