python爬虫--scrapy框架的学习和使用⭐⭐⭐---第一部分

Posted 胜天半月子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python爬虫--scrapy框架的学习和使用⭐⭐⭐---第一部分相关的知识,希望对你有一定的参考价值。


前言

  • 什么是框架?

就是一个集成了很多功能并且具有很强通用性的一个项目模板。

  • 如何学习框架?

专门学习框架封装的各种功能的详细用法。

  • 什么是scrapy?

爬虫中封装好的一个明星框架
功能

  • 高性能的持久化操作
  • 异步的数据下载操作
  • 高性能的数据解析操作
  • 分布式操作

一、scrapy框架的基本使用

  • 环境安装
linux和mac操作系统:pip install scrapy

windows系统:
pip install wheel
下载twisted,下载地址为http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
安装twisted:pip install Twisted‑17.1.0‑cp36‑cp36m‑win_amd64.whl
pip install pywin32
pip install scrapy
测试:在终端里录入scrapy指令,没有报错即表示安装成功!

1.1 windows下安装scrapy

  1. 安装wheel

pip install wheel

  1. 下载twisted


  2. 安装pywin32

pip install pywin32

  1. 安装scrapy

pip install scrapy

一开始安装失败,系统提示让我更新pip,更新pip后从新安装成功。

更新pip的命令:python -m pip install --upgrade pip

1.2 scrapy的基本使用

  • 步骤流程
  1. 通过终端指令创建一个工程:scrapy startproject xxxPro
  2. cd xxxPro
  3. 在spiders子目录中创建一个爬虫文件:scrapy genspider spiderName 域名
  4. 执行工程:scrapy crawl spiderName【不显示日志:scrapy crawl spiderName --nolog
  • 终端命令在VScode中演示

spiders文件里一定要放一个.py的爬虫文件


  • 讲解scrapy创建的spiderName爬虫文件
import scrapy
class FirstSpider(scrapy.Spider):
    # 爬虫文件的名称 :就是爬虫源文件的唯一标识,即不能够重复
    name = 'first'
    # 允许的域名:用来限定start_urls列表中哪些url可以进行请求发送,一般该字段不用
    # allowed_domains = ['www.xxx.com'] 
    # 起使的url列表:该列表中存放的url会被scrapy自动进行请求的发送,可以有多个url
    start_urls = ['https://www.baidu.com/','https://www.sogou.com/']

    # 用于数据解析 response参数表示的就是请求成功之后对应的响应对象
    # parse方法调用的次数由start_urls中url的个数决定的
    def parse(self, response):
        pass  # 将print(response代替pass进行验证)

发现上述输出并没有有关start_urls中url的相关内容,修改settings.pyROBOTSTXT_OBEY = False

不显示日志:scrapy crawl spiderName --nolog
在settings.py中加入LOG_LEVEL ='ERROR',显示指定类型的日志信息

  • 对比信息
  1. 使用上述scrapy框架
  2. 使用requests模块
import  requests 

if __name__ == "__main__":
    # 1. 指定url
    url = "https://www.sogo.com/"
    # 2. 发起请求  
    response = requests.get(url=url) 
    # get方法灰返回一个响应对象
    # 3. 获取相应数据 .text返回的是字符串形式的响应数据
    page_text = response
    print(page_text)


二、scrapy数据解析

scrapy框架的基本流程操作完毕后,一定要对settings.py文件进行如下操作:

  1. 修改USER_AGENT
  2. ROBOTSTXT_OBEY = False
  3. LOG_LEVEL = 'ERROR’
  • 爬取xx百科中的段子⭐⭐
import scrapy


class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    # allowed_domains = ['www.xxx.com']
    # 爬取糗事百科中段子的作者和内容
    start_urls = ['https://www.qiushibaike.com/text/']

    # 数据解析
    def parse(self, response):
        # 解析:作者的名称+段子内容
        div_list = response.xpath('//div[contains(@class,"col1")]/div')
        # print('div_list:',div_list)
        
        for div in div_list:
            # 所有的xpath返回的都是列表,但是列表元素一定是Selector类型的对象
            # extract()可以将Selector类型的对象中的字符串提取出来
            # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            # 如果保证返回的列表元素只有一个列表元素是可以使用extract_first()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            # 列表调用extract()表示将列表中每一个Selector对象中的字符串提取出来,并返回列表
            # 即列表调用extract()后返回还是列表
            content = div.xpath('./a[1]/div/span//text()').extract() # 由于有的标签中含有<br>标签,取所有内容用//

            content = ''.join(content)# 列表转换为字符串

            print(author, content)
            break # 只输出一次用来查看

  • 问题总结
  1. 遇到class中含有多个属性值

xpath如何取包含多个class属性


三、scrapy持久化存储

  • 基于终端指令
    要求:只可以将parse方法的返回值存储到本地的文本文件中【往数据库中存储是不行的】
  • 基于管道⭐⭐
  1. 数据解析
  2. 在Item类中定义相关的属性
  3. 将解析的数据封装到item类型的对象【使用items.py文件】
  4. 将Item类型对象提交给管道进行持久化存储操作
  5. 在管道类的process_item中呀将其接收到的item对象中存储的数据进行持久化存储操作【使用pipelines.py】
  6. 在配置文件中开启管道【scrapy默认情况是没有开启管道功能需要手动开启】

3.1 基于终端指令

例如第二章中爬取xx百科中的段子进行存储的代码如下:

import scrapy

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    # allowed_domains = ['www.xxx.com']
    # 爬取糗事百科中段子的作者和内容
    start_urls = ['https://www.qiushibaike.com/text/']

    # 数据解析
    def parse(self, response):      
        div_list = response.xpath('//div[contains(@class,"col1")]/div')
        all_data = [] # 存储所有数据
        
        # print('div_list:',div_list)
        for div in div_list:
            # 所有的xpath返回的都是列表,但是列表元素一定是Selector类型的对象
            # extract()可以将Selector类型的对象中的字符串提取出来
            # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            # 如果保证返回的列表元素只有一个列表元素是可以使用extract_first()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            # 列表调用extract()表示将列表中每一个Selector对象中的字符串提取出来,并返回列表
            # 即列表调用extract()后返回还是列表
            content = div.xpath('./a[1]/div/span//text()').extract() # 由于有的标签中含有<br>标签,取所有内容用//

            content = ''.join(content)# 列表转换为字符串

          	dic = {
				'author':author,
				'content':content
			}
			all_data.append(dic)
			
		return all_data

现在可以通过终端指令对parse方法的返回值进行持久化存储

  • scrapy crawl qiubai -o ./qiubai.csvscrapy crawl spiderName -o filePpath
  • 注意:持久化存储的文件类型是有限制的

  • 结论
  • 优点: 简洁高效便捷
  • 缺点:局限性比较强(数据只能存储到指定文件中)

3.2 基于管道⭐⭐

使用管道进行爬取xx百科中的段子

现在根据流程进行一步一步的操作

  1. 数据解析

qiubai.py文件进行数据解析

  • 代码
import scrapy
from qiubaipro.items import QiubaiproItem

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    # allowed_domains = ['www.xxx.com']
    # 爬取糗事百科中段子的作者和内容
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
            # 解析:作者的名称+段子内容
            div_list = response.xpath('//div[contains(@class,"col1")]/div')
          
            for div in div_list:
                # 所有的xpath返回的都是列表,但是列表元素一定是Selector类型的对象
                # extract()可以将Selector类型的对象中的字符串提取出来
                # author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
                # 如果保证返回的列表元素只有一个列表元素是可以使用extract_first()
                author = div.xpath('./div[1]/a[2]/h2/text() ').extract_first()
                # 列表调用extract()表示将列表中每一个Selector对象中的字符串提取出来,并返回列表
                # 即列表调用extract()后返回还是列表
                content = div.xpath('./a[1]/div/span//text()').extract() # 由于有的标签中含有<br>标签,取所有内容用//

                content = ''.join(content)# 列表转换为字符串
				
				# 3. 将解析的数据封装到item类型的对象
                item = QiubaiproItem()
                # 获取值用[]而不是.  ⭐⭐
                item['author'] = author
                item['content'] = content

				# 4. 将Item类型对象提交给管道进行持久化存储操作
                yield item
  1. 在Item类中定义相关的属性

在items.py文件中操作

import scrapy

class QiubaiproItem(scrapy.Item):
    # define the fields for your item here like:
    author = scrapy.Field()
    content = scrapy.Field()
    pass
  1. 管道类的process_item中呀将其接收到的item对象中存储的数据进行持久化存储操作

在pipelines.py文件中操作

from itemadapter import ItemAdapter

class QiubaiproPipeline:
    fp = None
    # 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次⭐
    def open_spider(self,spider):
        print('开始爬虫。。。。。。')
        self.fp = open('./qiubai.txt','w',encoding='utf-8')


    # 专门用来处理Item类型对象的
    # 该方法可以接收爬虫文件提交过来的item对象
    # 该方法每接收一次item就会被调用一次⭐⭐⭐
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']

        self.fp.write(author +":"+content+'\\n')

        return item
	
	# 只会调用一次⭐
    def close_spider(self,spider):
        print('结束爬虫!')
        self.fp.close()


  • 可能出现的问题

修改author的代码即可:author = div.xpath(’./div[1]/a[2]/h2/text() | ./div[1]/span/h2/text() ').extract_first()

  1. 在配置文件中开启管道

在settings.py中操作

  • 结论
  • 好处:通用性强,可以存储任意的文件或者数据库
  • 缺点:编码流程有些繁琐

3.3 面试题⭐⭐

将爬取到的数据一份存储到本地,一份存储到数据库,如何实现?
使用管道文件中的管道类:

  • 一个管道类对应一组数据存储到一个平台或者载体中
  • 爬虫文件中的item只会给管道文件中第一个被执行的管道类接收
  • pipelines.py中的process_item中的return item表示传递给下一个即将被执行的管道类【优先级高的传递给优先级低的】


  • 代码

同样根据编码流程进行编写,再次不再一一展示,只是展示核心代码

  1. 修改管道文件

对pipelines.py操作

from itemadapter import ItemAdapter
import pymysql 

class QiubaiproPipeline:
    fp = None
    # 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
    def open_spider(self,spider):
        print('开始爬虫。。。。。。')
        self.fp = open('./qiubai.txt','w',encoding='utf-8')


    # 专门用来处理Item类型对象的
    # 该方法可以接收爬虫文件提交过来的item对象
    # 该方法每接收一次item就会被调用一次
    def process_item(self, item, spider):
        author = item['author']
        content = item['content']

        self.fp.write(author +":"+content+'\\n')

        return item # 就会传递给下一个即将被执行的管道类

    def close_spider(self,spider):
        print('结束爬虫!')
        self.fp.close()

# 管道文件中一个管道类对应一组数据存储到一个平台或者载体中
class mysqlPipeline(object):
    conn = None
    cursor = None # 游标对象
    def open_spider(self,spider):
        self.conn = pymysql.Connect(host='127.0.0.1',user='test',password='123456',db='testdb',charset='utf8')

    def process_item(self, item, spider):
        self.cursor = self.conn.cursor()
        try:
            self.cursor.execute('insert into qiubai values("%s","%s")'%(item["author"],item["content"]))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback() # 出现错误,则回滚即这个事务从来没有执行过一样
            # rollback()数据回滚的作用就是确保数据库操作的原子性问题,多次操作要么都执行,要么都不执行
        return item

    def close_spider(self,spider):    
        self.cursor.close() # 关闭游标
        self.conn.close()   # 关闭数据库 

复习python对mysql数据库的操作:

  1. Python通过sql语句操作MySQL数据库
  2. python中实现Mysql数据回滚rollback()以及原理分析
  1. 修改配置文件
  • 结果

四、scrapy爬取全栈数据⭐⭐

全栈数据:就是将网站中某板块下的全部页码对应的页面数据进行爬取

  • 需求

爬取当前页的名称

  • 实现方式
  1. 将所有页面的url添加到start_urls列表中,列表中的元素会被自动请求发送【不推荐】
  2. 自行手动编码进行请求发送【推荐】⭐
  • yield scrapy.Request(url,callback):callback专门用于数据解析
  • 创建工程
  • xiaohua.py
import scrapy

class XiaohuaSpider(scrapy.Spider):
    name = 'xiaohua'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://nice.ruyile.com/?f=5']
    
    # 生成一个通用的模板(不可变)
    url = 'https://nice.ruyile.com/?f=5&p=%d'
    page_num =2

    # 基于全栈数据的爬取
    def parse(self, response):
        div_list = response.xpath('/html/body/div[4]/div[1]/div[2]/div[@class="tp_list"]')
        for div in div_list:
            # scrapy中xpath表达式返回的是Selector
            img_name = div.xpath('./div[2]/a[1]/text()').extract_first()
            print(img_name)
        # 一共117页   用5测试
        if self.page_num <= 5:
            new_url = format(self.url%self.page_num)# 不能使用self.new_url
            self.page_num += 1
            # 手动请求发送:callback回调函数是专门用于数据解析
            yield scrapy.Request(url=new_url,callback=self.parse)


五、scrapy五大核心组件

scrapy的基本使用我们已经掌握,但是各位心中一定会有些许的疑问,我们在编写scrapy工程的时候,我们只是在定义相关类中的属性或者方法,但是我们并没有手动的对类进行实例化或者手动调用过相关的方法,那么这些操作都是谁做的呢?接下来我们就来看看scrapy的五大核心组件的工作流程,然后大家就会上述的疑问有基本了解了。

  • 引擎(Scrapy)

用来处理整个系统的数据流处理, 触发事务(框架核心)

  • 调度器(Scheduler)

用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

  • 下载器(Downloader)

用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)

  • 爬虫(Spiders)

爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

  1. 产生url,对url进行手动发送
  2. 进行数据解析
  • 项目管道(Pipeline)

负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。


六、请求传参⭐⭐

在某些情况下,我们爬取的数据不在同一个页面中,例如,我们爬取一个电影网站,电影的名称,评分在一级页面,而要爬取的其他电影详情在其二级子页面中。这时我们就需要用到请求传参。

  • 使用场景

如果爬取解析的数据不在同一张页面中。深度爬取

  • 需求

爬取该网页的岗位名称,岗位描述


要爬取的这两个信息并没有在同一页面中,因此要使用请求传参,即深度爬取

  • 问题说明

在进行本节练习的时候,可能由于反爬机制导致包含岗位信息和岗位描述的li标签列表无法获得。因此,最终没有结果显示,但还是将源码放在这里,供大家阅读和学习!

  • 源码boss.py
import scrapy
from bossPro.items import BossproItem

class BossSpider(scrapy.Spider):
    name = 'boss'
    # allowed_domains = ['www.xxx.com']
    
    # 首页放进去
    start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=']

    url = 'https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=%d'
    page_num = 2

    # 用于解析详情页中的岗位描述
    def parse_detail(self, response):
        item = response.meta['item']
        job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
        job_desc = ''.join(job_desc)
        # print(job_desc)
        item['job_desc'] = job_desc

        yield item

    # 用于解析首页的岗位名称
    def parse(self, response):
        # 查看响应状态码
        print('code:',response.status)

        li_list = response.xpath('//*[@id="main"]/div/div[3]/ul/li')
        print('li_list:',li_list)
        for li in li_list:
            item = BossproItem()
            job_name = li.xpath('.//div[@class="primary-box"]/div[1]/span[1]/a/text()').extract_first()
            item['job_name'] = job_name
            # print(job_name)
            
            detail_url = 'https://www.zhipin.com/' + li.xpath('.//div[@class="primary-box"]/div[1]/span[1]/a/@href').extract_first()

            # 对详情页发起请求获取详情页的页面源码数据
            # 手动请求的发送
            # 请求传参
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'items':item})

        # 分页操作,对其他页面进行爬取
        # 一定要设置条件和 page_num+=1
        if self.page_num <= 3:
                new_url = format(self.url%self.page_num)
                self.page_num += 1

                yield scrapy.Request(new_url,callback=self.parse)
  • 代码解析
  1. 爬取更深度的信息
	detail_url = 'https://www.zhipin.com/' + li.xpath('.//div[@class="primary-box"]/div[1]/span[1]/a/@href').extract_first()
	
	# 请求传参
	yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'items':item})

----------------------------------------------------------------------------------------------------
    # 用于解析详情页中的岗位描述
    def parse_detail(self, response):
        item = response.meta['item'<

以上是关于python爬虫--scrapy框架的学习和使用⭐⭐⭐---第一部分的主要内容,如果未能解决你的问题,请参考以下文章

Python编程基础之(五)Scrapy爬虫框架

python爬虫--scrapy框架的学习和使用⭐⭐⭐---第一部分

Python | 初识爬虫框架Scrapy

Python开源爬虫框架scrapy的了解与认识

python爬虫--scrapy框架的学习和使用⭐---第二部分

python爬虫之scrapy框架