scrapy提供了两个Item Pipeline,专门用于下载文件(FilesPipeline)和图片(ImagesPipeline),可以将这两个Item Pipeline看做特殊的下载器。只需要通过item的一个特殊字段,将要下载的文件/图片的url传递进去,它们就能自动下载文件/图片到本地,并将下载结果存入item的另一个特殊字段。

下载文件FilesPipeline

  比如下载多本pdf格式的小说。步骤:①在设置文件中启用FilesPipeline,一般将其置于其它Item Pipeline之前→ITEM_PIPELINES={‘scrapy.pipelines.files.FilesPipeline’:1} ②在设置文件中使用FILES_STORE指定文件下载目录 ③在spider中将所有下载文件的url收集到一个列表,赋给item的file_urls字段(FilesPipeline在处理每项item时,会读取item[‘file_urls’],对其中每一个url进行下载)

1
2
3
4
5
6
7
8
9
10
#spider示例代码
class DownloadBookSpider(scrapy.Spider):
……
def parse(response):
item = { }
item['file_urls]=[ ] #下载列表
for url in response.xpath('//a/@href').extract():
download_url=response.urljoin(url)
item['file_urls'].append(download_url) #将url填入下载列表
yield item

  当FilesPipeline下载完item[‘file_urls’]中的所有文件后,会将各文件的下载结果收集到另一个列表,并赋给item的files字段(item[‘files’])。下载结果信息包含以下内容:①Path 文件下载到本地的路径(相对于FILES_STORE的相对路径)②Checksum 文件的校验和 ③url 文件的url地址

下载图片ImagesPipeline

  图片也是文件,所以下载图片本质上也是下载文件,ImagesPipeline是FilesPipeline的子类,使用上和FilesPipeline大同小异,只是在所使用的item字段和配置选项上略有差别:①导入路径不同 → scrapy.pipelines.files.FilesPipeline 和 scrapy.pipelines.images.ImagesPipeline;②Item字段不同:file_urls,files 和 image_urls,images;③指定下载目录的变量不同:FILES_STORE 和 IMAGES_STORE
  而且ImagesPipeline在FilesPipeline的基础上,针对图片增加了一些特有的功能,比如 ①为图片生成缩略图:在设置文件中设置IMAGES_THUMBS就能开启该功能,它是一个字典,每一项的值是缩略图的尺寸,比如IMAGES_THUMBS = {‘small’:(50,50),’big’:(270,270),},下载1张图片,本地会出现3张图片→1张原图+2张缩略图;②过滤掉尺寸过小的图片:在设置文件中设置IMAGES_MIN_WIDTH(指定图片最小的宽)、IMAGES_MIN_HEIGHT(最小的高)就能开启该功能,比如IMAGES_MIN_WIDTH=110 IMAGES_MIN_HEIGHT=110,如果下载一张105×200图片,这张图片就会被抛弃,因为宽度不符合标准。

项目1:爬取matplotlib例子的源码文件

(1)创建项目
  项目需求:matplotlib网站上有很多例子,并可以下载代码(点击source code),现在我想把所有例子的源码文件都下载到本地,可以编写一个爬虫程序完成这个任务。
  页面分析:①先看如何在[例子列表页面]中获得所有[例子页面]的链接,方法是在cmd中输入scrapy shell url下载页面,再用view(response)在浏览器中查看页面,发现所有[例子页面]的链接,都在属性class为”toctree-12”的li标签中,可以用LinkExtractor提取所有例子页面的链接;②调用fetch函数下载第一个例子页面,并调用view函数在浏览器中查看页面,fetch(‘url’),再用view查看下载页面,发现在第1个例子页面中的下载地址,在class为”reference external”的a标签中的href属性,用selector提取即可。

1
2
3
4
5
6
7
8
9
#所有例子页面:
from scrapy.linkextractors import LinkExtractor
le = LinkExtractor(restrict_css='li.toctree-l2')
links = le.extract_links(response)
[link.url for link in links]

#每个例子页面的下载链接:
href = response.css('a.reference.external::attr(href)').extract_first()
response.urljoin(href)

  创建项目:

1
2
3
>>scrapy startproject matplotlib_examples
>>cd matplotlib_examples
>>scrapy genspider examples matplotlib.org

(2)定义保存数据的容器Item

1
2
3
class ExampleItem(scrapy.Item):
file_urls = scrapy.Field()
files = scrapy.Field()

(3)编写提取数据的爬虫Spider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scrapy
from scrapy.linkextractors import LinkExtractor

class ExamplesSpider(scrapy.Spider):
name = 'examples'
allowed_domains = ['matplotlib.org']
start_urls = ['http://...']
def parse(self,response):
le = LinkExtractor(restrict_css='li.toctree-l2')
links = le.extract_links(response)
for link in links:
yield scrapy.Request(link.url,callback=self.parse_example)

#下面是例子页面的解析函数,目的是获得下载链接,将其放入一个列表赋给ExampliItem的file_urls字段
def parse_example(self,response):
href = response.css('a.reference.external::attr(href)').extract_first()
url = response.urljoin(href)
example = ExampleItem()
example['file_urls'] = [url]
return example

(4)构建处理数据的流水线Pipeline
  在settings.py中启用FilesPipeline,并设定文件下载目录

1
2
3
4
ITEM_PIPELINES={
'scrapy.pipelines.files.FilesPipeline':1,
}
FILES_STORE='examples_src'

(5)运行爬虫,并查看examples_src目录

1
2
$scrapy crawl examples -o examples.json
$tree examples_src

  507个源码文件被下载到了exmaples_src/full目录下,并且每个文件的名字都是一串长度相等的奇怪数字,这些数字是下载文件url的sha1散列值,这种命名方式可以防止重名的文件相互覆盖,但是文件名太不直观。如果你想把这些例子文件按照类别下载不同目录下,可以修改FilesPipeline为文件命名的规则。阅读FilesPipeline源码发现,原来是其中的file_path方法决定了文件的命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FilesPipeline(MediaPipeline):
……
def file_path(self,request,response=None,info=None):
……
if not isinstance(request,Request):
_warn()
url=request
else:
url=request.url
if not hasattr(self.file_key,'_base'):
_warn()
return self.file_key(url)
media_guid=hashib.sha1(to_bytes(url)).hexdigest()
media_ext=os.path.splitext(url)[1]
return 'full/%s%s'%(media_guid,media_ext)

  现在实现一个FilesPipeline子类,覆写file_path方法来实现所期望的文件命名规则,这些源码文件url的最后两部分是类别和文件名,在pipelines.py实现MyFilesPipeline

1
2
3
4
5
6
7
from scrapy.pipelines import FilesPipeline
from urllib.parse import urlparse
from os.path import basename,dirname,join
class MyFielsPipeline(FilesPipeline):
def file_path(self,request,response=None,info=None):
path = urlparse(request.url).path
return join(basename(dirname(path)),basename(path))

  修改配置文件,使用MyFielsPipeline替代FielsPipeline

1
2
3
ITEM_PIPELINES={
'matplotlib_examples.pipelines.MyFielsPipeline':1,
}

  删除之前下载的所有文件,重新运行爬虫后再查看examples_src目录

1
2
3
4
$rm -r examples_src/full
$rm examples.json
$scrapy crawl examples -o examples.json
$tree examples_src

  结果507个文件按类别被下载到26个目录下

项目2:下载360图片

(1)创建项目
  项目需求:下载360图片网站中艺术分类下的所有图片到本地
  页面分析:向下滚动鼠标滚轮便会有更多图片加载出来,图片加载是由javascript脚本完成的。jQuery发送的请求,其响应结果是一个json串。复制jQuery发送请求的url,使用scrapy shell进行访问,查看响应结果的内容(json)→ res = json.loads(response.body.decode(‘utf8’))
   响应结果(json)中的list字段是一个图片信息列表,count字段是列表中图片信息的数量,每一项图片信息的qhimg_url字段图片下载地址。
   url的规律:①ch参数:分类标签 ②sn参数:从第几张图片开始加载,即结果列表中第一张图片在服务器端的序号。可以通过这个API每次获取固定数量的图片信息,从中提取每一张图片的下载地址,直到响应结果中的count字段为0(意味着没有更多图片了)
  创建项目:取名为so_image,再使用scrapy genspider命令创建Spider
(2)定义保存数据的容器Item:略
(3)编写提取数据的爬虫Spider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import scrapy
from scrapy import Request
import json
class ImagesSpider(scrapy.Spider):
BASE_URL='...'
start_index=0
MAX_DOWNLOAD_NUM=1000 #限制最大下载数量,防止磁盘用量过大
name = 'images'
start_urls = [BASE_URL%0]
def parse(self,response):
infos=json.loads(response.body.decode('utf-8')) #使用json模块解析响应结果
#提取所有图片下载url到一个列表,赋给item的'image_urls'字段
yield {'image_urls':[info['qhimg_url'] for info in infos['list']]}
#如count字段大于0,并且下载数量不足MAX_DOWNLOAD_NUM,继续获取下一页图片信息
self.start_index += infos['count']
if infos['count']>0 and self.start_index < self.MAX_DOWNLOAD_NUM:
yield Request(self.BASE_URL%self.start_index)

(4)构建处理数据的流水线Pipeline
   在settings.py中启用ImagesPipeline,并设定图片下载目录

1
2
3
4
ITEM_PIPELINES={
'scrapy.pipelines.ImagesPipeline':1,
}
IMAGES_STORE='download_images'

(5)运行爬虫$scrapy crawl images