
本文详解如何在 Scrapy 中实现图片按数据库记录 ID 自动存入对应子目录(如 book_images/323/xxx.jpg),关键在于合理编排两个 Pipeline 的执行顺序:先入库获取 ID,再用该 ID 构造图片路径。
本文详解如何在 scrapy 中实现图片按数据库记录 id 自动存入对应子目录(如 `book_images/323/xxx.jpg`),关键在于合理编排两个 pipeline 的执行顺序:先入库获取 id,再用该 id 构造图片路径。
要让 Scrapy 将图片保存到以数据库主键 ID 命名的动态子目录(例如 book_images/323/),核心难点在于:图片下载发生在 Item 进入 Pipeline 阶段,而数据库 ID 只有在数据写入后才可获得。因此,必须通过 Pipeline 执行顺序控制依赖关系——确保数据库写入(并注入 id 字段)早于图片路径生成。
✅ 正确的 Pipeline 执行顺序
在 settings.py 或 custom_settings 中,为 Pipeline 指定优先级(数值越小,执行越早):
ITEM_PIPELINES = {
'project.pipelines.SQLiteDBPipeline': 300, # 先执行:入库并注入 item['id']
'scrapy.pipelines.images.ImagesPipeline': 350, # (可选)原生 ImagesPipeline(若未自定义)
'project.pipelines.CustomImageNamePipeline': 400, # 后执行:利用 item['id'] 构建路径
}⚠️ 注意:CustomImageNamePipeline 必须继承 scrapy.pipelines.images.ImagesPipeline,且不能与原生 ImagesPipeline 同时启用(避免重复下载)。若已启用自定义类,请禁用默认项。
✅ 步骤一:扩展 Item,预留 id 字段
在 items.py 中显式声明 id 字段,确保后续 Pipeline 可安全访问:
import scrapy
class BookItem(scrapy.Item):
id = scrapy.Field() # ← 关键:用于存储 DB 返回的主键
title = scrapy.Field()
author = scrapy.Field()
# ... 其他字段
image_urls = scrapy.Field() # 输入:原始图片 URL 列表(必需)
images = scrapy.Field() # 输出:Scrapy 自动填充的下载元信息✅ 步骤二:数据库 Pipeline —— 写入并注入 ID
在 pipelines.py 中实现数据库写入,并将新记录 ID 赋值给 item['id']:
import sqlite3
from project.items import BookItem
class SQLiteDBPipeline:
def __init__(self):
self.conn = sqlite3.connect('books.db')
self.cursor = self.conn.cursor()
self._create_table()
def _create_table(self):
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
author TEXT,
-- 其他字段...
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
self.conn.commit()
def process_item(self, item, spider):
# 插入数据(示例,按实际字段调整)
self.cursor.execute(
"INSERT INTO books (title, author) VALUES (?, ?)",
(item.get('title'), item.get('author'))
)
self.conn.commit()
# 获取刚插入记录的 ID 并写回 item
item['id'] = self.cursor.lastrowid
return item # ← 确保返回 item,供后续 Pipeline 使用
def close_spider(self, spider):
self.cursor.close()
self.conn.close()✅ 步骤三:自定义图片 Pipeline —— 动态构造路径
继承 ImagesPipeline,重写 file_path() 方法,利用 item['id'] 生成层级路径:
import hashlib
from scrapy.pipelines.images import ImagesPipeline
from scrapy.http import Request
class CustomImageNamePipeline(ImagesPipeline):
def file_path(self, request, response=None, info=None, *, item=None):
# 1. 基于 URL 生成唯一哈希(避免文件名冲突 + 支持重复 URL)
url_hash = hashlib.shake_256(request.url.encode()).hexdigest(16)
# 2. 提取原始扩展名(更健壮 than os.path.splitext)
ext = request.url.split('.')[-1].lower()[:4] # 限制长度防异常
ext = f'.{ext}' if ext and '.' not in ext else '.jpg'
# 3. 组合路径:ID/哈希.扩展名
return f"{item['id']}/{url_hash}{ext}"
def get_media_requests(self, item, info):
# 确保 image_urls 存在且非空
if item.get('image_urls'):
for url in item['image_urls']:
yield Request(url, meta={'item_id': item.get('id', 'unknown')})✅ 补充配置:启用图片下载
在 settings.py 中启用基础图片功能(即使使用自定义 Pipeline,IMAGES_STORE 仍需配置):
# 图片存储根目录(所有动态子目录均在此下) IMAGES_STORE = 'book_images' # 可选:提高图片处理鲁棒性 IMAGES_MIN_HEIGHT = 10 IMAGES_MIN_WIDTH = 10
? 关键注意事项
- 执行时序不可颠倒:若 CustomImageNamePipeline 优先级 ≤ 数据库 Pipeline,item['id'] 尚未设置,会触发 KeyError。
- ID 字段必须存在:item['id'] 在 file_path() 被调用前必须由上游 Pipeline 设置完成,否则报错。
- 并发安全:SQLite 在多线程下需注意连接隔离(本例中每个 Pipeline 实例独占连接,安全);若用 PostgreSQL/MySQL,建议使用连接池并确保 process_item 是线程安全的。
- 错误处理增强:生产环境建议在 file_path() 中添加 try/except 捕获 item.get('id') 异常,并记录警告日志。
通过以上结构化设计,Scrapy 即可严格遵循「先入库 → 得 ID → 下载并归档」流程,最终生成符合预期的目录结构:book_images/323/a1b2c3d4e5f67890.jpg、book_images/5211/xyz789abc123def4.jpg……真正实现图片与数据库记录的物理关联。


















