scrapy框架
- 什么是框架
- 就是一个集成了许多功能,并且具有很强通用性的一个项目模板,该模板可被应用在不同的项目需求中。也可被视为是一个项目的半成品。
- 如何学习框架
- 对于刚接触编程或者初级程序员来讲,对于一个新的框架,只需要掌握该框架的作用及其各个功能的使用和应用即可,对于框架的底层实现和原理,在逐步进阶的过程中在慢慢深入即可。
- 什么是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指令,没有报错即表示安装成功!
注:win10操作系统可以直接pip install scrapy
-
-
scrapy使用流程:
- 创建工程
- scrapy startproject ProName
- cd 工程目录
- 在spiders子目录中创建爬虫文件
- scrapy genspider spiderName url
- 编写操作代码
- 执行工程
- scrapy crawl spiderName
- 创建工程
示例:(一个简单的访问)
爬虫文件:
import scrapy
class FirstSpider(scrapy.Spider): # 继承自scrapy.Spider
name = 'first' # 爬虫文件名称:就是爬虫源文件的唯一标识
# allowed_domains = ['www.baidu.com'] # 允许的域名:用来限定start_urls列表中哪些url可以进行请求的发送, 通常不用
# 起始的url列表:该列表存放的url会被scrapy自动进行请求的发送
start_urls = ['https://www.baidu.com/', 'https://www.sogou.com//']
def parse(self, response):
'''
用作于数据解析
response: 表示的就是请求成功后对应的响应对象
调用次数由start_urls列表中url个数决定
'''
print(response)
print(response.text) #获取字符串类型的响应内容
print(response.body)#获取字节类型的相应内容
pass
对配置文件settings.py的修改
更改:ROBOTSTXT_OBEY = False # 可以忽略或者不遵守robots协议
更改:USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' # UA伪装
添加:LOG_LEVEL = 'ERROR' # 显示指定类型的日志信息(只显示错误信息)
示例:(数据解析-以糗事百科为例)
import scrapy
class QiushiSpider(scrapy.Spider):
name = 'qiushi'
# allowed_domains = ['www.qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析作者的名字加段子内容
# 可以直接调用xpath进行数据解析
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
# print(div_list)
for div in div_list:
# xpath返回的是列表,但是列表元素一定时selector类型的对象
# extract() 可以将select对象中data参数存储的字符串提取出来
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
# 列表直接调用extract(),表示将列表中每一个select对象中data参数存储的字符串提取出来
content = div.xpath('./a[1]//span[1]//text()').extract()
# join()将列表转换为字符串
content = ''.join(content)
print(author, content)
break
pass
持久化存储
基于终端指令的持久化存储
- 要求:只可以将parse方法的返回值存储到本地的文本文件中
示例:(对糗事百科内容进行持久化存储)
class QiushiSpider(scrapy.Spider):
name = 'qiushi'
# allowed_domains = ['www.qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析作者的名字加段子内容
# 可以直接调用xpath进行数据解析
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
# print(div_list)
all_data = []
for div in div_list:
# xpath返回的是列表,但是列表元素一定时selector类型的对象
# extract() 可以将select对象中data参数存储的字符串提取出来
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
# 列表直接调用extract(),表示将列表中每一个select对象中data参数存储的字符串提取出来
content = div.xpath('./a[1]//span[1]//text()').extract()
# join()将列表转换为字符串
content = ''.join(content)
#print(author, content)
dic = {
'author': author,
'content': content
}
all_data.append(dic)
return all_data
pass
终端命令:
scrapy crawl qiushi -o ./qiushi.csv
- **注意:**持久化存储对应的文本文件类型只能为:‘json’, ‘jsonlines’, ‘jl’, ‘csv’, ‘xml’, ‘marshal’, ‘pickle’。
- 优点:简洁、高效、便捷。
- 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)
基于管道的持久化存储(*)
-
相关文件
- items.py:数据结构模板文件,定义数据属性。
- pipelines.py:管道文件,接收数据(items),进行持久化操作。
-
编码流程
- 数据解析
- 在item类中定义相关的属性
- 将解析的数据封装存储在item类型的对象中
- 将item类型的对象提交给管道进行持久化存储的操作
- 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
- 在配置文件中开启管道
-
优点:通用性强。
-
面试题:将爬取到的数据一份存储到本地一份存储到数据库,如何实现?
- 在管道文件中,一个管道类对应将一组数据存到一个平台或者载体中。
- 爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收
- process_item()中的return item操作表示将item传递给下一个即将被执行的管道类。
- 示例代码如下:
管道文件中的代码为:
#该类为管道类,该类中的process_item方法是用来实现持久化存储操作的。
class DoublekillPipeline(object):
def process_item(self, item, spider):
#持久化操作代码 (方式1:写入磁盘文件)
return item
#如果想实现另一种形式的持久化操作,则可以再定制一个管道类:
class DoublekillPipeline_db(object):
def process_item(self, item, spider):
#持久化操作代码 (方式1:写入数据库)
return item
在settings.py开启管道操作代码为:
#下列结构为字典,字典中的键值表示的是即将被启用执行的管道文件和其执行的优先级。
ITEM_PIPELINES = {
'doublekill.pipelines.DoublekillPipeline': 300,
'doublekill.pipelines.DoublekillPipeline_db': 200,
}
#上述代码中,字典中的两组键值分别表示会执行管道文件中对应的两个管道类中的process_item方法,实现两种不同形式的持久化操作。
全站数据爬取
- 将网站中某板块下的全部页码对应的页面数据进行爬取。
- 实现方式:
- 将所有页面的url添加到start_urls列表(不推荐)
- 自行手动的进行请求发送
- yield scrapy.Request(url, callback) # callback用于数据解析
- 需求:将糗事百科所有页码的作者和段子内容数据进行爬取且持久化存储
import scrapy
from qiushi_all_Sites.items import QiushiAllSitesItem
class QiushiAllSpider(scrapy.Spider):
name = 'qiushi_all'
# allowed_domains = ['https://www.qiushibaike.com/text/']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
# 生成一个通用的url模板(不可变)
url = 'https://www.qiushibaike.com/text/page/%d/'
page_num = 2
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
content = div.xpath('./a[1]//span[1]//text()').extract()
# join()将列表转换为字符串
content = ''.join(content)
# 将解析的数据封装存储在item类型的对象中
item = QiushiAllSitesItem()
item['author'] = author
item['content'] = content
# 将item类型的对象提交给管道进行持久化存储的操作
yield item
pass
#爬取所有页码数据
if self.page_num <= 4: # 爬取4页
new_url = format(self.url%self.page_num)
self.page_num += 1
# 手动发送请求: call_back回调函数专门用作于数据解析
yield scrapy.Request(url=new_url, callback=self.parse) # parse被递归调用
pass
五大核心组件
- 引擎(Scrapy)
- 用来处理整个系统的数据流处理, 触发事务(框架核心)
- 调度器(Scheduler)
- 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader)
- 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders)
- 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline)
- 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
请求传参
- 使用场景:在某些情况下,我们爬取的数据不在同一个页面中,例如,我们爬取一个电影网站,电影的名称,评分在一级页面,而要爬取的其他电影详情在其二级子页面中。这时我们就需要用到请求传参。
- 需求:爬取豆瓣电影top250,电影名称和剧情简介。
import scrapy
from doubanPro.items import DoubanproItem
class DoubaiSpider(scrapy.Spider):
name = 'doubai'
# allowed_domains = ['movie.douban.com/top250']
start_urls = ['https://movie.douban.com/top250/']
# 通用模板
url = 'https://movie.douban.com/top250?start=%d'
page_num = 2
# 解析详情页
def parse_detail(self, response):
description = response.xpath('//*[@id="link-report"]/span[2]//text() | //*[@id="link-report"]/span[1]//text()').extract()
# join()将列表转换为字符串
description = ''.join(description)
# print(description)
item = response.meta['item']
item['description'] = description
yield item
pass
# 解析首页
def parse(self, response):
li_list = response.xpath('//*[@id="content"]/div/div[1]/ol/li')
for li in li_list:
movie_name = li.xpath('.//div[@class="info"]/div[1]/a/span[1]/text()').extract_first()
# print(movie_name)
# 对详情页发起请求获取详情页的页面源码数据
detail_url = li.xpath('.//div[@class="info"]/div[1]/a/@href').extract_first()
# 持久化存储
item = DoubanproItem()
item['movie_name'] = movie_name
# 手动请求的发送
# 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
# 分页
if self.page_num <= 10:
new_page_num = (self.page_num-1)*25
new_url = format(self.url%new_page_num)
self.page_num += 1
# 手动发送请求: call_back回调函数专门用作于数据解析
yield scrapy.Request(url=new_url, callback=self.parse) # parse被递归调用
pass
提升scrapy的爬取效率
- 增加并发:
- 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
- 降低日志级别:
- 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
- 禁止cookie:
- 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
- 禁止重试:
- 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
- 减少下载超时:
- 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
scrapy 图片数据爬取
-
在scrapy中已经为我们封装好了一个专门基于图片请求和持久化存储的管道类ImagesPipeline,那也就是说如果想要基于scrapy实现图片数据的爬取,则可以直接使用该管道类即可。
-
基于scrapy爬取字符串类型的数据和爬取图片类型数据的区别
- 字符串:只需要基于xpath进行接续且提交管道进行持久化存储
- 图片:xpath解析出图片src的属性值。单独对图片地址发起请求获取图片二进制类型的数据。
-
ImagesPipeline:
-
只需要将img的src属性值进行解析,提交到管道,管道就会对图片的src请求发送获取图片类型的数据,且进行持久化存储。
-
需求:爬取站长素材中的高清图片
-
使用流程
-
数据解析(图片的地址)
-
将存储图片地址的item提交到指定的管道类
-
在管道文件中自定义一个基于ImagePipeLine的一个管道类
- get_media_requests
- file_path
- item_completed
-
在配置文件中:
- 指定图片存储的目录:IMAGES_STORE = ‘./img_food’
- 指定开启的管道:自定义的管道类
-
-
管道类的编写:
import scrapy
from scrapy.pipelines.images import ImagesPipeline
class imagesPipeline(ImagesPipeline):
# 根据图片地址进行图片数据的请求
def get_media_requests(self, item, info):
print('开始请求')
# print(item['src'])
yield scrapy.Request(url=item['src'])
pass
# 定制图片的名称
def file_path(self, request, response=None, info=None):
url = request.url
file_name = url.split('/')[-1]
print(file_name)
return file_name
def item_completed(self, results, item, info):
return item # 该返回值会传递给下一个即将被执行的管道类
中间件
-
下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件。
-
作用:批量拦截到整个工程中所有的请求和响应
-
拦截请求:引擎将请求传递给下载器过程中, 下载中间件可以对请求进行一系列处理。比如设置请求的 User-Agent(process_request),设置代理(process_exception)等
-
拦截响应:篡改响应数据、响应对象。
拦截请求(示例):
import random class MiddleproDownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. # 拦截请求 user_agent_list = ['xxx','xxx','xxx'] # 可被选用的代理IP PROXY_http = ['xxx','xxx','xxx'] PROXY_https = ['xxx','xxx','xxx'] def process_request(self, request, spider): # UA伪装 request.headers['user-Agent'] = random.choice(self.user_agent_list) return None # 拦截发生异常的请求 def process_exception(self, request, exception, spider): # 代理 h = request.url.split(':')[0] # 请求的协议头 if h == 'https': ip = random.choice(self.PROXY_https) request.meta['proxy'] = 'https://' + ip else: ip = random.choice(self.PROXY_http) request.meta['proxy'] = 'http://' + ip # 将修正之后的请求对象进行重新的请求发送 return request
拦截响应:爬取网易新闻中的新闻数据(标题+内容)
- 通过网易新闻的首页解析出五大板块对应的详情页的url(没有动态加载)
- 每一个板块对应的新闻标题都是动态加载出来的(动态加载)
- 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻的内容
CrawlSpider
-
CrawlSpider类:spider的一个子类
-
全站数据的爬取方式
- 基于spider:手动请求发送
- 基于Crawlspider
-
CrawlSpider的使用
- 创建一个工程 (与之前命令相同)
- cd 进入工程目录
- 创建爬虫文件(Crawlspider)
- scrapy genspider -t crawl name www.xxx.com
-
链接提取器LinkExtractor
- 根据指定规则(allow=“正则表达式”)进行指定链接的提取
-
规则解析器Rule
- 将链接提取器提取到的链接,进行指定规则(callback)的解析操作