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

思路

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

① 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.用 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 的方法的返回值(是一个请求对象的列表)

③ 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

  • meta 为只读属性
# request.meta = {"item": item} 这里不行!meta为只读属性!
想法是给Requests的meta属性添加值,效果一样,都是为了传参用,
但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
  • 导入 settings 失败
 from bizhi import settings  # 记得把根目录标记为: 源/根 后导入

原因: 这个报错的意思是:试图在顶级包(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

其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,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'

⑤ start.py (启动文件)

from scrapy import cmdline
cmdline.execute('scrapy crawl photo'.split(' '))

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值