news 2026/9/17 20:14:13

Python爬取猫眼电影Top100:数据分析与可视化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬取猫眼电影Top100:数据分析与可视化实战

简介:面向计算机专业学生与Python实战学习者的猫眼电影数据综合项目,覆盖网络爬虫、数据清洗、数据库存储、Web可视化等完整流程,适合作为毕业设计、课程设计或期末大作业。压缩包共86个文件,体积仅1.05MB,内部包含9个Python源码文件(爬虫脚本、词云生成、Flask服务)、14个HTML页面、21个JavaScript与14个CSS样式文件,另有SQLite数据库、环境配置、启动脚本和Markdown说明文档,目录组织规范,便于快速定位和理解。项目基于Flask框架搭建Web应用,spiderTest.py负责抓取猫眼电影信息并写入movie.db,wordCloud.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/xhtml+xml,...让服务器返回页面而不是 JSON
Refererhttps://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/xhtml+xml,application/xml;q=0.9,*/*;q=0.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( r'<dd>.*?<a href="/films/.*?" ' r'title="(.*?)".*?' r'<p class="star">(.*?)</p>.*?' r'<p class="releasetime">(.*?)</p>.*?' r'<i class="integer">(.*?)</i>' r'<i class="fraction">(.*?)</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 源码在标签之间经常有换行和空格。integer9.fraction7,拼接后得到9.7,到分析阶段再统一转 float。

如果不想维护正则,也可以用BeautifulSoupselect解析,代码更容易读懂。两种方式的核心都是把字段从 HTML 结构里稳定抽出来,选一种固定写进文档即可。

2.3 翻页采集、去重和异常重试

猫眼榜单通过offset参数翻页,每页 10 条,Top100 就是 0 到 90 共 10 页。真实环境里网络抖动和反爬会同时出现,所以采集函数不能只写requests.get,要加上重试、异常捕获和失败跳过。

import time def fetch_page(session, url, retries=3): for attempt in range(retries): try: with session.get(url, timeout=10) 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, pages=10): result = [] seen = set() for page in range(pages): offset = page * 10 url = f"https://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 result

retries默认 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="", encoding="utf-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", encoding="utf-8-sig") df["title"] = df["title"].str.strip() df["star"] = df["star"].str.replace("\n", "", regex=False).str.strip() df["releasetime"] = df["releasetime"].str.replace("上映时间:", "", regex=False) df["score"] = pd.to_numeric(df["score"], errors="coerce") df["release_date"] = pd.to_datetime(df["releasetime"], format="%Y-%m-%d", errors="coerce") df["year"] = df["release_date"].dt.year

str.replace里加regex=False,意思是把第一个参数当作普通字符串处理,避免遇到[].这类正则符号时误伤。pd.to_numeric会把不能转为数字的评分变成 NaN,后面直接过滤。pd.to_datetime负责把2024-01-01转成时间类型,提取年份后就能按年度分组。

字段原始示例清洗处理清洗后类型
title霸王别姬去首尾空格str
star\n主演:张国荣,张丰毅去换行、去“主演:”前缀str
releasetime上映时间:1993-01-01去前缀datetime64
score9.7to_numericfloat64

如果日期格式里带月份中文,比如1993年01月01日,先统一替换成年月日分隔符,再交给to_datetime。把清洗逻辑写成一个函数,方便下次爬完新数据直接调用。

3.2 重复值和缺失值怎么处理才算数

清洗后的数据不一定完整。Top100 榜单本身不应该有重复电影,但翻页逻辑写错时会出现同一部电影出现在两页;评分字段也可能因为页面结构变化而解析失败。检查方法如下。

before = len(df) df = df.drop_duplicates(subset=["title"], keep="first") 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 默认SimHei,macOS 用Arial Unicode MS,Linux 常见的有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(ax=axes[0], bins=15, edgecolor="white") axes[0].set_title("猫眼Top100评分分布") axes[0].set_xlabel("评分") trend = df.groupby("year")["score"].mean() trend.plot(ax=axes[1], marker="o") axes[1].set_title("年度平均评分") axes[1].set_xlabel("年份") plt.tight_layout() plt.savefig("output/movie_analysis.png", dpi=150)

dpi=150保证导出图片在文档里不模糊。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_opts=opts.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_pair=list(zip(decade_count["decade"], decade_count["count"])), ) .set_global_opts(title_opts=opts.TitleOpts(title="年代占比")) ) grid = Grid() grid.add(pie, grid_opts=opts.GridOpts(pos_left="55%")) grid.add(bar, grid_opts=opts.GridOpts(pos_left="10%", pos_right="50%")) grid.render("output/dashboard.html")

Grid只负责布局,不负责重新渲染数据。bar是 4.2 里已经生成好的图表对象,必须先运行那段代码。这里要注意pos_rightpos_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.txt

requirements.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.py

5.2 详细文档说明里的数据字典

README 里除了运行步骤,还要有一张数据字典表。别人只看代码很难猜出releasetime是字符串还是时间类型。

字段含义示例类型
title电影名霸王别姬str
star主演列表张国荣,张丰毅str
releasetime上映时间1993-01-01str
score猫眼评分9.7float64
year上映年份1993int64
decade所属年代1990年代str

数据字典放在文档最前面,后面再写“常见问题”。常见问题里至少包含两条:中文乱码怎么解决,爬虫中途断了怎么办。

5.3 断点续抓的小技巧

全量爬取时如果第 5 页中断,重跑会从头开始,很浪费。给爬虫加一个已抓标题记录,下次启动时读入seen集合,就能跳过已经抓过的电影。数据量变大后,可以把seen集合从本地文件迁移到 Redis,再用 Redis 可视化客户端查看集合大小。

try: with open("seen.txt", encoding="utf-8") as f: seen = set(f.read().splitlines()) except FileNotFoundError: seen = set() # 抓到新电影后追加写入 with open("seen.txt", "a", encoding="utf-8") as f: f.write(row["title"] + "\n")

这个技巧不改变解析逻辑,只给crawl_all加一层缓存。最后再提一个文档里必须写的内容:Windows 控制台运行爬虫时如果报UnicodeEncodeError,先执行set PYTHONIOENCODING=utf-8,Linux 和 macOS 用 export 设置同名环境变量,这个值应该写进详细文档的“常见问题”部分。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 20:08:19

Cadence CIS数据库连接失败的底层原因与实战排错指南

1. 为什么Cadence CIS数据库配置总卡在“连不上”——多数人忽略的底层逻辑Cadence CIS&#xff08;Component Information System&#xff09;不是个简单的元件库管理界面&#xff0c;它本质是一套跨工具链、跨角色、跨生命周期的数据中枢。你点开Capture时看到的“Part Numbe…

作者头像 李华
网站建设 2026/9/17 20:05:34

长沙AIGC线下培训分布在哪?四个片区各有侧重

摘要&#xff1a;本文结合很多人偏好线下实操学习 AIGC、想寻找本地线下场所的普遍需求&#xff0c;围绕找线下场所时不清楚分布、不了解方向侧重的常见困扰&#xff0c;梳理岳麓区、雨花区、马栏山周边、麓谷周边四个区域的线下学习场所特点与方向侧重&#xff0c;同时说明长沙…

作者头像 李华
网站建设 2026/9/17 20:05:15

Agent技能体系设计与实践:从散装函数到可复用能力

“agent-skills”这个词&#xff0c;我第一次被它击中&#xff0c;是在一个开源的Agent项目文档里。当时项目方把几十个工具函数一股脑塞进一个工具文件夹&#xff0c;命名混乱到连作者本人都要翻半天才能找到某个功能&#xff0c;我就意识到&#xff0c;这件事不是“把函数换个…

作者头像 李华