Scrapy ImagesPipeline与FilesPipeline完全指南 - 多媒体资源下载与处理技术详解

📂 所属阶段:第二阶段 — 数据流转(数据处理篇)
🔗 相关章节:Pipeline管道实战 · 数据清洗与校验

目录


ImagesPipeline vs FilesPipeline速览

很多初学者分不清这两个内置Pipeline,其实核心区别很简单:

特性ImagesPipelineFilesPipeline
适用范围仅图片(JPG/PNG/GIF等)所有文件类型
核心功能自动下载+去重+尺寸/格式/质量处理+缩略图自动下载+去重+文件路径管理
运行速度略慢(多了图片验证和处理环节)
使用场景电商图、海报、头像、素材库等PDF文档、视频、压缩包、代码文件等

基础配置与最简实践

第一步:启用Pipeline

无论是用哪个,都要先在settings.py里启用——注意默认优先级就行:

# settings.py
# 优先级:ImagesPipeline>FilesPipeline(数值越小优先级越高)
ITEM_PIPELINES = {
    # 如果只需要图片:
    # 'scrapy.pipelines.images.ImagesPipeline': 1,
    # 如果只需要文件:
    # 'scrapy.pipelines.files.FilesPipeline': 1,
    # 两个都要:
    'scrapy.pipelines.images.ImagesPipeline': 1,
    'scrapy.pipelines.files.FilesPipeline': 2,
}

第二步:配置核心参数

ImagesPipeline配置

# settings.py - ImagesPipeline基础/常用配置
IMAGES_URLS_FIELD = 'image_urls'      # Item里存URL列表的字段名
IMAGES_RESULT_FIELD = 'images'        # Item里存下载结果的字段名
IMAGES_STORE = './static/images'       # 图片保存的**本地根目录**(也可以是FTP/S3等URI)
IMAGES_EXPIRES = 90                   # 图片缓存过期天数(相同URL不会重复下载)
IMAGES_MIN_WIDTH = 100                # 过滤宽度小于100px的图
IMAGES_MIN_HEIGHT = 100               # 过滤高度小于100px的图
IMAGES_THUMBS = {                     # 生成指定尺寸的缩略图
    'small': (100, 100),
    'medium': (300, 300),
}

FilesPipeline配置

# settings.py - FilesPipeline基础/常用配置
FILES_URLS_FIELD = 'file_urls'        # Item里存URL列表的字段名
FILES_RESULT_FIELD = 'files'          # Item里存下载结果的字段名
FILES_STORE = './static/files'         # 文件保存的本地根目录
FILES_EXPIRES = 90                    # 文件缓存过期天数

第三步:定义Item

# items.py
import scrapy

class MediaItem(scrapy.Item):
    # 共用的
    id = scrapy.Field()
    title = scrapy.Field()
    # 图片相关
    image_urls = scrapy.Field()  # 必须是列表!
    images = scrapy.Field()      # 存储下载后的图片信息
    # 文件相关
    file_urls = scrapy.Field()   # 必须是列表!
    files = scrapy.Field()        # 存储下载后的文件信息

第四步:Spider中提取URL并yield Item

# spiders/my_spider.py
import scrapy
from my_project.items import MediaItem

class ExampleSpider(scrapy.Spider):
    name = 'example'
    start_urls = ['https://example.com/media-page']

    def parse(self, response):
        item = MediaItem()
        item['id'] = '123'
        item['title'] = '示例媒体包'
        # 提取图片URL列表
        item['image_urls'] = response.css('.product-image::attr(src)').getall()
        # 提取文件URL列表
        item['file_urls'] = response.css('.download-link::attr(href)').getall()
        yield item

高频实用自定义技巧

默认的Pipeline能满足80%的需求,但剩下的20%(比如自定义文件名、防反爬Headers、统一图片格式)得自己继承重写。

自定义文件名(防重复+易查找)

默认文件名是URL的SHA1哈希,太乱了!我们改成[分类]/[时间戳]-[商品ID]-[原文件名]的结构:

ImagesPipeline自定义

# pipelines.py
import scrapy
from scrapy.pipelines.images import ImagesPipeline
from itemadapter import ItemAdapter
from urllib.parse import urlparse
import time
import os

class CustomImagesPipeline(ImagesPipeline):
    # 重写file_path:自定义主图路径
    def file_path(self, request, response=None, info=None, *, item=None):
        # 从meta或item获取信息
        item_adapter = ItemAdapter(item)
        item_id = item_adapter.get('id', 'unknown')
        category = item_adapter.get('category', 'uncategorized')
        
        # 从URL提取原文件名
        url_path = urlparse(request.url).path
        original_name = os.path.basename(url_path)
        if not original_name:
            original_name = 'image.jpg'
        
        # 添加时间戳防重复
        timestamp = str(int(time.time()))
        return f"{category}/{timestamp}-{item_id}-{original_name}"
    
    # 重写thumb_path:自定义缩略图路径(可选,默认是full/xxx对应thumbs/xxx)
    def thumb_path(self, request, thumb_id, response=None, info=None, *, item=None):
        base_path = self.file_path(request, item=item)
        name, ext = os.path.splitext(base_path)
        return f"{name}_{thumb_id}{ext}"

FilesPipeline自定义

和ImagesPipeline几乎一样,只是继承类不同,就不重复写了。

防反爬Headers(自定义Request)

很多网站会检查Referer、User-Agent、Cookie等,默认Pipeline的Request比较简陋,需要重写get_media_requests

# 接着上面的CustomImagesPipeline写
class CustomImagesPipeline(ImagesPipeline):
    # ...(file_path和thumb_path)
    
    # 重写get_media_requests:添加防反爬Headers和meta
    def get_media_requests(self, item, info):
        item_adapter = ItemAdapter(item)
        referer = item_adapter.get('source_url', 'https://example.com')
        
        for url in item_adapter.get(self.images_urls_field, []):
            yield scrapy.Request(
                url,
                headers={
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
                    'Referer': referer,
                },
                meta={
                    'dont_redirect': False,  # 允许重定向(默认也是允许的,但可以显式写)
                    'download_timeout': 60,  # 延长超时时间(大文件常用)
                },
                # 也可以把cookie传进来,但建议在settings.py的COOKIES_ENABLED=True全局配置
            )

ImagesPipeline统一图片格式(WebP最佳实践)

WebP比JPG/PNG小30%-80%,SEO友好,现在主流浏览器都支持:

# 接着上面的CustomImagesPipeline写
from PIL import Image

class CustomImagesPipeline(ImagesPipeline):
    # ...(前面的方法)
    
    # 重写convert_image:处理图片模式+统一格式+压缩
    def convert_image(self, image, size=None):
        # 1. 处理RGBA/LA/P模式(WebP支持RGBA,JPG不支持,所以统一转RGBA再存WebP)
        if image.mode in ('RGBA', 'LA', 'P'):
            if image.mode == 'P':
                image = image.convert('RGBA')
            # 如果不想保留透明度,也可以转RGB白背景:
            # background = Image.new('RGB', image.size, (255, 255, 255))
            # background.paste(image, mask=image.split()[-1])
            # image = background
        
        # 2. 调整尺寸(如果有IMAGES_THUMBS或传入size)
        if size:
            image = image.resize(size, Image.Resampling.LANCZOS)  # LANCZOS是最高质量的缩放算法
        
        return image

然后在file_path里把扩展名改成.webp就行,ImagesPipeline会自动调用PIL的WebP编码器。


常见问题与解决方案

问题1:图片/文件下载失败但没有日志

原因:默认Pipeline会忽略下载失败的URL,除非所有URL都失败才会报错。 解决:重写media_failed或者检查scrapy crawl xxx --logfile=scrapy.log里的DEBUG日志。

问题2:文件名冲突覆盖

原因:自定义文件名逻辑不够严谨(比如没有时间戳或URL哈希)。 解决:在自定义文件名时,必须加时间戳、URL的短哈希(前8位就行)或Item的唯一ID。

问题3:图片缓存过期后不想重新下载

原因:IMAGES_EXPIRES/FILES_EXPIRES设得太短。 解决:设成36500(100年)或者在settings.py里加MEDIA_ALLOW_REDIRECTS = True之外,检查有没有其他设置影响缓存。

问题4:ImagesPipeline处理大内存图片崩溃

原因:没有优化PIL的内存使用。 解决

  1. 在convert_image里,处理完图片后及时释放:del background等(虽然Python有GC,但显式释放大对象更快)。
  2. 限制图片的最大尺寸:在convert_image里加max_size = 2048,如果图片超过就等比例缩放。