一、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 = False3 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 Requestclass 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 BeautifulSoupclass 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()取列表的第一个元素