Scrapy框架(六) 通过Scrapy内置的ImagePipeline下载图片

引言

相信大家学了这么长时间的爬虫后都想干点什么。这章的主要任务是抓取网页上的图片并把它们下载到本地,听起来不太容易实现,但其实这很简单!

注:本章爬取的网页是我的博客: http://qzmvc1.top/


一、爬取图片链接

首先要做的当然是新建爬虫项目和爬虫文件,通过图下命令即可:

scrapy startproject ImageDownloads
cd ImageDownloads
scrapy genspider image

下面我们开始着手工作!

1.1 items.py

1
2
3
4
5
import scrapy

class ImagedownloadsItem(scrapy.Item):
# image_urls是存储图片链接的元数据
image_urls = scrapy.Field()

1.2 image.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding: utf-8 -*-
import scrapy
from urllib.parse import urljoin
from scrapy.http import Request
from ImageDownloads.items import ImagedownloadsItem


class ImageSpider(scrapy.Spider):
name = 'image'
allowed_domains = ['web']
start_urls = ['http://qzmvc1.top/']

def parse(self, response):
URL1 = response.xpath('//a[@class="extend next"]//@href').extract()
for url in URL1:
yield Request(urljoin(response.url,url),dont_filter=True)

URL2 = response.xpath('//a[@class="article-title"]/@href').extract()
for url in URL2:
yield Request(urljoin(response.url,url),callback=self.parse_item,dont_filter=True)

def parse_item(self,response):
item = ImagedownloadsItem()
image_urls = response.xpath('//img/@src').extract()
item['image_urls'] = image_urls
return item

水平爬取,垂直爬取一些基本的操作感到生疏的可以复习复习我以前的博客:
第一个Scrapy项目
抽取更多的URL

parse_item()这个解析函数主要做的就是对于每一个待解析的页面(就是我们垂直爬取的内容页面),通过Xpath表达式获取该页面内所有图片的一个URL列表,并把这个列表赋值给items.py 中我们定义的元数据。

之所以需要给元数据赋值为列表,是因为管道里面需要用到列表进行迭代操作,如果不是列表爬取日志上会显示 raise ValueError(‘Missing scheme in request url: %s’ % self._url) 这个错误信息。

所以,image.py 主要是获取每张页面的图片链接,并返回每个Item。

1.3 pipelines.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# -*- coding: utf-8 -*-

from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
from scrapy import Request
from pymysql import cursors
from twisted.enterprise import adbapi
from urllib.parse import unquote
import random

class ImagedownloadsPipeline(ImagesPipeline):
# 迭代获取图片URL链接并进行下载
def get_media_requests(self,item,info):
for url in item['image_urls']:
try:
if 'http' not in url:
continue
else:
yield Request(url)
except Exception as e:
print(e)

"""
文件下载完成之后,返回一个列表 results
列表中是一个元组,第一个值是布尔值,请求成功或失败,第二个值是下载到的资源
"""
def item_completed(self,results,item,info):
if not results[0][0]:
raise DropItem('Download failure!')
return item



class MysqlPipeline(object):
lst = set()
@classmethod
def from_settings(cls,settings):
adbparams = dict(
host = settings['MYSQL_HOST'],
user = settings['MYSQL_USER'],
password = settings['MYSQL_PASSWORD'],
db = settings['MYSQL_DB'],
cursorclass = cursors.DictCursor
)
dbpool = adbapi.ConnectionPool('pymysql',**adbparams)
return cls(dbpool)

def __init__(self,dbpool):
self.dbpool = dbpool

def process_item(self,item,spider):
query = self.dbpool.runInteraction(self.do_insert,item)
query.addErrback(self.handle_error)
return item

def handle_error(self,failure):
print('failure')

def do_insert(self,cursor,item):
sql = "insert into image(name,url) values(%s,%s);"
urls = item['image_urls']
for i in urls:
if i not in self.lst:
cursor.execute(sql, (random.randint(0, 10000), unquote(i)))
self.lst.add(i)

我们在 Pipelines.py 中定义了两个管道。一个是用来下载图片的管道,还有一个就是将图片的地址存入到数据库的管道。如果对于存储数据到数据库不熟悉的同学可以看我的另一篇博客:

Scrapy框架(五) 将数据存储到数据库

我们主要介绍一下 ImagedownloadsPipeline 这个管道。
我们在里面定义了两个函数,这两个函数的名字都是固定的。 一个是 get_media_requests() ,还有一个是 item_completed()。

1.3.1 get_media_requests()

首先我们将图片下载到本地,需要借助Scrapy内置的ImagesPipeline这个管道。

from scrapy.pipelines.images import ImagesPipeline

我们在 get_media_requests() 中循环迭代存储图片URL的列表,通过 Request() 进行访问并下载。为什么要引入异常操作呢?这是因为我们爬取的图片中包含了一些本地图片:

如果不引入异常操作的话会报错:

1
'NoneType' object is not subscriptable

于是我们访问那些具有http请求头的URL并进行下载。

1.3.2 item_completed()

文件下载完成之后,返回一个列表 results。列表中是一个元组,第一个值是布尔值,请求成功或失败,第二个值是下载到的资源。代码很简单,非常好懂!

1.4 settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
# 管道
ITEM_PIPELINES = {
'ImageDownloads.pipelines.ImagedownloadsPipeline': 300,
'ImageDownloads.pipelines.MysqlPipeline':100,
}
# 数据库
MYSQL_HOST = "localhost"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"
MYSQL_DB = "pysqltest"

# 图片存储路径
IMAGES_STORE = 'C:\QzmVc1\Code\PyCharm\Python_Project\Python_Spider\Spider_Test1\ImageDownloads\Spider_Picture'

我们在 settings.py 中设置图片的存储路径 IMAGES_STORE ,这可以是绝对路径,也可以是相对路径。

1.5 运行

scrapy crawl images

结果如下:

注1: 图片的名字是根据URL的SHA1 值进行生成的,如果想要让名字变得更规范,可以增加file_name()函数,没什么时间了这里就先不写了,emmm…

注2: settings.py里面关于图片的下载除了下载路径其实还可以添加很多东西,像一些失效时间,缩略图的设置等等…有需求的可以看看文档。


注:我们也可以通过Crawlspider进行爬取,里面的Rules也很好用。

本章完