公司动态

Python爬虫实战:猫眼电影票房数据采集与可视化分析

📅 2026/7/15 9:08:03
Python爬虫实战:猫眼电影票房数据采集与可视化分析
在数据驱动的互联网时代掌握网络数据采集和分析能力已经成为开发者的一项核心技能。Python 凭借其简洁的语法和强大的生态库在爬虫领域占据主导地位。猫眼电影作为国内权威的电影信息平台其票房数据是分析电影市场趋势的宝贵资源。本文将带领你从零开始构建一个完整的猫眼电影票房数据爬取与可视化项目涵盖从发送请求、解析数据到存储和图表展示的全流程。这个实战案例不仅适用于希望入门 Python 爬虫的初学者也适合需要巩固数据获取与处理流程的中级开发者。通过本项目你将学会如何处理常见的反爬机制、如何结构化地存储数据并最终通过直观的可视化图表揭示数据背后的规律。我们将使用requests、BeautifulSoup、pandas和pyecharts等主流库确保代码的实用性和可迁移性。1. 项目环境准备与核心库介绍在开始编写代码之前需要确保你的开发环境已经安装了必要的 Python 库。我们将使用 pip 进行安装。建议使用 Python 3.7 及以上版本。1.1 安装必备库打开命令行工具执行以下命令来安装项目依赖pip install requests beautifulsoup4 pandas pyecharts如果安装速度较慢可以考虑使用国内镜像源例如清华镜像pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 pandas pyecharts1.2 核心库的功能与作用requests: 用于发送 HTTP 请求获取网页源代码。它是爬虫项目中最基础且重要的库。BeautifulSoup: 用于解析 HTML 或 XML 文档从复杂的网页结构中提取所需的数据。pandas: 提供强大的数据处理能力可以将爬取的数据整理成结构化的 DataFrame并方便地导出为 CSV 或 Excel 文件。pyecharts: 一个生成 ECharts 图表的库能够创建交互式、美观的可视化图表。除了这些主要库本项目还会用到 Python 标准库中的time用于设置请求间隔和csv用于数据存储。2. 分析目标网页与制定爬取策略任何爬虫项目的第一步都是仔细分析目标网页的结构明确数据的位置和获取方式。2.1 确定数据源与页面结构猫眼电影专业版maoyan.com的票房页面包含了丰富的实时和历史数据。为了遵守网络爬虫规范我们应首先检查网站的robots.txt文件通常访问https://www.maoyan.com/robots.txt了解哪些路径是允许爬取的。在实际项目中务必尊重网站的爬虫协议避免对服务器造成过大压力。通过浏览器的开发者工具按 F12 键打开我们可以检查网页的网络请求和元素结构。找到票房数据对应的 HTML 元素发现数据通常包含在特定的div标签中并且有规律的 CSS 类名例如.movie-item。2.2 设计爬虫流程与规避反爬机制一个稳健的爬虫程序需要考虑以下几点设置请求头Headers: 模拟真实浏览器的访问行为特别是在User-Agent字段中声明合理的浏览器信息这是绕过基础反爬的最常见手段。控制请求频率: 在连续的请求之间插入随机的时间间隔例如time.sleep(random.uniform(1, 3))避免因请求过快导致 IP 被暂时封禁。处理分页: 票房数据通常是分页显示的。需要分析分页逻辑是通过 URL 参数如?offset0还是通过 JavaScript 加载并据此循环请求所有页面。异常处理: 网络请求可能失败页面结构可能变动。代码中需要包含try-except块来捕获异常保证程序的健壮性。基于以上分析我们制定如下爬取策略模拟浏览器访问逐页请求从每页的 HTML 中解析出电影名称、实时票房、累计票房、票房占比、排片占比等关键信息。3. 编写爬虫核心代码接下来我们将把策略转化为具体的代码实现。代码将分为几个函数每个函数职责单一便于理解和维护。3.1 定义请求函数首先定义一个函数用于发送 HTTP 请求并返回网页 HTML 内容。import requests import time import random from bs4 import BeautifulSoup def get_page(url): 发送请求获取网页源码 :param url: 目标页面的URL :return: 网页的HTML文本如果请求失败则返回None # 定义请求头模拟浏览器访问 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } try: # 发送GET请求 response requests.get(url, headersheaders) # 检查请求是否成功状态码为200 if response.status_code 200: # 返回网页文本内容并指定编码为UTF-8以正确解析中文 return response.text.encode(utf-8) else: print(f请求失败状态码{response.status_code}) return None except requests.RequestException as e: print(f请求发生异常{e}) return None finally: # 请求完成后随机休眠1-3秒减轻服务器压力 time.sleep(random.uniform(1, 3))3.2 定义数据解析函数获取到 HTML 后需要从中提取出结构化的数据。def parse_html(html): 解析HTML提取票房数据 :param html: 网页的HTML文本 :return: 包含单页电影数据的列表每个元素是一个字典 soup BeautifulSoup(html, html.parser) movies_list [] # 初始化一个空列表用于存储数据 # 通过CSS选择器找到所有包含电影信息的区块 # 此选择器需根据猫眼电影实际页面结构调整 movie_items soup.select(.movie-list dd) for item in movie_items: try: # 提取电影名称 name item.select(.movie-name a)[0].text.strip() # 提取实时票房 real_time_box_office item.select(.real-time-box-office)[0].text.strip() # 提取累计票房 total_box_office item.select(.total-box-office)[0].text.strip() # 提取票房占比 box_office_ratio item.select(.box-office-ratio)[0].text.strip() # 提取排片占比 screening_ratio item.select(.screening-ratio)[0].text.strip() # 将提取的数据组织成一个字典 movie_data { 电影名称: name, 实时票房万: real_time_box_office, 累计票房万: total_box_office, 票房占比: box_office_ratio, 排片占比: screening_ratio } # 将字典添加到列表中 movies_list.append(movie_data) except IndexError as e: # 如果某个元素查找失败打印错误并跳过当前项 print(f解析单个电影信息时出错可能页面结构有变{e}) continue return movies_list注意上面的 CSS 选择器如.movie-list dd是示例实际猫眼电影的页面结构可能不同。你需要使用浏览器的开发者工具右键点击数据区域选择“检查”来找到正确的元素和类名。3.3 主控函数与数据存储现在我们将请求和解析函数组织起来并加入分页逻辑和数据保存功能。import pandas as pd import os def main(): 主函数控制整个爬取流程 all_movies_data [] # 存储所有页面的数据 base_url https://maoyan.com/board/1 # 示例基础URL需替换为真实地址 total_pages 10 # 计划爬取的总页数 for page in range(total_pages): print(f正在爬取第 {page1} 页...) # 构造每一页的完整URL例如 offset0, 10, 20... url f{base_url}?offset{page * 10} html get_page(url) if html is None: print(f第 {page1} 页获取失败跳过。) continue page_data parse_html(html) if page_data: # 如果成功解析到数据 all_movies_data.extend(page_data) # 将当前页数据添加到总列表中 print(f第 {page1} 页爬取成功获得 {len(page_data)} 条数据。) else: print(f第 {page1} 页解析失败。) # 爬取完成后保存数据 if all_movies_data: # 使用pandas将列表转换为DataFrame df pd.DataFrame(all_movies_data) # 定义保存文件的路径 file_path maoyan_box_office.csv # 将DataFrame保存为CSV文件不保存行索引使用UTF-8编码避免中文乱码 df.to_csv(file_path, indexFalse, encodingutf-8-sig) print(f数据爬取完成共获得 {len(all_movies_data)} 条数据已保存至 {file_path}) else: print(未获取到任何数据。) if __name__ __main__: main()4. 数据清洗与预处理直接从网页爬取的数据往往不够规整需要进行清洗才能用于分析。4.1 处理常见的数据问题爬取到的文本数据可能包含多余的空格、换行符或者数字带有“万”、“%”等单位符号。我们需要将其转换为纯数值类型。def clean_data(df): 清洗和预处理爬取到的数据 :param df: 包含原始数据的DataFrame :return: 清洗后的DataFrame # 1. 去除字符串类型的列首尾的空格 df[电影名称] df[电影名称].str.strip() # 2. 将票房数据转换为数值类型假设原始数据格式为 1,234.5万 # 首先去除万字然后去除逗号最后转换为浮点数 df[实时票房万] df[实时票房万].str.replace(万, ).str.replace(,, ).astype(float) df[累计票房万] df[累计票房万].str.replace(万, ).str.replace(,, ).astype(float) # 3. 将百分比数据转换为小数假设原始数据格式为 12.3% df[票房占比] df[票房占比].str.replace(%, ).astype(float) / 100 df[排片占比] df[排片占比].str.replace(%, ).astype(float) / 100 return df # 在主函数中调用清洗函数 # 在保存CSV之前添加以下代码 df_cleaned clean_data(df) # 然后使用 df_cleaned 来保存文件4.2 处理缺失值与异常值在实际爬取中可能会因为页面结构不完全一致或网络问题导致某些数据缺失。# 检查缺失值 print(df_cleaned.isnull().sum()) # 简单的处理方式删除包含缺失值的行 df_cleaned df_cleaned.dropna() # 或者对于数值列可以用均值或中位数填充 # df_cleaned[实时票房万].fillna(df_cleaned[实时票房万].mean(), inplaceTrue)5. 使用 PyEcharts 进行数据可视化数据清洗完成后就可以利用pyecharts库来创建直观的图表了。我们将创建两个常见图表票房排名柱状图和占比饼图。5.1 绘制实时票房排名柱状图柱状图非常适合展示排名和比较数值大小。from pyecharts import options as opts from pyecharts.charts import Bar import pandas as pd def create_bar_chart(data_file): 创建实时票房排名柱状图 :param data_file: 清洗后的数据文件路径 # 读取数据 df pd.read_csv(data_file) # 按实时票房降序排列取前10名 top10 df.nlargest(10, 实时票房万) # 初始化Bar图表对象 bar Bar() # 添加X轴数据电影名称 bar.add_xaxis(top10[电影名称].tolist()) # 添加Y轴数据实时票房并设置系列名 bar.add_yaxis(实时票房万, top10[实时票房万].tolist()) # 设置全局配置项 bar.set_global_opts( title_optsopts.TitleOpts(title猫眼电影实时票房TOP10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate-15)), # X轴标签旋转避免重叠 yaxis_optsopts.AxisOpts(name票房万), datazoom_optsopts.DataZoomOpts(), # 添加数据区域缩放组件 ) # 设置系列配置项例如显示数值标签 bar.set_series_opts( label_optsopts.LabelOpts(is_showTrue) ) # 生成HTML文件可在浏览器中打开查看交互式图表 bar.render(real_time_box_office_top10.html) print(实时票房TOP10柱状图已生成real_time_box_office_top10.html)5.2 绘制票房占比饼图饼图可以直观地显示各部分在整体中的比例。from pyecharts.charts import Pie def create_pie_chart(data_file): 创建票房占比饼图 :param data_file: 清洗后的数据文件路径 df pd.read_csv(data_file) # 为饼图准备数据格式为 [(电影名1, 票房1), (电影名2, 票房2), ...] # 这里使用累计票房作为占比依据 pie_data [(row[电影名称], row[累计票房万]) for index, row in df.iterrows()] pie Pie() pie.add( series_name票房占比, data_pairpie_data, radius[30%, 75%], # 设置内外半径形成环形图效果 center[50%, 50%], # 设置饼图中心位置 ) pie.set_global_opts( title_optsopts.TitleOpts(title电影累计票房占比), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%), # 图例垂直排列 ) pie.set_series_opts( label_optsopts.LabelOpts(formatter{b}: {c} ({d}%)) # 标签格式名称: 数值 (百分比) ) pie.render(box_office_ratio_pie.html) print(票房占比饼图已生成box_office_ratio_pie.html) # 在主流程中调用可视化函数 # 在数据保存后添加以下代码 create_bar_chart(maoyan_box_office.csv) create_pie_chart(maoyan_box_office.csv)6. 常见问题与排查指南在运行爬虫项目时你可能会遇到一些问题。下面是一些常见问题及其解决方案。问题现象可能原因检查与解决方式请求返回状态码 4031. 请求头未正确设置被服务器识别为爬虫。2. IP 地址被暂时封禁。1. 检查并完善headers特别是User-Agent。2. 增加请求间隔时间或使用代理 IP。解析不到数据返回空列表1. 网页结构已更新CSS 选择器失效。2. 所需数据是通过 JavaScript 动态加载的。1. 重新使用开发者工具检查元素更新选择器。2. 考虑使用Selenium等工具模拟浏览器获取渲染后的页面。保存的 CSV 文件中文乱码文件编码问题。使用encodingutf-8-sig参数保存 CSV 文件。程序中途崩溃1. 网络波动导致单次请求失败。2. 解析个别数据项时出错。1. 加强异常处理try-except使单次失败不影响整体流程。2. 在解析每个数据项时进行异常捕获。图表无法显示或显示不全1. 数据清洗步骤出错导致数值列仍为字符串。2. 图表配置参数有误。1. 检查数据清洗后的 DataFrame 的数据类型df.dtypes。2. 查阅 PyEcharts 官方文档调整配置。7. 项目优化与最佳实践为了让爬虫项目更加稳健、高效和可维护可以考虑以下优化措施。7.1 使用 Session 保持连接对于需要多次请求同一网站的场景使用requests.Session()可以复用 TCP 连接提高效率并保持某些会话状态。session requests.Session() session.headers.update(headers) # 为Session设置统一的请求头 # 然后在get_page函数中使用session.get(url)代替requests.get(url)7.2 将配置参数外置将 URL、请求头、总页数、文件路径等配置信息提取到代码文件顶部的变量或单独的配置文件中如config.py便于管理和修改。# 在代码开头定义配置 TARGET_URL https://maoyan.com/board/1 USER_AGENT Mozilla/5.0 ... TOTAL_PAGES 10 OUTPUT_FILE maoyan_box_office.csv7.3 添加日志记录使用 Python 的logging模块替代print语句可以更规范地记录程序运行信息、警告和错误方便后期排查问题。7.4 考虑数据存储的多样化除了保存为 CSV 文件根据项目需求还可以将数据存入数据库如 SQLite, MySQL。import sqlite3 # 连接SQLite数据库如果不存在则会创建 conn sqlite3.connect(box_office.db) cursor conn.cursor() # 创建表 cursor.execute( CREATE TABLE IF NOT EXISTS movies ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, real_time_box_office REAL, total_box_office REAL, box_office_ratio REAL, screening_ratio REAL, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) # 将DataFrame数据写入数据库 df_cleaned.to_sql(movies, conn, if_existsappend, indexFalse) # 关闭连接 conn.close()这个完整的 Python 爬虫实战项目涵盖了从环境搭建、页面分析、代码编写、数据清洗到可视化展示的全过程。最重要的是理解每一步的原理和设计考量而不仅仅是复制代码。你可以在此基础上进行扩展例如爬取更多维度的数据演员、评分、评论或者使用更复杂的可视化库如 Matplotlib, Seaborn来创建静态报告。