引言:为什么需要批量下载豆瓣电影数据?
在当今数据驱动的时代,电影数据分析已成为影视行业研究、市场分析和学术研究的重要组成部分。豆瓣电影作为中国最具影响力的电影评分平台之一,拥有海量的电影信息、用户评分和影评数据。无论是进行电影市场趋势分析、用户偏好研究,还是构建推荐系统,获取这些数据都具有极高的价值。
然而,手动逐个复制粘贴电影信息不仅效率低下,而且容易出错。批量下载技术可以帮助我们自动化地获取大量电影数据,显著提高工作效率。本文将详细介绍如何使用Python编程语言,通过多种方法高效、合规地获取豆瓣电影数据,并解析常见问题及解决方案。
一、准备工作:环境配置与必要库的安装
在开始编写爬虫程序之前,我们需要配置Python开发环境并安装必要的第三方库。这些库将帮助我们发送HTTP请求、解析HTML内容、处理数据以及避免被反爬虫机制拦截。
1.1 安装必要的Python库
打开终端或命令提示符,运行以下命令安装所需库:
pip install requests beautifulsoup4 lxml pandas openpyxl fake-useragent selenium webdriver-manager
各库的作用如下:
- requests:发送HTTP请求,获取网页内容
- beautifulsoup4 和 lxml:解析HTML文档,提取所需数据
- pandas:数据处理和分析,方便将数据导出为Excel或CSV格式
- openpyxl:支持Excel文件的读写操作
- fake-useragent:生成随机User-Agent,模拟浏览器访问,降低被封禁风险
- selenium 和 webdriver-manager:用于处理动态加载内容或需要JavaScript渲染的页面
1.2 验证安装
安装完成后,可以通过以下代码验证是否成功安装:
import requests
from bs4 import BeautifulSoup
import pandas as pd
from fake_useragent import UserAgent
print("所有库已成功安装!")
二、基础爬虫:使用Requests和BeautifulSoup获取静态页面
对于大多数静态网页,我们可以使用requests库发送HTTP请求,然后用BeautifulSoup解析HTML内容。豆瓣电影的简介页面通常是静态的,因此这种方法适用于大部分场景。
2.1 获取单个电影的简介信息
以下是一个获取单个电影简介的示例代码:
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
def get_movie_info(movie_id):
"""
获取单个电影的简介信息
:param movie_id: 电影ID,例如 '26794435' 对应《肖申克的救赎》
:return: 包含电影信息的字典
"""
# 构造URL
url = f"https://movie.douban.com/subject/{movie_id}/"
# 生成随机User-Agent
ua = UserAgent()
headers = {
'User-Agent': ua.random,
'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',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
}
try:
# 发送请求
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
# 解析HTML
soup = BeautifulSoup(response.text, 'lxml')
# 提取电影标题
title = soup.find('span', property='v:itemreviewed').text.strip() if soup.find('span', property='v:itemreviewed') else 'N/A'
# 提取导演
director = soup.find('a', rel='v:directedBy').text.strip() if soup.find('a', rel='v:directedBy') else 'N/A'
# 提取编剧
screenwriter = []
screenwriter_tags = soup.find_all('a', class_='attrs')
for tag in screenwriter_tags:
if '编剧' in tag.text:
screenwriter = [a.text for a in tag.find_all('a')]
break
# 提取主演
actors = [a.text for a in soup.find_all('a', rel='v:starring')][:5] # 只取前5个主演
# 提取类型
genres = [g.text for g in soup.find_all('span', property='v:genre')]
# 提取制片国家/地区
country_tag = soup.find(text=lambda text: text and '制片国家/地区' in text)
country = country_tag.next_sibling.strip() if country_tag else 'N/A'
# 提取语言
language_tag = soup.find(text=lambda text: text and '语言' in text)
language = language_tag.next_sibling.strip() if language_tag else 'N/A'
# 提取上映日期
release_date = soup.find('span', property='v:initialReleaseDate').text.strip() if soup.find('span', property='v:initialReleaseDate') else 'N/A'
# 提取片长
runtime = soup.find('span', property='v:runtime').text.strip() if soup.find('span', property='v:runtime') else 'N/A'
# 提取评分
rating = soup.find('strong', property='v:average').text.strip() if soup.find('strong', property='v:average') else 'N/A'
# 提取评分人数
rating_count = soup.find('span', property='v:votes').text.strip() if soup.find('span', property='v:votes') else 'N/A'
# 提取简介
summary_tag = soup.find('span', property='v:summary')
summary = summary_tag.text.strip() if summary_tag else 'N/A'
# 返回数据
return {
'电影ID': movie_id,
'标题': title,
'导演': director,
'编剧': ', '.join(screenwriter) if screenwriter else 'N/A',
'主演': ', '.join(actors) if actors else 'N/A',
'类型': ', '.join(genres) if genres else 'N/A',
'制片国家/地区': country,
'语言': language,
'上映日期': release_date,
'片长': runtime,
'评分': rating,
'评分人数': rating_count,
'简介': summary
}
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
except Exception as e:
print(f"解析失败: {e}")
return None
# 测试获取单个电影信息
if __name__ == '__main__':
movie_data = get_movie_info('26794435') # 《肖申克的救赎》
if movie_data:
for key, value in movie_data.items():
print(f"{key}: {value}")
2.2 代码解析与注意事项
- User-Agent伪装:使用
fake_useragent库生成随机的浏览器User-Agent,避免被豆瓣识别为爬虫。 - 异常处理:使用
try-except结构捕获网络请求和解析过程中的异常,确保程序稳定性。 - 超时设置:设置
timeout=10,避免因网络延迟导致程序长时间卡住。 - HTML解析:使用
lxml解析器,比默认的html.parser更快且容错性更好。 - 数据提取:通过
find和find_all方法定位特定HTML元素,注意检查元素是否存在,避免AttributeError。
2.3 批量获取多个电影信息
有了单个电影的获取函数,我们可以扩展为批量获取:
def batch_get_movies(movie_ids, delay=2):
"""
批量获取多个电影信息
:param movie_ids: 电影ID列表
:param delay: 请求间隔时间(秒)
:return: 包含所有电影信息的列表
"""
import time
movie_data_list = []
for i, movie_id in enumerate(movie_ids):
print(f"正在获取第 {i+1}/{len(movie_ids)} 个电影: {movie_id}")
movie_data = get_movie_info(movie_id)
if movie_data:
movie_data_list.append(movie_data)
time.sleep(delay) # 避免请求过于频繁
return movie_data_list
# 示例:批量获取电影信息
if __name__ == '__main__':
# 电影ID列表(示例)
movie_ids = [
'26794435', # 肖申克的救赎
'1292052', # 霸王别姬
'1291546', # 阿甘正传
'1295124', # 这个杀手不太冷
'1292720', # 辛德勒的名单
]
movies = batch_get_movies(movie_ids, delay=2)
# 转换为DataFrame并保存
if movies:
df = pd.DataFrame(movies)
df.to_excel('douban_movies.xlsx', index=False)
print(f"成功保存 {len(movies)} 条电影数据到 douban_movies.xlsx")
三、进阶技巧:处理动态加载内容与反爬虫机制
豆瓣电影的部分内容(如影评、短评)是动态加载的,需要使用Selenium模拟浏览器操作。此外,豆瓣有严格的反爬虫机制,需要采取更多措施来避免被封禁IP或限制访问。
3.1 使用Selenium获取动态内容
Selenium可以模拟真实浏览器行为,适用于需要JavaScript渲染的页面。以下示例展示如何获取电影的短评数据:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
import time
import random
def get_movie_short_reviews(movie_id, max_pages=3):
"""
使用Selenium获取电影短评数据
:param movie_id: 电影ID
:param max_pages: 最大获取页数
:return: 短评数据列表
"""
# 配置Chrome选项
chrome_options = Options()
chrome_options.add_argument('--headless') # 无头模式,不显示浏览器窗口
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--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')
# 初始化WebDriver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
reviews = []
try:
for page in range(max_pages):
url = f"https://movie.douban.com/subject/{movie_id}/comments?start={page*20}&limit=20&sort=new_score&status=P"
print(f"正在获取第 {page+1} 页短评: {url}")
driver.get(url)
time.sleep(random.uniform(2, 4)) # 随机等待
# 等待内容加载
try:
# 等待评论容器出现
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".comment-item"))
)
except:
print(f"第 {page+1} 页加载超时,跳过")
continue
# 提取评论
comment_items = driver.find_elements(By.CSS_SELECTOR, ".comment-item")
for item in comment_items:
try:
# 评论者
user = item.find_element(By.CSS_SELECTOR, ".comment-info a").text
# 评分
rating_elem = item.find_element(By.CSS_SELECTOR, ".rating")
rating = rating_elem.get_attribute("title") if rating_elem else "N/A"
# 评论时间
comment_time = item.find_element(By.CSS_SELECTOR, ".comment-time").text
# 评论内容
content = item.find_element(By.CSS_SELECTOR, ".short").text
reviews.append({
'用户': user,
'评分': rating,
'时间': comment_time,
'内容': content
})
except Exception as e:
print(f"提取评论失败: {e}")
continue
# 随机延迟,模拟人类行为
time.sleep(random.uniform(1, 3))
# 如果没有更多评论,提前退出
if not comment_items:
break
finally:
driver.quit()
return reviews
# 示例:获取《肖申克的救赎》短评
if __name__ == '__main__':
short_reviews = get_movie_short_reviews('26794435', max_pages=2)
if short_reviews:
df = pd.DataFrame(short_reviews)
df.to_excel('douban_short_reviews.xlsx', index=False)
print(f"成功保存 {len(short_reviews)} 条短评数据")
3.2 反爬虫应对策略
- IP代理池:使用代理IP轮换,避免单一IP被封禁。可以使用免费代理(如
free-proxy-list.net)或付费代理服务(如阿布云、快代理)。
import random
def get_proxies():
"""
获取代理IP列表(示例)
"""
# 这里可以替换为从代理API获取
proxies = [
{'http': 'http://123.45.67.89:8080', 'https': 'https://123.45.67.89:8080'},
{'http': 'http://98.76.54.32:3128', 'https': 'https://98.76.54.32:3128'},
]
return proxies
def use_proxy_request(url, headers):
"""
使用代理发送请求
"""
proxies = get_proxies()
proxy = random.choice(proxies)
try:
response = requests.get(url, headers=headers, proxies=proxy, timeout=10)
return response
except:
# 如果代理失败,尝试其他代理或直接请求
return requests.get(url, headers=headers, timeout=10)
- 请求频率控制:严格控制请求间隔,建议设置2-5秒的随机延迟。
- Session保持:使用
requests.Session()保持会话,模拟真实用户行为。
def use_session_request(url):
"""
使用Session保持会话
"""
session = requests.Session()
ua = UserAgent()
headers = {
'User-Agent': ua.random,
'Referer': 'https://movie.douban.com/',
}
session.headers.update(headers)
try:
response = session.get(url, timeout=10)
return response
except Exception as e:
print(f"请求失败: {e}")
return None
- 识别验证码:如果遇到验证码,可以使用OCR库(如
pytesseract)识别,或使用打码平台(如超级鹰、云打码)人工识别。
四、数据存储与处理
获取数据后,需要将其存储为结构化格式,便于后续分析。常见格式包括Excel、CSV和JSON。
4.1 保存为Excel文件
使用pandas可以轻松将数据保存为Excel:
import pandas as pd
def save_to_excel(data, filename):
"""
将数据保存为Excel文件
:param data: 数据列表(字典列表)
:param filename: 文件名
"""
if not data:
print("没有数据可保存")
return
df = pd.DataFrame(data)
df.to_excel(filename, index=False, engine='openpyxl')
print(f"数据已保存到 {filename}")
# 示例
movies = [
{'标题': '肖申克的救赎', '评分': 9.7},
{'标题': '霸王别姬', '评分': 9.6},
]
save_to_excel(movies, 'movies.xlsx')
4.2 保存为CSV文件
CSV格式更通用,适合大数据量:
def save_to_csv(data, filename):
"""
将数据保存为CSV文件
"""
if not data:
print("没有数据可保存")
return
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig') # utf-8-sig 支持中文
print(f"数据已保存到 {filename}")
4.3 保存为JSON文件
JSON格式适合存储嵌套结构的数据:
import json
def save_to_json(data, filename):
"""
将数据保存为JSON文件
"""
if not data:
print("没有数据可保存")
return
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
print(f"数据已保存到 {filename}")
五、常见问题解析
5.1 问题1:请求被拒绝(403 Forbidden)
原因:豆瓣检测到爬虫行为,封禁了IP或User-Agent。
解决方案:
- 更换User-Agent,使用
fake-useragent库生成随机浏览器标识。 - 使用代理IP轮换。
- 降低请求频率,增加随机延迟。
- 使用Session保持会话。
5.2 问题2:动态内容无法获取
原因:页面内容通过JavaScript动态加载,requests无法获取。
解决方案:
- 使用Selenium模拟浏览器操作。
- 分析AJAX请求,直接调用数据接口(如果存在)。
- 使用
requests-html库(支持JavaScript渲染)。
5.3 问题3:数据提取失败
原因:HTML结构变化或元素定位方式不正确。
解决方案:
- 使用浏览器开发者工具检查最新HTML结构。
- 使用更灵活的定位方式,如CSS选择器或XPath。
- 添加异常处理,跳过无法解析的元素。
5.4 问题4:IP被封禁
原因:短时间内请求过于频繁。
解决方案:
- 立即停止请求,等待一段时间后重试。
- 使用代理IP池轮换。
- 联系豆瓣官方申请API权限(如果需要大量数据)。
5.5 问题5:中文乱码
原因:文件保存时编码格式不正确。
解决方案:
- CSV文件使用
utf-8-sig编码。 - Excel文件使用
openpyxl引擎。 - JSON文件确保使用
ensure_ascii=False。
六、最佳实践与法律合规
6.1 遵守robots.txt协议
在爬取任何网站前,应检查其robots.txt文件,了解哪些内容允许爬取。豆瓣电影的robots.txt地址:https://movie.douban.com/robots.txt
6.2 控制请求频率
- 每个请求之间至少间隔2-5秒。
- 避免在高峰时段(如晚上8-10点)大量爬取。
- 设置每日爬取上限,如不超过1000条数据。
6.3 数据使用范围
- 仅用于个人学习或研究目的。
- 不得用于商业用途或二次分发。
- 尊重版权,不侵犯用户隐私。
6.4 使用官方API(如果可用)
豆瓣开放平台提供有限的API接口,虽然功能有限,但合法合规。建议优先考虑使用官方API。
七、总结
本文详细介绍了使用Python批量下载豆瓣电影简介和影评数据的完整流程,包括环境配置、基础爬虫编写、动态内容处理、反爬虫策略、数据存储以及常见问题解决方案。通过这些技巧,你可以高效地获取所需的电影数据,同时避免常见的陷阱和问题。
记住,爬虫技术是一把双刃剑,合理使用可以带来巨大价值,但滥用可能导致法律风险。请始终遵守网站的使用条款,尊重数据版权,做一名负责任的数据使用者。
通过本文的示例代码和详细解析,相信你已经掌握了批量获取豆瓣电影数据的核心技能。在实际应用中,可以根据具体需求调整代码,扩展功能,构建属于自己的电影数据分析系统。# 豆瓣电影简介批量下载技巧与常见问题解析助你高效获取影评数据
引言:为什么需要批量下载豆瓣电影数据?
在当今数据驱动的时代,电影数据分析已成为影视行业研究、市场分析和学术研究的重要组成部分。豆瓣电影作为中国最具影响力的电影评分平台之一,拥有海量的电影信息、用户评分和影评数据。无论是进行电影市场趋势分析、用户偏好研究,还是构建推荐系统,获取这些数据都具有极高的价值。
然而,手动逐个复制粘贴电影信息不仅效率低下,而且容易出错。批量下载技术可以帮助我们自动化地获取大量电影数据,显著提高工作效率。本文将详细介绍如何使用Python编程语言,通过多种方法高效、合规地获取豆瓣电影数据,并解析常见问题及解决方案。
一、准备工作:环境配置与必要库的安装
在开始编写爬虫程序之前,我们需要配置Python开发环境并安装必要的第三方库。这些库将帮助我们发送HTTP请求、解析HTML内容、处理数据以及避免被反爬虫机制拦截。
1.1 安装必要的Python库
打开终端或命令提示符,运行以下命令安装所需库:
pip install requests beautifulsoup4 lxml pandas openpyxl fake-useragent selenium webdriver-manager
各库的作用如下:
- requests:发送HTTP请求,获取网页内容
- beautifulsoup4 和 lxml:解析HTML文档,提取所需数据
- pandas:数据处理和分析,方便将数据导出为Excel或CSV格式
- openpyxl:支持Excel文件的读写操作
- fake-useragent:生成随机User-Agent,模拟浏览器访问,降低被封禁风险
- selenium 和 webdriver-manager:用于处理动态加载内容或需要JavaScript渲染的页面
1.2 验证安装
安装完成后,可以通过以下代码验证是否成功安装:
import requests
from bs4 import BeautifulSoup
import pandas as pd
from fake_useragent import UserAgent
print("所有库已成功安装!")
二、基础爬虫:使用Requests和BeautifulSoup获取静态页面
对于大多数静态网页,我们可以使用requests库发送HTTP请求,然后用BeautifulSoup解析HTML内容。豆瓣电影的简介页面通常是静态的,因此这种方法适用于大部分场景。
2.1 获取单个电影的简介信息
以下是一个获取单个电影简介的示例代码:
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
def get_movie_info(movie_id):
"""
获取单个电影的简介信息
:param movie_id: 电影ID,例如 '26794435' 对应《肖申克的救赎》
:return: 包含电影信息的字典
"""
# 构造URL
url = f"https://movie.douban.com/subject/{movie_id}/"
# 生成随机User-Agent
ua = UserAgent()
headers = {
'User-Agent': ua.random,
'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',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
}
try:
# 发送请求
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
# 解析HTML
soup = BeautifulSoup(response.text, 'lxml')
# 提取电影标题
title = soup.find('span', property='v:itemreviewed').text.strip() if soup.find('span', property='v:itemreviewed') else 'N/A'
# 提取导演
director = soup.find('a', rel='v:directedBy').text.strip() if soup.find('a', rel='v:directedBy') else 'N/A'
# 提取编剧
screenwriter = []
screenwriter_tags = soup.find_all('a', class_='attrs')
for tag in screenwriter_tags:
if '编剧' in tag.text:
screenwriter = [a.text for a in tag.find_all('a')]
break
# 提取主演
actors = [a.text for a in soup.find_all('a', rel='v:starring')][:5] # 只取前5个主演
# 提取类型
genres = [g.text for g in soup.find_all('span', property='v:genre')]
# 提取制片国家/地区
country_tag = soup.find(text=lambda text: text and '制片国家/地区' in text)
country = country_tag.next_sibling.strip() if country_tag else 'N/A'
# 提取语言
language_tag = soup.find(text=lambda text: text and '语言' in text)
language = language_tag.next_sibling.strip() if language_tag else 'N/A'
# 提取上映日期
release_date = soup.find('span', property='v:initialReleaseDate').text.strip() if soup.find('span', property='v:initialReleaseDate') else 'N/A'
# 提取片长
runtime = soup.find('span', property='v:runtime').text.strip() if soup.find('span', property='v:runtime') else 'N/A'
# 提取评分
rating = soup.find('strong', property='v:average').text.strip() if soup.find('strong', property='v:average') else 'N/A'
# 提取评分人数
rating_count = soup.find('span', property='v:votes').text.strip() if soup.find('span', property='v:votes') else 'N/A'
# 提取简介
summary_tag = soup.find('span', property='v:summary')
summary = summary_tag.text.strip() if summary_tag else 'N/A'
# 返回数据
return {
'电影ID': movie_id,
'标题': title,
'导演': director,
'编剧': ', '.join(screenwriter) if screenwriter else 'N/A',
'主演': ', '.join(actors) if actors else 'N/A',
'类型': ', '.join(genres) if genres else 'N/A',
'制片国家/地区': country,
'语言': language,
'上映日期': release_date,
'片长': runtime,
'评分': rating,
'评分人数': rating_count,
'简介': summary
}
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
except Exception as e:
print(f"解析失败: {e}")
return None
# 测试获取单个电影信息
if __name__ == '__main__':
movie_data = get_movie_info('26794435') # 《肖申克的救赎》
if movie_data:
for key, value in movie_data.items():
print(f"{key}: {value}")
2.2 代码解析与注意事项
- User-Agent伪装:使用
fake-useragent库生成随机的浏览器User-Agent,避免被豆瓣识别为爬虫。 - 异常处理:使用
try-except结构捕获网络请求和解析过程中的异常,确保程序稳定性。 - 超时设置:设置
timeout=10,避免因网络延迟导致程序长时间卡住。 - HTML解析:使用
lxml解析器,比默认的html.parser更快且容错性更好。 - 数据提取:通过
find和find_all方法定位特定HTML元素,注意检查元素是否存在,避免AttributeError。
2.3 批量获取多个电影信息
有了单个电影的获取函数,我们可以扩展为批量获取:
def batch_get_movies(movie_ids, delay=2):
"""
批量获取多个电影信息
:param movie_ids: 电影ID列表
:param delay: 请求间隔时间(秒)
:return: 包含所有电影信息的列表
"""
import time
movie_data_list = []
for i, movie_id in enumerate(movie_ids):
print(f"正在获取第 {i+1}/{len(movie_ids)} 个电影: {movie_id}")
movie_data = get_movie_info(movie_id)
if movie_data:
movie_data_list.append(movie_data)
time.sleep(delay) # 避免请求过于频繁
return movie_data_list
# 示例:批量获取电影信息
if __name__ == '__main__':
# 电影ID列表(示例)
movie_ids = [
'26794435', # 肖申克的救赎
'1292052', # 霸王别姬
'1291546', # 阿甘正传
'1295124', # 这个杀手不太冷
'1292720', # 辛德勒的名单
]
movies = batch_get_movies(movie_ids, delay=2)
# 转换为DataFrame并保存
if movies:
df = pd.DataFrame(movies)
df.to_excel('douban_movies.xlsx', index=False)
print(f"成功保存 {len(movies)} 条电影数据到 douban_movies.xlsx")
三、进阶技巧:处理动态加载内容与反爬虫机制
豆瓣电影的部分内容(如影评、短评)是动态加载的,需要使用Selenium模拟浏览器操作。此外,豆瓣有严格的反爬虫机制,需要采取更多措施来避免被封禁IP或限制访问。
3.1 使用Selenium获取动态内容
Selenium可以模拟真实浏览器行为,适用于需要JavaScript渲染的页面。以下示例展示如何获取电影的短评数据:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
import time
import random
def get_movie_short_reviews(movie_id, max_pages=3):
"""
使用Selenium获取电影短评数据
:param movie_id: 电影ID
:param max_pages: 最大获取页数
:return: 短评数据列表
"""
# 配置Chrome选项
chrome_options = Options()
chrome_options.add_argument('--headless') # 无头模式,不显示浏览器窗口
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--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')
# 初始化WebDriver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
reviews = []
try:
for page in range(max_pages):
url = f"https://movie.douban.com/subject/{movie_id}/comments?start={page*20}&limit=20&sort=new_score&status=P"
print(f"正在获取第 {page+1} 页短评: {url}")
driver.get(url)
time.sleep(random.uniform(2, 4)) # 随机等待
# 等待内容加载
try:
# 等待评论容器出现
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".comment-item"))
)
except:
print(f"第 {page+1} 页加载超时,跳过")
continue
# 提取评论
comment_items = driver.find_elements(By.CSS_SELECTOR, ".comment-item")
for item in comment_items:
try:
# 评论者
user = item.find_element(By.CSS_SELECTOR, ".comment-info a").text
# 评分
rating_elem = item.find_element(By.CSS_SELECTOR, ".rating")
rating = rating_elem.get_attribute("title") if rating_elem else "N/A"
# 评论时间
comment_time = item.find_element(By.CSS_SELECTOR, ".comment-time").text
# 评论内容
content = item.find_element(By.CSS_SELECTOR, ".short").text
reviews.append({
'用户': user,
'评分': rating,
'时间': comment_time,
'内容': content
})
except Exception as e:
print(f"提取评论失败: {e}")
continue
# 随机延迟,模拟人类行为
time.sleep(random.uniform(1, 3))
# 如果没有更多评论,提前退出
if not comment_items:
break
finally:
driver.quit()
return reviews
# 示例:获取《肖申克的救赎》短评
if __name__ == '__main__':
short_reviews = get_movie_short_reviews('26794435', max_pages=2)
if short_reviews:
df = pd.DataFrame(short_reviews)
df.to_excel('douban_short_reviews.xlsx', index=False)
print(f"成功保存 {len(short_reviews)} 条短评数据")
3.2 反爬虫应对策略
- IP代理池:使用代理IP轮换,避免单一IP被封禁。可以使用免费代理(如
free-proxy-list.net)或付费代理服务(如阿布云、快代理)。
import random
def get_proxies():
"""
获取代理IP列表(示例)
"""
# 这里可以替换为从代理API获取
proxies = [
{'http': 'http://123.45.67.89:8080', 'https': 'https://123.45.67.89:8080'},
{'http': 'http://98.76.54.32:3128', 'https': 'https://98.76.54.32:3128'},
]
return proxies
def use_proxy_request(url, headers):
"""
使用代理发送请求
"""
proxies = get_proxies()
proxy = random.choice(proxies)
try:
response = requests.get(url, headers=headers, proxies=proxy, timeout=10)
return response
except:
# 如果代理失败,尝试其他代理或直接请求
return requests.get(url, headers=headers, timeout=10)
- 请求频率控制:严格控制请求间隔,建议设置2-5秒的随机延迟。
- Session保持:使用
requests.Session()保持会话,模拟真实用户行为。
def use_session_request(url):
"""
使用Session保持会话
"""
session = requests.Session()
ua = UserAgent()
headers = {
'User-Agent': ua.random,
'Referer': 'https://movie.douban.com/',
}
session.headers.update(headers)
try:
response = session.get(url, timeout=10)
return response
except Exception as e:
print(f"请求失败: {e}")
return None
- 识别验证码:如果遇到验证码,可以使用OCR库(如
pytesseract)识别,或使用打码平台(如超级鹰、云打码)人工识别。
四、数据存储与处理
获取数据后,需要将其存储为结构化格式,便于后续分析。常见格式包括Excel、CSV和JSON。
4.1 保存为Excel文件
使用pandas可以轻松将数据保存为Excel:
import pandas as pd
def save_to_excel(data, filename):
"""
将数据保存为Excel文件
:param data: 数据列表(字典列表)
:param filename: 文件名
"""
if not data:
print("没有数据可保存")
return
df = pd.DataFrame(data)
df.to_excel(filename, index=False, engine='openpyxl')
print(f"数据已保存到 {filename}")
# 示例
movies = [
{'标题': '肖申克的救赎', '评分': 9.7},
{'标题': '霸王别姬', '评分': 9.6},
]
save_to_excel(movies, 'movies.xlsx')
4.2 保存为CSV文件
CSV格式更通用,适合大数据量:
def save_to_csv(data, filename):
"""
将数据保存为CSV文件
"""
if not data:
print("没有数据可保存")
return
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig') # utf-8-sig 支持中文
print(f"数据已保存到 {filename}")
4.3 保存为JSON文件
JSON格式适合存储嵌套结构的数据:
import json
def save_to_json(data, filename):
"""
将数据保存为JSON文件
"""
if not data:
print("没有数据可保存")
return
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
print(f"数据已保存到 {filename}")
五、常见问题解析
5.1 问题1:请求被拒绝(403 Forbidden)
原因:豆瓣检测到爬虫行为,封禁了IP或User-Agent。
解决方案:
- 更换User-Agent,使用
fake-useragent库生成随机浏览器标识。 - 使用代理IP轮换。
- 降低请求频率,增加随机延迟。
- 使用Session保持会话。
5.2 问题2:动态内容无法获取
原因:页面内容通过JavaScript动态加载,requests无法获取。
解决方案:
- 使用Selenium模拟浏览器操作。
- 分析AJAX请求,直接调用数据接口(如果存在)。
- 使用
requests-html库(支持JavaScript渲染)。
5.3 问题3:数据提取失败
原因:HTML结构变化或元素定位方式不正确。
解决方案:
- 使用浏览器开发者工具检查最新HTML结构。
- 使用更灵活的定位方式,如CSS选择器或XPath。
- 添加异常处理,跳过无法解析的元素。
5.4 问题4:IP被封禁
原因:短时间内请求过于频繁。
解决方案:
- 立即停止请求,等待一段时间后重试。
- 使用代理IP池轮换。
- 联系豆瓣官方申请API权限(如果需要大量数据)。
5.5 问题5:中文乱码
原因:文件保存时编码格式不正确。
解决方案:
- CSV文件使用
utf-8-sig编码。 - Excel文件使用
openpyxl引擎。 - JSON文件确保使用
ensure_ascii=False。
六、最佳实践与法律合规
6.1 遵守robots.txt协议
在爬取任何网站前,应检查其robots.txt文件,了解哪些内容允许爬取。豆瓣电影的robots.txt地址:https://movie.douban.com/robots.txt
6.2 控制请求频率
- 每个请求之间至少间隔2-5秒。
- 避免在高峰时段(如晚上8-10点)大量爬取。
- 设置每日爬取上限,如不超过1000条数据。
6.3 数据使用范围
- 仅用于个人学习或研究目的。
- 不得用于商业用途或二次分发。
- 尊重版权,不侵犯用户隐私。
6.4 使用官方API(如果可用)
豆瓣开放平台提供有限的API接口,虽然功能有限,但合法合规。建议优先考虑使用官方API。
七、总结
本文详细介绍了使用Python批量下载豆瓣电影简介和影评数据的完整流程,包括环境配置、基础爬虫编写、动态内容处理、反爬虫策略、数据存储以及常见问题解决方案。通过这些技巧,你可以高效地获取所需的电影数据,同时避免常见的陷阱和问题。
记住,爬虫技术是一把双刃剑,合理使用可以带来巨大价值,但滥用可能导致法律风险。请始终遵守网站的使用条款,尊重数据版权,做一名负责任的数据使用者。
通过本文的示例代码和详细解析,相信你已经掌握了批量获取豆瓣电影数据的核心技能。在实际应用中,可以根据具体需求调整代码,扩展功能,构建属于自己的电影数据分析系统。
