Python实战:春节电影信息爬取与数据可视化分析 📅 发布时间:2026/9/13 17:52:59 👁 浏览次数: 简介面向Python爬虫与数据可视化方向毕业设计的完整项目包内含可运行源码、答辩PPT与项目说明文档可直接作为毕业设计参考。压缩包共39个文件涵盖Python脚本py/pyc、前端资源js/css/html、数据库与配置db/xml/txt以及PDF、PNG等文档图片类型整体大小约20.87MB。已有91人浏览学习。项目以春节电影信息爬取为主题main.py与DataBase.py构成核心逻辑实现电影数据抓取、数据清洗、存储入库与前端可视化展示的完整流程代码中附带app.log运行日志和README.md说明配置有data.db数据库、MovieName.txt电影名称清单及run.bat一键启动脚本templates与static目录则分别对应页面模板和静态资源结构清晰、便于本地运行和二次开发。配套的答辩PPT和PDF设计文档系统阐述了选题背景、需求分析、数据采集与可视化方案能够帮助学习者快速理解项目整体架构与实现细节适合作为Python课程设计、毕业设计或爬虫项目学习的重要参考资料。1. 春节档影片信息爬下来不难难的是让数据口径一致每年春节档一结束公开渠道能拿到的数据并不少猫眼专业版有实时票房艺恩有档期报告豆瓣有评分和想看灯塔有排片占比。真正动手做「基于 Python 的春节电影信息爬取与数据可视化分析」时第一个被卡住的往往不是爬虫而是同一部片子在不同来源里字段长得完全不一样有的写「累计票房」、有的写「总票房」有的带「亿」、有的只给数字评分还会因样本量太小而显示「暂无」。这个标题落到工程上本质是一条「采集 → 清洗 → 分析 → 可视化 → 交付」的完整链路适合用 Python 完成课程设计、述职报告或自媒体内容生产的读者。下面的方案就是我通常会搭的一套可复现代码骨架核心是先用 requests 和 BeautifulSoup 把数据拿下来再靠 pandas 把口径对齐最后交给 Matplotlib 和 Pyecharts 出图。2. 用 requests BeautifulSoup 采集春节电影信息先把数据源和字段定下来2.1 两个数据源榜单页用来抓「有哪些片」详情页用来抓「片子到底怎么样」常见做法是抓公开的影评/票房数据站点比如豆瓣电影榜单和猫眼专业版的公开页面。榜单页的价值是快速得到「今年春节档都有哪些影片」的清单详情页的价值是补全「导演、主演、评分、评分人数、上映日期、类型」这些单部影片属性。我不会一上来就写全量爬虫而是先抓一个最小页面确认目标标签在当前 HTML 结构里真实存在再写通用抽取函数。字段设计建议在爬取之前就定好后续清洗和可视化全靠这张表。以下是我常用的字段清单字段名含义示例采集来源movie_name影片名«热辣滚烫»榜单页release_date上映日期2024-02-10详情页box_office累计票房原始字符串34.6亿榜单页/详情页rating评分7.5详情页rating_people评分人数482134详情页director导演贾玲详情页genre类型剧情 / 喜剧详情页crawl_date采集日期2024-02-25自动生成提示字段名统一用英文小写加下划线好处是后续 pandas 读取时不用处理中文列名图表标题里再单独映射中文避免编码问题在可视化阶段反复出现。2.2 最小可跑通的采集脚本requests bs4 的骨架先写一个最朴素的版本目标是把「某部电影的评分和票房」从 HTML 页面里抠出来。下面这段代码是骨架不绑定任何具体站点但套路是通用的请求 → 解析 → 抽取 → 落盘。import requests from bs4 import BeautifulSoup import pandas as pd HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(url: str) - BeautifulSoup: # 超时和重试都放到请求层避免单个页面卡死整个任务 resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding utf-8 return BeautifulSoup(resp.text, html.parser) def parse_movie(soup: BeautifulSoup) - dict: # 注意这里的选择器必须根据实际页面结构调整下面只是示例 return { movie_name: soup.select_one(h1 span).text.strip(), rating: soup.select_one(strong.rating_num).text.strip(), rating_people: soup.select_one(span.rating_people span).text.strip(), genre: / .join( [e.text for e in soup.select(span.genre a)] ), } url https://example.com/movie/1 soup fetch_page(url) data parse_movie(soup) print(data)这段代码的逻辑分三层fetch_page负责网络请求把响应包成 BeautifulSoup 对象parse_movie负责从对象里抽字段最后的主流程只做组装。timeout10是必调参数没有超时的爬虫在目标站点变慢时会无限阻塞resp.encoding utf-8很关键不指定时 requests 会从响应头猜编码中文站点经常猜错出现乱码就查这一行。真正要做的有三件事一是把 URL 换成实际可访问的影片页二是按目标站的 HTML 结构调整select_one的选择器三是在正式跑批前先打印一次结果确认字段不是空值再扩规模。2.3 反爬处理要有但不是主角UA、Referer、休眠和重试春节档数据的采集量通常只有几十部属于轻量请求不需要上代理池。我一般只做四件事统一 UA、带上 Referer、请求间随机休眠、对失败请求做指数退避重试。下面的代码把这部分封装成可复用的fetch_page_with_retry函数import time import random BASE_REFERER https://www.example.com/ def fetch_page_with_retry(url: str, max_retries: int 3) - BeautifulSoup: for attempt in range(max_retries): try: headers { User-Agent: HEADERS[User-Agent], Referer: BASE_REFERER, Accept-Language: zh-CN,zh;q0.9, } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() resp.encoding utf-8 # 休眠范围建议在 1~3 秒之间太短起不到间隔作用太长影响调试 time.sleep(random.uniform(1, 3)) return BeautifulSoup(resp.text, html.parser) except requests.RequestException: if attempt max_retries - 1: raise # 退避重试第一次等 2 秒第二次等 4 秒 time.sleep(2 ** attempt)参数说明max_retries3表示最多尝试三次time.sleep(random.uniform(1, 3))让每次请求间隔不完全一致避免出现固定频率的访问模式2 ** attempt是退避时间第二次重试前等 4 秒给目标服务器留出恢复时间。这里没有做 cookie 维护和登录模拟因为公开榜单页一般不需要登录如果页面明确要求登录就直接把「需要登录的数据」排除在采集范围之外不值得为课程设计去做账号相关的逆向操作。2.4 把结果落成统一格式的 CSV以便 pandas 直接读采集结果建议一开始就写成带表头的 CSV而不是 JSON因为后续清洗和可视化多数基于 pandasCSV 读进来就是 DataFrame省一次转换。写入时注意两个参数encodingutf-8-sig和indexFalse。import pandas as pd rows [ {movie_name: 示例电影, box_office: 34.6亿, rating: 7.5}, {movie_name: 示例电影2, box_office: 12.3亿, rating: 8.1}, ] df pd.DataFrame(rows) # 用 utf-8-sig 编码Excel 打开不会乱码indexFalse 避免写入行号列 df.to_csv(spring_festival_movies.csv, indexFalse, encodingutf-8-sig)indexFalse是最容易被忽略的参数不写的话 CSV 第一列会多出 0、1、2 编号后续读回来会误认为是数据列utf-8-sig是 Windows 环境打开文件的兼容方案不放这个参数Excel 直接打开会出现中文乱码pandas 读回则不受影响。3. pandas 清洗春节票房数据把带单位、带符号、缺值的字符串换成可计算的数字3.1 字段类型检查读进来先看 dtype 和缺失情况爬回来的表里几乎所有字段都是字符串box_office是「34.6亿」这样的文本rating可能是空字符串release_date可能是「2024-02-10」也可能带时间。不做检查就画图折线图的横轴会变成字符串排序。读取后的第一件事永远是info()和isnull().sum()。import pandas as pd df pd.read_csv(spring_festival_movies.csv, encodingutf-8-sig) print(df.info()) print(df.isnull().sum())df.info()输出每列的非空数量、dtype 和内存占用一眼能看出哪些列被读成了 objectdf.isnull().sum()统计每列缺失数。票房列为 object 是预期内结果因为原始数据是带单位的字符串重点是确认rating缺失了几条、box_office有没有空行这决定后面按什么策略补缺。3.2 写单位换算函数把「12.3亿」「4567万」统一成以亿元为单位的 float票房字段在不同页面里单位不一致有的是亿有的是万甚至同一页里两部影片单位都不同。清洗时不能逐个手工改写一个正则解析函数把字符串拆成「数字 单位」两部分再统一换算成亿元。import re def box_office_to_yi(value: object) - float: 把 34.6亿 / 4567万 / 1345 统一换算成亿元。 if value is None or (isinstance(value, float) and pd.isna(value)): return float(nan) text str(value).strip() if not text or text 暂无: return float(nan) match re.search(r([0-9]\.?[0-9]*)\s*(亿|万)?, text) if not match: return float(nan) num float(match.group(1)) unit match.group(2) if unit 亿: return num if unit 万: return round(num / 10000, 4) return num df[box_office_yi] df[box_office].apply(box_office_to_yi) print(df[[movie_name, box_office, box_office_yi]].head())正则表达式([0-9]\.?[0-9]*)\s*(亿|万)?是核心第一组匹配数字部分支持小数第二组匹配单位?表示单位可有可无\s*处理「34.6 亿」中间的空格。数值落在 1 到 100 之间同时字符串里带「亿」字的往往是亿元为单位数值很大且带「万」就除以 10000。单位缺失的裸数字默认按原始单位处理如果页面本身以万元展示这里的return num需要配合业务口径调整——清洗不是纯技术活数据口径要由后续分析目标决定。3.3 评分缺失和票房缺失删、填、还是分组填充爬虫对评分处理最常见的是把空值写成「暂无」或空字符串导致rating列读成字符串而不是 float。这一步先pd.to_numeric(..., errorscoerce)把非法值统一转成NaN再决定填充策略。df[rating_float] pd.to_numeric(df[rating], errorscoerce) # 方案一缺失评分直接删除适用于缺失样本很少 df_drop df.dropna(subset[rating_float]) # 方案二用全部影片的中位数填充 median_rating df[rating_float].median() df[rating_filled] df[rating_float].fillna(median_rating) print(df[[movie_name, rating, rating_float, rating_filled]])方案一适合只有一两条缺失的场景删除后不影响总样本方案二适合缺失量在 10%~20% 之间的情况用中位数填充比均值更稳健因为评分分布通常左偏均值会拉低。errorscoerce是pd.to_numeric的必记参数它把无法解析成数字的值转换成NaN否则整列会被ValueError打断脚本跑不完。3.4 分析口径从票房和评分里能算出哪几组关系数据清洗完分析维度就可以铺开了。常见的春节档分析维度包括单片票房 Top10、评分与票房是否存在相关性、上映日期初一/初二/初三对票房均值的影响、影片类型分布、场均人次与档期热度关系。下面用一小段代码同时输出票房占比和评分排名作为后续可视化的输入# 票房占比单位亿元 total_box df[box_office_yi].sum() df[box_office_share] df[box_office_yi] / total_box # 按评分排序评分相同的按票房排序 df_sorted df.sort_values( by[rating_float, box_office_yi], ascending[False, False] ) print(df_sorted[[movie_name, rating_float, box_office_yi, box_office_share]])sort_values的两个排序条件里rating_float是主键box_office_yi是次键解决评分相同时谁排前面的一致性问题。box_office_share是后续做饼图和占比堆叠图的直接输入也可以用于 PPT 里的「单片贡献度」描述。4. 用 Matplotlib 和 Pyecharts 做数据可视化单图、组合图、大屏4.1 面向 PPT 的单图票房 Top10 水平条形图课程设计和 PPT 材料里最常用的是单图单图要能独立讲述一个结论。票房 Top10 用水平条形图比垂直条形图更合适因为影片名通常 4~6 个字放在 y 轴不拥挤且排名第 10 到第 1 的视觉是从上往下递增。这里用 Matplotlib 输出 PNG便于后面插入 PPT 或大屏。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False # 假设 df_sorted 是清洗后的 DataFrame取前 10 top10 df_sorted.head(10) fig, ax plt.subplots(figsize(10, 7)) bars ax.barh(top10[movie_name], top10[box_office_yi], color#1f77b4) ax.invert_yaxis() # 让票房最高的显示在顶部 ax.set_xlabel(票房亿元) ax.set_title(春节档影片票房 Top10) for bar, value in zip(bars, top10[box_office_yi]): ax.text(bar.get_width() 0.3, bar.get_y() bar.get_height() / 2, f{value:.1f}亿, vacenter) plt.tight_layout() plt.savefig(box_office_top10.png, dpi150, bbox_inchestight)invert_yaxis()是水平条形图的默认惯例操作不调用时票房最高那部会在最底部视觉上不符合阅读习惯plt.rcParams[font.sans-serif]必须指定中文字体否则图里中文全是方框。ax.text的横坐标用bar.get_width() 0.3把数值标签放在条尾外侧避免数值被条体覆盖dpi150是 PPT 插入的合理值低于 96 放大后发虚高于 300 文件体量变大且没必要。4.2 反映影片走势的折线图累计票房随时间变化如果采集脚本连续几天运行就会得到同一部电影的多天累计票房记录。数据形式是长表movie_name、date、box_office_yi。画折线前必须先做数据透视把「日期」变成索引把「影片名」变成列。import pandas as pd import matplotlib.pyplot as plt # trend_df 包含 movie_name / date / box_office_yi 三列 pivot_df trend_df.pivot_table( indexdate, columnsmovie_name, valuesbox_office_yi, aggfuncsum ) print(pivot_df.head()) fig, ax plt.subplots(figsize(12, 6)) pivot_df.plot(axax, markero, markersize4) ax.set_ylabel(累计票房亿元) ax.set_title(春节档影片累计票房走势) plt.xticks(rotation45) plt.tight_layout() plt.savefig(box_office_trend.png, dpi150)pivot_table的三个参数里index是转化后的行索引columns是每一列代表的影片values是填充单元格的指标。因为同一部影片同一天只有一条票房记录aggfuncsum实际上不会发生聚合但保留它是稳妥写法——万一采集逻辑里同一天跑了两遍重复记录会被求和而不是报错。4.3 用 Pyecharts 页面组装成一张可交互的大屏Matplotlib 出静态图Pyecharts 出可以悬浮查看数值的交互图二者在「源码 PPT 材料」的组合里分工明确Matplotlib 图插进 PPT 正文Pyecharts 页面留给现场演示或打包成 HTML 交付。Pyecharts 组装大屏常用Page容器把多个图按顺序放进一个 HTML 页面滚动查看。from pyecharts.charts import Bar, Page from pyecharts import options as opts bar ( Bar() .add_xaxis(top10[movie_name].tolist()) .add_yaxis(票房亿元, top10[box_office_yi].tolist()) .set_global_opts( title_optsopts.TitleOpts(title春节档票房 Top10), xaxis_optsopts.AxisOpts(name影片), yaxis_optsopts.AxisOpts(name亿元), ) ) pie ( Pie() .add(, [list(z) for z in zip(top10[movie_name], top10[box_office_share])]) .set_global_opts(title_optsopts.TitleOpts(title票房占比)) ) page Page(layoutPage.SimplePageLayout) page.add(bar, pie) page.render(spring_festival_dashboard.html)Page的参数SimplePageLayout是常用的快速布局模式不做复杂网格时够用需要两列排布时改用DraggablePageLayout图可以拖动但演示前要手动调好位置。.render()输出独立 HTML不依赖后端适合直接放进 PPT 超链接或作为附件交付。下面是不同变量组合对应的图表选型参考变量类型想要表达的结论推荐图表对应代码片段影片名 票房谁最强水平条形图ax.barh日期 累计票房走势是否稳健折线图pivot_df.plot评分 票房口碑与收益关系散点图ax.scatter单片票房占比头部集中度饼图PyechartsPie类型 影片数档期供给结构柱状图value_counts后bar5. 完整源码包与 PPT 材料的组织方式让代码可复现让分析可讲述5.1 源码包的目录结构与模块划分标题里的「完整源码」指的是一个下载解压就能python main.py跑通的工程而不是单个脚本。一个合格交付包的结构我通常这样组织spring_festival_project/ ├── main.py # 入口按顺序调用 crawl / analyze / visualize ├── requirements.txt # 依赖列表必须锁定版本 ├── config.py # URL、UA、字段名、输出路径等配置 ├── crawler/ │ ├── __init__.py │ ├── fetcher.py # 请求、重试、休眠 │ └── parser.py # BeautifulSoup 解析逻辑 ├── analysis/ │ ├── __init__.py │ └── clean.py # 清洗、换算、填充 ├── visualize/ │ ├── __init__.py │ ├── static_charts.py # Matplotlib 出图 │ └── dashboard.py # Pyecharts 大屏 ├── data/ │ ├── raw/ # 原始爬取结果 │ └── processed/ # 清洗后的数据 └── output/ ├── charts/ # PNG 图片 └── dashboard.html模块划分的核心逻辑是crawler只负责网络和解析不碰业务计算analysis只碰 DataFrame不关心里面的数据从哪来visualize只消费清洗后的数据。这样改其中一个模块不会波及另外两个别人接手时也能按目录定位问题。requirements.txt里至少要锁requests、beautifulsoup4、pandas、matplotlib、pyecharts这五个包版本号建议直接使用自己本机测试通过的版本。main.py的常见写法是先检查data/raw里是否已有当天数据有就跳过爬取直接进入清洗和可视化。这个判断用os.path.exists就能实现可以避免每次跑入口脚本都重新请求目标站点也是增量爬取的最简形态。5.2 用 python-pptx 批量生成图表页PPT 材料如果手工从 PNG 插图和排版批量交付时效率很低。更工程化的做法是用python-pptx生成占位页把 Matplotlib 输出的图按顺序插入再统一设置标题和版式。from pptx import Presentation from pptx.util import Inches prs Presentation() title_layout prs.slide_layouts[0] # 标题页版式 charts [ (box_office_top10.png, 春节档票房 Top10), (box_office_trend.png, 累计票房走势), (rating_boxoffice_scatter.png, 评分与票房关系), ] for image_path, title in charts: slide prs.slides.add_slide(prs.slide_layouts[5]) # 标题内容版式 slide.shapes.title.text title # 图片从左侧 1 英寸处开始宽度占 8 英寸高度按比例自适应 slide.shapes.add_picture(image_path, Inches(1), Inches(1.5), widthInches(8)) prs.save(f{title}.pptx) # 实际应该统一到最后保存一次这里的关键参数是Inches(8)PPT 默认页面宽度 13.33 英寸图片宽度设 8 英寸并置于左侧右侧留出空间给文字备注。python-pptx不会纠正图片比例传入width后高度自动等比缩放所以不需要手算高度。批量生成时用一个for循环从图表清单读取路径和标题脚本可复用新一年的数据只要替换 PNG 文件即可重新生成整套 PPT 骨架。5.3 把分析结论写进备注栏的讲法PPT 材料不只是图表的堆叠。每页除了图表本体还应该在备注栏里写明「这张图回答了什么问题、数据口径是什么、异常点怎么解释」。python-pptx提供了notes_slide接口可以直接写备注。slide prs.slides.add_slide(prs.slide_layouts[5]) slide.shapes.title.text 评分与票房散点图 # 备注栏内容建议包含结论一句话 口径 可被质疑的点 slide.notes_slide.notes_text_frame.text ( 结论高评分影片的票房下限更高但最高票房并非评分最高。\n 口径票房含服务费评分截至采集当日。\n 风险样本仅春节档主力影片不代表全年大盘。 )这样生成的 PPT 即使换一个人去讲也能快速抓住每页的核心信息现场答疑被问到「为什么票房和评分不一致」时备注里预先写好的口径说明就派得上用场。整个交付链到这里才是真正完整的源码能让数据重新跑出来图表能让结果被看见备注栏能让结论被口头表达出来。6. 增量爬取与结果验证把脚本从「跑一次」改成「每天能跑」6.1 用影片名加上映日期做指纹避免重复入库春节档数据的特点是影片数量少、但每天数值都在变。重复跑全量爬取没问题但如果分析目标包含走势同一部电影每天会新增一条记录。此时需要做「指纹去重」以movie_name release_date为唯一键当天数据入库前先判断这条记录是否已存在。import pandas as pd def merge_today_data(existing_path: str, today_df: pd.DataFrame) - pd.DataFrame: existing pd.read_csv(existing_path, encodingutf-8-sig) # 生成指纹列 existing[fingerprint] existing[movie_name] _ existing[release_date] today_df[fingerprint] today_df[movie_name] _ today_df[release_date] # 今天已存在的指纹直接替换不存在的追加 merged pd.concat([existing, today_df], ignore_indexTrue) merged merged.drop_duplicates(subset[fingerprint], keeplast) return merged.drop(columns[fingerprint])drop_duplicates的keeplast是关键今天爬到的数据拼接在末尾保持「最后一条为准」就天然实现了当天更新覆盖旧记录的效果。fingerprint列在完成去重后删掉避免污染正式字段。6.2 用三个数字校验爬取完整性跑完增量更新后至少看三个指标影片总数、缺失率、票房总和。影片总数应该和榜单页显示的条目一致缺失率用df[box_office_yi].isnull().mean()看超过 10% 说明采集脚本有批量失败需要回看日志票房总和的量级要和公开报道对得上比如春节档总票房通常在几十亿到上百亿的区间如果算出来只有几千万大概率是单位换算出了问题。这三个校验写成assert或打印告警让脚本在数据异常时发出明显提示而不是把错误数据直接送去画图。6.3 收尾技巧每次跑批前自动打印字段统计在main.py末尾加一段输出记录本次爬取和清洗的关键数字这个习惯能省下大量排错时间。print(f影片总数: {len(df)}) print(f票房缺失: {df[box_office_yi].isnull().sum()} 条) print(f总票房: {df[box_office_yi].sum():.1f} 亿) print(f评分均值: {df[rating_float].mean():.2f})输出的数字同时写进当日日志文件下次跑批时能对照判断数据波动是真实市场变化还是采集故障。增量爬取加上这三个校验数字整套基于 Python 的春节电影信息爬取与数据可视化分析方案就能稳定运行到档期结束。本文还有配套的精品资源点击获取