1. 项目概述
最近在准备Python爬虫教学的第四课时,我决定以豆瓣电影Top250作为案例。这个项目特别适合教学使用,因为它包含了网页爬取、数据解析、反爬应对等典型爬虫技术要点,同时数据规整、结构清晰,非常适合初学者练手。
在实际教学中,我发现这个案例能很好地展示requests库的基本用法、BeautifulSoup的解析技巧,以及如何应对网站的基础反爬措施。通过这个项目,学生可以掌握从网页获取数据到存储的完整流程,为后续更复杂的爬虫项目打下坚实基础。
2. 核心需求解析
2.1 爬取目标分析
豆瓣电影Top250页面包含了250部评分最高的电影信息,每部电影都包含以下关键数据:
- 电影名称
- 导演和主演信息
- 上映年份
- 制片国家/地区
- 类型
- 评分
- 评价人数
- 经典台词(部分电影有)
我们的爬虫需要完整获取这些信息,并以结构化的方式存储。考虑到教学目的,我们会采用CSV格式存储,方便后续分析使用。
2.2 技术难点预判
根据以往经验,这个项目可能会遇到以下几个技术难点:
- 分页处理:Top250分布在10个页面中,需要正确处理分页逻辑
- 反爬机制:豆瓣有基础的访问频率限制
- 数据清洗:部分字段需要特殊处理,如"导演/主演"信息的拆分
- 异常处理:网络请求不稳定时的重试机制
3. 环境准备
3.1 必要工具安装
建议使用Python 3.8+版本,需要安装以下库:
bash复制pip install requests beautifulsoup4 lxml pandas
各库的作用说明:
- requests:用于发送HTTP请求获取网页内容
- beautifulsoup4:HTML解析库
- lxml:BeautifulSoup的解析器,比内置的html.parser更快更准确
- pandas:数据整理和CSV导出
3.2 开发环境配置
推荐使用VSCode作为开发环境,配置Python插件后可以方便地进行代码调试。关键配置包括:
- 安装Python扩展
- 设置合适的代码格式化工具(如autopep8)
- 配置调试启动文件
4. 爬虫实现详解
4.1 网页结构分析
首先我们需要分析豆瓣Top250的页面结构。打开任意一个分页(如https://movie.douban.com/top250?start=0),通过浏览器开发者工具可以观察到:
- 每页显示25部电影
- 每部电影信息包含在
<div class="item">标签中 - 电影名称在
<span class="title">标签内 - 评分在
<span class="rating_num">标签内 - 其他信息在
<div class="bd">下的<p>标签中
4.2 基础爬取代码实现
python复制import requests
from bs4 import BeautifulSoup
import pandas as pd
def get_movie_info(url):
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'
}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'lxml')
movies = soup.find_all('div', class_='item')
data = []
for movie in movies:
title = movie.find('span', class_='title').text
rating = movie.find('span', class_='rating_num').text
# 其他字段解析...
data.append({
'title': title,
'rating': rating,
# 其他字段...
})
return data
4.3 分页处理实现
Top250分布在10个页面中,每页25条。分页URL规律如下:
- 第一页:start=0
- 第二页:start=25
- ...
- 第十页:start=225
我们可以用循环来处理所有分页:
python复制base_url = 'https://movie.douban.com/top250?start={}'
all_data = []
for i in range(0, 250, 25):
url = base_url.format(i)
page_data = get_movie_info(url)
all_data.extend(page_data)
time.sleep(3) # 防止请求过于频繁
5. 数据解析技巧
5.1 复杂字段处理
电影的基本信息(导演、主演、年份、地区、类型)都在同一个<p>标签中,需要特殊处理:
python复制info = movie.find('div', class_='bd').find('p').text.strip()
info_parts = [part.strip() for part in info.split('\n') if part.strip()]
# 示例解析逻辑
director = info_parts[0].replace('导演: ', '')
year = info_parts[1].split('/')[0].strip()
country = info_parts[1].split('/')[1].strip()
genre = info_parts[1].split('/')[2].strip()
5.2 评分人数提取
评分人数在<div class="star">下的最后一个<span>中:
python复制votes = movie.find('div', class_='star').find_all('span')[-1].text
votes = votes.replace('人评价', '').strip()
6. 反爬应对策略
6.1 请求头设置
豆瓣会检查User-Agent,我们需要设置合理的请求头:
python复制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',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://movie.douban.com/'
}
6.2 请求频率控制
在分页请求之间添加延时,并随机化延时时间:
python复制import random
import time
time.sleep(random.uniform(2, 5)) # 随机等待2-5秒
6.3 IP代理使用(可选)
如果需要大规模爬取,可以考虑使用代理IP:
python复制proxies = {
'http': 'http://your_proxy:port',
'https': 'https://your_proxy:port'
}
response = requests.get(url, headers=headers, proxies=proxies)
7. 数据存储实现
7.1 CSV格式存储
使用pandas将数据保存为CSV:
python复制df = pd.DataFrame(all_data)
df.to_csv('douban_top250.csv', index=False, encoding='utf_8_sig')
7.2 数据清洗建议
在存储前建议进行以下数据清洗:
- 去除字符串两端的空白字符
- 统一数字格式(如评分人数转为整数)
- 处理缺失值(如部分电影没有经典台词)
8. 完整代码示例
python复制import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
def get_movie_info(url):
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',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://movie.douban.com/'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'lxml')
movies = soup.find_all('div', class_='item')
data = []
for movie in movies:
title = movie.find('span', class_='title').text
rating = movie.find('span', class_='rating_num').text
info = movie.find('div', class_='bd').find('p').text.strip()
info_parts = [part.strip() for part in info.split('\n') if part.strip()]
director = info_parts[0].replace('导演: ', '')
year = info_parts[1].split('/')[0].strip()
country = info_parts[1].split('/')[1].strip()
genre = info_parts[1].split('/')[2].strip()
votes = movie.find('div', class_='star').find_all('span')[-1].text
votes = votes.replace('人评价', '').strip()
quote_tag = movie.find('span', class_='inq')
quote = quote_tag.text if quote_tag else ''
data.append({
'title': title,
'director': director,
'year': year,
'country': country,
'genre': genre,
'rating': rating,
'votes': votes,
'quote': quote
})
return data
except Exception as e:
print(f"Error fetching {url}: {str(e)}")
return []
def main():
base_url = 'https://movie.douban.com/top250?start={}'
all_data = []
for i in range(0, 250, 25):
url = base_url.format(i)
print(f"Fetching {url}...")
page_data = get_movie_info(url)
all_data.extend(page_data)
time.sleep(random.uniform(2, 5))
df = pd.DataFrame(all_data)
df.to_csv('douban_top250.csv', index=False, encoding='utf_8_sig')
print("Data saved to douban_top250.csv")
if __name__ == '__main__':
main()
9. 常见问题与解决方案
9.1 请求被拒绝(403错误)
可能原因:
- User-Agent被识别为爬虫
- 请求频率过高
解决方案:
- 更换User-Agent
- 增加请求间隔时间
- 添加Referer头
9.2 数据解析失败
可能原因:
- 网页结构发生变化
- 字段位置不固定
解决方案:
- 重新检查网页结构
- 增加更灵活的解析逻辑
- 添加try-catch块处理异常情况
9.3 中文乱码问题
解决方案:
- 确保使用utf-8编码
- CSV保存时使用
utf_8_sig编码 - 检查响应内容的实际编码
10. 项目扩展思路
这个基础爬虫可以进一步扩展:
- 数据可视化:使用matplotlib或pyecharts对电影数据进行分析和可视化
- 数据库存储:将数据存入MySQL或MongoDB
- 定时任务:设置定期爬取,跟踪评分变化
- Web展示:用Flask或Django搭建简单网站展示数据
在实际教学中,我会让学生先完成基础版本,然后选择1-2个扩展功能进行实现,这样可以更好地巩固所学知识。
