1. 项目概述
最近在做一个数据分析项目时,需要收集大量电影评分数据。作为Python开发者,我第一时间想到用爬虫技术来解决这个问题。豆瓣电影排行榜是个很好的数据源,它包含了丰富的电影信息和用户评分。下面我就来分享如何用Python实现这个爬虫项目。
这个爬虫主要使用requests和BeautifulSoup两个库,前者负责获取网页内容,后者负责解析HTML。整个过程看似简单,但实际操作中会遇到各种问题,比如反爬机制、数据清洗等。我会把这些实战经验都分享出来,让你少走弯路。
2. 环境准备与工具选型
2.1 Python环境配置
建议使用Python 3.6+版本,我使用的是Python 3.8.5。可以通过以下命令检查Python版本:
bash复制python --version
如果还没有安装Python,可以从官网下载安装包。我推荐使用Anaconda来管理Python环境,它能很好地处理各种依赖关系。
2.2 必备库安装
我们需要安装两个核心库:
bash复制pip install requests beautifulsoup4
requests库是Python中最流行的HTTP客户端库,相比Python内置的urllib,它提供了更简洁的API和更好的异常处理。beautifulsoup4则是HTML解析神器,支持多种解析器。
注意:安装beautifulsoup4时可能会遇到名称冲突,确保安装的是最新版本。如果遇到权限问题,可以加上--user参数。
2.3 开发工具选择
我习惯使用VS Code作为开发环境,它轻量且插件丰富。对于爬虫开发,特别推荐安装以下插件:
- Python:提供语法高亮和代码补全
- REST Client:方便测试API请求
- Code Runner:快速运行代码片段
3. 爬虫核心原理详解
3.1 HTTP请求与响应
爬虫工作的第一步是发送HTTP请求获取网页内容。requests库支持所有常见的HTTP方法:
python复制import requests
# GET请求示例
response = requests.get('https://www.example.com')
# POST请求示例
data = {'key': 'value'}
response = requests.post('https://www.example.com', data=data)
每个请求都会返回一个Response对象,包含状态码、响应头和响应体等重要信息:
python复制print(response.status_code) # HTTP状态码
print(response.headers) # 响应头
print(response.text) # 响应内容
3.2 HTML解析技术
获取HTML内容后,需要用BeautifulSoup解析文档结构。它支持多种解析器:
python复制from bs4 import BeautifulSoup
# 使用Python内置的html.parser
soup = BeautifulSoup(html_content, 'html.parser')
# 使用lxml解析器(需要额外安装,速度更快)
# soup = BeautifulSoup(html_content, 'lxml')
BeautifulSoup提供了多种查找元素的方法:
python复制# 通过标签名查找
soup.find('div') # 查找第一个div
soup.find_all('a') # 查找所有a标签
# 通过CSS类查找
soup.find(class_='movie-item')
# 通过属性查找
soup.find(attrs={'id': 'main'})
4. 豆瓣电影爬虫实战
4.1 网页结构分析
首先打开豆瓣电影排行榜页面(https://movie.douban.com/chart),使用浏览器开发者工具(F12)查看页面结构。
我发现电影信息主要包含在以下元素中:
- 电影名称:在class="pl2"的div下的a标签
- 上映信息:在class="pl"的p标签
- 评分:在class="rating_nums"的span标签
4.2 反爬策略处理
豆瓣有基本的反爬机制,我们需要做以下处理:
- 设置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'
}
- 控制请求频率,避免被封IP:
python复制import time
time.sleep(2) # 每次请求间隔2秒
- 使用代理IP(如果需要大量爬取):
python复制proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
response = requests.get(url, headers=headers, proxies=proxies)
4.3 完整代码实现
python复制import requests
from bs4 import BeautifulSoup
import json
import time
def get_douban_movies():
# 设置请求头
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': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
}
url = 'https://movie.douban.com/chart'
try:
# 发送请求
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 检查请求是否成功
# 解析HTML
soup = BeautifulSoup(response.text, 'html.parser')
movies = []
# 查找所有电影条目
items = soup.find_all('tr', class_='item')
for item in items:
# 提取电影名称
name_tag = item.find('div', class_='pl2').find('a')
movie_name = name_tag.get_text(strip=True).replace('\n', '').replace('/', ' / ')
# 提取上映信息和主演
info_tag = item.find('p', class_='pl')
if info_tag:
info = info_tag.get_text(strip=True)
info_parts = info.split('/')
release_time = info_parts[0].strip() if len(info_parts) > 0 else ''
actors = [actor.strip() for actor in info_parts[1:4]] if len(info_parts) > 1 else []
else:
release_time = ''
actors = []
# 提取评分
rating_tag = item.find('span', class_='rating_nums')
rating = rating_tag.get_text(strip=True) if rating_tag else '暂无评分'
# 提取评价人数
votes_tag = item.find('span', class_='pl')
votes = votes_tag.get_text(strip=True).replace('(', '').replace(')', '') if votes_tag else '0人评价'
# 构建电影字典
movie_data = {
'name': movie_name,
'release_time': release_time,
'actors': actors,
'rating': rating,
'votes': votes
}
movies.append(movie_data)
# 控制爬取速度
time.sleep(1)
return movies
except Exception as e:
print(f'爬取失败: {e}')
return None
def save_to_json(data, filename='douban_movies.json'):
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f'数据已保存到 {filename}')
except Exception as e:
print(f'保存文件失败: {e}')
if __name__ == '__main__':
movies = get_douban_movies()
if movies:
save_to_json(movies)
4.4 代码优化与扩展
- 增加异常处理:
python复制try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f'请求出错: {e}')
return None
- 支持分页爬取:
python复制base_url = 'https://movie.douban.com/top250?start={}'
for page in range(0, 250, 25):
url = base_url.format(page)
# 发送请求并解析...
- 使用Session保持会话:
python复制session = requests.Session()
session.headers.update(headers)
response = session.get(url)
5. 常见问题与解决方案
5.1 请求被拒绝(403错误)
可能原因:
- User-Agent被识别为爬虫
- 请求频率过高
解决方案:
- 更换User-Agent
- 增加请求间隔时间
- 使用代理IP
5.2 数据提取不准确
可能原因:
- 网页结构发生变化
- CSS类名被修改
解决方案:
- 定期检查爬虫脚本
- 使用更稳定的选择器,如XPath
- 添加数据验证逻辑
5.3 编码问题
可能原因:
- 网页编码与解析编码不一致
解决方案:
python复制response.encoding = response.apparent_encoding # 自动检测编码
6. 数据存储与分析
6.1 存储格式选择
- JSON:适合小型项目,易于阅读和解析
python复制import json
with open('data.json', 'w') as f:
json.dump(data, f)
- CSV:适合表格数据,可用Excel打开
python复制import csv
with open('data.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['name', 'rating']) # 写入表头
for movie in movies:
writer.writerow([movie['name'], movie['rating']])
- 数据库:适合大量数据
python复制import sqlite3
conn = sqlite3.connect('movies.db')
c = conn.cursor()
c.execute('''CREATE TABLE movies
(name text, rating real)''')
for movie in movies:
c.execute("INSERT INTO movies VALUES (?, ?)",
(movie['name'], movie['rating']))
conn.commit()
conn.close()
6.2 数据分析示例
使用pandas进行简单分析:
python复制import pandas as pd
# 读取JSON数据
df = pd.read_json('douban_movies.json')
# 查看评分分布
print(df['rating'].describe())
# 筛选高分电影
high_rating = df[df['rating'].astype(float) > 8.5]
print(high_rating[['name', 'rating']])
7. 爬虫伦理与法律问题
在开发爬虫时,必须注意以下几点:
- 遵守robots.txt规则
- 控制请求频率,不对服务器造成过大负担
- 不爬取敏感或个人隐私数据
- 遵守网站的使用条款
可以在爬虫中添加以下代码检查robots.txt:
python复制from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://www.douban.com/robots.txt')
rp.read()
can_fetch = rp.can_fetch('MyBot', 'https://www.douban.com/chart')
print(f'允许爬取: {can_fetch}')
8. 进阶学习方向
掌握了基础爬虫后,可以进一步学习:
- 动态网页爬取:使用Selenium或Playwright
- 反反爬技术:处理验证码、指纹识别等
- 分布式爬虫:使用Scrapy-Redis
- 数据清洗与存储:使用Pandas、MongoDB等
- 数据可视化:使用Matplotlib、Seaborn
我在实际项目中发现,最耗时的往往不是编写爬虫代码,而是处理各种异常情况和反爬机制。建议在开发时多写日志,方便排查问题:
python复制import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='spider.log'
)
logging.info('开始爬取豆瓣电影')