学会运用爬虫框架 Scrapy (四) —— 高效下载图片

图片来自 unsplash

爬虫程序爬取的目标通常不仅仅是文字资源,经常也会爬取图片资源。这就涉及如何高效下载图片的问题。这里高效下载指的是既能把图片完整下载到本地又不会对网站服务器造成压力。也许你会这么做,在 pipeline 中自己实现下载图片逻辑。但 Scrapy 提供了图片管道ImagesPipeline,方便我们操作下载图片。

1 为什么要选用 ImagesPipeline ?

ImagesPipeline 具有以下特点:

  • 将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
  • 避免重新下载最近已经下载过的图片
  • 缩略图生成
  • 检测图像的宽/高,确保它们满足最小限制

2 具体实现

2.1 定义字段

在 item.py 文件中定义我们两个字段image_urlsimages_path

import scrapy

class PicsDownloadItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()

    image_urls = scrapy.Field()  # 图片的下载地址, 该字段是存储图片的列表
    image_path = scrapy.Field()  # 图片本地存储路径(相对路径)

2.2 编写 spider

我以爬取 freebuf 首页部分图片为例子讲解。具体代码如下:

import scrapy

from pics_download.items import PicsDownloadItem

class freebuf_pic_spider(scrapy.Spider):

    name = 'freebuf'

    allowed_domains = ['freebuf.com']

    start_urls = [
        'http://www.freebuf.com/'
    ]

    def parse(self, response):
        self.log(response.headers)
        # 获取 freebuf 首页所有的图片, 以列表形式保存到 image_urls 字段中。
        piclist = response.xpath("//div[@class='news-img']/a/img/@src").extract()
        if piclist:
            item = PicsDownloadItem()
            item['image_urls'] =  piclist  
            yield item

2.3 实现 Pipeline

我新建一个名为PicsDownloadPipeline的类。需要注意一点的是: Scrapy 默认生成的类是继承Object, 要将该类修改为继承ImagesPipeline。然后实现get_media_requestsitem_completed这两个函数。

  • get_media_requests(item, info)

ImagePipeline 根据 image_urls 中指定的 url 进行爬取,可以通过 get_media_requests 为每个 url 生成一个 Request。具体实现如下:

def get_media_requests(self, item, info):
    for image_url in item['image_urls']:
        yield scrapy.Request(image_url)
  • item_completed(self, results, item, info)

当一个单独项目中的所有图片请求完成时,该方法会被调用。处理结果会以二元组的方式返回给 item_completed() 函数。这个二元组定义如下:(success, image_info_or_failure)
其中,第一个元素表示图片是否下载成功;第二个元素是一个字典,包含三个属性:

  1. url - 图片下载的url。这是从 get_media_requests() 方法返回请求的url。
  2. path - 图片存储的路径(类似 IMAGES_STORE)
  3. checksum - 图片内容的 MD5 hash

具体实现如下:

def item_completed(self, results, item, info):
    # 将下载的图片路径(传入到results中)存储到 image_paths 项目组中,如果其中没有图片,我们将丢弃项目:
    image_path = [x['path'] for ok, x in results if ok]
    if not image_path:
        raise DropItem("Item contains no images")
    item['image_path'] = image_path
    return item

综合起来,PicsDownloadPipeline 的实现下载图片逻辑的代码如下:

import scrapy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline

class PicsDownloadPipeline(ImagesPipeline):

    def get_media_requests(self, item, info):
        for image_url in item['image_urls']:
            yield scrapy.Request(image_url)

    def item_completed(self, results, item, info):
        # 将下载的图片路径(传入到results中)存储到 image_paths 项目组中,如果其中没有图片,我们将丢弃项目:
        image_path = [x['path'] for ok, x in results if ok]
        if not image_path:
            raise DropItem("Item contains no images")
        item['image_path'] = image_path
        return item

2.4 配置设置

在 setting.py 配置存放图片的路径以及自定义下载的图片管道。

# 设置存放图片的路径
IMAGES_STORE = 'D:\\freebuf'

# 配置自定义下载的图片管道, 默认是被注释的
ITEM_PIPELINES = {
    # 第二行的填写规则
    #  yourproject.middlewares(文件名).middleware类
   'pics_download.pipelines.PicsDownloadPipeline': 300,  # pics_download 是你项目的名称
}

2.5 运行程序

在 Scrapy 项目的根目录下,执行以下命令:

scrapy crawl freebuf  # freebuf 是我们在 spider 定义的 name 属性

如果你使用的 Python 版本是 3.x 的,可能会报出以下的错误。

  File "c:\program files (x86)\python36-32\lib\site-packages\scrapy\pipelines\images.py", line 15, in <module>
    from PIL import Image
ModuleNotFoundError: No module named 'PIL'

这是因为 Scrapy 框架用到这个Python Imaging Library (PIL)图片加载库,但是这个库只支持 2.x 版本,所以会运行出错。对于使用 Python 3.x 版本的我们,难道就束手无策?Scrapy 的开发者建议我们使用更好的图片加载库Pillow。为什么说更好呢?一方面是兼容了 PIL,另一方面在该库支持生成缩略图。

因此,我们安装 Pillow 就能解决运行报错的问题。具体安装 Pillow命令如下:

pip install pillow 
# 如果出现因下载失败导致安装不上的情况,可以先启动 ss 再执行安装命令
# 或者在终端中使用代理
pip --proxy http://代理ip:端口 install pillow 

安装之后,重新运行爬虫程序。Scrapy 会运行结果中显示我们定义的image_urlsimages_path字段。

2.6 运行结果

我们会发现在 D 盘有个名为freebuf的文件夹。在该文件夹中有个full文件夹,里面存放我们刚才爬取到的图片。

点击查看大图

如果有在 setting.py 文件中设置生成缩略图。

IMAGES_THUMBS = {
    'small': (50, 50),   # (宽, 高)
    'big': (270, 270),
}

那么到时候,与full同级的目录下会多出个thumbs文件夹。里面会有两个文件夹smallbig,分别对应小分辨率的图片和大分辨率的图片。

3 优化

3.1 避免重复下载

在 setting.py 中新增以下配置可以避免下载最近已经下载的图片。

# 90天的图片失效期限
IMAGES_EXPIRES = 90

设置该字段,对于已经完成爬取的网站,重新运行爬虫程序。爬虫程序不会重新下载新的图片资源。

3.2自动限速(AutoTrottle)

下载图片是比较消耗服务器的资源以及流量。如果图片资源比较大,爬虫程序一直在下载图片。这会对目标网站造成一定的影响。同时,爬虫有可能遭到封杀的情况。

因此,我们有必要对爬虫程序做爬取限速处理。Scrapy 已经为我们提供了AutoTrottle功能。

只要在 setting.py 中开启AutoTrottle功能并配置限速算法即可。我采用默认的配置,具体配置如下:

# 启用AutoThrottle扩展
AUTOTHROTTLE_ENABLED = True
# 初始下载延迟(单位:秒)
AUTOTHROTTLE_START_DELAY = 5
# 在高延迟情况下最大的下载延迟(单位秒)
AUTOTHROTTLE_MAX_DELAY = 60
# 设置 Scrapy应该与远程网站并行发送的平均请求数, 目前是以1个并发请求数
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# 启用AutoThrottle调试模式
#AUTOTHROTTLE_DEBUG = False

值得注意的是,启用AutoThrottle扩展时,仍然受到DOWNLOAD_DELAY(下载延迟)和CONCURRENT_REQUESTS_PER_DOMAIN(对单个网站进行并发请求的最大值)以及CONCURRENT_REQUESTS_PER_IP(对单个IP进行并发请求的最大值)的约束。


系列文章:
学会运用爬虫框架 Scrapy (一)
学会运用爬虫框架 Scrapy (二)
学会运用爬虫框架 Scrapy (三)
学会运用爬虫框架 Scrapy (五) —— 部署爬虫

推荐阅读:
爬虫实战二:爬取电影天堂的最新电影
爬虫与反爬虫的博弈
爬虫系列的总结


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容