Python爬虫实战:Flask+requests+Pandas构建电影数据分析系统
2026/9/6 4:37:20 网站建设 项目流程

1. 先搞清楚这个项目到底能帮你解决什么问题

如果你正在学Python爬虫,或者需要快速获取电影数据做分析,这个Flask+requests+Pandas的组合方案特别适合拿来练手。它最直接的价值是:用最简单的代码结构,把数据采集、清洗、可视化的完整流程跑通。

很多人一提到爬虫就想到Scrapy这类重型框架,但实际工作中,很多中小型数据抓取任务根本用不到那么复杂的调度和中间件。requests库直接发HTTP请求,Pandas做表格化处理,Flask提供本地预览界面——这个组合最大的优势是入门快、依赖少、调试直观

我一般会建议新手先从这个方案开始,因为:

  • 不需要额外安装数据库,Pandas的DataFrame足够存放几千条电影数据
  • 不需要理解复杂的异步爬虫架构,单线程顺序抓取更容易控制频率
  • 可视化部分用Matplotlib或Seaborn画基础图表,能快速验证数据质量

但要注意,这个方案不适合抓取大规模数据(比如超过1万条记录),也不适合需要高并发的生产环境。它的定位很明确:学习原型、课程作业、小型数据分析项目

2. 环境准备:别在依赖版本上踩坑

虽然项目标题写着“简单易懂”,但很多人在环境配置阶段就会卡住。不是代码复杂,而是Python库版本兼容问题。

2.1 基础环境要求

  • Python版本:3.6+(推荐3.8+,太老的版本可能遇到SSL证书问题)
  • 操作系统:Windows/macOS/Linux都可以,但路径处理方式略有不同
  • 网络条件:需要能正常访问猫眼电影网站

2.2 核心库安装顺序

我习惯按这个顺序安装,能减少很多依赖冲突:

# 先装基础数据处理库 pip install pandas numpy # 再装网络请求相关 pip install requests lxml # 最后装Web框架和可视化 pip install flask matplotlib seaborn

特别注意requests的版本:有些教程用的老版本可能遇到TLS握手失败。直接用最新版就好,但如果遇到429 Too Many Requests错误,不是版本问题,是请求频率太高被网站限制了。

2.3 验证安装是否成功

不要直接运行完整代码,先开个Python命令行逐库测试:

# 测试requests import requests response = requests.get('http://httpbin.org/get', timeout=5) print(response.status_code) # 应该返回200 # 测试pandas import pandas as pd df = pd.DataFrame({'test': [1, 2, 3]}) print(df.shape) # 应该显示(3, 1) # 测试flask from flask import Flask app = Flask(__name__) print('Flask导入成功') # 没报错就是环境OK

如果任何一步报错,先解决环境问题再继续。最常见的是No module named 'pandas'——不是没安装就是装了多个Python版本搞混了。

3. 爬虫部分:重点不是代码,是请求策略

猫眼电影的反爬机制不算严格,但对请求频率很敏感。很多人一上来就写循环抓取,结果前几条成功,后面全是429错误。

3.1 单页面抓取测试

先从单个电影页面开始,确认能拿到基础数据:

import requests from lxml import html def get_movie_detail(movie_id): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } url = f'https://maoyan.com/films/{movie_id}' try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: tree = html.fromstring(response.text) # 提取电影名称 name = tree.xpath('//h1[@class="name"]/text()') # 提取评分 score = tree.xpath('//span[@class="score"]/text()') return { 'name': name[0].strip() if name else '未知', 'score': score[0].strip() if score else '暂无评分' } else: print(f'请求失败,状态码:{response.status_code}') return None except Exception as e: print(f'抓取异常:{e}') return None # 测试单条数据 test_data = get_movie_detail(1292722) # 阿凡达的ID print(test_data)

这个测试能帮你验证三件事:

  1. 请求头设置是否正确(猫眼会检查User-Agent)
  2. XPath路径是否有效(网站改版后选择器可能失效)
  3. 网络连接是否稳定

3.2 控制请求频率的关键技巧

直接上time.sleep(1)是最简单的方法,但实际使用时我建议这样优化:

import random import time def safe_request(url, headers, max_retry=3): for attempt in range(max_retry): try: response = requests.get(url, headers=headers, timeout=15) if response.status_code == 429: # 触发了频率限制 wait_time = (2 ** attempt) + random.uniform(0, 1) print(f'触发限流,等待{wait_time:.1f}秒后重试') time.sleep(wait_time) continue return response except requests.exceptions.Timeout: print(f'请求超时,第{attempt+1}次重试') time.sleep(2) except Exception as e: print(f'其他错误:{e}') break return None

这种带指数退避的重试机制,比固定间隔更不容易被封。特别是抓列表页时,不要在for循环里直接sleep,要把等待时间随机化。

3.3 列表页抓取的数据结构设计

猫眼电影列表页通常包含20部电影,抓取时要考虑数据如何存储:

import pandas as pd from datetime import datetime class MovieSpider: def __init__(self): self.movies_data = [] self.columns = ['movie_id', 'title', 'score', 'release_date', 'actors', 'country', 'crawl_time'] def parse_list_page(self, page_num): """解析电影列表页""" # 实际代码中这里填充具体的抓取逻辑 pass def save_to_dataframe(self): df = pd.DataFrame(self.movies_data, columns=self.columns) df['crawl_time'] = pd.to_datetime(df['crawl_time']) return df

用类来组织代码的好处是:数据在内存中集中管理,后续清洗和导出都更方便。特别是crawl_time字段,很多人会忽略,但做时间序列分析时这个时间戳很重要。

4. 数据清洗:Pandas比你想的更实用

原始爬取的数据往往存在缺失值、格式不一致等问题,直接可视化会出各种问题。

4.1 常见数据问题及处理

假设我们已经抓取了100部电影数据,先用Pandas快速诊断:

# 假设df是抓取到的DataFrame print("数据概览:") print(f"总记录数:{len(df)}") print(f"字段结构:{df.dtypes}") print("\n缺失值统计:") print(df.isnull().sum()) # 评分数据清洗 df['score'] = pd.to_numeric(df['score'], errors='coerce') # 转换数字,无效值变NaN print(f"有效评分数量:{df['score'].notna().sum()}") # 处理文本字段的空白字符 df['title'] = df['title'].str.strip() df['actors'] = df['actors'].str.replace('\s+', ' ', regex=True) # 合并多个空格

特别要注意评分数据:猫眼的评分可能有“暂无评分”文本,直接转数字会失败。errors='coerce'参数会把转换失败的变成NaN,后续可以过滤或填充。

4.2 数据增强处理

单纯清洗还不够,好的分析需要衍生字段:

# 从日期字段提取年份 df['release_year'] = pd.to_datetime(df['release_date']).dt.year # 评分分段(用于可视化分组) df['score_level'] = pd.cut(df['score'], bins=[0, 3, 6, 8, 10], labels=['差评(0-3)', '一般(3-6)', '良好(6-8)', '优秀(8-10)']) # 演员数量统计(有些电影主演很多) df['actor_count'] = df['actors'].str.split(',').str.len()

这些衍生字段看起来简单,但能极大丰富可视化维度。比如按score_level分组统计,比直接看分数分布更直观。

5. Flask可视化界面:重点在数据传递,不是炫技

很多人把Flask部分想得太复杂,其实在这个项目中它只负责三件事:

  1. 接收Pandas处理好的数据
  2. 用Matplotlib/Seaborn生成图表
  3. 在网页上展示结果

5.1 最小可用的Flask应用结构

from flask import Flask, render_template import matplotlib.pyplot as plt import seaborn as sns import io import base64 app = Flask(__name__) @app.route('/') def index(): # 1. 从CSV文件或数据库读取数据(这里用示例数据) df = load_movie_data() # 2. 生成图表 plot_url = generate_plot(df) # 3. 传递到模板 return render_template('index.html', plot_url=plot_url) def generate_plot(df): """生成评分分布图并返回Base64编码的图片""" plt.figure(figsize=(10, 6)) sns.histplot(df['score'].dropna(), bins=20, kde=True) plt.title('电影评分分布') plt.xlabel('评分') plt.ylabel('数量') # 将图片转为Base64 img = io.BytesIO() plt.savefig(img, format='png', bbox_inches='tight') img.seek(0) plot_url = base64.b64encode(img.getvalue()).decode() plt.close() # 重要!不关闭会内存泄漏 return f"data:image/png;base64,{plot_url}" if __name__ == '__main__': app.run(debug=True)

关键点:每次请求都要plt.close(),否则多次访问后内存会持续增长。这是Matplotlib在Web环境下的常见坑。

5.2 实用图表类型选择

根据电影数据特点,我建议优先做这几类图表:

  1. 评分分布直方图:看整体评分倾向
  2. 年度电影数量折线图:看产量变化趋势
  3. 国家/地区饼图:看产地分布
  4. 评分与演员数量散点图:看是否存在相关性
def generate_multiple_plots(df): """生成多个图表""" plots = {} # 图表1:评分分布 plt.figure(figsize=(8, 5)) df['score'].hist(bins=20) plots['score_dist'] = plot_to_base64() # 图表2:年度产量 plt.figure(figsize=(10, 5)) df['release_year'].value_counts().sort_index().plot(kind='line') plots['year_trend'] = plot_to_base64() return plots

不要追求图表的视觉效果多炫酷,重点是把数据关系表达清楚。很多初学者花大量时间调整颜色和样式,反而忽略了数据本身的洞察。

6. 项目整合:从脚本到可复用工程

单个Python文件能跑通demo,但要作为完整项目,需要考虑文件组织:

movie_analysis/ ├── spider/ # 爬虫模块 │ ├── __init__.py │ ├── maoyan_spider.py │ └── utils.py # 请求工具函数 ├── analysis/ # 数据分析模块 │ ├── data_clean.py │ └── visualization.py ├── app.py # Flask主程序 ├── templates/ # 网页模板 │ └── index.html ├── data/ # 数据存储 │ ├── raw/ # 原始数据 │ └── processed/ # 清洗后数据 └── requirements.txt

6.1 配置管理

不要硬编码URL和参数,用配置文件或环境变量:

# config.py class Config: MAOYAN_BASE_URL = 'https://maoyan.com' REQUEST_TIMEOUT = 15 MAX_PAGES = 10 # 控制抓取深度 OUTPUT_DIR = 'data/raw'

6.2 错误处理与日志

生产环境一定要加日志记录:

import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('movie_spider.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def crawl_movie_list(): try: # 爬虫逻辑 logger.info(f'开始抓取第{page}页') except Exception as e: logger.error(f'抓取第{page}页失败:{e}')

7. 实际部署时的注意事项

7.1 避免被封IP的策略

  • 控制请求频率:单IP每秒不超过1个请求
  • 使用代理IP池(如果需要大规模抓取)
  • 模拟真实浏览器行为:随机User-Agent、Referer
  • 定期检查Cookie有效性

7.2 数据存储方案选择

  • 小规模测试:直接存CSV文件,Pandas读写方便
  • 中等规模:用SQLite,不需要单独安装数据库服务
  • 生产环境:MySQL/PostgreSQL,支持并发访问

7.3 性能优化方向

当数据量增大时,考虑这些优化:

# 使用Pandas的块处理大文件 chunk_size = 1000 for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size): process_chunk(chunk) # 使用多线程抓取(但要小心频率限制) from concurrent.futures import ThreadPoolExecutor def parallel_crawl(movie_ids): with ThreadPoolExecutor(max_workers=3) as executor: # 控制并发数 results = list(executor.map(get_movie_detail, movie_ids)) return results

8. 常见问题排查清单

遇到问题按这个顺序检查:

  1. 请求失败:先检查网络连接,再验证URL是否有效,最后看请求头设置
  2. 数据抓不到:用浏览器开发者工具检查元素选择器是否匹配当前页面结构
  3. 内存占用高:定期清理Pandas DataFrame,使用del释放大对象
  4. 图表不显示:检查Matplotlib后端设置,Web环境需要Agg后端
  5. Flask访问慢:确认是否开了debug模式,生产环境要关闭

这个项目最值得花时间的是理解整个数据流水线:从网页抓取到结构化存储,再到分析可视化。每步的异常处理和数据验证比代码本身更重要。

我建议先确保单部电影数据能完整抓取,再扩展列表抓取,最后做可视化。不要试图一步到位写完美代码,迭代开发能更快看到效果。

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

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

立即咨询