Python 协程详解----高性能爬虫

目录

1.基本概念

asyncio和async的关系

asyncio

async & await关键字

协程基本语法

 多任务协程返回值

案例1

协程在爬虫中的使用

 aiohttp模块基本使用

协程案例-扒光一部小说需要多久?

操作数据库

异步redis

异步MySQL

案例2:


知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具

我们知道爬虫是 IO 密集型任务,比如如果我们使用 requests 库来爬取某个站点的话,发出一个请求之后,程序必须要等待网站返回响应之后才能接着运行,而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何的事情。对于这种情况我们有没有优化方案呢?

协程不是计算机提供,程序员人为创造。

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行

同步异步速度对比

image.png

image.png

1.基本概念

异步

为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。

例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。

同步

不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,我们称这些程序单元是同步执行的。

阻塞

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

非阻塞

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

同步/异步关注的是消息通信机制 (synchronous communication/ asynchronouscommunication) 。

阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

需要下载的包

pip install aiohttp

aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio(Python用于支持异步编程的标准库)的异步库。asyncio可以实现单线程并发IO操作,其实现了TCP、UDP、SSL等协议,aiohttp就是基于asyncio实现的http框架。

async 用来声明一个函数为异步函数

await 用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序

asyncio和async的关系

asyncio

在python3.4及之后的版本。

import asyncio
​
@asyncio.coroutine
def func1():print(1)# 网络IO请求:下载一张图片yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务print(2)
​
​
@asyncio.coroutine
def func2():print(3)# 网络IO请求:下载一张图片yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务print(4)
​
​
tasks = [asyncio.ensure_future( func1() ),asyncio.ensure_future( func2() )
]
​
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

注意:遇到IO阻塞自动切换

async & await关键字

在python3.5及之后的版本。

import asyncio
​
async def func1():print(1)# 网络IO请求:下载一张图片await asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务print(2)
​
​
async def func2():print(3)# 网络IO请求:下载一张图片await asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务print(4)
​
​
tasks = [asyncio.ensure_future( func1() ),asyncio.ensure_future( func2() )
]
​
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))


协程基本语法

协程的基本写法: 咱就介绍一种, 也是最好用的一种.

先上手来一下.

async def func():print("我是协程")
​
​
if __name__ == '__main__':# print(func())  # 注意, 此时拿到的是一个协程对象, 和生成器差不多.该函数默认是不会这样执行的
​coroutine = func()asyncio.run(coroutine)  # 用asyncio的run来执行协程.# lop = asyncio.get_event_loop()# lop.run_until_complete(coroutine)   # 这两句顶上面一句

效果不明显, 继续加码

import time
import asyncio
​
# await: 当该任务被挂起后, CPU会自动切换到其他任务中
async def func1():print("func1, start")await asyncio.sleep(3)print("func1, end")
​
​
async def func2():print("func2, start")await asyncio.sleep(4)print("func2, end")
​
​
async def func3():print("func3, start")await asyncio.sleep(2)print("func3, end")
​
​
async def run():start = time.time()tasks = [  # 协程任务列表asyncio.ensure_future(func1()),  # create_task创建协程任务asyncio.ensure_future(func2()),asyncio.ensure_future(func3()),]await asyncio.wait(tasks)  # 等待所有任务执行结束print(time.time() - start)
​
if __name__ == '__main__':asyncio.run(run())

 多任务协程返回值

import asyncio
​
​
async def faker1():print("任务1开始")await asyncio.sleep(1)print("任务1完成")return "任务1结束"
​
​
async def faker2():print("任务2开始")await asyncio.sleep(2)print("任务2完成")return "任务2结束"
​
​
async def faker3():print("任务3开始")await asyncio.sleep(3)print("任务3完成")return "任务3结束"
​
​
async def main():tasks = [asyncio.create_task(faker3()),asyncio.create_task(faker1()),asyncio.create_task(faker2()),]# 方案一, 用wait, 返回的结果在result中result, pending = await asyncio.wait(tasks,timeout=None)for r in result:print(r.result())# 方案二, 用gather, 返回的结果在result中, 结果会按照任务添加的顺序来返回数据#   return_exceptions如果任务在执行过程中报错了. 返回错误信息. result = await asyncio.gather(*tasks, return_exceptions=True)for r in result:print(r)
​
​
if __name__ == '__main__':asyncio.run(main())
​

案例1

import asyncio
​
async def download(url):print("开始抓取")await asyncio.sleep(3)  # 我要开始下载了print("下载结束", url)return "老子是源码你信么"
​
​
async def main():urls = ["http://www.baidu.com","http://www.h.com","http://luoyonghao.com"]# 生成任务列表tasks = []for url in urls:tasks.append(asyncio.create_task(download(url)))done, pedding = await asyncio.wait(tasks)for d in done:print(d.result())
​
if __name__ == '__main__':asyncio.run(main())

协程在爬虫中的使用

aiohttp是python的一个非常优秀的第三方异步http请求库. 我们可以用aiohttp来编写异步爬虫(协程)

安装:

pip install aiohttp
pip install aiofiles

 aiohttp模块基本使用

实例代码:

import aiohttp
import asyncio
import aiofiles
​
​
async def download(url):try:name = url.split("/")[-1]# 创建session对象 -> 相当于requsts对象async with aiohttp.ClientSession() as session:# 发送请求, 这里和requests.get()几乎没区别, 除了代理换成了proxyasync with session.get(url) as resp:# # resp.text(encoding='') 这可以设置字符集 # 读取数据. 如果想要读取源代码. 直接resp.text()即可. 比原来多了个()content = await resp.content.read()# 写入文件, 用默认的open也OK. 用aiofiles能进一步提升效率async with aiofiles.open(name, mode="wb") as f:await f.write(content)return "OK"except:print(123)return "NO"
​
​
async def main():url_list = ["https://x.u5w.cc/Uploadfile/202110/20/42214426253.jpg","https://x.u5w.cc/Uploadfile/202110/20/B3214426373.jpg","https://www.xiurenji.vip/uploadfile/202110/20/1F214426892.jpg","https://www.xiurenji.vip/uploadfile/202110/20/91214426753.jpg"]tasks = []
​for url in url_list:# 创建任务task = asyncio.create_task(download(url))tasks.append(task)
​await asyncio.wait(tasks)
​
​
if __name__ == '__main__':asyncio.run(main())
​

从最终运行的结果中能非常直观的看到用异步IO完成爬虫的效率明显高了很多

协程案例-扒光一部小说需要多久?

目标, 明朝那些事儿 明朝那些事儿-明朝那些事儿全集在线阅读


import asyncio
import aiohttp
import aiofiles
import requests
from lxml import etree
import osdef get_chapter_info(url):resp = requests.get(url)resp.encoding = 'utf-8'page_source = resp.textresp.close()result = []# 解析page_sorucetree = etree.HTML(page_source)mulus = tree.xpath("//div[@class='main']/div[@class='bg']/div[@class='mulu']")for mulu in mulus:trs = mulu.xpath("./center/table/tr")title = trs[0].xpath(".//text()")chapter_name = "".join(title).strip()chapter_hrefs = []for tr in trs[1:]:  # 循环内容hrefs = tr.xpath("./td/a/@href")chapter_hrefs.extend(hrefs)result.append({"chapter_name": chapter_name, "chapter_hrefs": chapter_hrefs})return resultasync def download_one(name, href):async with aiohttp.ClientSession() as session:async with session.get(href) as resp:hm = await resp.text(encoding="utf-8", errors="ignore")# 处理hmtree = etree.HTML(hm)title = tree.xpath("//div[@class='main']/h1/text()")[0].strip()content_list = tree.xpath("//div[@class='main']/div[@class='content']/p/text()")content = "\n".join(content_list).strip()async with aiofiles.open(f"{name}/{title}.txt", mode="w", encoding="utf-8") as f:await f.write(content)print(title)# 方案一
async def download_chapter(chapter):chapter_name = chapter['chapter_name']if not os.path.exists(chapter_name):os.makedirs(chapter_name)tasks = []for href in chapter['chapter_hrefs']:tasks.append(asyncio.create_task(download_one(chapter_name, href)))await asyncio.wait(tasks)# 方案二
async def download_all(chapter_info):tasks = []for chapter in chapter_info:name = chapter['chapter_name']if not os.path.exists(name):os.makedirs(name)for url in chapter['chapter_hrefs']:task = asyncio.create_task(download_one(name, url))tasks.append(task)await asyncio.wait(tasks)def main():url = "http://www.mingchaonaxieshier.com/"# 获取每一篇文章的名称和url地址chapter_info = get_chapter_info(url)# 可以分开写. 也可以合起来写.# 方案一,分开写:# for chapter in chapter_info:#     asyncio.run(download_chapter(chapter))# 方案e,合起来下载:asyncio.run(download_all(chapter_info))if __name__ == '__main__':main()

操作数据库

异步redis

在使用python代码操作redis时,链接/操作/断开都是网络IO。

pip3 install aioredis

示例1:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aioredis
​
​
async def execute(address, password):print("开始执行", address)# 网络IO操作:创建redis连接redis = await aioredis.create_redis(address, password=password)
​# 网络IO操作:在redis中设置哈希值car,内部在设三个键值对,即: redis = { car:{key1:1,key2:2,key3:3}}await redis.hmset_dict('car', key1=1, key2=2, key3=3)
​# 网络IO操作:去redis中获取值result = await redis.hgetall('car', encoding='utf-8')print(result)
​redis.close()# 网络IO操作:关闭redis连接await redis.wait_closed()
​print("结束", address)
​
​
asyncio.run( execute('redis://47.93.4.198:6379', "root!2345") )

示例2:

import asyncio
import aioredis
​
​
async def execute(address, password):print("开始执行", address)
​# 网络IO操作:先去连接 47.93.4.197:6379,遇到IO则自动切换任务,去连接47.93.4.198:6379redis = await aioredis.create_redis_pool(address, password=password)
​# 网络IO操作:遇到IO会自动切换任务await redis.hmset_dict('car', key1=1, key2=2, key3=3)
​# 网络IO操作:遇到IO会自动切换任务result = await redis.hgetall('car', encoding='utf-8')print(result)
​redis.close()# 网络IO操作:遇到IO会自动切换任务await redis.wait_closed()
​print("结束", address)
​
​
task_list = [execute('redis://47.93.4.197:6379', "root!2345"),execute('redis://47.93.4.198:6379', "root!2345")
]
​
asyncio.run(asyncio.wait(task_list))


异步MySQL

pip3 install aiomysql

示例1:

import asyncio
import aiomysql
​
​
async def execute():# 网络IO操作:连接MySQLconn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', )
​# 网络IO操作:创建CURSORcur = await conn.cursor()
​# 网络IO操作:执行SQLawait cur.execute("SELECT Host,User FROM user")
​# 网络IO操作:获取SQL结果result = await cur.fetchall()print(result)
​# 网络IO操作:关闭链接await cur.close()conn.close()
​
​
asyncio.run(execute())

示例2:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import asyncio
import aiomysql
​
​
async def execute(host, password):print("开始", host)# 网络IO操作:先去连接 47.93.40.197,遇到IO则自动切换任务,去连接47.93.40.198:6379conn = await aiomysql.connect(host=host, port=3306, user='root', password=password, db='mysql')
​# 网络IO操作:遇到IO会自动切换任务cur = await conn.cursor()
​# 网络IO操作:遇到IO会自动切换任务await cur.execute("SELECT Host,User FROM user")
​# 网络IO操作:遇到IO会自动切换任务result = await cur.fetchall()print(result)
​# 网络IO操作:遇到IO会自动切换任务await cur.close()conn.close()print("结束", host)
​
​
task_list = [execute('47.93.41.197', "root!2345"),execute('47.93.40.197', "root!2345")
]
​
asyncio.run(asyncio.wait(task_list))

案例2:

import hashlib
import random
import timeimport redis
from lxml import etreeimport aiohttp
import asyncio
import aiomysqlclass QiChe():def __init__(self):self.url = 'https://www.che168.com/china/a0_0msdgscncgpi1ltocsp{}exf4x0/?pvareaid=102179#currengpostion'self.red = redis.Redis()self.headers = {'cookie': 'listuserarea=0; fvlid=1680001956301DaYJ4eiunV2R; sessionid=21b958ec-44d7-4d2e-94c0-e2adb575f619; sessionip=113.246.154.77; area=430104; che_sessionid=0EF0CDB5-A9C1-45CC-A742-DA21F9043918%7C%7C2023-03-28+19%3A12%3A36.699%7C%7C0; sessionvisit=9effbe46-2133-4609-8bfd-847ca49d7087; sessionvisitInfo=21b958ec-44d7-4d2e-94c0-e2adb575f619||100943; Hm_lvt_d381ec2f88158113b9b76f14c497ed48=1680001957,1680068945; che_sessionvid=9E2BB2F8-234E-4851-8DCC-3B590C8B5098; userarea=410100; ahpvno=22; showNum=15; Hm_lpvt_d381ec2f88158113b9b76f14c497ed48=1680069967; ahuuid=C4CD708B-1BE2-456B-AF64-FEB954906092; v_no=17; visit_info_ad=0EF0CDB5-A9C1-45CC-A742-DA21F9043918||9E2BB2F8-234E-4851-8DCC-3B590C8B5098||-1||-1||17; che_ref=0%7C0%7C0%7C0%7C2023-03-29+14%3A06%3A06.926%7C2023-03-28+19%3A12%3A36.699; sessionuid=21b958ec-44d7-4d2e-94c0-e2adb575f619','referer': 'https://www.che168.com/','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'}self.json_url = 'https://cacheapigo.che168.com/CarProduct/GetParam.ashx?specid={}'def get_md5(self, val):"""把目标数据进行哈希,用哈希值去重更快"""md5 = hashlib.md5()md5.update(str(val).encode('utf-8'))# print(md5.hexdigest())return md5.hexdigest()async def info_get(self, specid, client, pool):response = await client.get(self.json_url.format(specid))res_json = await response.json()if res_json['result'].get('paramtypeitems'):item = {}item['name'] = res_json['result']['paramtypeitems'][0]['paramitems'][0]['value']item['price'] = res_json['result']['paramtypeitems'][0]['paramitems'][1]['value']item['brand'] = res_json['result']['paramtypeitems'][0]['paramitems'][2]['value']item['altitude'] = res_json['result']['paramtypeitems'][1]['paramitems'][2]['value']item['breadth'] = res_json['result']['paramtypeitems'][1]['paramitems'][1]['value']item['length'] = res_json['result']['paramtypeitems'][1]['paramitems'][0]['value']await self.save_data(item, pool)async def get_data(self, page, client, pool):response = await client.get(self.url.format(page))data = await response.text(encoding='gbk')html = etree.HTML(data)li_list = list(set(html.xpath('//ul[@class="viewlist_ul"]/li/@seriesid')))tasks = []for li in li_list:res = self.info_get(li, client, pool)task = asyncio.create_task(res)tasks.append(task)await asyncio.wait(tasks)async def save_data(self, item, pool):# # 连接mysqlasync with pool.acquire() as conn:# 创建游标async with conn.cursor() as cursor:print(item)value = self.get_md5(item)res = self.red.sadd('qiche:filter', value)if res:# sql插入语法sql = 'INSERT INTO qiche(id, name, price, brand, altitude, breadth, length) values(%s, %s, %s, %s, %s, %s, %s)'try:# print(sql, (0, item['authors'], item['title'], item['score']))await cursor.execute(sql, (0, item['name'], item['price'], item['brand'], item['altitude'], item['breadth'],item['length']))# 提交到数据库执行await conn.commit()print('数据插入成功...')except Exception as e:print(f'数据插入失败: {e}')# 如果发生错误就回滚await conn.rollback()else:print('数据重复!!!!')async def main(self):# 异步创建连接池pool = await aiomysql.create_pool(host='127.0.0.1', port=3306, user='root', password='root', db='spiders',loop=loop)conn = await pool.acquire()cursor = await conn.cursor()# 使用预处理语句创建表create_sql = '''CREATE TABLE IF NOT EXISTS qiche(id int primary key auto_increment not null,name VARCHAR(255) NOT NULL,price VARCHAR(255) NOT NULL,brand VARCHAR(255) NOT NULL,altitude VARCHAR(255) NOT NULL,breadth VARCHAR(255) NOT NULL,length VARCHAR(255) NOT NULL);'''# 执行sqlawait cursor.execute(create_sql)async with aiohttp.ClientSession(headers=self.headers) as client:tasks = []for i in range(1, 40):res = self.get_data(i, client, pool)task = asyncio.create_task(res)tasks.append(task)await asyncio.sleep(random.randint(500, 800) / 1000)await asyncio.wait(tasks)await cursor.close()conn.close()if __name__ == '__main__':start = time.time()qczj = QiChe()# 获取事件循环 Eventloop 我们想运用协程,首先要生成一个loop对象,然后loop.run_xxx()就可以运行协程了,而如何创建这个loop, 方法有两种:对于主线程是loop=get_event_loop().loop = asyncio.get_event_loop()# 执行协程loop.run_until_complete(qczj.main())print('运行时间{}'.format(time.time() - start))

 

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

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

相关文章

Java篇图书管理系统

目录 前言 一. 图书管理系统的核心 二. 图书管理系统基本框架 2.1 book包 2.1.1 Book(书籍类) 2.1.2 Booklist (书架类) 2.2 user包 2.2.1 User类 2.2.2 Administrator(管理员类) 2.2.3 Visitor(用户类) 2.…

基于Python大数据的王者荣耀战队数据分析及可视化系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

Mybatis-03.入门-配置SQL提示

一.配置SQL提示 目前的Springboot框架在mybatis程序中编写sql语句并没有给到任何的提示信息,这对于开发者而言是很不友好的。因此我们需要配置SQL提示。 配置SQL提示 这样再去写SQL语句就会有提示了。 但是会发现指定表名时并没有给出提示。这是因为&#xff1a…

【综述整理】2015年至2022年图像美学质量评估数据集【附下载链接】

文章目录 2012年-美学数据集AVA-25万-MOS1~10数据集介绍 2015年-移动设备拍摄CLIVE-1K-MOS1~5数据集介绍 2016年-美学数据集AADB-10K-MOS1~10综述摘要 2017年-美学数据集FLICKR-AES-MOS1~5数据集介绍 2018年-户外自然场景KonIQ-10K-MOS1~5数据集介绍标签MOS,1-5分 2…

信息安全工程师(72)网络安全风险评估概述

前言 网络安全风险评估是一项重要的技术任务,它涉及对网络系统、信息系统和网络基础设施的全面评估,以确定存在的安全风险和威胁,并量化其潜在影响以及可能的发生频率。 一、定义与目的 网络安全风险评估是指对网络系统中存在的潜在威胁和风险…

记一次:使用使用Dbeaver连接Clickhouse

前言:使用了navicat连接了clickhouse我感觉不太好用,就整理了一下dbeaver连接 0、使用Navicat连接clickhouse 测试连接 但是不能双击打开,可是使用命令页界面,右键命令页界面,然后可以用sql去测试 但是不太好用&#…

LeetCode_231. 2 的幂_java

1、题目 231. 2 的幂https://leetcode.cn/problems/power-of-two/ 给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。 如果存在一个整数 x 使得 n ,则认为 n 是 2 的幂次方…

6.1 特征值介绍

一、特征值和特征向量介绍 本章会开启线性代数的新内容。前面的第一部分是关于 A x b A\boldsymbol x\boldsymbol b Axb:平衡、均衡和稳定状态;现在的第二部分是关于变化的。时间会加入进来 —— 连续时间的微分方程 d u / d t A u \pmb{\textrm{d}…

CTF--Misc题型小结

(萌新笔记,多多关照,不足之处请及时提出。) 不定时更新~ 目录 密码学相关 文件类型判断 file命令 文件头类型 strings读取 隐写术 尺寸修改 文件头等缺失 EXIF隐写 thumbnail 隐写 文件分离&提取 binwalk foremo…

索引的使用和优化

索引就是一种快速查询和检索数据的数据结构,mysql中的索引结构有:B树和Hash。 索引的作用就相当于目录的作用,我么只需先去目录里面查找字的位置,然后回家诶翻到那一页就行了,这样查找非常快, 创建一个表结…

短视频矩阵系统源码开发优势,短视频矩阵系统oem部署

短视频矩阵系统就是在多个短视频平台上构建自己的内容生态,通过多平台、多账号、多内容的运营策略,实现品牌曝光、用户引流、产品销售等目的。短视频矩阵的核心在于通过矩阵式的布局,实现资源优化配置和利用,提升企业市场竞争力。…

.Net 8 Web API CRUD 操作

本次介绍分为3篇文章: 1:.Net 8 Web API CRUD 操作https://blog.csdn.net/hefeng_aspnet/article/details/143228383 2:在 .Net 8 API 中实现 Entity Framework 的 Code First 方法https://blog.csdn.net/hefeng_aspnet/article/details/1…

【C++干货篇】——类和对象的魅力(四)

【C干货篇】——类和对象的魅力(四) 1.取地址运算符的重载 1.1const 成员函数 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。const实际修饰该成员函数隐含的this指针(this指向的对…

nuxt3项目创建

安装 npx nuxilatest init <project-name> 此时会出现报错&#xff0c;需要在host文件中加入 185.199.108.133 raw.githubusercontent.com 再次执行命令&#xff0c;进入安装 此处选择npm&#xff0c;出现下图表示安装成功 启动项目 执行npm run dev&#xff0c;访…

【力扣 + 牛客 | SQL题 | 每日4题】牛客大厂笔试真题SQLW6, W7, W8

1. 牛客大厂笔试真题SQLW6&#xff1a;统计所有课程参加培训人次 1.1 题目&#xff1a; 描述 某公司员工培训信息数据如下&#xff1a; 员工培训信息表cultivate_tb(info_id-信息id,staff_id-员工id,course-培训课程)&#xff0c;如下所示&#xff1a; 注&#xff1a;该公…

【大数据技术基础 | 实验五】ZooKeeper实验:部署ZooKeeper

文章目录 一、实验目的二、实验要求三、实验原理四、实验环境五、实验步骤&#xff08;一&#xff09;安装JDK&#xff08;二&#xff09;修改ZooKeeper配置文件&#xff08;三&#xff09;启动ZooKeeper集群 六、实验结果七、实验心得 一、实验目的 掌握ZooKeeper集群安装部署…

基于Netty构建WebSocket服务并实现项目群组聊天和实时消息通知推送

文章目录 前言需求分析技术预研Web端方案服务端技术 技术方案设计思路功能实现添加依赖自定义NettyServer自定义webSocketHandler使用NettyServer向在线用户发送消息 需要完善的地方 前言 我们的项目有个基于项目的在线文档编制模块&#xff0c;可以邀请多人项目组成员在线协同…

python爬虫-爬取蛋白晶体和分子结构

文章目录 前言一、环境准备二、爬取PDB蛋白结构1.下载指定数量的随机PDB2.下载指定靶标的PDB二、从ZINC爬取小分子mol2结构1.下载指定数量的随机分子2.下载指定分子三、从ChEMBL爬取小分子信息1.下载指定ID的SMILES(测试不成功,网站变成readonly了)四、总结爬虫1.查看对应的…

AMD锐龙8845HS+780M核显 虚拟机安装macOS 15 Sequoia 15.0.1 (2024.10)

最近买了机械革命无界14X&#xff0c;CPU是8845HS&#xff0c;核显是780M&#xff0c;正好macOS 15也出了正式版&#xff0c;试试兼容性&#xff0c;安装过程和之前差不多&#xff0c;这次我从外网获得了8核和16核openCore&#xff0c;分享一下。 提前发一下ISO镜像地址和open…

JavaScript完整笔记

JS引入 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。 通过 script 标签将 JavaScript 代码引入到 HTML 中&#xff0c;有两种方式&#xff1a; 内部方式 通过 script 标签包裹 JavaScript 代码 我们将 &…