• Scrapy 下载多层请求、多页图片 (重写get_media_requests、file_path方法)


    思路

      	1. 确定数据结构 item
      	2. 写爬虫程序 spider
      		① 每一页的每一个详情页 url
      		② 翻页
      		③ 详情页匹配目标数据
      	3. 管道处理数据 piplines
      		① 保存到 excel
      		② 下载图片
      	4. 配置设置 settings
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ① items.py

    import scrapy
    
    class BizhiItem(scrapy.Item):
        image_urls = scrapy.Field()  # 如果要调用 ImagesPipeline 的 get_media_requests并且不重写它的返回值(是一个Requests请求对象列表),必须要用 imag_urls 这个数据名(而且该数据必须传送来一个 list列表)!
        image = scrapy.Field()  # 暂时用不到 # list类型,用于保存下载的image对象,该字段无需赋值,由scrapy自动完成
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 如果涉及下列方式下载图片

      • 1.用 ImagesPipeline 的默认方法下载或者不修改 ImagesPipeline 的 get_media_requests的返回值

      • 2.重写 ImagesPipeline 的 get_media_requests 方法,但不修改其返回值(即返回值还是一个 Request请求对象列表)
        那么item中存储图片路径字段必须是 list! 即:这里要下载的图片(即使只有一张) url 必须命名为 image_urls 放到 list 中传送给 item

    除非重写 ImagesPipeline 的 get_media_requests 的方法的返回值(本是一个请求对象的列表,修改为返回单个Request请求对象即可)

    ② photo.py (spiders)

    import scrapy
    from ..items import BizhiItem
    
    class PhotoSpider(scrapy.Spider):
        name = 'photo'
        allowed_domains = ['wallpaperscraft.com']
        # start_urls = ['http://wallpaperscraft.com']
    
        domain = 'https://wallpaperscraft.com'
        page_url = 'https://wallpaperscraft.com/all/page'
        page = 1
    
        def start_requests(self):
            main_url = 'https://wallpaperscraft.com/all/page1'
            yield scrapy.Request(url=main_url, callback=self.parse_page_html)
    
        def parse_page_html(self, response):
            detail_urls = response.xpath('/html/body/div[1]/div[2]/div[2]/div/div[2]/div[1]/ul/li/a/@href').extract()
            # print(detail_urls)
            for detail_url in detail_urls:
                detail_url = self.domain + detail_url
                print('正在请求详情页:' + detail_url + '...')
                yield scrapy.Request(url=detail_url, callback=self.parse_detail_html)
    
            if self.page < 21:
                self.page += 1
                page_url = self.page_url + str(self.page)
                yield scrapy.Request(url=page_url, callback=self.parse_page_html)
    
        def parse_detail_html(self, response):
            image_url = response.xpath('/html/body/div[1]/div[2]/div[2]/div/div[2]/div[1]/div[1]/img/@src').extract_first()
            print('请求下载: ' + image_url)
            yield BizhiItem(image_urls=[image_url])
            # 如果要用 ImagesPipeline 的默认方法下载或者不修改 ImagesPipeline 的 get_media_requests的返回值
            # 那么这里要下载的图片(即使只有一张) url 必须命名为 image_urls 放到 list 中传送给 item
            # 除非重写 ImagesPipeline 的 get_media_requests 的方法的返回值(是一个请求对象的列表)
    
    
    • 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

    ③ piplines.py

    import os.path
    import xlwt
    
    # 保存到 excel
    class BizhiPipeline:
        def open_spider(self, spider):
            self.workbook = xlwt.Workbook()
            self.worksheet = self.workbook.add_sheet('sheet1')
            self.line_cnt = 0
            self.col_name = ['image_urls']
            # 写入表头
            self.worksheet.write(self.line_cnt, 0, self.col_name[0])
            self.line_cnt += 1
    
        def process_item(self, item, spider):
            try:
                # 写入数据
                for i in range(1):
                    self.worksheet.write(self.line_cnt, i, item[self.col_name[i]][0])
                self.line_cnt += 1
                self.workbook.save('wall_paper.xls')
    
                return item  # 必须加,否则其他管道就无法获得item了!
            except Exception as e:
                print('写入失败!有残缺数据!已自动跳过!')
    
    
    from scrapy.pipelines.images import ImagesPipeline
    from bizhi import settings  # 记得把根目录标记为: 源/根 后导入(此处没用到)
    class DownloadImagePipeline(ImagesPipeline):
        def get_media_requests(self, item, info):
            # 直接推送 request请求(重写了返回值)
            # yield scrapy.Request(url=item['img_url'], meta={'folder': item['img_url'].split('/')[-1].split('_')[0],
            #                                                 'name': item['img_url'].split('/')[-1]})
            
            request_list = super().get_media_requests(item, info)  # 调用父类的方法获得请求对象列表
            for request in request_list:
                request.item = item  # 给 request请求对象 增加一个属性 item,用于在其他函数中可以用 item
                # request.item  = item 直接给每一个 Request对象 添加一个 item属性;
                # request.meta = {"item": item} 这里不行!meta为只读属性!想法是给Requests的meta属性添加值,效果一样,都是为了传参用,但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
    
            return request_list
            
            # 或直接
            # urls = ItemAdapter(item).get(self.images_urls_field, [])
            # return [Request(u,meta = {'item' : item}) for u in urls]  # 加个meta
    
        # 重命名,若不重写这函数,图片名为哈希,就是一串乱七八糟的名字
        def file_path(self, request, response=None, info=None):
            # category = request.item['image_urls'][0].split('/')[-1].split('_')[0]
            # dir_category = os.path.join(settings.IMAGES_STORE,category)  # 没必要
            # name = request.item['image_urls'][0].split('/')[-1]
            # print(name)
            # file_path = os.path.join(dir_category, name)
            # return file_path
            
    		# 在 settings.py 中设置了 IMAGES_STORE = 'D:\img6'后,储存路径就会定位到此处,再加入类别目录即可
            category = request.item['image_urls'][0].split('/')[-1].split('_')[0]
            name = request.item['image_urls'][0].split('/')[-1]
            file_path = os.path.join(category, name)
            return file_path
    
    
    • 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
    • meta 为只读属性
    # request.meta = {"item": item} 这里不行!meta为只读属性!
    想法是给Requests的meta属性添加值,效果一样,都是为了传参用,
    但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
    
    • 1
    • 2
    • 3
    • 导入 settings 失败
     from bizhi import settings  # 记得把根目录标记为: 源/根 后导入
    
    • 1

    原因: 这个报错的意思是:试图在顶级包(top-level package)之外进行相对导入。也就是说相对导入只适用于顶级包之内的模块

    由于在"顶层模块"之外引用包,这里用到"顶层模块"的概念,“顶层模块” 是这执行文件同级的文件

    from . import XXX
      或者 
    from .. import XXX
    
      时会遇到这样两个错误:
    SystemError: Parent module '' not loaded, cannot perform relative impor
      和   
    ValueError: attempted relative import beyond top-level package
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。

    文件夹被python解释器视作package需要满足两个条件:

    • 1、文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件

    • 2、不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口 模块的__name__ 不能等于__main__)

    很多时候就是导入和当前执行的py文件同级的package中的模块时报错: attempted relative import beyond top-level package

    此时,该包作为顶层模块(和执行文件同级),已经不被视为一个package了,需要将他们的父级目录标记为源/根

    补充:在"from YY import XX"这样的代码中,无论是XX还是YY,只要被python解释器视作package,就会首先调用该package的__init__.py文件。如果都是package,则调用顺序是YY,XX。

    也就是说 你不能在一个x.py 文件中 执行 from .模块名 import * 同时运行 python x.py

    另外,练习中“from . import XXX”和“from … import XXX”中的’.‘和’…‘,可以等同于linux里的shell中’.‘和’…'的作用,表示当前工作目录的package和上一级的package。

    Pycharm中的解决方案:把根目录标记为: 源/根 后导入(右键相应文件夹,选择 Mark Directory as Sources 即可)

    ④ settings.py

    BOT_NAME = 'bizhi'
    
    SPIDER_MODULES = ['bizhi.spiders']
    NEWSPIDER_MODULE = 'bizhi.spiders'
    
    
    ROBOTSTXT_OBEY = False
    
    ITEM_PIPELINES = {
       'bizhi.pipelines.BizhiPipeline': 300,
       # 'scrapy.pipelines.images.ImagesPipeline':1,           #自动保存时使用,引入ImagesPipeline,优先级设为最高
       'bizhi.pipelines.DownloadImagePipeline':1,
    }
    
    DOWNLOAD_DELAY = 2
    RANDOMIZE_DOWNLOAD_DELAY = True
    
    IMAGES_STORE = 'D:\img6'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ⑤ start.py (启动文件)

    from scrapy import cmdline
    cmdline.execute('scrapy crawl photo'.split(' '))
    
    
    • 1
    • 2
    • 3

    Scrapy 基础链接: Python爬虫|Scrapy 基础用法

  • 相关阅读:
    【Python_Zebra斑马打印机编程学习笔记(五)】基于zebra控制斑马打印机实现自动化打印标贴
    「零基础从零开始写VO视觉里程计」曲线拟合g2oCurveFitting.cpp(7-3)
    Springboot知识点必知必会(一)
    MySQL高级-SQL优化- update 优化(尽量根据主键/索引字段进行数据更新,避免行锁升级为表锁)
    Pro_11丨跟踪+目标出场自适应切换
    十年前对敏捷开发的体会
    C++ 虚函数表和虚函数表指针的创建时机
    工厂是否需要单独的设备管理部门
    【算法与数据结构】669、LeetCode修剪二叉搜索树
    64.C++运算符重载
  • 原文地址:https://blog.csdn.net/Syc1102g/article/details/126168194