在互联网数据采集领域,爬虫技术一直是开发者们关注的重点。豆瓣电影作为国内最权威的电影资料库之一,储存了海量的电影海报、剧照等图片资源。本文将详细介绍如何使用 Python 的 requests 库和 BeautifulSoup 工具,快速搭建一个高效稳定的豆瓣电影图片爬虫,并配合亿牛云代理服务突破 IP 限制,实现稳定持续的数据采集。
一、环境准备与依赖安装
在开始编写爬虫之前,我们需要准备好 Python 运行环境以及必要的第三方库。建议使用 Python 3.7 及以上版本,以确保最佳兼容性和性能。
各个库的作用说明如下:
requests:Python 最流行的 HTTP 请求库,用于向豆瓣服务器发送网络请求beautifulsoup4:强大的 HTML/XML 解析库,能够快速定位和提取页面中的目标元素lxml:高效的 XML 和 HTML 解析器,作为 BeautifulSoup 的底层解析引擎
二、爬虫基本原理与请求头设置
豆瓣电影页面采用动态加载技术,常规的直接抓取方式可能无法获取完整的电影图片资源。我们需要先分析豆瓣的页面结构,了解图片资源的加载方式。
2.1 请求头伪装
为了避免被豆瓣服务器识别为爬虫程序并限制访问,我们需要在请求时设置合理的 User-Agent 和其他请求头信息。模拟真实浏览器的请求头是最基本的反反爬策略。
def get_headers():
"""生成随机请求头,模拟真实浏览器访问"""
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
]
return {
'User-Agent': random.choice(user_agents),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Referer': 'https://movie.douban.com/',
}
2.2 构建请求会话
使用 requests.Session() 可以维护一个持久的会话,保持 Cookie 和连接状态,提高请求效率。同时设置合理的超时时间,防止请求无限等待:
session = requests.Session()
session.headers.update(get_headers())
def fetch_page(url, timeout=10):
"""获取页面内容"""
try:
response = session.get(url, timeout=timeout)
response.raise_for_status()
response.encoding = 'utf-8'
return response.text
except requests.RequestException as e:
print(f"请求失败: {url}, 错误: {e}")
return None
2.3 亿牛云代理配置
在实际生产环境中,单纯的请求头伪装往往不足以应对严格的反爬机制。豆瓣等主流平台会基于 IP 频率进行流量管控,单一 IP 持续请求很快就会被限流甚至封禁。此时,使用高匿名代理 IP 是最有效的解决方案。
亿牛云是国内知名的代理服务提供商,提供覆盖全国的高匿名代理 IP 池,能够有效隐藏真实请求来源。我们可以轻松地将亿牛云的代理服务集成到爬虫架构中:
# 亿牛云代理配置示例
PROXY_HOST = "http://ip.16yun.cn" # 代理服务器地址
PROXY_PORT = 31111 # 代理端口
PROXY_USER = "your_username" # 亿牛云用户名
PROXY_PASS = "your_password" # 亿牛云密码
def get_proxy():
"""获取亿牛云代理配置"""
return {
"http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
"https": f"https://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
}
def fetch_with_proxy(url, timeout=15):
"""使用亿牛云代理获取页面内容"""
try:
response = session.get(url, timeout=timeout, proxies=get_proxy())
response.raise_for_status()
response.encoding = 'utf-8'
return response.text
except requests.RequestException as e:
print(f"代理请求失败: {url}, 错误: {e}")
return None
三、解析豆瓣电影页面结构
豆瓣电影有多个页面可以获取电影图片资源。热门电影列表页面和电影详情页面是我们主要的数据来源。我们以豆瓣电影 Top250 页面为例,分析页面结构并提取图片链接。
3.1 分析页面元素
打开豆瓣电影 Top250 页面,查看页面源码可以发现,每部电影的信息都包裹在一个特定的 div 容器中。电影的封面图片通常位于 img 标签的 src 属性或懒加载属性 data-origin 中。
def parse_movie_covers(html_content):
"""解析页面中的电影封面图片链接"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'lxml')
movie_items = soup.select('div.item')
cover_urls = []
for item in movie_items:
# 尝试获取高清封面图
img_tag = item.select_one('img[width]')
if img_tag:
# 优先获取原始大图
cover_url = img_tag.get('src') or img_tag.get('data-origin', '')
if cover_url and 'cover' in cover_url:
# 将小图替换为大图
cover_url = cover_url.replace('s_ratio_poster', 'r_ratio_poster')
cover_urls.append(cover_url)
return cover_urls
3.2 提取多页电影数据
豆瓣 Top250 共有 10 页,每页 25 部电影。我们可以通过循环遍历所有页面,获取尽可能多的电影封面:
def get_all_movie_covers(base_url='https://movie.douban.com/top250'):
"""获取豆瓣 Top250 所有电影的封面链接"""
all_covers = []
for page in range(10):
if page == 0:
url = base_url
else:
url = f'{base_url}?start={page * 25}'
print(f'正在抓取第 {page + 1} 页...')
html = fetch_with_proxy(url) # 使用代理请求
if html:
covers = parse_movie_covers(html)
all_covers.extend(covers)
# 随机延时,避免请求过于频繁
time.sleep(random.uniform(2, 4))
return all_covers
四、下载保存图片资源
获取到图片 URL 后,我们需要编写函数将图片下载到本地磁盘。为了保证下载的稳定性,需要处理各种异常情况,并提供进度反馈。
4.1 单张图片下载函数
def download_image(url, save_path, timeout=30):
"""下载单张图片到指定路径"""
try:
response = session.get(url, timeout=timeout, stream=True, proxies=get_proxy())
response.raise_for_status()
# 确保目录存在
os.makedirs(os.path.dirname(save_path), exist_ok=True)
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
return True
except Exception as e:
print(f'下载失败: {url}, 错误: {e}')
return False
4.2 批量下载与进度管理
def download_batch(urls, save_dir='./douban_covers', delay_range=(2, 3)):
"""批量下载图片"""
os.makedirs(save_dir, exist_ok=True)
success_count = 0
fail_count = 0
for index, url in enumerate(urls, 1):
filename = f'cover_{index:03d}.jpg'
save_path = os.path.join(save_dir, filename)
print(f'[{index}/{len(urls)}] 正在下载: {filename}')
if download_image(url, save_path):
success_count += 1
else:
fail_count += 1
# 下载间隔,使用代理后可适当缩短
time.sleep(random.uniform(*delay_range))
print(f'\n下载完成!成功: {success_count}, 失败: {fail_count}')
return success_count, fail_count
五、完整爬虫程序整合
将上述各个模块整合成一个完整的爬虫程序,增加配置管理、错误处理、日志记录和亿牛云代理轮换等功能:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
豆瓣电影图片爬虫
功能:抓取豆瓣电影 Top250 的封面图片
依赖:requests + BeautifulSoup + 亿牛云代理
"""
import requests
from bs4 import BeautifulSoup
import os
import time
import random
import logging
from datetime import datetime
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('crawler.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 亿牛云代理配置
PROXY_HOST = "http://ip.16yun.cn"
PROXY_PORT = 31111
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
class DoubanCoverCrawler:
def __init__(self):
self.session = requests.Session()
self.session.headers.update(self._get_headers())
self.proxy_pool = self._build_proxy_pool()
def _get_headers(self):
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101',
]
return {
'User-Agent': random.choice(user_agents),
'Accept': 'text/html,application/xhtml+xml',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://movie.douban.com/',
}
def _build_proxy_pool(self):
"""构建亿牛云代理池"""
return {
"http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
"https": f"https://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
}
def fetch(self, url):
"""使用亿牛云代理获取页面"""
try:
resp = self.session.get(url, timeout=15, proxies=self.proxy_pool)
resp.raise_for_status()
resp.encoding = 'utf-8'
return resp.text
except Exception as e:
logger.error(f'请求失败: {url}, {e}')
return None
def parse_covers(self, html):
covers = []
soup = BeautifulSoup(html, 'lxml')
for item in soup.select('div.item'):
img = item.select_one('img[width]')
if img:
url = img.get('data-origin') or img.get('src', '')
if 'cover' in url:
url = url.replace('s_ratio_poster', 'r_ratio_poster')
covers.append(url)
return covers
def download(self, url, path):
"""使用亿牛云代理下载图片"""
try:
resp = self.session.get(url, timeout=30, stream=True, proxies=self.proxy_pool)
resp.raise_for_status()
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wb') as f:
for chunk in resp.iter_content(8192):
f.write(chunk)
return True
except Exception as e:
logger.error(f'下载失败: {url}, {e}')
return False
def run(self, pages=10, save_dir='./covers'):
"""运行爬虫主流程"""
all_covers = []
base_url = 'https://movie.douban.com/top250'
for page in range(pages):
url = f'{base_url}?start={page * 25}' if page else base_url
logger.info(f'正在抓取第 {page + 1} 页')
html = self.fetch(url)
if html:
covers = self.parse_covers(html)
all_covers.extend(covers)
logger.info(f'本页获取 {len(covers)} 张封面')
time.sleep(random.uniform(2, 4))
logger.info(f'共获取 {len(all_covers)} 个封面链接')
# 下载图片
success = fail = 0
for i, url in enumerate(all_covers, 1):
path = os.path.join(save_dir, f'cover_{i:03d}.jpg')
if self.download(url, path):
success += 1
else:
fail += 1
if i % 10 == 0:
logger.info(f'下载进度: {i}/{len(all_covers)}')
time.sleep(random.uniform(1.5, 3))
logger.info(f'完成!成功: {success}, 失败: {fail}')
if __name__ == '__main__':
crawler = DoubanCoverCrawler()
crawler.run(pages=10, save_dir='./douban_covers')
六、运行效果与注意事项
运行上述程序后,我们可以看到控制台输出的抓取进度。图片会按照下载顺序保存在指定的目录中,文件名格式为 cover_001.jpg、cover_002.jpg 等。
6.1 常见问题与解决方案
在实际运行过程中,可能会遇到以下问题:
IP 被封禁:豆瓣有严格的反爬机制,高频请求会导致 IP 被临时封禁。配合代理服务使用后,系统会自动切换不同的代理 IP 进行请求,彻底规避单一 IP 被封的风险。亿牛云提供的高匿名代理能够完全隐藏真实 IP 地址,让目标服务器无法追踪到真实请求来源。
图片 URL 失效:部分图片可能使用了防盗链技术,直接通过 URL 无法下载。这时需要分析目标网站的防盗链策略,可能需要添加 Referer 头或使用 cookies。配合代理使用时,Referer 头信息可以进一步增强请求的真实性。
解析规则失效:豆瓣可能会调整页面结构,导致原有的解析规则无法匹配。发现问题时需要重新分析页面源码,更新解析逻辑。
总结
本文详细介绍了使用 Python requests 和 BeautifulSoup 爬取豆瓣电影图片的完整方案,涵盖了请求伪装、亿牛云代理集成、页面解析、数据存储、错误处理等核心技术点。通过亿牛云代理服务的加持,爬虫能够稳定高效地完成大规模数据采集任务,有效应对目标网站的反爬机制。通过本文的学习,读者可以掌握网页爬虫的基本编写方法,并将其应用到其他网站的图片资源抓取中。