Python爬虫之异步爬虫

异步爬虫

一、协程的基本原理

1、案例

案例网站:https://www.httpbin.org/delay/5、这个服务器强制等待了5秒时间才返回响应

测试:用requests写一个遍历程序,遍历100次案例网站:

import requests
import logging
import timelogging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s:%(message)s')
TOTAL_NUMBER = 100
URL = 'https://www.httpbin.org/delay/5'start_time = time.time()
for _ in range(1,TOTAL_NUMBER + 1):logging.info('scraping %s',URL)response = requests.get(URL)
end_time = time.time()
logging.info('total time %s seconds',end_time - start_time)# 爬取总时间约为11分钟

2、基本知识

2.1、阻塞

阻塞状态程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上市阻塞的 。

常见的阻塞形式有:网络I/O阻塞、磁盘I/O阻塞、用户输入阻塞等。阻塞是无处不在的,包括在CPU切换上下文时,所有进程都无法真正干事情,它们也会被阻塞。在多核CPU的情况下,正在执行上下文切换操作的核不可被利用。

2.2、非阻塞

程序在等待某操作的过程中,自身不被阻塞,可以继续干别的事情,则称该程序在该操作上是非阻塞的。

非阻塞并不是在任何程序级别、任何情况下都存在的。仅当程序封装的级别可以囊括独立的子程序单元时,程序才可能存在非阻塞状态。

非阻塞因阻塞的存在而存在,正因为阻塞导致程序运行时的耗时增加与效率低下,我们才要把它变成非阻塞的。

2.3、同步

不同单元为了共同完成某个任务在执行过程中需要靠某种通信方式保持协调一致,此时这些程序单元是同步执行的。

同步意味着有序

2.4、异步

为了完成某个任务,有时不同程序单元之间戊戌通信协调也能完成任务,此时不相关的程序单元之间可以是异步的。

异步意味着无序

2.5、多进程

多进程就是利用CPU的多核优势,在同一时间并行执行多个任务,可以大大提高执行效率。

2.6、协程

协程,英文叫做coroutine,又称微线程、纤程,是一种运行在用户太的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程在调度切换时,将寄存器上下文和栈保存到其他地方,等切回来的时候,再恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用的状态,即所有局部状态的一个特定组合,每次过程重入,就相当于上一次调用的状态。

协程本质是单进程,相对于多进程来说,它没有线程上下文切换的开销,没有原子操作锁定及同步的开销,编程模型也非常简单。

3、协程的用法

Python3.4开始,Python中加入了协程的概念,但这个版本的协程还是以生成器对象为基础。Python3.5中增加了asyncio、await,使得协程的实现更为方便。

Python中使用协程最常用的莫过于asyncio库。

  • event_loop事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上、当满足发生条件的时候,就调用对应的处理方法。
  • coroutine:中文翻译叫协程,在Python中长指代协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是会返回一个协程对象
  • task:任务,这是对协程对象的进一步封装,包含协程对象的各个状态。
  • future:代表将来执行或者没有执行的任务的结果,实际上和task没有本质。

4、准备工作

确保安装的Python版本为3.5以上。

5、定义协程

import asyncio		# 引入asyncio包,这样才能使用async和await关键字。async def execute(x):		# 使用async定义了一个execute方法,该方法接受一个数字参数x,执行之后会打印这个数字。print('Number:',x)
coroutine = execute(1)		# 调用这execute方法,然而没有被执行,而是返回了一个coroutine协程对象。
print('Coroutine:',coroutine)
print('After calling execute')loop = asyncio.get_event_loop() # 使用get_event_loop方法创建了一个事件循环loop
loop.run_until_complete(coroutine)	# 调用loop对象的run_until_complete方法将协程对象注册到了事件循环中,接着启动。才看见了这个数字。
print('After calling loop')
  • 可见,async定义的方法会变成一个无法执行的协程对象必须将此对象注册到事件循环中才可以执行
  • 前面提到的task,是对协程对象的进一步封装,比协程对象多了个运行状态,例如runingfinished等,我们可以利用这些状态获取协程对象的执行情况。
  • 在上述例子中,我们把协程对象coroutine传递给run_untill_complete方法的时候,实际上它进行了一个操作,就是将coroutine封装成task对象。对此,我们也可以显式的进行声明:
import asyncioasync def execute(x):print('Number:',x)return xcoroutine = execute(1)
print('Coroutine:',coroutine)
print('After calling execute')loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)		# 调用loop对象的create_task方法,将协程对象转为task对象,随后打印输出一下,发现它处于pending状态
print('Task:',task)	# pending
loop.run_until_complete(task)		# 将task对象加入到事件循环中执行后,发现状态变为finished
print('Task:',task)	# finished
print('After calling loop')

定义task对象还有另外一种方式,就是直接调用asyncio包的ensure_future方法,返回结果也是task对象,这样的话就可以不借助loop对象。即使还没有声明loop,也可以提取定义好task对象:

async def execute(x):print('Number:',x)return xcoroutine = execute(1)
print('Coroutine:',coroutine)
print('After calling execute')task = asyncio.ensure_future(coroutine)
print('Task:',task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task:',task)
print('After calling loop')

6、绑定回调

我们也可以为某个task对象绑定一个回调方法

import asyncio
import requestsasync def request():	# 定义request方法,请求百度,返回状态码。url = 'https://www.baidu.com'status = requests.get(url)return statusdef callback(task):		# 定义callback方法,接受一个task对象参数,打印task对象的结果。print('Status:',task.result())coroutine = request()		
task = asyncio.ensure_future(coroutine)
task.add_done_callback(callback)	# 将callback方法传递给封装好的task对象,这样当task执行完毕后,就可以调用callback方法了。同时task对象还会作为参数传递给callback方法,调用task对象的result方法就可以获取返回结果。
print('Task:',task)loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task:',task)

实际上,即使不适用回调方法,在task运行完毕后,也可以直接调用result方法获取结果:

import asyncio
import requestsasync def request():url = 'https://www.baidu.com'status = requests.get(url)return statuscoroutine = request()
task = asyncio.ensure_future(coroutine)
print('Task:',task)loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print('Task:',task)
print('Result:',task.result())

7、多任务协程

如果想执行多次请求,可以定义一个task列表,然后使用asyncio包中的wait方法执行

import asyncio
import requestsasync def request():url = 'https://www.baidu.com'status = requests.get(url)return statustasks = [asyncio.ensure_future(request()) for _ in range(5)]    # 列表推导式
print('Tasks:',tasks)loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))for task in tasks:print('Tasl result:',task.result())

使用一个for循环创建了5个task,它们组成一个列表(列表推导式),然后把这个列表首先传递给asyncio包的wait方法,再将其注册到事件循环中,就可以发起5个任务了。

8、协程实现

await不能和requests返回的Response对象一起使用。await后面的对象必须是以下:

  • 一个原生协程对象
  • 一个由types.coroutine修饰的生成器,这个生成器可以返回协程对象
  • 由一个包含_await_方法的对象返回一个迭代器
import asyncio
import requests
import timestart = time.time()
async def get(url):return requests.get(url)async def request():url = 'https://www.httpbin.org/delay/5'print('Waiting for',url)response = await get(url)print('Get response from',url,'response',response)tasks = [asyncio.ensure_future(request()) for _ in range(10)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))end = time.time()
print('Cost time:',end - start)

报错,说明仅仅将涉及I/O操作的代码封装到async修饰的方法是不可行的。只有使用支持异步操作的请求方式才可以实现真正的异步。aiohttp就派上用场了

9、使用aiohttp

aiohttp是一个支持异步请求的库,它和asyncio配合使用,可以使我们方便的实现异步请求操作。

pip3 install aiohttp
import asyncio
import requests
import aiohttp
import timestart = time.time()async def get(url):session = aiohttp.ClientSession()response = await session.get(url)await response.text()await session.close()return requestsasync def request():url = 'https://www.httpbin.org/delay/5'print('Waiting for',url)response = await get(url)print('Get response from',url,'response',response)tasks = [asyncio.ensure_future(request()) for _ in range(10)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))end = time.time()
print('Cost time:',end - start)	  # 6秒

二、aiohttp的使用

1、基本介绍

aiohttp是一个基于asyncio的异步HTTP网络模块,它既提供了服务端,又提供了客户端

  • 其中,我们用服务端可以搭建一个支持异步处理的服务器,这个服务器就是用来处理请求并返回响应的,类似Djaongo、Flask等。
  • 而客户端可以用来发起请求,类似于使用requests发起一个HTTP请求然后获得响应,但requests发起的是同步的网络请求,aiohttp是异步的。

2、基本实例

import aiohttp
import asyncioasync def fetch(session,url):async with session.get(url) as response:return await response.text(),response.statusasync def main():async with aiohttp.ClientSession() as session:html,status = await fetch(session,'https://cuiqingcai.com')print(f'html:{html[:100]}...')print(f'status:{status}')if __name__ == '__main__':loop = asyncio.get_event_loop()loop.run_until_complete(main())
# asyncio.run(main())	

aiohttp的请求方法与之前的差别

  • 必须引入aiohttp库asyncio库。因为实现异步爬取,需要启动协程,而协程需要借助asyncio里面的事件循环才能执行。除了事件循环,aasyncio里面也提供了许多基础的异步操作。
  • 异步爬取方法的定义不同,每个异步方法的前面都要统一加async来修饰
  • with as 语句前面同样需要加async来修饰。在Python中,with as 语句用来声明一个上下文管理器,能够帮我们自动分配和释放资源。而在异步方法中,with as前面加上async代表声明一个支持异步的上下文管理器
  • 对于一些返回协程对象的操作,前面需要加await来修饰
  • 定义完爬取方法之后,实际上是main方法调用了fetch方法。要运行的话,必须启用事件循环

在Python3.7及以后的版本,我们可以使用asyncio.run(main())代替最后的启动操作,不需要显示声明事件循环,run()方法内部会自动启用一个事件循环。

3、URL参数设置

对于URL参数的设置,我们可以借助params参数,传入一个字典即可:

import aiohttp
import asyncioasync def main():params = {'name':'germey','age':25}async with aiohttp.ClientSession() as session:async with session.get('https://www.httpbin.org/get',params=params) as response:print(await response.text())if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

实际请求的URL为https://www.httpbin.org/get?name=germey&age=25、其中的参数对应params的内容

4、其他请求类型

aiohttp还支持其他请求类型:

async with session.get('http://www.httpbin.org/get',data=b'data')
async with session.put('http://www.httpbin.org/get',data=b'data')
async with session.delete('http://www.httpbin.org/get',)
async with session.head('http://www.httpbin.org/get',)
async with session.options('http://www.httpbin.org/get',)
async with session.patch('http://www.httpbin.org/get',data=b'data')

5、POST请求

对于POST表单提交,其对应的请求头中的Content-Type为application/x-www-form-urlencoded,实现:

import asyncio
import aiohttpasync def main():data = {'name':'germey','age':25}async with aiohttp.ClientSession() as session:async with session.post('https://www.httpbin.org/post',data=data) as response:print(await response.text())if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

对于POST JSON数据提交,其对应的请求头中的Content-Type为application/json,将post方法里的data参数改成json即可:

async def main():data = {'name':'germey','age':25}async with aiohttp.ClientSession() as session:async with session.post('https://www.httpbin.org/post',json=data) as response:print(await response.text())

6、响应

对于响应来说,我们可以用如下方法分别获取其中的状态码、响应头、响应体,响应体二进制内容、响应体JSON结果:

import aiohttp
import asyncioasync def main():data = {'name':'germey','age':25}async with aiohttp.ClientSession() as session:async with session.post('https://www.httpbin.org/post',data=data) as response:print('status:',response.status)print('headers:',response.headers)print('body:',await response.text())print('bytes:',await response.read())print('json:',await response.json())if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

有些字段需要加await的原因是,如果返回的是一个协程对象(如async修饰的方法),那么前面就要加await。

7、超时设置

可以借助ClientTimeout对象设置超时,例如要设置1秒的超时时间:

import aiohttp
import asyncioasync def main():timeout = aiohttp.ClientTimeout(total=1)async with aiohttp.ClientSession(timeout=timeout) as session:async with session.get('https://www.httpbin.org/get') as response:print('status:',response.status)if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

如果超时则抛出TimeoutRrror异常。

8、并发限制

由于aiohttp可以支持非常高的并发量,面对高的并发量,目标网站可能无法在短时间内响应,而且有瞬间将目标网站爬挂掉。因此需要借助asyncio的Semaphore控制一下爬取的并发量:

import asyncio
import aiohttpCONCURRENCY = 5
URL = 'https://www.baidu.com'semaphore = asyncio.Semaphore(CONCURRENCY)
session = Noneasync def scrap_api():async with semaphore:print('scraping',URL)async with session.get(URL) as response:await asyncio.sleep(1)return await  response.text()
async def main():global sessionsession = aiohttp.ClientSession()scrape_index_tasks = [asyncio.ensure_future(scrap_api()) for _ in range(10000)]await asyncio.gather(*scrape_index_tasks)if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

这里声明CONCURRENCY(代表爬取的最大并发量)为5,同时声明爬取的目标为百度…

三、aiohttp异步爬取实战

1、案例介绍

网站:https://spa5.scrape.center/

2、准备工作

  • 安装好了Python
  • 了解Ajax爬取的一些基本原理和模拟方法
  • 了解异步爬虫的基本原理和asyncio库的基本用法
  • 了解asiohttp库的基本用法

3、页面分析

  • 列表页的Ajax请求接口格式为https://spa5.scrape.center/api/book/?limit=18&offset={offset}
  • 在列表页的Ajax接口返回的数据里,results字段包含当前页里18本图书的信息,其中每本书的数据里都含有一个id字段,这个id就是图书本身的ID
  • 详情页的Ajax请求格式为https://spa5.scrape.center/api/book{id}

4、实现思路

  • 第一阶段:异步爬取所有列表页,将所有列表页的爬取任务集合在一起,并将其声明为由task组成的列表,进行异步爬取。
  • 第二阶段:拿到上一步列表页的所有内容解析,将所有图书的ID信息组合为所有详情页的爬取任务集合,并将其声明为task组成的列表。进行异步爬取,结果也以异步新式存储到数据库。

5、基本配置

import asyncio
import aiohttp
import logginglogging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s:%(message)s')
INDEX_URL = 'https://spa5.scrape.center/api/book/?limit=18&offset={offset}'
DETAIL_URL = 'https://spa5.scrape.center/api/book/{id}'
PAGE_SIZE =18
PAGE_NUMBER = 100
CONCURRENCY = 5

6、爬取列表页

爬取列表页,先定义一个通用的爬取方法:

semaphore = asyncio.Semaphore(CONCURRENCY)	# 声明信号量,控制最大并发数量
session = Noneasync def scrape_api(url):	# 定义scrape_api方法,接受一个参数apiasync with semaphore:	# 用async with语句引入信号量作为上下文try:logging.info('scraping %s',url)async with session.get(url) as response:	# 调用session的get方法请求url,return await response.json()	# 返回响应的JSON格式except aiohttp.ClientError:		# 进行异常处理logging.error('error occurred while scraping %s',url,exc_info=True)

爬取列表页:

async def scrape_index(page):	# 爬取列表页方法,接受一个参数page,url = INDEX_URL.format(offset=PAGE_SIZE * (page - 1))	# 构造一个列表页的URLreturn await scrape_api(url)	# scripe_api调用之后本身会返回一个协程对象,所以加await

定义main()方法,将上面的方法串联起来调用:

async def main():global session		# 声明最初声明的全局变量sessionsession = aiohttp.ClientSession()	scrape_index_tasks = [asyncio.ensure_future(scrape_index(page)) for page in range(1,PAGE_NUMBER + 1)]	# 用于爬取列表页所有的task组成的列表results = await asyncio.gather(*scrape_index_tasks)	# 调用gather方法,将task列表传入其参数,将结果赋值为results,它是由所有task返回结果组成的列表。logging.info('results %s',json.dumps(results,ensure_ascii=False,indent=2))if __name__ == '__main__':	# 调用main方法,开启事件循环。asyncio.get_event_loop().run_until_complete(main())

7、爬取详情页

在main方法里增加results的解析代码

ids = []
for index_data in results:if not index_data: continuefor item in index_data.get('results'):ids.append(item.get('id'))

在定义两个方法用于爬取详情页保存数据

async def save_data(data):logging.info('saving data %s',data)...   # (以后再补)async def scrape_detail(id):url = DETAIL_URL.format(id=id)data = await scrape_api(url)await save_data(data)

接着在main方法里面增加对scrape_detail方法的调用即可爬取详情页

scrape_detail_tasks = [asyncio.ensure_future(scrape_detail(id)) for id in ids]await asyncio.wait(scrape_detail_tasks)await session.close()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/784766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux C:变量、运算符

linux C 文章目录 变量运算符 一、变量 [存储类型] 数据类型 标识符 值 标识符:由数字、字母、下划线组成的序列,不能以数字开头。 数据类型:基本数据类型构造类型 存储类型:auto static…

Linux(CentOS7)配置系统服务以及开机自启动

目录 前言 两种方式 /etc/systemd/system/ 进入 /etc/systemd/system/ 文件夹 创建 nginx.service 文件 重新加载 systemd 配置文件 ​编辑 配置开机自启 /etc/init.d/ 进入 /etc/init.d/ 文件夹 创建 mysql 文件 编写脚本内容 添加/删除系统服务 配置开机自启 …

大前端-postcss安装使用指南

PostCSS 是一款强大的 CSS 处理工具,可以用来自动添加浏览器前缀、代码合并、代码压缩等,提升代码的可读性,并支持使用最新的 CSS 语法。以下是一份简化的 PostCSS 安装使用指南: 一、安装 PostCSS 在你的项目目录中&#xff0c…

【MySQL笔记】SELECT COUNT(*) 的时候,加不加where条件有差别吗?

文章目录 前言实验结论 前言 这部分很多帖子都只在问题里罗列下,好像也没详细解答 其实就是跟InnoDB优先走二级索引的优化有关,前面也提到了”优化的前提是查询语句中不包含where条件和group by条件“ 还不太了解这个优化的朋友可以看上一篇帖子 实验 …

数据结构与算法(2)链表的初始化,插入,打印和删除——C语言实现

目录&#xff1a; 1.头文件 2.单链表的定义 3.菜单栏的设置 4.单链表的初始化 5.添加元素 6.打印元素 7.插入元素 8.删除元素 9.主函数 1.头文件 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h>typedef int El…

为什么好的产品经理极度稀缺?

目录 简介 对于个体 对于企业 作者简介 简介 要回答这个问题&#xff0c;一是要界定是谁提出的这个问题。 如果是企业负责人提问的。 这个问题的本质更多的是出在了企业的内部流程和管理上。 说明企业对产品经理的定位多数是有问题的或者是不清晰的。 而且内部也缺乏明…

学习计划.

每周打cf&#xff0c;每天保持刷一到两道题目&#xff0c;完成实验室安排的任务 第六周 Java集合&#xff0c;JavaFx 第七周 范型、异常处理 第八周 MySQL数据库和JDBC编程、多线程 第九周 网络编程、观看视频学习如何写项目 并尝试开始Java项目 第十周 完成项目

JAVA面试大全之微服务篇

目录 1、Spring Cloud 1.1、什么是微服务?谈谈你对微服务的理解? 1.2、什么是Spring Cloud? 1.3、springcloud中的组件有那些? 1.4、具体说说SpringCloud主要项目

编曲知识13:弦乐技法应用 合成器应用 声场摆位

弦乐技法 技法分类 Sustain(长音)类: Legato、Port、Gliss、Tremolo、Trills Staccato(短音)类: Staccato、Pizzicato、Spiccato Legato:连奏 Port:滑音 Gliss:慢速滑音 Tremolo:震音 Trills:颤音 Staccato:顿弓 Pizzicato:拨奏 Spiccato:跳弓 长音类技法 主…

如何调整Node内存限制

Node为什么会出现内存溢出&#xff0c;如何优化&#xff1f;如何调整Node内存限制&#xff1f; 一、内存限制以及溢出原因 Node.js默认的内存限制 Node.js默认的内存限制取决于你的操作系统和Node.js的版本。在大多数情况下&#xff0c;Node.js默认的内存限制应该是1.4GB&#…

从0到1:兼职招聘小程序开发笔记(一)

可行性分析 兼职招聘小程序&#xff1a;为雇主和求职者提供便利的平台&#xff0c;旨在帮助雇主招聘兼职员工&#xff0c;并让求职者寻找合适的兼职工作。提供简单、快捷的方式来匹配兼职岗位和候选人&#xff0c;节省了招聘和求职的时间和精力。其主要功能模块包括&#xff1…

机器视觉系统在工业零件检测中的应用

随着工业自动化和智能制造的不断发展&#xff0c;工业零件检测的准确性和效率变得越来越重要。传统的检测方法通常依赖人工目检或使用简单的机械工具&#xff0c;这种方法不仅效率低下&#xff0c;而且容易受到人为因素的干扰&#xff0c;难以保证检测的准确性。相比之下&#…

C练习题(1)

变种水仙花&#xff08;来自牛课网&#xff09; 题目 变种水仙花数 - Lily Number&#xff1a;把任意的数字&#xff0c;从中间拆分成两个数字&#xff0c;比如1461 可以拆分成&#xff08;1和461&#xff09;,&#xff08;14和61&#xff09;,&#xff08;146和1),如果所有拆…

精进TypeScript--private真的能隐藏信息吗?

JavaScript缺乏一种使类的属性成为私有的方法。 private访问修饰符只有通过类型系统才能被强制执行。它在运行时没有效果&#xff0c;可以被一个类型断言轻松绕过。不要以为它能保持数据的隐蔽性。 通常的变通方法是将下划线作为不属于公共API的字段的前缀&#xff1a; class …

C++经典面试题目(十五)

1、什么是面向对象编程&#xff08;OOP&#xff09;&#xff1f;请解释其基本概念。 当谈论面向对象编程&#xff08;OOP&#xff09;时&#xff0c;我们指的是一种软件开发范式&#xff0c;其中程序被组织为一组对象的集合&#xff0c;这些对象之间通过消息传递来进行交互。 …

索引失效的场景有哪些

索引失效的场景有以下几种: 1.联合索引非最左匹配:当使用联合索引时&#xff0c;未遵循最左匹配原则&#xff0c;则不能正常使用索引&#xff0c;也就是索引失效了 2.不当模糊查询:模糊查询 like 的常见用法有3种(只有第1种的会走索引&#xff0c;其他都会导致索引失效): a.模…

力扣刷题Days29-128.最长连续数列(js)

目录 1&#xff0c;题目 2&#xff0c;代码 2.1自己实现 2.2哈希表 3&#xff0c;学习与收获 枚举思想&#xff1a; 遍历的核心逻辑 碎碎念 本题 先是想到利用数组排序&#xff0c;从而简化遍历处理逻辑&#xff0c;再在提交错误提醒的情况下&#xff0c;考虑到数组中存…

zookeeper如何保证数据强一致性的?

原子广播特性用来保证zookeeper集群数据强一致性的机制。 数据的强一致性是当一个外部客户端去请求一个分布式系统中的数据时一旦这个数据允许被查询&#xff0c;那么我们在任何一个被要求存储该数据的分布式节点上在任何时候都能够查到这份数据且数据内容要求一摸一样。 原子广…

基于FreeRTOS系统的STM32简易遥控器设计

项目说明 该项目是一个基于FreeRTOS系统的Stm32遥控器设计。使用该项目主要是自己学习FreeRTOS的使用&#xff0c;以及模块化编程的思想。这个项目应该长期会有更新。 项目开源 github:https://github.com/snqx-lqh/Stm32RemoteControl gitee:https://gitee.com/snqx-lqh/S…

Electron安全防护实战:应对常见安全问题及权限控制措施

Electron安全防护实战&#xff1a;应对常见安全问题及权限控制措施 引言常见安全问题及其危害提升 Electron 应用安全性的措施限制渲染进程权限防止XSS与内容注入加固应用更新流程严格管理硬件权限使用安全的第三方模块加密敏感数据存储实现进程间通信&#xff08;IPC&#xff…