1. 项目概述:构建高效图片采集流水线
在数据驱动的互联网时代,图片采集已成为许多业务场景的基础需求。无论是电商比价、内容聚合还是AI训练数据准备,都需要从海量网页中高效获取图片资源。传统手动下载方式不仅效率低下,还面临重复下载、存储混乱等问题。
这个Python爬虫项目实现了一个完整的"下载-去重-入库"自动化流水线,核心解决三个痛点:
- 自动化批量下载:通过爬虫技术解放双手
- 智能去重:基于内容特征避免重复存储
- 结构化存储:使用SQLite实现持久化管理
整套系统采用模块化设计,各环节可独立扩展。我曾用类似方案为一家电商公司搭建商品图片库,单机日采集量稳定在5万张以上,重复率控制在3%以内。
2. 核心组件与技术选型
2.1 爬虫框架选择:Requests+BeautifulSoup组合
对于图片采集这种相对简单的爬取任务,轻量级的Requests库配合BeautifulSoup解析器是最佳选择。相比Scrapy等全功能框架,这个组合有三大优势:
- 学习曲线平缓,适合快速开发
- 资源占用低,适合长时间运行
- 灵活性强,可应对各种反爬策略
实测案例:在采集某图片网站时,使用Scrapy平均每秒处理5个页面,而Requests+多线程可达15页/秒。
2.2 去重算法:感知哈希(pHash)
传统MD5校验只能识别完全相同的文件,而感知哈希可以识别内容相似的图片。我们采用pHash算法,其工作原理是:
- 缩小图片至32x32像素
- 转换为灰度图
- 计算DCT变换
- 取左上角8x8的DCT系数
- 计算平均值并生成64位哈希值
关键参数说明:
- 哈希距离阈值设为5(经验值)
- 支持JPG/PNG/GIF等常见格式
- 内存中维护最近1000个哈希值缓存
2.3 存储方案:SQLite数据库
相比文件系统直接存储,SQLite提供了三大核心价值:
- 结构化存储元数据(URL、尺寸、标签等)
- 支持复杂查询(按尺寸/时间/来源筛选)
- 事务保证数据一致性
数据库表设计:
sql复制CREATE TABLE images (
id INTEGER PRIMARY KEY,
url TEXT UNIQUE,
filepath TEXT,
phash TEXT,
width INTEGER,
height INTEGER,
format TEXT,
source TEXT,
download_time DATETIME
);
3. 完整实现步骤
3.1 环境准备与依赖安装
推荐使用Python 3.8+环境,依赖包通过requirements.txt管理:
code复制requests==2.26.0
beautifulsoup4==4.10.0
Pillow==9.0.0
imagehash==4.2.1
tqdm==4.62.3
安装命令:
bash复制pip install -r requirements.txt
注意:建议使用虚拟环境避免依赖冲突,可通过python -m venv venv创建
3.2 爬虫核心代码实现
基础爬取流程分为四个步骤:
- 发送HTTP请求获取页面
python复制def fetch_page(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36'
}
try:
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
return resp.content
except Exception as e:
print(f"Failed to fetch {url}: {str(e)}")
return None
- 解析HTML提取图片链接
python复制def extract_image_links(html, base_url):
soup = BeautifulSoup(html, 'html.parser')
img_tags = soup.find_all('img')
links = []
for img in img_tags:
src = img.get('src') or img.get('data-src')
if not src:
continue
# 处理相对路径
if src.startswith('//'):
src = 'https:' + src
elif src.startswith('/'):
src = urljoin(base_url, src)
links.append(src)
return links
- 多线程下载优化
python复制from concurrent.futures import ThreadPoolExecutor
def batch_download(urls, max_workers=5):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(download_image, url) for url in urls]
for future in tqdm(as_completed(futures), total=len(urls)):
future.result()
3.3 去重模块实现
基于ImageHash库的完整去重流程:
python复制from PIL import Image
import imagehash
def calculate_phash(image_path):
try:
with Image.open(image_path) as img:
return str(imagehash.phash(img))
except Exception as e:
print(f"Error processing {image_path}: {str(e)}")
return None
def is_duplicate(db_conn, phash, threshold=5):
if not phash:
return False
cursor = db_conn.cursor()
cursor.execute("SELECT phash FROM images WHERE phash IS NOT NULL")
existing_hashes = [row[0] for row in cursor.fetchall()]
for existing in existing_hashes:
if imagehash.hex_to_hash(phash) - imagehash.hex_to_hash(existing) <= threshold:
return True
return False
3.4 SQLite存储实现
数据库操作封装类:
python复制import sqlite3
from datetime import datetime
class ImageDB:
def __init__(self, db_path='images.db'):
self.conn = sqlite3.connect(db_path)
self._create_table()
def _create_table(self):
self.conn.execute('''CREATE TABLE IF NOT EXISTS images
(id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT UNIQUE,
filepath TEXT,
phash TEXT,
width INTEGER,
height INTEGER,
format TEXT,
source TEXT,
download_time DATETIME)''')
self.conn.commit()
def insert_image(self, image_data):
try:
self.conn.execute(
'''INSERT INTO images
(url, filepath, phash, width, height, format, source, download_time)
VALUES (?,?,?,?,?,?,?,?)''',
image_data)
self.conn.commit()
return True
except sqlite3.IntegrityError:
return False # URL已存在
4. 高级优化技巧
4.1 反爬策略应对方案
常见反爬手段及应对措施:
| 反爬类型 | 解决方案 | 实现示例 |
|---|---|---|
| User-Agent检测 | 轮换UA池 | 准备20+常见UA随机选择 |
| IP限制 | 使用代理IP | 维护IP池,失败自动切换 |
| 请求频率限制 | 动态延迟 | 根据响应时间自动调整间隔 |
| JS渲染 | 简单模拟执行 | 提取 |
