IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    小白进阶之Scrapy第四篇(图片下载管道篇)

    哎哟卧槽发表于 2017-04-23 16:37:29
    love 0

    这几天一直有小伙伴而给我吐槽说,由于妹子图站长把www.mzitu.com/all这个地址取消了。导致原来的那个采集爬虫不能用啦。

    正好也有小伙伴儿问Scrapy中的图片下载管道是怎么用的。

    就凑合在一起把mzitu.com给重新写了一下。

    首先确保你的Python环境已安装 Scrapy!!!!!!!!

    命令行下进入你需要存放项目的目录并创建项目:

    比如我放在了D:\PycharmProjects

    D:
    cd PycharmProjects
    scrapy startproject mzitu_scrapy

    我是Windows!其余系统的伙伴儿自己看着办哈。

    这都不会的小伙伴儿,快去洗洗睡吧。养足了精神从头看一遍教程哈!

    在PyCharm中打开我们的项目目录。

    在mzitu_scrapy目录创建run.py。写入以下内容:

    from scrapy.cmdline import execute
    execute(['scrapy', 'crawl', 'mzitu'])

    其中的 mzitu 就为待会儿spider.py文件中的name属性。这点请务必记住哦!不然是跑不起来的。

    在mzitu_scrapy\spider目录中创建spider.py。文件作为爬虫文件。

    好了!现在我们来想想,怎么来抓mzitu.com了。

    首先我们的目标是当然是全站的妹子图片!!!

    但是问题来了,站长把之前那个mzitu.com\all 这个URL地址给取消了,我们没办法弄到全部的套图地址了!

    我们可以去仔细观察一下站点所有套图的地址都是:http://www.mzitu.com/几位数字结尾的。 这种格式地址。

    有木有小伙伴儿想到了啥?

    CrawlSpider !!!就是这玩儿!!

    有了它我们就能追踪“http://www.mzitu.com/几位数字结尾的”这种格式的URL了。

    Go Go Go Go!开始搞事。

    首先在item.py中新建我们需要的字段。我们需要啥?我们需要套图的名字和图片地址!!

    那我们新建两个字段:

    import scrapy
    
    
    class MzituScrapyItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        name = scrapy.Field()
        image_urls = scrapy.Field()

    第一步完成啦!开始写spider.py啦!

    首先导入我们需要的包:

    from scrapy import Request
    from scrapy.spider import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    from mzitu_scrapy.items import MzituScrapyItem

    都是干啥的我不说了哈!不知道的小伙伴儿自己去翻翻官方文档。

    接下来是:

    class Spider(CrawlSpider):
        name = 'mzitu'
        allowed_domains = ['mzitu.com']
        start_urls = ['http://www.mzitu.com/']
        img_urls = []
        rules = (
            Rule(LinkExtractor(allow=('http://www.mzitu.com/\d{1,6}',), deny=('http://www.mzitu.com/\d{1,6}/\d{1,6}')), callback='parse_item', follow=True),
        )

    第五行的img_urls=[] 这个列表是我们之后用来存储每个套图的全部图片的URL地址的。

    rules中的语句是:匹配http://www.mzitu.com/1至6位数的的URL(\d:数字;{1,6}匹配1至6次。就能匹配出1到6位数)

    但是我们会发现网页中除了http://www.mzitu.com/XXXXXXX 这种格式的URL之外;还有 http://www.mzitu.com/XXXX/XXXX 这个格式的URL。所以我们需要设置 deny来不匹配http://www.mzitu.com/XXXX/XXXX这种格式的URL。

    然后将匹配到的网页交给parse_item来处理。并且持续追踪

    看这儿敲黑板!!划重点!!:::

    重点说明!!!!不能parse函数!!这是CrawlSpider进行匹配调用的函数,你要是使用了!rules就没法进行匹配啦!!!

    现在spider.py是这样的:

    from scrapy import Request
    from scrapy.spider import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    from mzitu_scrapy.items import MzituScrapyItem
    
    
    class Spider(CrawlSpider):
        name = 'mzitu'
        allowed_domains = ['mzitu.com']
        start_urls = ['http://www.mzitu.com/']
        img_urls = []
        rules = (
            Rule(LinkExtractor(allow=('http://www.mzitu.com/\d{1,6}',), deny=('http://www.mzitu.com/\d{1,6}/\d{1,6}')), callback='parse_item', follow=True),
        )
    
    
        def parse_item(self, response):
            print(response.url)

    来跑一下试试 别忘了怎么测试的哈!!上面新建的那个run.py!

    Good!!真棒!全是我们想要的!!!

    现在干啥?啥?你不知道?EXM你没逗我吧!

    当然是解析我们拿到的response了!从里面找我们要的套图名称和所有的图片地址了!

    我们随便打开一个URL。

    首先用xpath取套图名称:

    啥?你不知道怎么用xpath??少年少女 你走吧。出去别说看过我的博文。

    ./*//div[@class=’main’]/div[1]/h2/text() 这段xpath就是套图名称的xpath了!看不懂的少年少女赶快去http://www.w3school.com.cn/看看xpath的教程!

    当然你直接用Chrome拷贝出来的那个xpath也行。(有一定的概率不能使)

    现在来找图片地址了,怎么找我在 小白爬虫第一弹中已经写过了哈!这就不详细赘述了!

    首先找到每套图有多少张图片:

    就是红框中的那个东东。

    Xpath这样写:

    descendant::div[@class='main']/div[@class='content']/div[@class='pagenavi']/a[last()-1]/span/text()

     

    意思是选取根节点下面所有后代标签,在其中选取出 div[@class=’main’]下面的div[@class=’content’]下面的/div[@class=’pagenavi’]下面的倒数第二个a标签 下面的span标签中的文本。(有点长哈哈哈哈哈!其实还可以短一些,我懒就不改了)

    然后循环拼接处每张图片的的网页地址,现在spider.py是这样:

    from scrapy import Request
    from scrapy.spider import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    from mzitu_scrapy.items import MzituScrapyItem
    
    
    class Spider(CrawlSpider):
        name = 'mzitu'
        allowed_domains = ['mzitu.com']
        start_urls = ['http://www.mzitu.com/']
        img_urls = []
        rules = (
            Rule(LinkExtractor(allow=('http://www.mzitu.com/\d{1,6}',), deny=('http://www.mzitu.com/\d{1,6}/\d{1,6}')), callback='parse_item', follow=True),
        )
    
    
        def parse_item(self, response):
            """
            :param response: 下载器返回的response
            :return:
            """
            item = MzituScrapyItem()
            # max_num为页面最后一张图片的位置
            max_num = response.xpath("descendant::div[@class='main']/div[@class='content']/div[@class='pagenavi']/a[last()-1]/span/text()").extract_first(default="N/A")
            item['name'] = response.xpath("./*//div[@class='main']/div[1]/h2/text()").extract_first(default="N/A")
            for num in range(1, int(max_num)):
                # page_url 为每张图片所在的页面地址
                page_url = response.url + '/' + str(num)
                yield Request(page_url, callback=self.img_url)

    extract_first(default=”N/A”)的意思是:取xpath返回值的第一个元素。如果xpath没有取到值,则返回N/A

    然后调用函数img_url来提取每个网页中的图片地址。img_url长这样:

    def img_url(self, response,):
            """取出图片URL 并添加进self.img_urls列表中
            :param response:
            :param img_url 为每张图片的真实地址
            """
            img_urls = response.xpath("descendant::div[@class='main-image']/descendant::img/@src").extract()
            for img_url in img_urls:
                self.img_urls.append(img_url)

     

    descendant::div[@class=’main-image’]/descendant::img/@src这段xpath取出div[@class=’main-image’]下面所有的img标签的src属性(有的套图一个页面有好几张图)

    .extract()不跟上[0]返回的是列表

    完整的spider.py如下:

    from scrapy import Request
    from scrapy.spider import CrawlSpider, Rule
    from scrapy.linkextractors import LinkExtractor
    from mzitu_scrapy.items import MzituScrapyItem
    
    
    class Spider(CrawlSpider):
        name = 'mzitu'
        allowed_domains = ['mzitu.com']
        start_urls = ['http://www.mzitu.com/']
        img_urls = []
        rules = (
            Rule(LinkExtractor(allow=('http://www.mzitu.com/\d{1,6}',), deny=('http://www.mzitu.com/\d{1,6}/\d{1,6}')), callback='parse_item', follow=True),
        )
    
    
        def parse_item(self, response):
            """
            :param response: 下载器返回的response
            :return:
            """
            item = MzituScrapyItem()
            # max_num为页面最后一张图片的位置
            max_num = response.xpath("descendant::div[@class='main']/div[@class='content']/div[@class='pagenavi']/a[last()-1]/span/text()").extract_first(default="N/A")
            item['name'] = response.xpath("./*//div[@class='main']/div[1]/h2/text()").extract_first(default="N/A")
            for num in range(1, int(max_num)):
                # page_url 为每张图片所在的页面地址
                page_url = response.url + '/' + str(num)
                yield Request(page_url, callback=self.img_url)
            item['image_urls'] = self.img_urls
            yield item
    
    
        def img_url(self, response,):
            """取出图片URL 并添加进self.img_urls列表中
            :param response:
            :param img_url 为每张图片的真实地址
            """
            img_urls = response.xpath("descendant::div[@class='main-image']/descendant::img/@src").extract()
            for img_url in img_urls:
                self.img_urls.append(img_url)

    下面开始把图片弄回本地啦!!

    开写我们的pipelines.py

    首先根据官方文档说明我们如果需要使用图片管道 则需要使用ImagesPipeline:

    我们可以依葫芦画瓢写一个。但是这样有一个很麻烦的问题就是,这样下载下来的图片没有分类,很是难看啊!

    所以 我们需要重写一下ImagesPipeline中的file_path方法!

    具体如下:

    from scrapy import Request
    from scrapy.pipelines.images import ImagesPipeline
    from scrapy.exceptions import DropItem
    import re
    
    
    class MzituScrapyPipeline(ImagesPipeline):
    
        def file_path(self, request, response=None, info=None):
            """
            :param request: 每一个图片下载管道请求
            :param response: 
            :param info: 
            :param strip :清洗Windows系统的文件夹非法字符,避免无法创建目录
            :return: 每套图的分类目录
            """
            item = request.meta['item']
            folder = item['name']
            folder_strip = strip(folder)
            image_guid = request.url.split('/')[-1]
            filename = u'full/{0}/{1}'.format(folder_strip, image_guid)
            return filename
    
        def get_media_requests(self, item, info):
            """
            :param item: spider.py中返回的item
            :param info: 
            :return: 
            """
            for img_url in item['image_urls']:
                yield Request(img_url, meta={'item': item})
    
    
        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")
            return item
    
        # def process_item(self, item, spider):
        #     return item
    
    def strip(path):
        """
        :param path: 需要清洗的文件夹名字
        :return: 清洗掉Windows系统非法文件夹名字的字符串
        """
        path = re.sub(r'[?\\*|“<>:/]', '', str(path))
        return path

     

    最后一步设置ImagesPipeline的存储目录!

    在settings.py中写入:

    IMAGES_STORE = 'F:\mzitu\\'

    则ImagesPipeline将所有下载的图片放置在此目录下!

    设置图片实效性:

    图像管道避免下载最近已经下载的图片。使用 FILES_EXPIRES (或 IMAGES_EXPIRES) 设置可以调整失效期限,可以用天数来指定:

    在settings.py中写入以下配置。

    # 30 days of delay for images expiration
    IMAGES_EXPIRES = 30

     

    settings.py中开启item_pipelines:

    ITEM_PIPELINES = {
       'mzitu_scrapy.pipelines.MzituScrapyPipeline': 300,
    }

     

    如果你需要缩略图之类的请参考官方文档:

    将其写入settings.py文件中。

    至此完毕!!!

    来看看效果:

     

    下载速度简直飞起!!友情提示:请务必配置代理哦!

    可以参考大才哥的http://cuiqingcai.com/3443.html做一个代理,就不需要重写Scrapy中间件啦!更能避免费代理总是不能用的坑爹行为。

    总之省事省时又省心啊!

    转载请注明:静觅 » 小白进阶之Scrapy第四篇(图片下载管道篇)



沪ICP备19023445号-2号
友情链接