Scrapy爬虫——壁纸爬虫

说在前面:

今天把之前写过的壁纸爬虫拿出来用了一下,发现是很久以前写过的,用的还是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

原文地址:https://www.cnblogs.com/GF66/p/9785474.html