Python爬虫实战:用urllib和正则搞定E站图片批量下载(附完整代码与避坑指南)
·
Python爬虫实战:从零构建稳健的图片批量下载工具
1. 爬虫项目规划与核心思路
在开始编写代码之前,我们需要明确项目的整体架构和关键设计决策。一个健壮的爬虫系统应该具备以下几个核心模块:
- 页面请求模块:负责发送HTTP请求并处理响应
- 内容解析模块:从HTML中提取目标数据
- 文件存储模块:将获取的资源持久化到本地
- 异常处理模块:应对网络波动、反爬机制等意外情况
提示:在实际开发中,建议先完成核心功能的最小可行版本,再逐步添加异常处理和性能优化。
对于图片爬取项目,我们特别需要注意以下几点技术细节:
- 请求头设置:合理的User-Agent能减少被屏蔽的风险
- 延迟控制:避免短时间内发送过多请求
- 代理管理:处理可能的IP限制问题
- 断点续传:确保程序中断后能从中断点继续
2. 环境准备与基础配置
2.1 安装必要依赖
本项目主要使用Python标准库,无需额外安装包。但为了更好的开发体验,可以准备以下工具:
# 基础依赖
import urllib.request
import urllib.parse
import re
import time
import os
from pathlib import Path
# 可选:用于更复杂的HTML解析
# from bs4 import BeautifulSoup
2.2 配置请求头
合理的请求头设置能显著降低被目标网站屏蔽的概率:
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': 'en-US,en;q=0.5',
'Connection': 'keep-alive'
}
3. 核心爬取逻辑实现
3.1 页面请求与响应处理
我们封装一个稳健的请求函数,包含超时控制和重试机制:
def make_request(url, max_retries=3, timeout=30):
for attempt in range(max_retries):
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=timeout) as response:
return response.read().decode('utf-8')
except Exception as e:
print(f"请求失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # 指数退避
continue
return None
3.2 图片链接提取策略
使用正则表达式从HTML中提取图片链接时,需要考虑多种可能的格式:
def extract_image_links(html_content):
# 匹配常见的图片格式
pattern = re.compile(
r'(https?://[^\s\'"]+?\.(?:jpg|jpeg|png|gif|webp|bmp))',
re.IGNORECASE
)
return list(set(pattern.findall(html_content))) # 去重
注意:实际项目中可能需要根据目标网站的具体HTML结构调整正则表达式。
4. 文件下载与存储管理
4.1 安全的文件保存方法
def save_image(image_url, save_path, filename):
try:
Path(save_path).mkdir(parents=True, exist_ok=True)
full_path = os.path.join(save_path, filename)
req = urllib.request.Request(image_url, headers=headers)
with urllib.request.urlopen(req, timeout=30) as response:
with open(full_path, 'wb') as f:
f.write(response.read())
return True
except Exception as e:
print(f"保存图片失败: {str(e)}")
return False
4.2 文件名生成策略
为避免文件名冲突,我们可以采用以下命名方案:
| 策略 | 优点 | 缺点 |
|---|---|---|
| 原始文件名 | 保持原样 | 可能冲突 |
| 时间戳+随机数 | 唯一性好 | 可读性差 |
| 内容哈希值 | 唯一且可验证 | 计算成本高 |
推荐实现:
def generate_filename(url, index, content=None):
if content:
import hashlib
return f"{hashlib.md5(content).hexdigest()}.{url.split('.')[-1]}"
return f"img_{index:04d}.{url.split('.')[-1]}"
5. 异常处理与日志记录
5.1 完善的错误处理机制
def download_with_retry(url, save_path, filename, max_retries=3):
for attempt in range(max_retries):
try:
if save_image(url, save_path, filename):
print(f"成功保存: {filename}")
return True
except Exception as e:
print(f"下载失败 (尝试 {attempt + 1}/{max_retries}): {str(e)}")
time.sleep(1)
return False
5.2 日志记录最佳实践
建议配置完整的日志系统:
import logging
def setup_logger():
logger = logging.getLogger('image_crawler')
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 控制台输出
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
# 文件记录
fh = logging.FileHandler('crawler.log')
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
6. 完整项目集成与测试
6.1 主流程整合
def crawl_gallery(start_url, save_dir, start_index=0):
logger = setup_logger()
current_index = start_index
page_num = 0
while True:
page_url = f"{start_url}?p={page_num}" if page_num > 0 else start_url
logger.info(f"开始处理页面: {page_url}")
html = make_request(page_url)
if not html:
logger.warning(f"无法获取页面内容: {page_url}")
break
image_links = extract_image_links(html)
if not image_links:
logger.info("没有找到更多图片,爬取结束")
break
for img_url in image_links:
filename = generate_filename(img_url, current_index)
if download_with_retry(img_url, save_dir, filename):
current_index += 1
else:
logger.error(f"无法下载图片: {img_url}")
page_num += 1
time.sleep(1) # 礼貌性延迟
6.2 性能优化技巧
-
并发下载:使用线程池提高下载效率
from concurrent.futures import ThreadPoolExecutor def concurrent_download(urls, save_dir, start_index): with ThreadPoolExecutor(max_workers=4) as executor: futures = [] for i, url in enumerate(urls): filename = generate_filename(url, start_index + i) futures.append(executor.submit( download_with_retry, url, save_dir, filename )) for future in futures: future.result() # 等待所有任务完成 -
缓存机制:避免重复下载已获取的资源
-
增量爬取:记录已处理页面,实现断点续爬
7. 项目扩展与进阶方向
7.1 反反爬策略实践
- 随机User-Agent轮换
- IP代理池实现
- 请求频率动态调整
- 浏览器自动化模拟(如Selenium)
7.2 分布式爬虫架构
对于大规模爬取需求,可以考虑以下架构:
主节点(任务调度) → 多个工作节点(实际爬取) → 中央存储
关键组件包括:
- 任务队列(Redis)
- 去重过滤器(Bloom Filter)
- 结果聚合器
7.3 合法合规注意事项
- 严格遵守目标网站的robots.txt协议
- 尊重版权和内容使用条款
- 控制请求频率,避免对目标服务器造成过大负担
- 私人数据保护与隐私合规
在实际项目中,我发现最有效的调试方法是逐步验证每个环节:先确保能获取正确的HTML内容,再测试链接提取逻辑,最后验证下载功能。这种分阶段的方法能快速定位问题所在。
更多推荐



所有评论(0)