说在前面:
今天把之前写过的壁纸爬虫拿出来用了一下,发现是很久以前写过的,用的还是python基本库urllib去做网络请求的,正好最近在学scrapy,于是就用scrapy框架重新写了一遍。
环境要求:
python:3.6
Scrapy:1.51
正式开始:
感谢壁纸来源Wallhaven
网页分析
通过分析可以得知,只需要给https://alpha.wallhaven.cc/latest?page=后加上数字即可获取到该页数的图片的url,并且可以知道图片的url是由https://alpha.wallhaven.cc/wallpaper/加数字组成
进入到图片url后,通过网页源码可以定位到图片的信息,通过scrapy的xpath选择器可以轻松的获取图片的真实url
到这里,对网页分析完毕,开始代码实现。
实现爬虫
首先创建scrapy项目,在items文件中添加本次项目需要的item,其中type是照片的格式,在存储的时候会用到
class WallhavenSpdierItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
url = scrapy.Field()
type = scrapy.Field()
在spiders文件中新建spider.py文件。
scrapy框架中有Spider类、CrawlSpider类以及其他的爬虫类可以使用,我这里使用的是CrawlSpider,通过与Rule配合使用,可以实现全站的爬取。
class Spider(CrawlSpider):
name = 'wallhaven'
allowed_domains = ['alpha.wallhaven.cc']
bash_url = 'https://alpha.wallhaven.cc/latest?page='
def start_requests(self):
for i in range(1, PAGE_NUMBER+1):
yield Request(self.bash_url + str(i))
PAGE_NUMBER在新增的config配置文件中,代表着爬取的页数。
start_requests使用爬取页数构造了所有需要爬取页码url,然后通过Request发出请求。
rules = (
Rule(LinkExtractor(allow=('https://alpha.wallhaven.cc/wallpaper/d{1,6}',)), callback='parse_item'),
)
不清楚Rule和LinkExtractor,请移步官方文档查看。
上一步网页分析时说过,图片的url的组成,所以使用在基础url后加上d{1,6}$来提取出图片的url,之后传给回调函数做进一步处理。
def parse_item(self, response):
item = WallhavenSpdierItem()
tags = response.xpath('//*[@id="tags"]/child::li/a/text()').extract()
ban_tags = BAN_TAGS
for tag in ban_tags:
if tag in tags:
return
item['url'] = str('https:' + response.xpath('//*[@id="wallpaper"]/@src').extract_first(default=0))
item['name'] = item['url'].split('/')[-1].split('.')[0]
item['type'] = item['url'].split('/')[-1].split('.')[1]
yield item
对xpath不清楚,请移步w3cschool
创建item对象,然后通过xpath选择器选出你需要的部分,加入到item中,最后返回item给下载器即可。
注意
我还提取了图片的标签,就是tags。因为我发现爬下的很多壁纸都是人物写真等不适合当作壁纸的图片,所以我选择过滤了那些带有标签的图片。BAN_TAGS同样在config配置文件中。
实现下载管道
scrapy可以通过ImagesPipeline类来实现图片下载管道,官网中也有简单的例子。例子中使用了item_completed和get_media_requests两个方法来实现下载。
item_completed方法是检测item中是否存在图片url
get_media_requests方法是提取出item中url再进行下载
class WallhavenSpdierPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
"""
:param item: spider.py中返回的item
:param info:
:return:
"""
img_url = item['url']
if img_url == 0:
return
yield Request(img_url, meta={'item': item})
在这里,我直接使用了get_media_requests方法,在方法内部对url进行检验,然后对图片真实的url发出了最后请求。
def file_path(self, request, response=None, info=None):
item = request.meta['item']
folder = item['name']
type = item['type']
filename = u'full/{0}.{1}'.format(folder, type)
return filename
同时,还使用了file_path方法,使用item中的name和type构建了文件名,完成下载。
注意:
如果只是写好了这些,就去运行爬虫,并不能下载图片到本地,还需要在settings文件中做最后的配置。
IMAGES_EXPIRES = 30 # 30天内不下载重复文件 如果不设置默认90天
IMAGES_STORE = 'E:p\' # 图片保存的路径
IMAGES_THUMBS = { # 图片缩略图
'small': (50, 50),
'big': (270, 270)
}
如果设置了缩略图,那么会在图片路径下,生成thumbs文件夹,其中有big和small两个文件夹。
修改:
经过检查,我发现直接使用了item_completed和get_media_requests两个方法来下载图片,会压缩图片的质量,比如之前大小为2.6mb大小的壁纸,直接压缩到160kb,这样极其影响当作壁纸的使用体验,所以我有下面两个想法进行改进:
- 使用自定义管道
- 重写父类方法
上面两种方法我都进行了实现,第一种自定义管道如下:
class WallhavenSpdierPipeline(object):
def process_item(self, item, spider):
url = item['url']
name = item['name']
type = item['type']
filepath = IMAGES_STORE + '/full/{0}.{1}'.format(name, type)
agent = random.choice(agents)
headers = {
'User-Agent': agent
}
fp = open(filepath, 'wb')
image = requests.get(url, headers=headers).content
fp.write(image)
fp.close()
直接使用requests请求url,然后直接写入文件。但是因为请求url需要等待一个响应的完成,又加上对于网址的连接本身效率就比较低,于是我又研究了下ImagesPipeline的源码,进行了第二次修改:
def get_images(self, response, request, info):
path = IMAGES_STORE + self.file_path(request, response=response, info=info)
orig_image = response.body
fp = open(path, 'wb')
fp.write(orig_image)
fp.close()
return None, None, None
因为scrapy本身是异步爬虫,而且在方法中已经对图片的url请求好,返回了response,那么使用起来,效率最高。就是在父类中,有其他方法调用该方法,所以会报错误,但是在父类中处理下异常即可。
可能会有些更好的方法,希望大家可以评论在下面。
最后
源码放在Github上了,希望喜欢或者觉得有用的朋友点个star或者follow。
有任何问题可以在下面评论或者通过私信或联系方式找我。
联系方式
QQ:791034063
Wechat:liuyuhang791034063
CSDN:https://blog.csdn.net/Sun_White_Boy
Github:https://github.com/liuyuhang791034063