Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

p.s.高产量博主,点个关注不迷路!(文章较长,赶时间可以点个收藏或直接跳转完整源码)

目录

I. 实战需求分析与思路

II. 接口的获取与scrapy项目的创建

III.items数据结构文件配置

IV. 爬虫文件的书写

V. 管道的配置

VI. 多页下载处理

VII. 完整源码


I. 实战需求分析与思路

首先,笔记承接上一篇,我们知道一个完整的scrapy框架项目文件有六个部分:

1️⃣ Spiders文件夹:这文件夹我们不陌生,因为每一次新建scrapy爬虫项目后,我们都需要终端进入Spiders文件夹,生产爬虫文件。在Spiders文件夹下,又有两个文件,一个是_init_.py文件,一个是tc.py。_init_.py文件是我们创建项目时默认生成的一个py文件,我们用不到这个py文件,因此我们可以忽略它,另一个tc.py文件是我们爬虫的核心文件,后续的大部分代码都会写入这个文件,因此它是至关重要的py文件。

2️⃣_init_.py文件:它和上面提到的Spiders文件夹下的_init_.py一样,都是不被使用的py文件,无需理会。

3️⃣ items.py文件:这文件定义了数据结构,这里的数据结构与算法中的数据结构不同,它指的是爬虫目标数据的数据组成结构,例如我们需要获取目标网页的图片和图片的名称,那么此时我们的数据组成结构就定义为 图片、图片名称。后续会专门安排对scrapy框架定义数据结构的学习。

4️⃣ middleware.py文件:这py文件包含了scrapy项目的一些中间构件,例如代理、请求方式、执行等等,它对于项目来说是重要的,但对于我们爬虫基础学习来说,可以暂时不考虑更改它的内容。

5️⃣ pipelines.py文件:这是我们之前在工作原理中提到的scrapy框架中的管道文件,管道的作用是执行一些文件的下载,例如图片等,后续会安排对scrapy框架管道的学习,那时会专门研究这个py文件。

6️⃣ settings.py文件:这文件是整个scrapy项目的配置文件,里面是很多参数的设置,我们会偶尔设计到修改该文件中的部分参数,例如下一部分提到的ROBOTS协议限制,就需要进入该文件解除该限制,否则将无法实现爬取。

本次笔记重点针对于pipelines.py文件与items.py文件的使用进行讲解,并辅以实战。首先我们简单做一下实战的需求分析:

我们打开当当网的首页,之后任意点击一种图书,例如这里点击【青春文学】: 

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

之后可以再选择一个子类,例如我们选择【爱情/情感】:

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

之后我们的实战需求是把最终打开的这个页面中的图书图片、价格、图片名称一起下载下来,要求必须同步下载数据(开启多管道),并且能实现多页下载。 

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

于是我们可以做简单的分析:

首先,我们需要创建项目文件,生成爬虫文件;接下来,从response中提取我们需要的数据;最后是把数据放入管道下载,这是一个简单的思路,省略了一些步骤,但总体上来看思路是可行的。

最后我们应该能获得:

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】


II. 接口的获取与scrapy项目的创建

接下来,我们开始创建项目

打开之前创建过的文件夹,用终端进入这个文件夹(如果之前笔记没有看过的朋友,直接新建一个空文件夹即可,之后用终端进入该文件夹):

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

运行项目生成指令:

scrapy startproject dangdang

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

之后我们不着急新建爬虫文件,因为此时我们缺少目的地的url,于是我们需要简单的抓一下接口:

这个接口不需要通过F12检查网页,只需要直接复制刚才进入的【爱情/情感】页面的url即可(通过F12也可以找到接口,接口就是这个页面的url,因此无需用F12)

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

http://category.dangdang.com/cp01.01.02.00.00.00.html

拿到之后,我们可以生成爬虫文件:

首先终端进入Spiders文件夹

cd scrapy_dangdang/scrapy_dangdang/spiders

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

运行爬虫文件生成指令
 

scrapy genspider dangdangwang http://category.dangdang.com/cp01.01.02.00.00.00.html

其中 "dangdangwang" 是生成的爬虫文件的文件名,大家可以任意起名

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

正确生成后,我们可以用pycharm打开项目文件,打开后我们点击爬虫文件dangdangwang.py,先把默认多生成的http://头和html后面的斜线都删去,否则影响它的工作。 

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

最后我们的基础操作还剩下一步:上次笔记提到了我们有一个参数:allowed_domains,这是我们整个项目爬取的url的范围,由于本次我们的需求是爬去三种数据,无法固定某一个url,因此我们修改allowed_domains的参数为:

allowed_domains = ['category.dangdang.com']

此时它代表了整个的域名,也就是域名下的其他子域名或者子url都被包括,这个操作在大型项目中也经常会遇到,我们只需要把allowed_domains的参数修改成目的网站的域名即可


III.items数据结构文件配置

接下来,我们需要配置一下items数据结构文件,此时我们先不要尝试理解它,而是先去做:

打开items.py文件:

我们刚才提到了一共要下载三样东西:图片、图片的名称以及图片对应图书的单价,于是我们可以理解成我们需要下载的目标数据结构有三种类型:图片(src)、图片名称和单价。

那么我们可以在items.py文件中写入:

    # 图片的url
    src = scrapy.Field()
    # 图片名称
    name = scrapy.Field()
    # 价格
    price = scrapy.Field()

它的格式是:数据类型的名称 = scrapy.Field(),理解起来可能初学不太容易,但是我们可以先这么写,这些内容统统写在 class ScrapyDangdangItem(scrapy.Item): 下面

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】


IV. 爬虫文件的书写

定义数据结构之后,我们可以开始准备写核心爬虫文件,在此之前,我们还需要一项工作:

分析页面源码,并得出xpath解析语句,解析之前提到的三种数据

我们回到刚才的当当网页面,按F12解析页面,选择元素检查,把鼠标放到第一本书的图片上:

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】可以看出,我们需要的图片src和名称分别在img标签的src和alt属性中,于是xpath语法是这样的:

//ul[@id = "component_59"]/li//img/@src
//ul[@id = "component_59"]/li//img/@alt

但是在当当网中,有个小陷阱,那就是当我们把鼠标放在第二张图时,可以发现它的图片的src并不在src属性下,而是在data-original中

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

于是针对第一张图片的src,我们可以采用上面的xpath语法,其他的页面的图片src,我们用下面这句语法

//ul[@id = "component_59"]/li//img/@data-original

图书的价格,我们发现可以用这句语法拿到:

//ul[@id = "component_59"]/li//p[@class = "price"]/span[1]/text()

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

xpath语句准备好之后,我们开始编写爬虫文件:

打开dangdangwang.py文件,代码书写的区域应该聚焦在parse(self,response)这个函数中(原因不做赘述,可以参考上一篇笔记)

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

我们先简化一下目标,把多页爬取先简化成爬取第一页的上述三种数据,那么关于响应与提取数据部分代码应该是这样的

 def parse(self, response):
        # 所有的selector对象都可以调用xpath方法
        li_list = response.xpath('//ul[@id = "component_59"]/li')

        # 判断一下,第一张图是src,第二张图开始在data-original里
        for li in li_list:
            src = li.xpath('.//img/@data-original').extract_first()
            if src:
                src = src
            else:
                src = li.xpath('.//img/@src').extract_first()

            name = li.xpath('.//img/@alt').extract_first()

            price = li.xpath('.//p[@class = "price"]/span[1]/text()').extract_first()

这部分需要做一个简单的注解:

按照传统的思路,例如我们要在scrapy框架下,用xpath解析目的地的图片,那么我们应该直接写上:

src = response.xpath('//ul[@id = "component_59"]/li//img/@src或@data-original')

但是这里我们没有采用这种直接的方式,而是首先用一个xpath语句:

li_list = response.xpath('//ul[@id = "component_59"]/li')

然后对这个li_list进行二次xpath解析。这是在scrapy框架下的一种特有的写法,它的依据是在scrapy框架下,.xpath()方法并不会直接返回我们的数据列表,而是会返回一个selector对象(上一篇笔记中有解释),而对于一个selector对象,可以再次xpath,于是有了这种写法:先总体xpath,解析后对每一个部分,继续在剩余的部分中进行xpath解析。


V. 管道的配置

完成xpath解析后,拿到了需要的数据。根据我们的整体思路,这些数据中的一部分需要放到管道中执行下载。回想scrapy框架的工作原理,管道负责下载一些文件,这里我们实战中,这个文件指的是图书的图片、图书的价格和名称(保存在json文件)!接下来,我们开始学习管道的配置

1️⃣ 首先,我们需要封装一下管道文件:

打开pipelines.py,并把注意力放在这个class中,更确切地说是process_item()函数中: 

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

这函数是干什么的呢?是在爬虫文件调用了管道(调用的代码后面会再解释,这里先知道是当调用的时候)时,会执行的一个函数,我们可以简单理解为被爬虫文件调用的某个函数。这个函数的主要功能就是下载

那我们首先先解决把图片的src、图片名称和图片价格三个数据写入json文件中并下载json文件这项任务,下载图片后续再处理。

class ScrapyDangdangPipeline:
    def open_spider(self, sipder):
        self.fp = open('book.json','w',encoding = 'utf-8')

    def process_item(self, item, spider):
        self.fp.write(str(item))
        return item

    def close_spider(self,spider):
        self.fp.close()

这可以由上面的代码实现。解释一下上面的部分:

首先,我们原来只有一个process_item()函数,但是如果只有一个下载的函数,我们每次向文件写入后都要执行文件关闭,下一次执行文件的打开,这会导致频繁的操作文件的打开与关闭,不利于我们的目的,于是我们采用另一种方法:在管道文件中,除了process_item()函数外,还有两个函数,分别是:open_spider()和close_spider(),这两个函数不会被初始化生成,但是我们可以手动添加这两个函数,它们的特点是:

open_spider()函数和close_spider()函数分别在项目的启动和终止时各自被调用一次。

于是我们通过这个特点,在open_spider()函数中打开文件,在close_spider()中关闭文件,在process_item()中执行写入操作,即可避免频繁的文件打开关闭。(使用self.fp能保证三个函数操作的是同一个文件对象)

比葫芦画瓢,由于要下载的还有图片,我们在初始的class下面自行定义一个新的class,并在class下定义process_item()函数,在这个process_item()函数中写上图片的下载操作

import urllib.request
# 图片下载管道:
class DangDangDownloadImgPipeline:
    def process_item(self, item, spider):
        url = 'http:' + item.get('src') # 这里是因为item本身是获取的json数据,存在字典里,所以我们用字典的get函数获取
        filename = './books/' + item.get('name') + '.jpg'
        urllib.request.urlretrieve(url = url,filename = filename)
        return item

别忘了导入urllib.request库,我们是通过这个库下载图片的(这个如果不知道的话,可以去看之前的笔记)。

所以现在的pipelines.py文件应该是这样的

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】


2️⃣ 修改settings.py文件,使管道可以被使用:

第一步中,我们为下载图片,新定义了一个class,那么可能会有一个疑惑点:我们新建的class会被项目运行吗?这里给出明确的答案如果没有第二步修改settings.py文件,那么新建的class下的代码"不会"被执行,而且甚至初始的class也不会被执行。所以第二步是必不可少的,也是容易被忽略的。

我们打开settings.py文件找到有一个被注释了的ITEM_PIPELINES字典,这就是定义管道的地方,我们首先需要解除它的注释: 

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

这个字典中,键值对的值是一个300,它的意思是管道的优先级,也就是下载的优先级 ,这个值在1-1000中,值越小,优先级越高

我们复制这个字典对象,然后在它下方粘贴一下把pipeline.ScrapyDangdangPipeline改成我们自己起的类名

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

到这里,我们完成了管道文件的配置,不过还没有完成全部内容,我们还需要填一个坑:在爬虫文件中调用管道文件


3️⃣ 在爬虫文件中调用管道文件:

首先,我们需要在爬虫文件中导入之前定义的items数据结构导入的格式是这样的:

from 项目名.items import items.py文件中的类名

对于本项目,应该是这样的:

from scrapy_dangdang.items import ScrapyDangdangItem

导入后 是这样的:

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】

这之后,可能会爆红线错误,但是我们不需要理会,继续做就好。

接下来,在爬虫文件中紧挨着刚才写的xpath解析的下一行,写上两句代码:

book = ScrapyDangdangItem(src = src,name = name,price = price)

# 获取一个book对象,就传入pipeline进行下载
yield book

第一句代码,是生成一个数据结构对应的对象,我们需要调用刚才导入的items文件下的ScrapyDangdangItem()类,这部分对于学过面向对象编程的朋友来说不难理解。

第二句代码是真正调用了管道,这句代码执行后,管道就被调用去下载文件

因此此时我们的爬虫文件是这样的:

Python爬虫学习笔记_DAY_30_1万字快速上手scrapy框架多管道数据下载【Python爬虫】


VI. 多页下载处理

最后,接近本次实战的尾声,我们对多页下载需求进行一个满足

多页时,我们首先需要在爬虫文件中修改allowed_domains为当当网的域名,这一点之前已经提到,也已经修改了,于是不做赘述。

接下来,我们发现当当网除了第一页之外,其他页面的url有下面的规律

http://category.dangdang.com/pg页码-cp01.01.02.00.00.00.html

也就是说我们下载了第一页之后,后面的页面的url可以由http://category.dangdang.com/pg + 页码 + -cp01.01.02.00.00.00.html生成。

于是我们首先在爬虫文件最上面定义全局变量:初始url段和页码page

base_url = 'http://category.dangdang.com/pg'
page = 1

在parse()函数的for循环外,写上下面的代码:

        if self.page < n:
            self.page = self.page + 1

            url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'
        # 调用parse函数
        # scrapy.Request是scrapy的get请求:
        # url是请求地址,callback是要执行的函数,不需要加圆括号
            yield  scrapy.Request(url = url,callback = self.parse)

解释一下这段代码:

我们在for循环外判断当前的page是否小于某个数nn代表我们想要下载多少页,之后我们拼接出url,用yield关键字,我们可以调用一个叫Request的函数这个函数是在执行了第一次Request请求后会调用的函数,可理解成是一个回调函数,在这个回调函数中,传参是url和需要回调的具体函数,我们很自然填入拼接后的url,回调函数我们就继续回调本函数parse即可。

也即这样的逻辑:项目第一次执行Request请求 - - - > 请求成功,回调了scrapy.Request()参数中的callback函数 - - - > callback函数仍然会发起第二次Request请求,直至page等于n结束。


VII. 完整源码

最后附上本次实战的源码:

1️⃣ 爬虫文件dangdang.py:

import scrapy
from scrapy_dangdang.items import ScrapyDangdangItem

class DangdangwangSpider(scrapy.Spider):
    name = 'dangdangwang'

    allowed_domains = ['category.dangdang.com']
    start_urls = ['http://http://category.dangdang.com/cp01.01.02.00.00.00.html/']
    
    base_url = 'http://category.dangdang.com/pg'
    
    page = 1

    def parse(self, response):
        li_list = response.xpath('//ul[@id = "component_59"]/li')
        # 判断一下,第一张图是src,第二张图开始在data-original里
        for li in li_list:
            src = li.xpath('.//img/@data-original').extract_first()
            if src:
                src = src
            else:
                src = li.xpath('.//img/@src').extract_first()
            
            name = li.xpath('.//img/@alt').extract_first()
           
            price = li.xpath('.//p[@class = "price"]/span[1]/text()').extract_first()
           
            book = ScrapyDangdangItem(src = src,name = name,price = price)

            # 获取一个book对象,就传入pipeline进行下载
            yield book

            # 多页爬取时,单页的逻辑是相通的,只需要把页的页码请求再次调用parse()函数即可
        if self.page < 5:
            self.page = self.page + 1

            url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'

            yield  scrapy.Request(url = url,callback = self.parse)

2️⃣ settings.py文件:

# Scrapy settings for scrapy_dangdang project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'scrapy_dangdang'

SPIDER_MODULES = ['scrapy_dangdang.spiders']
NEWSPIDER_MODULE = 'scrapy_dangdang.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'scrapy_dangdang (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'scrapy_dangdang.middlewares.ScrapyDangdangSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'scrapy_dangdang.middlewares.ScrapyDangdangDownloaderMiddleware': 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html

ITEM_PIPELINES = {
   'scrapy_dangdang.pipelines.ScrapyDangdangPipeline': 300,
}

ITEM_PIPELINES = {
   'scrapy_dangdang.pipelines.DangDangDownloadImgPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

3️⃣ pipelines.py文件:

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html

# useful for handling different item types with a single interface
from itemadapter import ItemAdapter

class ScrapyDangdangPipeline:
    def open_spider(self, sipder):
        self.fp = open('book.json','w',encoding = 'utf-8')

    def process_item(self, item, spider):
        self.fp.write(str(item))
        return item

    def close_spider(self,spider):
        self.fp.close()
        
import urllib.request
# 图片下载管道:
class DangDangDownloadImgPipeline:
    def process_item(self, item, spider):
        url = 'http:' + item.get('src') # 这里是因为item本身是获取的json数据,存在字典里,所以我们用字典的get函数获取
        filename = './books/' + item.get('name') + '.jpg'
        urllib.request.urlretrieve(url = url,filename = filename)
        return item

到此,本次实战完结

上一篇:redis相关


下一篇:Java基础--- 小应用:比较两个数值大小