一、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文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
2、pip3 install lxml
3、pip3 install pyopenssl
4、下载并安装pywin32:它是windows上的exe执行文件
https://sourceforge.net/projects/pywin32/files/pywin32/
5、下载twisted的wheel文件:http://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 = ['http://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 = ['http://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 = ['http://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()取列表的第一个元素