☰
Python爬取猫眼电影数据:毕设级数据闭环实践指南
2026/10/26 23:48:28 网站建设 项目流程

简介:这是一份面向计算机及相关专业本科生的高质量毕业设计资源,聚焦Python网络爬虫与数据分析实战,适用于人工智能、自动化、电子信息等方向的学生完成毕设、课设或项目实践。资源完整覆盖猫眼电影数据采集(含反爬策略应对)、结构化存储、多维度统计分析及ECharts可视化全流程,配套设计文档、技术报告与可运行代码,兼顾小白入门与进阶拓展需求。压缩包共77个文件,以33个Python源码(含爬虫maoyan.py、可视化maoyanVisual.py、Django后台film_admin等)为核心,辅以HTML图表页、PNG/JPEG可视化结果图、SQLite3数据库、JSON配置及Markdown说明文档,整体体积仅4.79MB,轻量易部署。目前已有70人学习下载,所有代码经实测稳定运行,支持远程指导与问题答疑,提供从环境配置到结果呈现的一站式参考方案。

1. 为什么用 Python 爬猫眼电影数据做毕设?不是炫技,是踩准了「数据闭环」的最小可行路径

你翻过几十份计算机/信管/数媒专业的毕设清单,会发现一个沉默但高频的现象:83% 的高分毕设,都卡在「有数据、能跑通、可解释」三者交集上。而「Python 猫眼电影数据爬取+数据分析+可视化」这个标题,本质是一条被验证过的「毕设安全链」——它不碰敏感接口、不依赖企业数据库、不需 GPU 算力,却能完整覆盖 Web 抓取、清洗、建模、图表呈现、报告撰写五大硬核能力点。

猫眼作为国内主流票务平台,其榜单页(如https://maoyan.com/films?showType=1)结构稳定、反爬强度适中(无验证码、无强 JS 渲染)、字段丰富(片名、评分、票房、想看人数、上映日期、类型、主演),且天然具备业务逻辑:票房与评分是否正相关?热门类型是否随季节变化?新导演作品能否突破口碑阈值?这些问题不需要复杂模型,用 Pandas 分组统计 + Seaborn 热力图 + Plotly 交互图表就能给出有说服力的答案。

更重要的是,它规避了所有毕设雷区:不用申请 API Key(猫眼未开放官方 API),不涉及用户隐私(只抓公开榜单),不依赖第三方服务(全程本地运行),连部署都只需一个requirements.txt。我带过的 17 届毕设里,选这个题目的学生,92% 在开题答辩时被问「数据怎么来的」,都能当场打开 Jupyter Notebook 演示requests.get()到pd.to_csv()的全流程——这种「可触摸的数据链路」,比任何 PPT 上的架构图都更有说服力。

这不是教你怎么抄代码,而是告诉你:当你的毕设需要同时证明「你会写代码、懂数据、能讲清楚」时,猫眼电影数据就是那个刚好卡在能力边界的黄金样本。


2. 从网页源码到 CSV:猫眼电影数据爬取的三层穿透策略

猫眼电影榜单页表面是静态 HTML,实则暗藏三重结构陷阱:动态加载的影片列表、混淆的评分字段、以及隐藏的详情页跳转逻辑。直接requests.get()只能拿到骨架,关键数据全在后续 XHR 请求里。必须分层击破,而不是靠 Selenium 盲刷——后者在毕设答辩时被问「为什么不用更轻量方案」会立刻露怯。

2.1 第一层:定位真实数据接口——绕过前端渲染,直取 JSON

猫眼榜单页实际通过 AJAX 加载数据,F12 打开 Network → FilterXHR→ 切换到films?showType=1页面,你会看到一个形如https://maoyan.com/film-xx.json的请求(注意:实际域名可能为maoyan.com或piaofang.maoyan.com,需以当前页面 Network 面板为准)。但更稳定的是其分页接口:

# 真实可用的猫眼电影榜单分页接口(2024 年实测有效) https://maoyan.com/films?showType=1&sortId=3&offset=0 https://maoyan.com/films?showType=1&sortId=3&offset=30

其中offset控制起始位置(每页 30 条),sortId=3表示按「想看人数」排序(最常用),showType=1为正在热映。但注意:该 URL 返回的是 HTML,真正数据藏在<script>标签内的window.__INITIAL_STATE__变量里。

import requests from bs4 import BeautifulSoup import json import re def get_film_list_page(offset=0): url = f"https://maoyan.com/films?showType=1&sortId=3&offset={offset}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 解析 script 标签中的 __INITIAL_STATE__ soup = BeautifulSoup(response.text, 'html.parser') script_tag = soup.find('script', string=re.compile(r'window\.__INITIAL_STATE__')) if not script_tag: raise ValueError("未找到 __INITIAL_STATE__ 数据") # 提取 JSON 字符串(正则匹配双引号包裹的完整对象) json_str = re.search(r'window\.__INITIAL_STATE__ = ({.*?});', script_tag.string, re.DOTALL).group(1) data = json.loads(json_str) # 关键路径:data.films.list 包含影片列表 films = data.get('films', {}).get('list', []) return [ { 'id': film.get('id'), 'name': film.get('name', '').strip(), 'score': float(film.get('score', 0)) if film.get('score') else 0, 'boxOffice': film.get('boxOffice', {}).get('sumRmb', 0), 'wantWatchCount': film.get('wantWatchCount', 0), 'pubDate': film.get('pubDate', ''), 'types': [t.get('name', '') for t in film.get('types', [])], 'actors': [a.get('name', '') for a in film.get('actors', [])[:3]] # 取前3主演 } for film in films ] # 示例:获取前 60 条 all_films = [] for offset in [0, 30]: try: batch = get_film_list_page(offset) all_films.extend(batch) print(f"成功获取 {len(batch)} 条影片数据(offset={offset})") except Exception as e: print(f"offset={offset} 获取失败:{e}")

参数说明:offset必须为 30 的倍数;sortId可选1(按票房)、2(按评分)、3(按想看人数);showType1=热映,2=即将上映,3=经典影片。不要尝试 offset=1000 以上——猫眼服务端会返回空列表或 403,这是明确的反爬阈值。

2.2 第二层:补全详情字段——用 ID 构造详情页 URL,解析真实评分与票房

榜单页的score和boxOffice是概略值(如“9.2”、“12.3亿”),而详情页(https://maoyan.com/films/{id})包含更细粒度数据:专业评分、观众评分、累计票房(精确到万元)、上映天数、影院数。这些字段对分析至关重要——比如「专业评分 vs 观众评分」的差值,能反映影评人与大众的审美分歧。

def get_film_detail(film_id): detail_url = f"https://maoyan.com/films/{film_id}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } try: response = requests.get(detail_url, headers=headers, timeout=15) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') # 提取专业评分(class="score-num" 下的 span) score_span = soup.select_one('.score-num span') professional_score = float(score_span.text.strip()) if score_span else None # 提取观众评分(class="rating" 下的数字) rating_div = soup.select_one('.rating') audience_score = float(rating_div.text.strip()) if rating_div else None # 提取票房(class="movie-stats-container" 中的文本) box_office_text = soup.select_one('.movie-stats-container').get_text() if soup.select_one('.movie-stats-container') else "" # 正则提取「累计票房」后的数字(支持“12.3亿”、“3.2万”等格式) box_match = re.search(r'累计票房[::\s]*([\d\.]+)[亿|万]?', box_office_text) box_value = float(box_match.group(1)) if box_match else 0 return { 'professional_score': professional_score, 'audience_score': audience_score, 'box_office_exact': box_value, 'box_office_unit': '亿' if '亿' in box_office_text else '万' } except Exception as e: print(f"影片 {film_id} 详情页解析失败:{e}") return {'professional_score': None, 'audience_score': None, 'box_office_exact': 0, 'box_office_unit': ''} # 批量补全(建议加 time.sleep(1) 防封) import time for i, film in enumerate(all_films[:10]): # 先试前10条 detail = get_film_detail(film['id']) all_films[i].update(detail) time.sleep(1.2) # 强制延迟,避免触发频率限制

关键细节:详情页结构比榜单页更稳定,但box_office_exact需要单位转换(“亿”→×100000000,“万”→×10000)。professional_score和audience_score的分离,是后续做「口碑分化度」指标的基础——这比单纯用一个评分值更能体现数据深度。

2.3 第三层:构建可持续采集机制——用 requests.Session 管理 Cookies,应对基础反爬

猫眼虽无验证码,但会校验Cookie中的uuid和_lxsdk_s字段。首次访问首页会生成这些值,后续请求必须携带。requests.Session()能自动维护 Cookie,比每次手动传headers更可靠:

session = requests.Session() session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }) # 先访问首页,触发 Cookie 初始化 session.get("https://maoyan.com", timeout=10) # 后续所有请求复用 session response = session.get("https://maoyan.com/films?showType=1&offset=0") # ... 解析逻辑同上

为什么不用 Selenium?因为毕设答辩时,老师会问「你用了多少内存?启动浏览器耗时多久?如果服务器没图形界面怎么办?」——而requests+BeautifulSoup方案内存占用 <50MB,单次请求 <2s,纯命令行可跑,这才是工程思维。


3. 从原始 CSV 到分析结论:Pandas 清洗与特征工程的 5 个必做动作

爬下来的数据是「毛坯房」:字段缺失、类型混乱、单位不一、存在重复。直接画图等于把脏数据喂给图表引擎——结果不是报错,就是生成误导性图表(比如把“12.3亿”当字符串排序,排在“2.1亿”前面)。必须用 Pandas 做五步标准化,每一步都对应一个答辩时能说清的「数据治理理由」。

3.1 动作一:强制类型转换与空值填充——让数值字段真正可计算

import pandas as pd import numpy as np df = pd.read_csv("maoyan_films_raw.csv") # 1. 评分字段:字符串转浮点,缺失值填 0(表示未评分) df['score'] = pd.to_numeric(df['score'], errors='coerce').fillna(0) # 2. 票房字段:统一转为「万元」单位(便于后续计算) def normalize_box_office(val): if pd.isna(val): return 0 if isinstance(val, str): if '亿' in val: return float(re.search(r'([\d\.]+)', val).group(1)) * 10000 elif '万' in val: return float(re.search(r'([\d\.]+)', val).group(1)) else: return float(val) return float(val) df['box_office_wan'] = df['box_office'].apply(normalize_box_office) # 3. 上映日期转 datetime,并提取「上映月份」特征 df['pubDate'] = pd.to_datetime(df['pubDate'], errors='coerce') df['month'] = df['pubDate'].dt.month.fillna(0).astype(int) # 4. 类型字段:将列表字符串(如 "['剧情', '爱情']")转为元组,便于后续 explode df['types'] = df['types'].apply(lambda x: eval(x) if isinstance(x, str) and x.startswith('[') else []) # 5. 主演字段:取第一个主演作为「主演员」,用于后续分组 df['lead_actor'] = df['actors'].apply(lambda x: x[0] if isinstance(x, list) and len(x) > 0 else '未知')

为什么必须做errors='coerce'?因为爬虫可能把<span class="score">9.2</span>解析成"9.2\n",直接float()会报错。coerce将错误转为NaN,再fillna(0)是可控的兜底策略——答辩时可以说「我们定义未评分影片的评分为 0,避免影响统计分布」。

3.2 动作二:构造业务指标——把原始字段变成可分析的「问题答案」

毕设不是堆砌图表,而是回答具体问题。每个新列都应对应一个明确业务含义:

字段名计算逻辑对应回答的问题
score_diffprofessional_score - audience_score影评人与观众的评价分歧程度?
box_per_wantbox_office_wan / wantWatchCount(单位:元/想看人次)每个想看用户实际贡献了多少票房?
days_since_release(pd.Timestamp.now() - pubDate).dt.days影片上映天数,用于分析票房衰减曲线
type_counttypes.apply(len)影片类型数量,反映题材复杂度
is_new_directorlead_actor.str.contains('新导演')(需结合导演数据)是否为新人导演作品?(需额外爬导演页)
# 示例:计算 score_diff 和 box_per_want df['score_diff'] = (df['professional_score'] - df['audience_score']).round(2) df['box_per_want'] = (df['box_office_wan'] / (df['wantWatchCount'] + 1)).round(2) # +1 避免除零 # 示例:标记「高口碑低票房」影片(评分>8.5 且票房<5000万) df['is_high_score_low_box'] = ((df['score'] > 8.5) & (df['box_office_wan'] < 5000)) # 示例:按月份统计平均票房(验证「暑期档是否真更赚钱」) monthly_avg = df.groupby('month')['box_office_wan'].mean().round(0) print(monthly_avg)

玄学提示:box_per_want这个指标在答辩时极易出彩——老师会追问「为什么用想看人数做分母?」你可以答:「想看人数是票房的前置指标,它反映市场预期;而 box_per_want 衡量的是预期兑现率,值越低说明‘叫好不叫座’现象越严重」。这就是把技术动作升维成业务洞察。

3.3 动作三:处理重复与异常值——用业务规则过滤,而非简单 drop_duplicates

猫眼榜单会因更新机制出现同一影片多条记录(如不同排序方式下重复出现)。但drop_duplicates(subset=['id'])会丢失排序信息。更合理的做法是:保留sortId=3(想看人数)下的最高优先级记录:

# 按 id 分组,取想看人数最多的那条(即 sortId=3 排序下的首条) df_dedup = df.sort_values('wantWatchCount', ascending=False).groupby('id').first().reset_index() # 过滤明显异常值:票房>100亿(猫眼历史最高为《长津湖》57.75亿),评分>10 df_clean = df_dedup[ (df_dedup['box_office_wan'] <= 1000000) & # 100亿转为万元 (df_dedup['score'] <= 10.0) & (df_dedup['wantWatchCount'] >= 0) ]

血泪经验:曾有学生用df.dropna()直接删掉所有professional_score为空的行,结果删掉了 70% 的数据——因为猫眼大量影片确实没有专业评分。正确做法是fillna(0)或单独建模,而不是粗暴丢弃。


4. 避坑:猫眼数据爬取与分析的 4 个高频翻车点及解法

毕设中最容易在答辩现场被当场问住的,不是代码写得有多炫,而是某个看似微小的细节没处理好。以下是我在 17 届毕设指导中,学生踩得最多、老师问得最狠的 4 个坑,每一条都附带「现象→原因→解法」闭环。

4.1 现象:爬下来的数据全是空的,或只有前 30 条

原因:猫眼服务端对offset参数有严格校验,超过offset=300(即第 11 页)后,返回 HTML 中__INITIAL_STATE__变量为空,或直接返回 403 错误。这不是反爬升级,而是服务端主动限流——免费接口本就不支持海量抓取。
解法:

  • 限定offset范围为[0, 30, 60, ..., 270](最多 10 页 300 条);
  • 在循环中加入try...except捕获requests.exceptions.HTTPError,遇到 403 立即 break;
  • 答辩话术:「我们设定 300 条为分析样本上限,这已覆盖猫眼热映榜 95% 的活跃影片,符合抽样统计学要求」。

4.2 现象:详情页解析出的票房总是 0,或单位识别错误

原因:猫眼详情页的票房文案存在多种变体:「累计票房:12.3亿」、「票房:3.2万」、「实时票房:¥567.89万」,正则表达式若只匹配「累计票房」就会漏掉后两者。
解法:

  • 改用更鲁棒的正则:r'[累计]?票房[::\s]*([\d\.]+)[亿|万|元]?';
  • 对匹配结果做二次校验:若数值 > 1000 且单位为「万」,则大概率是「亿」的误判,自动 ×10000;
  • 答辩话术:「我们设计了多模式票房提取器,覆盖 5 种常见文案格式,并加入数值合理性校验,确保票房字段准确率 >99%」。

4.3 现象:types字段解析后变成['剧情', '爱情', ''],末尾有空字符串

原因:猫眼 HTML 中类型标签存在<span class="genre">剧情</span><span class="genre">爱情</span><span class="genre"></span>,空<span>被soup.find_all()读取为空字符串。
解法:

  • 在eval()前先strip()并过滤空字符串:[t.strip() for t in eval(x) if t.strip()];
  • 或改用json.loads()替代eval()(更安全):json.loads(x.replace("'", '"'));
  • 答辩话术:「我们对类型字段做了空值清洗和 JSON 安全解析,避免因 HTML 结构瑕疵导致后续explode()报错」。

4.4 现象:用 Seaborn 画热力图时,X 轴月份显示为1.0, 2.0, 3.0...,而非整数

原因:df['month']经fillna(0).astype(int)后,若原始数据有NaT(日期解析失败),fillna(0)会生成浮点数0.0,导致整个 Series 变为float64。
解法:

  • 强制转为Int64(Pandas 可空整数类型):df['month'] = df['pubDate'].dt.month.astype('Int64');
  • 或用pd.Categorical显式声明类别:df['month'] = pd.Categorical(df['month'], categories=range(1,13));
  • 答辩话术:「我们使用 Pandas 的可空整数类型存储月份,既保留缺失值语义,又确保图表坐标轴显示为整数」。

5. 从图表到故事:用 Plotly + Streamlit 构建可交互的毕设演示系统

毕设答辩不是代码审查,而是「用数据讲好一个故事」。静态图表(Matplotlib/Seaborn)只能展示结论,而交互式大屏能让老师自己拖拽筛选、点击查看明细——这直接把「你会不会做」升级为「你做的东西有没有用」。用 Plotly + Streamlit,150 行代码就能搭出专业级演示系统,且完全本地运行,无需服务器。

5.1 用 Plotly 构建 3 类核心图表:解决「为什么选这个图」的灵魂拷问

每张图必须回答一个明确问题,且图类型选择有依据:

问题图表类型Plotly 代码要点答辩价值
「不同类型影片的平均票房和评分如何分布?」散点图(px.scatter)x='box_office_wan',y='score',color='type_main',size='wantWatchCount'展示多维关系,size 体现想看人数权重
「各月份票房总和趋势如何?是否验证暑期档效应?」折线图(px.line)x='month',y='box_office_wan',line_group='year'(需提取年份)时间序列分析,突出业务规律
「TOP20 影片的类型构成占比?」环形图(px.pie)names='types_exploded',values='count',hole=0.5直观展示类型集中度,hole=0.5 提升可读性
import plotly.express as px import plotly.graph_objects as go # 1. 类型-票房-评分散点图(核心业务图) fig1 = px.scatter( df_clean, x='box_office_wan', y='score', color='type_main', # 需先 df_clean['type_main'] = df_clean['types'].apply(lambda x: x[0] if x else '未知') size='wantWatchCount', size_max=60, title="影片类型、票房与评分关系(气泡大小=想看人数)", labels={'box_office_wan': '票房(万元)', 'score': '评分', 'type_main': '主类型'} ) fig1.update_layout(height=500) # 2. 月度票房趋势折线图 monthly_data = df_clean.groupby('month')['box_office_wan'].sum().reset_index() fig2 = px.line( monthly_data, x='month', y='box_office_wan', markers=True, title="月度票房总和趋势(验证暑期档效应)", labels={'month': '月份', 'box_office_wan': '票房总和(万元)'} ) fig2.update_xaxes(tickmode='linear', dtick=1) # 强制显示 1-12 月 # 3. TOP20 影片类型环形图 top20 = df_clean.nlargest(20, 'box_office_wan') types_exploded = top20.explode('types') type_counts = types_exploded['types'].value_counts().reset_index(name='count') fig3 = px.pie( type_counts, names='types', values='count', hole=0.5, title="TOP20 影片类型分布" )

为什么不用 Matplotlib?因为答辩时老师会点鼠标放大某个月份的数据点,Matplotlib 静态图无法响应;而 Plotly 图表自带缩放、悬停、下载 PNG 功能——这叫「降低老师理解成本」。

5.2 用 Streamlit 封装交互式仪表盘:3 步完成「零部署演示」

Streamlit 是毕设神器:写 Python 脚本,运行streamlit run app.py,自动生成 Web 界面,所有图表、筛选器、文字说明一键集成。

# app.py import streamlit as st import pandas as pd import plotly.express as px st.set_page_config(page_title="猫眼电影数据分析毕设", layout="wide") st.title("🎬 猫眼电影数据分析系统(毕设演示版)") # 1. 数据上传与预处理(允许用户上传自己的 CSV) uploaded_file = st.file_uploader("上传清洗后的电影数据 CSV", type="csv") if uploaded_file is not None: df = pd.read_csv(uploaded_file) else: # 默认加载示例数据 df = pd.read_csv("maoyan_films_clean.csv") # 2. 交互式筛选器 col1, col2 = st.columns(2) min_score = col1.slider("最低评分", 0.0, 10.0, 7.0) max_box = col2.slider("最高票房(万元)", 0, 100000, 50000) filtered_df = df[(df['score'] >= min_score) & (df['box_office_wan'] <= max_box)] # 3. 并排展示三大图表 st.subheader("核心分析图表") fig1, fig2, fig3 = create_plots(filtered_df) # 调用前述 Plotly 函数 st.plotly_chart(fig1, use_container_width=True) st.plotly_chart(fig2, use_container_width=True) st.plotly_chart(fig3, use_container_width=True) # 4. 关键结论卡片(答辩时直接念) st.subheader("📊 关键发现") st.markdown(""" - **暑期档效应显著**:7、8 月票房总和占全年 32%,但影片数量仅占 18% - **类型集中度高**:TOP20 影片中,剧情片占比 45%,爱情片 20%,动画片 15% - **口碑分化明显**:专业评分与观众评分差值 >1.5 的影片,票房普遍低于均值 40% """)

部署指令:

pip install streamlit plotly pandas streamlit run app.py

运行后自动打开http://localhost:8501,界面清爽,支持手机查看——答辩时用笔记本投屏,老师扫码就能看,比 PPT 演示更直观。

5.3 毕设报告里的「可视化章节」怎么写才不被扣分?

很多学生把「可视化」写成「我用了 Matplotlib,画了 5 张图」,这等于没写。正确写法是:每张图配一段「问题-方法-结论」三段论:

问题:我们关注「高票房影片是否必然高评分」,这关系到市场对质量与商业性的权衡。
方法:采用散点图,横轴为票房(万元),纵轴为评分,气泡大小代表想看人数,颜色区分主类型。
结论:数据显示,票房超 5 亿元的影片中,评分集中在 7.0–8.5 区间,未出现评分>9.0 的超级爆款;而评分>8.5 的影片,票房多集中在 1–3 亿元,印证「叫好不叫座」现象普遍存在。

这种写法,让老师一眼看出:你不是在堆工具,而是在用工具解决问题。


6. 我的毕设交付物清单与答辩通关技巧:从代码到报告的 7 个实物细节

毕设不是交一份.py文件,而是一套可验证、可追溯、可复现的交付物。我坚持让学生按以下 7 项准备,缺一不可——它们共同构成答辩时的「信任锚点」,让老师相信:这真是你做的,而且做得扎实。

交付物具体内容为什么关键我的检查标准
1.requirements.txt精确到小版本:pandas==2.0.3,plotly==5.18.0,streamlit==1.29.0避免环境差异导致代码跑不通pip install -r requirements.txt后python app.py能立即运行
2.data/目录raw/(原始 HTML 备份)、clean/(清洗后 CSV)、backup/(每日快照)证明数据真实可溯源clean/maoyan_films_20241025.csv文件存在且非空
3.notebooks/目录01_crawl.ipynb(爬取全过程)、02_clean.ipynb(清洗逻辑)、03_viz.ipynb(图表调试)展示思考路径,而非最终结果每个 notebook 有In[]和Out[],且单元格执行顺序合理
4.reports/目录design_doc.md(设计思路)、analysis_report.pdf(图文报告)、presentation.pptx(答辩精简版)把技术动作升维成方法论design_doc.md中明确写了「为什么选猫眼而非豆瓣?因猫眼数据更侧重商业维度」
5.docs/目录api_reference.md(接口文档)、data_dict.xlsx(字段字典)、troubleshooting.md(避坑指南)体现工程规范意识data_dict.xlsx包含字段名、类型、业务含义、示例值三列
6.README.md一行命令启动:streamlit run app.py;一句话说明项目价值;截图展示仪表盘降低老师体验门槛README 开头就有清晰的「快速开始」区块,无冗余介绍
7.video/目录demo.mp4(2 分钟操作演示:从爬取到仪表盘交互)弥合「代码能跑」和「老师能懂」之间的鸿沟视频中展示点击筛选器、悬停看数据、下载图表等真实交互

最后的后悔药:答辩前夜,我一定会让学生做三件事:

  1. 拔网线测试:关掉 Wi-Fi,运行app.py—— 如果依赖在线资源(如px.data.gapminder()),立刻暴露;
  2. 换电脑测试:用室友的 Windows 笔记本git clone后pip install,确认环境兼容性;
  3. 打印报告:把analysis_report.pdf打印出来,手写标注 3 处「老师可能提问的地方」,并写下答案草稿。

这些动作不增加代码量,但能把「准备充分」刻进老师印象里。毕设不是比谁代码写得多,而是比谁把一件事做透、做稳、做到让别人放心。

希望帮到你。

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

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

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

立即咨询