在上一篇介绍使用 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类似。