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中演示
  1. 在这里插入图片描述
  2. 在这里插入图片描述

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

  1. 在这里插入图片描述
    在这里插入图片描述
  • 讲解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']
        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
  1. 分页操作
url = 'https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=%d'
page_num = 2

# 分页操作,对其他页面进行爬取
# 一定要设置条件和 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)

七、scrapy爬取图片⭐⭐⭐

7.1 项目分析

图片数据爬取之ImagesPipeline

基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?

  • 字符串:只需要xpath解析且提交管道持久化存储即可
  • 图片:xpath解析出图片src的属性值。单独对图片地址发起请求获取图片二进制类型的数据
  • ImagesPipeline:
    只需要将img的src属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型数据,且还会帮我们进行持久化存储
  • ImagesPipeline使用流程:

需求:爬取该页面中的高清图片

  1. 数据解析(图片的地址)
  2. 将存储图片地址的item提交到制定的管道类
  3. 在管道中自定制一个基于ImagesPipeLine的一个管道类,并重写方法
    • get_media_requests()
    • file_path()
    • item_completed()
  1. 在配置文件中:
  • 指定图片存储的目录:IMAGES_STORE='./imgs'
  • 指定开启的管道:自定制的管道类
  • 爬取图片的url
import scrapy
from scrapy import item
from imgsPro.items import ImgsproItem

class ImgSpider(scrapy.Spider):
    name = 'img'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://sc.chinaz.com/tupian/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        # print('div_list:',div_list)

        for div in div_list:

            src =div.xpath('./div[1]/a/img/@src2').extract_first()
            print('src :',src )
            

在这里插入图片描述

  • 原因分析
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

最后发现打开浏览器后显示出来的图片的属性值是src,没有可视化显示的图片属性值是src2,由于scrapy爬取是无法可视化,因此使用scr2属性值
该地址是短缺的,记得加上https://

7.2 项目源码

  1. img.py
import scrapy
from scrapy import item
from imgsPro.items import ImgsproItem

class ImgSpider(scrapy.Spider):
    name = 'img'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://sc.chinaz.com/tupian/']

    def parse(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        # print('div_list:',div_list)

        for div in div_list:
            # scrapy爬取不是可视化显示图片,因此使用伪属性src2
            src = 'https:' +  div.xpath('./div[1]/a/img/@src2').extract_first()
            
            item = ImgsproItem()
            item['src'] = src

            yield item

在这里插入图片描述

  1. 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
from scrapy.pipelines.images import ImagesPipeline ⭐⭐
import scrapy

# class ImgsproPipeline:
#     def process_item(self, item, spider):
#         return item

 #ImagesPipeline专门用于文件下载的管道类,下载过程支持异步和多线程
class imgsPipeline(ImagesPipeline):
    #对item中的图片进行请求操作
    def get_media_requests(self, item, info):
        # 手动发送请求
        yield scrapy.Request(item['src'])

    #指定图片存储的路径
    def file_path(self, request, response=None, info=None):
        # 获取图片的请求地址:request.url 
        # 将https://.../bpic24372_s.jpg后的bpic24372_s.jpg作为名称
        url = request.url 
        image_name = url.split('/')[-1]
        return image_name

    def item_completed(self, results, item, info):
        return item  #该返回值会传递给下一个即将被执行的管道类

参考:scrapy图片数据爬取


八、scrapy中间件的应用

8.1 基础知识

  • 位置
    在这里插入图片描述

下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件。

  • 作用:批量拦截到整个工程中所有的请求和响应
  • 拦截请求:
  1. UA伪装:process_request
  2. 代理IP:process_exceptionreturn request
  • 拦截响应:
  1. 篡改响应数据,响应对象
  2. 需求:爬取该网页的几个板块的新闻数据

我们主要使用下载中间件处理请求,一般会对请求设置随机的User-Agent ,设置随机的代理。目的在于防止爬取网站的反爬虫策略。

  • (1)引擎将请求传递给下载器过程中, 下载中间件可以对请求进行一系列处理。比如设置请求的 User-Agent,设置代理等
  • (2)在下载器完成将Response传递给引擎中,下载中间件可以对响应进行一系列处理。比如进行gzip解压等。

8.2 拦截请求⭐⭐

  • 现在
    在这里插入图片描述
  • 使用代理ip后
    在这里插入图片描述
    在这里插入图片描述
  • 步骤详解
  1. middle.py
import scrapy

class MiddleSpider(scrapy.Spider):
    # 爬取百度
    name = 'middle'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.baidu.com/s?wd=ip']

    def parse(self, response):
        page_text = response.text
        with open('ip.html','w',encoding='utf-8') as fp:
            fp.write(page_text)
  1. middlewares.py
class MiddleproDownloaderMiddleware:

	# User-Agent池
    user_agent_list = [
              "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
              "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
              "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
              "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
              "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
              "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
              "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
              "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
              "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
              "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
              "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
              "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
              "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
              "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
              "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
              "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
              "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
              "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
              "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
              "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
              "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
              "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
              "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
        ]

          #可被选用的代理IP
    PROXY_http = [
        '153.180.102.104:80',
        '195.208.131.189:56055',
        ]
    PROXY_https = [
        '120.83.49.90:9000',
        '95.189.112.214:35508',
        ]


    # 用于拦截请求
    def process_request(self, request, spider):
        # UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        
        # 为了验证代理的操作是否生效
        request.meta['proxy'] = 'http://61.178.149.237:59042	'
        
        return None

    # 拦截所有响应
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    # 拦截异常 
    def process_exception(self, request, exception, spider):
        # 使用代理
        # 只有当自己的ip被服务器屏蔽掉时,需要使用代理IP
        if request.url.split(':')[0] == 'http':
            request.meta['proxy'] = 'http://' +  random.choice(self.PROXY_http)
        else:
            request.meta['proxy'] = 'https://' +  random.choice(self.PROXY_https)

        return request # 将修正之后的请求对象进行重新的请求发送
  • User-Agent池
    作用:尽可能多的将scrapy工程中的请求伪装成不同类型的浏览器身份。
    操作流程:
  1. 在下载中间件中拦截请求
  2. 将拦截到的请求的请求头信息中的UA进行篡改伪装
  3. 在配置文件中开启下载中间件
  1. 修改settings.py文件
    在这里插入图片描述
    在这里插入图片描述

8.3 拦截响应⭐⭐

  • 需求

爬取该网页的几个板块的新闻数据(标题和内容)

  1. 通过该网页的首页解析出五大板块的详情页的url(没有动态加载,直接爬取)
  2. 每一个板块对应的新闻标题都是动态加载的
  3. 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容
  • 四大板块
    在这里插入图片描述
  • 总体结构
  • wangyi.py
  1. 实例化浏览器对象
  2. 解析板块的url
  3. 解析每一个板块页面中新闻标题和新闻详情页的url
  4. 解析每一个模板中每个标题对应的内容
  • item.py
    定义所需要的item
  • pipelines.py
    用来处理数据的持久化存储,在这里我们仅仅输出
  • middlewares.py⭐⭐⭐
  1. 使用process_response进行响应拦截
  2. 使用spider爬虫对象对响应模块进行篡改
  3. 使用selenium获取动态加载出的新闻数据并将新的响应对象返回

源码

  1. wangyi.py
import scrapy
from scrapy import item
from selenium import webdriver
from wangyiPro.items  import WangyiproItem

class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://news.163.com/']

    models_urls = [] # 存储五个板块详情页的url

    # 实例化一个浏览器对象
    def __init__(self):
        self.bro = bro = webdriver.Chrome(executable_path=r'E:\Google\chromedriver')

    # 解析五大板块对应的详情页的url
    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        alist = [2,3,5,6] # 取出指定的li标签
        for index in alist:
            # 获取每个板块的url
            model_url = li_list[index].xpath('./a/@href').extract_first()
            self.models_urls.append(model_url)

        # 依次对每一个板块对应得页面进行请求
        for url in self.models_urls:
            # print('================================================================')
            # print("url:",url)
            yield scrapy.Request(url,callback=self.parse_model)


    # 解析每一个板块页面中新闻标题和新闻详情页的url
    def parse_model(self,response):
        
        div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div')
        print('div_list:',div_list)
        for div in div_list:
            title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
            url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
            # print('name:',name,'url:',url)

            item = WangyiproItem()
            item['title'] = title
            yield scrapy.Request(url=url, callback = self.parse_detail,meta={
    
    'item':item})
    
    # 解析每一个模板中每个标题对应的内容
    def parse_detail(self,response):
        content = response.xpath('//*[@id="content"]/div[2]//text()').extract()

        content = ''.join(content)

        item = response.meta['item']
        item['content'] = content

        yield item

    def closed(self,spider):
        self.bro.quit()#关闭浏览器
  1. middlewares.py⭐⭐⭐

引入的包

  • from scrapy.http import HtmlResponse
  • from time import sleep
    # 该方法拦截五大板块对应的响应对象,进行篡改
    # spider爬虫对象
    def process_response(self, request, response, spider):
        bro = spider.bro # 获取了在爬虫类中定义的浏览器对象

        # 挑选出指定的响应对象进行篡改
        # 通过url指定request
        # 通过request指定response
        if request.url in spider.models_urls:
            bro.get(request.url) # 对5个板块对应的url进行请求
            sleep(3)
            page_text = bro.page_source # 获取页面源码数据,包含动态加载的新闻数据
            # response # 五大板块对应的响应对象
            # 针对定位到的这些response进行篡改
            # 实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据)代替原来旧的响应数据
            
            # 如何获取动态加载出的新闻数据?selenium
            new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)

            return new_response
        else:
            # response # 其他板块对应的响应对象
            return response
  1. pipelines.py
# 管道用来处理数据的持久化存储
class WangyiproPipeline:
    fp = None
    # 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
    def open_spider(self,spider):
        print('开始爬虫。。。。。。')
        self.fp = open('./wangyi_news.txt','w',encoding='utf-8')


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

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

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

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

在这里插入图片描述


总结

  1. ROBOTS协议⭐⭐

Robots协议是国际互联网界通行的道德规范,基于以下原则建立:

  1. 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权;
  2. 网站有义务保护其使用者的个人信息和隐私不被侵犯。
  1. 爬虫的"盗亦有道"-Robots协议
  2. robots协议
  3. Robots协议是什么?
  1. 无法爬取岗位信息对应的标签列表

目的:获取li标签列表,从而获取岗位信息和岗位描述

在这里插入图片描述

  • scrapy效果
    在这里插入图片描述

查看响应状态码:
print('code:',response.status)

在这里插入图片描述

所以就很奇怪,明明响应状态码成功,但是却出现了无法爬取指定标签的问题

  • requests效果
    在这里插入图片描述

  • selenium效果
    在这里插入图片描述

使用selenium可以爬取li标签列表,但是使用scrapy框架和requests模块无法得到li标签列表

猜你喜欢

转载自blog.csdn.net/HG0724/article/details/120677656