不积跬步,无以至千里;不积小流,无以成江海。

Dean's blog

  • Join Us on Facebook!
  • Follow Us on Twitter!
  • LinkedIn
  • Subcribe to Our RSS Feed

Scrapy 下载图片和文件

在上一篇介绍使用 Scrapy Pipeline将数据保存到JSON文件,实现了记录了自定义管道开发过程。而针对爬虫开发过程中,常用的一些操作,例如下载图片和文件,Scrapy也内置了相应的Pipeline,分别有:FilesPipeline、ImagesPipeline,统称为MediaPipeline:

MediaPipeline:实现多媒体文件下载的基类
FilesPipeline:实现文件的下载,为MediaPipeline的直接子类,默认支持将文件保存到本地文件系统(FS)、S3和GCS
ImagesPipeline:实现图片的下载,为FilesPipeline的直接子类,支持图片的下载和生成缩略图

这里在上一篇使用 Scrapy Pipeline将数据保存到JSON文件的基础上,实现下载作者的头像,其开发过程大致如下:

下载图片

步骤一、定义Item,位于items.py文件

import scrapy


class CnblogsItem(scrapy.Item):
    diggnum     = scrapy.Field()    #推荐数
    title       = scrapy.Field()    #标题
    titlelnk    = scrapy.Field()    #标题链接
    summary     = scrapy.Field()    #摘要
    fack        = scrapy.Field()    #作者头像
    author      = scrapy.Field()    #作者名称
    authorlnk   = scrapy.Field()    #作者主页
    time        = scrapy.Field()    #发表时间
    comment     = scrapy.Field()    #评论数
    view        = scrapy.Field()    #阅读数

    fack_imgs   = scrapy.Field()    #图片信息字段

这里增加了fack_imgs字段,这个用业保存图片信息,稍后详细介绍。

步骤二、启用ImagesPipeline管道

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'cnblogs.pipelines.CnblogsPipeline': 100,
    'scrapy.pipelines.images.ImagesPipeline': 1,
}

这里启用了scrapy.pipelines.images.ImagesPipeline,它优先于自定义的CnblogsPipeline执行。

步骤三、设置图片保存的路径、URL字段、图片信息保存字段,如果有需要,还可以指定生成缩略图

IMAGES_STORE = 'facks'               #图片保存目录
IMAGES_URLS_FIELD = 'fack'           #图片URL字段
IMAGES_RESULT_FIELD = 'fack_imgs'    #图片信息字段
IMAGES_THUMBS = {                    #缩略图
    'small' :   (80, 80),
    'big'   :   (300, 300)
}

这里把文件保存到facks,由于没有指定完整的路径,它界时会在工作目录下自动创建facks保存图片。

注:这些配置键在scrapy.pipelines.images.ImagesPipeline类的定义。

步骤四、返回的CnblogsItem的图片字段(fack)必须是一个集合

def parse_page(self, response):
    posts = response.css(".post_item")
    for post in posts:
        item = CnblogsItem()
        item['diggnum'] = post.css(".diggnum::text").re_first(r'\d+')                        #推荐数
        item['title'] = post.css(".titlelnk::text").extract_first()                          #标题
        item['titlelnk'] = post.css(".titlelnk::attr(href)").extract_first()                 #标题链接
        item['summary'] = "".join(post.css(".post_item_summary::text").extract())            #摘要
        #item['fack'] = post.css(".pfs::attr(src)").extract_first()                           #作者头像
        item['fack'] = post.css(".pfs::attr(src)").extract()                           #作者头像
        item['author'] = post.css(".lightblue::text").extract_first()                        #作者名称
        item['authorlnk'] = post.css(".lightblue::attr(href)").extract_first()               #作者主页
        item['time'] = post.css(".post_item_foot::text").re_first(r'\d+-\d+-\d+ \d+:\d+')    #发表时间
        item['comment'] = post.css(".article_comment .gray::text").re_first(r'\d+')          #评论数
        item['view'] = post.css(".article_comment .gray::text").re_first(r'\d+')             #阅读数
        self.strip(item)
        yield item

这里返回的fack变成 了一个集合,如果不是集合的时候,直接运行会报错:

ValueError: Missing scheme in request url: h

爬虫测试

完成这四步后,即可运行爬虫进行测试:

scrapy crawl blog

运行后,它会自动下载图片,并在工作目录下创建facks目录,形成类似下面的目录结构和文件:

├─full
│      04524259e7aaf837d953014f35becd2be0615445.jpg
│      ......
│
└─thumbs
    ├─big
    │      04524259e7aaf837d953014f35becd2be0615445.jpg
    │      ......
    └─small
            04524259e7aaf837d953014f35becd2be0615445.jpg
            ......

其中:

full目录:保存的是图片原图

thumbs目录:图片缩略图目录,每个子目录对应于settings.py中的IMAGES_THUMBS配置。

图片的文件名是根据图片路径生成的sha1码,如果文件已经存在,则不会再次下载。

自定义图片文件名

默认生成的文件名可读性太差,如果希望调整文件名生成规则,可以创建一个定义Pipeline让它继承ImagesPipeline,并重写file_path方法,例如:

import json
from pathlib import PurePath

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

class CnblogsImagesPipeline(ImagesPipeline):
    def image_key(self, url):
        '''定义原图路径'''
        path = PurePath(url)
        return f'full/{path.stem}{path.suffix}'

    def thumb_key(self, url, thumb_id):
        '''定义缩略图路径'''
        path = PurePath(url)
        return f'thumbs/{thumb_id}/{path.stem}{path.suffix}'

其中:

file_path():生成原图路径

thumb_path():生成缩略图路径 

这里生成的图片路径都应该基于settings.py中IMAGES_STORE指定的子目录。

定义ImagesPipline后,在settings.py中启动:

ITEM_PIPELINES = {
    'cnblogs.pipelines.CnblogsPipeline': 100,
    'cnblogs.pipelines.CnblogsImagesPipeline': 1,
    #'scrapy.pipelines.images.ImagesPipeline': 1,
}

这样,再次运行爬虫,图片名称就和原名是一致的了。

├─full
│      20161204160738.png
│      ....
│
└─thumbs
    ├─big
    │      20161204160738.png
    │      ....
    │
    └─small
            20161204160738.png
            ....

仔细观察会发现,默认ImagesPipeline会将图片转换为 .jpg 格式的图片。

文件信息

在这里,将文件下载的信息保存在item['fack_imgs']中,这里它保存了图片URL和保存文件的关系,例如:

"fack_imgs":[
    {
        "url":"https://pic.cnblogs.com/face/1771072/20191118225815.png",
        "path":"full/20191118225815.png",
        "checksum":"c19e620f888536d41a655692a1086196"
    }
]

 

下载文件

由于ImagesPipeline是FilesPipeline的子类,下载文件的方式与下载图片的过程类似,差异的只有几个位置:

1、文件的保存路径配置:

FILES_STORE = 'files'                #文件保存目录
FILES_URLS_FIELD = 'file_urls'       #文件URL字段
FILES_RESULT_FIELD = 'files'         #下载的文件信息字段

2、如果希望自定义文件名,可以这样:

class CnblogsFilesPipeline(FilesPipeline):
    def file_key(self, url):
        '''定义文件路径'''
        path = PurePath(url)
        return f'{path.stem}{path.suffix}'

其它的过程与ImagesPipeline类似。

 

 

不允许评论
粤ICP备17049187号-1