BeautifulSoup实战:从豆瓣TOP250到构建个人电影数据库
1. 为什么需要构建个人电影数据库
每次想找部好电影看的时候,你是不是也经常遇到这种情况:打开豆瓣TOP250页面,翻来翻去却记不清哪些已经看过,哪些评分更高?或者突然想起某部电影的情节,却死活想不起片名?这时候如果有个自己的电影数据库就方便多了。
我最早也是手动整理Excel表格,但每次更新都要复制粘贴特别麻烦。后来发现用Python爬取豆瓣数据再存到数据库里,不仅能自动更新,还能实现复杂查询。比如:"找出评分9分以上的美国犯罪片"、"显示还没看过的TOP50电影"等等。
这个项目特别适合:
- 影迷想系统化管理观影记录
- 学习Python爬虫的实战案例
- 需要数据可视化练手的数据爱好者
实测下来,用BeautifulSoup+SQLite的方案既轻量又灵活,手机电脑都能访问。下面我就带你从零开始,一步步构建这个实用工具。
2. 环境准备与基础爬取
2.1 安装必要的库
首先确保你的Python环境已经安装这些库:
pip install beautifulsoup4 requests lxml sqlalchemy我推荐用lxml作为解析器,比标准库的html.parser速度更快。SQLAlchemy则是为了更方便地操作数据库,避免直接写SQL语句的麻烦。
2.2 分析豆瓣页面结构
打开豆瓣TOP250页面,按F12查看网页源码。你会发现每部电影的信息都包裹在<li>标签中,关键信息分布很有规律:
- 电影名在
<span class="title"> - 评分在
<span class="rating_num"> - 详情链接在
<a href="..."> - 海报图片在
<img src="...">
用这个基础爬取代码就能获取第一页数据:
import requests from bs4 import BeautifulSoup url = "https://movie.douban.com/top250" headers = {'User-Agent': 'Mozilla/5.0'} response = requests.get(url, headers=headers) soup = BeautifulSoup(response.text, 'lxml') for item in soup.find_all('div', class_='item'): title = item.find('span', class_='title').text rating = item.find('span', class_='rating_num').text print(f"{title}: {rating}")3. 数据清洗与结构化
3.1 处理异常数据
实际爬取时会遇到各种意外情况:
- 有些电影没有副标题
- 部分电影缺少简介
- 个别评分显示为"暂无"
这就需要增加异常处理:
def safe_extract(element, attr=None): try: return element.text if not attr else element[attr] except (AttributeError, KeyError): return None3.2 转换数据格式
原始数据都是字符串,存入数据库前需要转换:
import re def clean_data(movie): # 提取评分中的数字 movie['rating'] = float(re.search(r'\d+\.?\d*', movie['rating']).group()) # 转换评价人数 "100人评价" → 100 movie['votes'] = int(re.search(r'\d+', movie['votes']).group()) # 处理空值 movie['quote'] = movie.get('quote') or "无简介" return movie3.3 获取详情页数据
要构建完整数据库,还需要爬取详情页的:
- 导演/主演信息
- 电影类型
- 片长
- 上映日期
- 剧情简介
这里有个小技巧:豆瓣的详情页URL就是主页面中<a>标签的href属性值。
4. 数据库设计与存储
4.1 SQLite数据库设计
我设计的movies表结构如下:
from sqlalchemy import create_engine, Column, Integer, String, Float from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Movie(Base): __tablename__ = 'movies' id = Column(Integer, primary_key=True) title = Column(String(100), nullable=False) year = Column(Integer) rating = Column(Float) votes = Column(Integer) director = Column(String(50)) genres = Column(String(100)) duration = Column(String(20)) summary = Column(String(500)) poster_url = Column(String(200))4.2 批量插入数据
使用SQLAlchemy的session批量操作:
from sqlalchemy.orm import sessionmaker engine = create_engine('sqlite:///movies.db') Session = sessionmaker(bind=engine) session = Session() # 假设movies_list是清洗后的数据列表 for data in movies_list: movie = Movie(**data) session.add(movie) session.commit()4.3 数据去重策略
防止重复插入的小技巧:
existing = {m.title for m in session.query(Movie.title)} new_movies = [m for m in movies_list if m['title'] not in existing]5. 高级功能实现
5.1 自动更新机制
用定时任务每周更新一次:
import schedule import time def weekly_update(): # 爬取最新数据 # 比较并更新数据库 schedule.every().sunday.do(weekly_update) while True: schedule.run_pending() time.sleep(3600) # 每小时检查一次5.2 数据可视化
用Pandas+Matplotlib生成统计图表:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_sql('SELECT * FROM movies', engine) # 评分分布直方图 df['rating'].hist(bins=10) plt.title('豆瓣TOP250评分分布') plt.show()5.3 构建查询接口
用Flask快速搭建Web界面:
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): movies = session.query(Movie).order_by(Movie.rating.desc()).limit(50) return render_template('index.html', movies=movies)6. 项目优化与扩展
6.1 反爬虫策略应对
豆瓣有基本的反爬措施,需要:
- 设置随机User-Agent
- 添加请求延迟
- 使用代理IP池
from fake_useragent import UserAgent import random import time ua = UserAgent() headers = {'User-Agent': ua.random} time.sleep(random.uniform(1, 3))6.2 数据备份方案
定期备份数据库到云端:
import datetime import shutil def backup_db(): today = datetime.datetime.now().strftime('%Y%m%d') shutil.copy2('movies.db', f'backups/movies_{today}.db')6.3 扩展其他数据源
除了豆瓣TOP250,还可以整合:
- 豆瓣新片榜
- IMDB Top 100
- 烂番茄新鲜度
建立更全面的电影评价体系。
7. 实际应用场景
这个数据库可以支持很多实用功能:
- 个人观影记录管理
- 电影推荐系统
- 影评数据分析
- 导演/演员作品统计
比如查询诺兰导演的高分电影:
nolan_movies = session.query(Movie).filter( Movie.director.like('%诺兰%'), Movie.rating >= 9.0 ).all()我在实际使用中发现,配合Jupyter Notebook做数据分析特别方便。你可以随时查询特定类型的电影,或者分析不同年份的评分分布。
构建过程中最大的教训是:一定要做好异常处理。网络请求失败、页面结构变化、数据格式异常等情况,都会导致程序中断。我现在会在关键步骤都加上try-catch和日志记录,确保程序能稳定运行。
