Scrapy(Pillow)无法识别URL图片但本地可识别的原因
我的爬取代码
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请求时未携带浏览器级别的请求头,导致服务器返回的并非真实图片内容,而浏览器请求时自带完整的头信息,能拿到正确的图片数据。
具体排查与修复步骤:
添加模拟浏览器的请求头
服务器会通过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/' # 替换为图片所在页面的域名 } )检查响应内容的有效性
在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)如果输出不是图片魔法头,说明服务器返回的是反爬页面、空内容或其他非图片数据。
改用ImagesPipeline处理图片
你当前继承的是FilesPipeline,但报错中出现了ImagesPipeline的逻辑,建议直接继承ImagesPipeline,它专门针对图片处理做了优化,包括格式验证、缩略图生成等:from scrapy.pipelines.images import ImagesPipeline class MyImagePipeline(ImagesPipeline): # 保留原有的get_media_requests和file_path逻辑 pass检查内容压缩情况
部分服务器会返回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

