利用Scrapy下载WallHeaven的图片

稍微试了一下怎么使用scrapy,确实非常方便.

Scrapy的安装

安装指南可以戳这里因为安装太久了所以也忘了有啥需要的地方

开始用scrapy创建项目

在磁盘的任意位置(最好还是先新建一个文件夹)按住shift,鼠标右键打开CMD,输入以下命令

1
scrapy startproject wallheaven_download

该命令会创建包含下列内容的

1
2
3
4
5
6
7
8
9
wallheaven_download/
scrapy.cfg
tutorial/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py

这些文件分别是:

scrapy.cfg: 项目的配置文件
tutorial/: 该项目的python模块。之后您将在此加入代码。
tutorial/items.py: 项目中的item文件.
tutorial/pipelines.py: 项目中的pipelines文件.
tutorial/settings.py: 项目的设置文件.
tutorial/spiders/: 放置spider代码的目录.

定义Item

Item是保存爬取到数据的一个容易,在抓wallheaven这个项目里,我们需要保存的信息都储存在里面,主要是图片的下载地址还有在本地保存的地址.
我们编辑wallheaven文件夹里的items.py

1
2
3
4
5
6
7
8
9
import scrapy
class WallheavenDownloadItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
image_paths = scrapy.Field()

编写第一个爬虫

编辑wallheaven文件夹里的wallheaven_spider.py
一个正常的spider需要包含以下三个属性:

  • name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同 的Spider设定相同的名字。
  • start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
  • parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
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
class WallHeaven(Spider):
name = 'wallheaven_image' #spider的名字
allowed_domains = ['wallhaven.cc']
start_urls = ['http://alpha.wallhaven.cc/random?page=1'] # 需要爬取的原始页面
for i in range(2, 5288):
new_url = 'http://alpha.wallhaven.cc/random?page=' + str(i)
start_urls.append(new_url) # 生成包含所有需要下载的URL的列表
def parse(self, response):
req = []
hxs = Selector(response)
page_urls = hxs.xpath('//div[@id="thumbs"]//ul/li/figure/a/@href').extract() # 提取抓取页面的所有的image的链接,可以用chrome F12模式,找到图片,右键复制XPATH
for url in page_urls:
r = Request(url, callback=self.parse_image_page)
req.append(r)
return req
def parse_image_page(self, response):
hxs = Selector(response)
download_link = hxs.xpath('//img[@id="wallpaper"]/@src').extract() #打开某一张图片所在页面获取下载地址
# 特别注意一下download_link返回的是一个list
for link in download_link:
real_download_link = re.split(r'//', link)[1]
postfix = re.split(r'/', link)[-1]
Item = WallheavenDownloadItem() #将获取到的下载地址存到ITEM里面,item结构类似于字典
Item['image_urls'] = ["http://" + real_download_link]
return Item

修改配置文件settings.py

1
2
3
4
5
BOT_NAME = 'wallheaven_download'
SPIDER_MODULES = ['wallheaven_download.spiders']
NEWSPIDER_MODULE = 'wallheaven_download.spiders'
ITEM_PIPELINES = {'wallheaven_download.pipelines.MyImagesPipeline': 1} # 处理图片下载的管道,将对应的权值调大
IMAGES_STORE = './image'

下载图片

Scrapy提供了一个Item pipeline用来下载这个项目的图片

这条管道,被称作图片管道,在 ImagesPipeline 类中实现,提供了一个方便并具有额外特性的方法,来下载并本地存储图片:
将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
避免重新下载最近已经下载过的图片
缩略图生成
检测图像的宽/高,确保它们满足最小限制

在我们这个项目里没有下载缩略图,壁纸当然要下载原始的

使用图片管道

当使用 ImagesPipeline ,典型的工作流程如下所示:

在一个爬虫里,你抓取一个项目,把其中图片的URL放入 image_urls 组内,就是我们在spider里面做的
项目从爬虫内返回,进入项目管道。
当项目进入 ImagesPipeline,image_urls 组内的URLs将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,当优先级更高,会在其他页面被抓取前处理。项目会在这个特定的管道阶段保持“locker”的状态,直到完成图片的下载(或者由于某些原因未完成下载)。
当图片下载完,另一个组(images)将被更新到结构中。这个组将包含一个字典列表,其中包括下载图片的信息,比如下载路径、源抓取地址(从 image_urls 组获得)和图片的校验码。 images 列表中的图片顺序将和源 image_urls 组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在 images 组中。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class MyImagesPipeline(ImagesPipeline):
def file_path(self, request, response=None, info=None):
image_guid = request.url.split('/')[-1]
return 'full/%s' % (image_guid) #生成下载图片的名字
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):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item
class WallheavenDownloadPipeline(object):
def process_item(self, item, spider):
return item

最后在根目录输入

1
scrapy crawl wallheaven_image

就可以了
在目录下会新建一个full文件夹,图片会源源不断的下载进来
不过有一个问题,停不下来,接不上去,我还没想好怎么去解决,sigh