You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy(Pillow)无法识别URL图片但本地可识别的原因

Scrapy爬取图片时Pillow无法识别,但本地保存后正常的问题

我的爬取代码

class MyImagePipeline(FilesPipeline):
    def get_media_requests(self, item, info):
        meta = {
            'folder_name': item.get('uuid'),
        }        
        if item.get('file_urls'):
            for image_url in item.get('file_urls'):
                yield scrapy.Request(image_url, meta=meta)
            
            
    def file_path(self, request, response=None, info=None, *, item=None):
        image_guid = hashlib.sha1(request.url.encode('utf-8')).hexdigest()
        file_name = f'{image_guid}.jpg'
        return '%s/%s' % (request.meta["folder_name"], file_name)

运行Scrapy时的报错信息

DEBUG: File (downloaded): Downloaded file from <GET https://pictures.immobilienscout24.de/prod.www.immobilienscout24.at/pictureserver/loadPicture?q=70&id=012.0012000001E3qxe-1724758328-b45723a13abc4ebbb72758ac00706366> referred in <None>
ERROR: File (unknown-error): Error processing file from <GET https://pictures.immobilienscout24.de/prod.www.immobilienscout24.at/pictureserver/loadPicture?q=70&id=012.0012000001E3qxe-1724758328-b45723a13abc4ebbb72758ac00706366> referred in <None>
Traceback (most recent call last):
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/files.py", line 493, in media_downloaded
    checksum = self.file_downloaded(response, request, info, item=item)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/media.py", line 153, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/images.py", line 119, in file_downloaded
    return self.image_downloaded(response, request, info, item=item)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/media.py", line 153, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/images.py", line 123, in image_downloaded
    for path, image, buf in self.get_images(response, request, info, item=item):
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/scrapy/pipelines/images.py", line 139, in get_images
    orig_image = self._Image.open(BytesIO(response.body))
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/adam/project/.venv/lib/python3.11/site-packages/PIL/Image.py", line 3498, in open
    raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x113d93290>
ERROR: [Failure instance: Traceback: <class 'scrapy.pipelines.files.FileException'>: cannot identify image file <_io.BytesIO object at 0x113d93290>

本地验证图片的脚本及结果

我将图片从浏览器右键保存到本地后,用以下Pillow脚本验证:

from PIL import Image

def is_image(file_path):
    try:
        # 尝试以图片格式打开文件
        with Image.open(file_path) as img:
            img.verify()
        return True
    except (IOError, SyntaxError, Image.UnidentifiedImageError) as e:
        print(f"Error: {e}")
        return False

# 示例使用
file_path = '/Users/adam/test_img.jpg'
if is_image(file_path):
    print(f"{file_path} is a valid image.")
else:
    print(f"{file_path} is not a valid image.")

脚本输出:

/Users/adam/test_img.jpg is a valid image.

问题

为何Scrapy调用Pillow无法识别该URL返回的图片,而本地保存后却可以?


解决分析

核心原因是Scrapy请求时未携带浏览器级别的请求头,导致服务器返回的并非真实图片内容,而浏览器请求时自带完整的头信息,能拿到正确的图片数据。

具体排查与修复步骤:

  1. 添加模拟浏览器的请求头
    服务器会通过User-Agent、Referer等字段识别请求来源,拒绝爬虫请求。在生成scrapy.Request时补充这些头:

    yield scrapy.Request(
        image_url, 
        meta=meta,
        headers={
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
            'Referer': 'https://www.immobilienscout24.at/'  # 替换为图片所在页面的域名
        }
    )
    
  2. 检查响应内容的有效性
    在file_path方法中打印响应的前几个字节,确认是否为图片的"魔法头"(比如JPG的\xff\xd8\xff):

    def file_path(self, request, response=None, info=None, *, item=None):
        # 打印响应前10个字节,验证是否为图片
        print(response.body[:10])
        image_guid = hashlib.sha1(request.url.encode('utf-8')).hexdigest()
        file_name = f'{image_guid}.jpg'
        return '%s/%s' % (request.meta["folder_name"], file_name)
    

    如果输出不是图片魔法头,说明服务器返回的是反爬页面、空内容或其他非图片数据。

  3. 改用ImagesPipeline处理图片
    你当前继承的是FilesPipeline,但报错中出现了ImagesPipeline的逻辑,建议直接继承ImagesPipeline,它专门针对图片处理做了优化,包括格式验证、缩略图生成等:

    from scrapy.pipelines.images import ImagesPipeline
    
    class MyImagePipeline(ImagesPipeline):
        # 保留原有的get_media_requests和file_path逻辑
        pass
    
  4. 检查内容压缩情况
    部分服务器会返回gzip压缩的内容,虽然Scrapy默认会自动解压,但如果出现异常,可以手动处理:

    import gzip
    from io import BytesIO
    
    def file_downloaded(self, response, request, info, item=None):
        if response.headers.get('Content-Encoding') == 'gzip':
            response.body = gzip.decompress(response.body)
        return super().file_downloaded(response, request, info, item=item)
    

为什么本地保存后正常?

浏览器请求图片时,会自动携带完整的请求头(包括User-Agent、Referer),服务器返回真实的图片数据;而Scrapy默认的请求头过于简单,被服务器识别为爬虫,返回了非图片内容,导致Pillow无法识别。当你从浏览器保存图片到本地时,已经拿到了正确的图片数据,自然能被Pillow验证通过。


内容的提问来源于stack exchange,提问作者user984621

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:27:07