Scrapy框架

一、Scrapy 介绍

Scrapy是一个Python编写的开源和协作的框架。起初是用于网络页面抓取所设计的,使用它可以快速、简单、可扩展的方式从网站中提取所需的数据。

Scrapy也是通用的网络爬虫框架,爬虫界的django(设计原则很像),可用于数据挖掘、监测和自动化测试、也可以应用在获取api所返回数据。
Scrapy是基于twisted框架开发而来,twisted是事件驱动python网络框架,因此scrapy使用的是非阻塞(异步)的代码来实现并发,可以发一堆请求出去(异步,不用等待),谁先回来就处理谁(事件驱动,发生变化再处理)。整体架构如下图

二、Scrapy 执行流程

五大组件
  1、引擎(EGINE)
     大总管,负责控制系统所有组件之间的数据流向,并在某些动作发生时触发事件

  2、调度器(SCHEDULER)
     由它来决定下一个要抓取的网址是什么,同时去除重复的网址(scrapy框架用的集合)
  # 通过配置来实现这一堆请求按深度优先(一条url爬到底),还是广度优先(都先爬第一层,再往下爬)
  # 先进先出(队列)-->第一层请求从调度器出去,爬虫解析后发出第二层请求,但要在队列等先进的其他第一层请求出去,因此需要把第一层先爬完,这是广度优先
  # 后进先出(堆栈)-->出去的第一层经解析,发出第二层请求,进入调度器,它又先出去,一条线把它爬完,才能爬其他url请求,这是深度优先

  3、下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
  # 下载器前有一个下载中间件:用来拦截,这里就是包装requests对象的地方(加头、加代理)

  4、爬虫(SPIDERS):开发人员自定义的类,用来解析responses,并且提取items对象(解析出来的内容),或者发送新的请求request(解析出要继续爬取的url)

  5、项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清洗(剔除掉不需要的数据)、验证、持久化(比如存到数据库、文件、redis)等操作


两大中间件
  1、爬虫中间件:位于EGINE和SPIDERS之间,主要处理SPIDERS的输入(responses)和输出(requests),还包括items对象,处理的东西过多就需要先判断是哪个对象,用的很少

  2、下载中间件:引擎和下载器之间,主要用来处理引擎传到下载器的请求request, 从下载器传到引擎的响应response;
     可以处理加代理,加头,集成selenium; 它用的不是requests模块发请求,用的底层的urllib模块,因此不能执行js; 
     框架的灵活就在于,你可以在下载中间件自己集成selenium,拦截后走你的下载方式,拿到数据再包装成response对象返回;
        
# 开发者只需要在固定的位置写固定的代码即可(写的最多的spider)

三、安装

#1 pip3 install scrapy(mac,linux可以直接下载)
#2 windows上(主要是tweited装不上)
解决方法
    1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网://www.lfd.uci.edu/~gohlke/pythonlibs
    2、pip3 install lxml
    3、pip3 install pyopenssl
    4、下载并安装pywin32:它是windows上的exe执行文件
    //sourceforge.net/projects/pywin32/files/pywin32/
    5、下载twisted的wheel文件://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    6、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    7、pip3 install scrapy
# 3 装完后就有scrapy命令,命令窗口输入
-D:\Python39\Scripts\scrapy.exe  此文件用于创建项目(相当于安装完django有个django-admin.exe)

四、scrapy 创建项目,创建爬虫,运行爬虫

1 创建scrapy项目

# pycharm不支持创建scrapy,只能用命令来创建项目
  scrapy startproject 项目名
  eg: scrapy startproject firstscrapy (cmd先cd到项目创建的目录上再执行命令)

  -项目创建后会生成firstscray文件夹,用pycharm打开,类似django项目的目录结构

2 创建爬虫

# 先cd到创建的scrapy项目中, eg: 在firstscrapy项目路径下执行命令
  scrapy genspider 爬虫名 爬取地址           # 相当于django创建app, python manage.py startapp app01
  scrapy genspider chouti dig.chouti.com    # 地址一开始写错了,后面可以修改

  一执行就会在spider文件夹下创建出一个py文件,名字叫chouti,爬虫就是一个个的py文件
  执行命令-->生成py文件-->里面包含了初始代码,因此直接拷贝py文件改下里面的代码(爬虫名、地址),就相当于创建一个新的爬虫,新的py文件,django的app也可以这样操作

3 运行爬虫

方式一:通过命令启动爬虫
scrapy crawl chouti           # 带运行日志
scrapy crawl chouti --nolog   # 不带日志

方式二:支持右键执行爬虫
# 在项目路径下新建一个main.py,跟spiders文件夹同一级
    from scrapy.cmdline import execute

    execute(['scrapy','crawl','chouti','--nolog']) --->右键运行main.py文件
    execute(['scrapy','crawl','baidu']) 

    # scrapy框架中,当一个爬虫执行完毕后,会直接退出程序,而不是继续执行后面代码	  
    # 用scrapy.cmdline.execute并不能同时运行多个爬虫

    注意:框架用了异常捕获,如果不带日志,有异常不会抛到控制台
    scrapy框架爬网页会先去读robots.txt协议,如果网站不让爬,它就不爬了
    可以在settings.py中,ROBOTSTXT_OBEY = True 改为False,不遵循它的协议

    # 可以几个爬虫同时执行,它是异步框架,里面开的多线程
    # 如果是同步,你放再多,也只能排队一个个爬,前面的IO阻塞,后面也都等着
    # 异步的优势在于,前面的IO阻塞了,后面的不会等,继续执行起走,谁的请求回来就处理谁

五、scrapy项目目录

firstscrapy           # 项目名字
   firstscrapy        # 包,存放项目py文件
      -spiders        # 所有的爬虫文件放在里面
        -baidu.py     # 一个个的爬虫(以后基本上都在这写东西)
        -chouti.py
      -middlewares.py # 中间件(爬虫,下载中间件都写在这)
      -pipelines.py   # 持久化相关写在这(处理items.py中类的对象)
      -main.py        # 自己建的py文件,执行爬虫
      -items.py       # 一个一个的类(类似django中的models)
      -settings.py    # 配置文件
   scrapy.cfg         # 上线相关

六、settings介绍

1 默认情况,scrapy会去遵循爬虫协议
2 修改配置文件参数,强行爬取,不遵循协议 ROBOTSTXT_OBEY = False
3 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
4 LOG_LEVEL='ERROR' 日志等级配置为error,info信息不打印,可以带上日志运行

七、scrapy的数据解析(重点)

1 spiders爬虫

爬虫解析示例
class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['dig.chouti.com']
    start_urls = ['//dig.chouti.com/']

    def parse(self, response, *args, **kwargs):
        print(response.text)

# 爬虫类中定义了start_urls, 配了地址
# 一启动会调用Spider类中start_requests方法--->从start_urls循环出url,包装成requests对象返回
# 进入调度后,走到下载器--->下载回来的responses对象返回到爬虫,调用parse方法进行解析
---------------------------------------

如果解析出一个网址,想继续爬取这个网址,需要调用Requset类传入url,生成requests对象并返回,它会继续走scrapy框架的流程
from scrapy.http import Request

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['dig.chouti.com']
    start_urls = ['//dig.chouti.com/']

    def parse(self, response, *args, **kwargs):
        print(response.text)
        return Request('http:www.chouti.com/article.html/')
       # return Request('http:www.baidu.com/', dont_filter=True)   解析出来的百度的域不能爬,需要设置dont_filter
        
# 先爬dig.chouti.com,如果解析出http:www.chouti.com/article.html这个地址,要继续爬
# 调用Request类,生成对象并返回-->调度器-->下载器-->返回response对象-->爬虫,调用parse继续解析
# allowed_domains设定了只能在某个域下爬,如果解析出来其他域就不能爬,dont_filter参数可以让继续爬其他域

2 解析方案一:自己用第三方解析,用bs4,lxml

bs4解析
from bs4 import BeautifulSoup

class ChoutiSpider(scrapy.Spider):
    name = 'chouti'
    allowed_domains = ['dig.chouti.com']
    start_urls = ['//dig.chouti.com/']

    def parse(self, response, *args, **kwargs):
        print(response.text)
        soup=BeautifulSoup(response.text, 'lxml')
        div_list=soup.find_all(class_='link-title') 
        for div in div_list:
           print(div.text)

3 解析方案二:scrapy框架自带的解析

css或xpath解析
xpath解析:
  # 取到所有link-title类的标签对象
  response.xpath('//a[contains(@class,"link-title")]')
  # 取到所有link-title类的标签的html(doc)文档
  response.xpath('//a[contains(@class,"link-title")]').extract()
  # 取到所有link-title类的标签的文本
  response.xpath('//a[contains(@class,"link-title")]/text()').extract()  
  # 取到所有link-title类的标签的href属性
  response.xpath('//a[contains(@class,"link-title")]/@href').extract() 
    
css解析:
  # 取到所有link-title类的标签对象
  response.css('.link-title')
  # 取到所有link-title类的标签的html(doc)文档
  response.css('.link-title').extract()
  # 取到所有link-title类的标签的文本
  response.css('.link-title::text').extract()
  # 取到所有link-title类的标签的href属性
  response.css('.link-title::attr(href)').extract()  
    
# extract()取出的内容放在列表里,即使取一个也放在列表,extract_first()取列表的第一个元素