python 协程并发数控制
作者:梦想橡皮擦 发布时间:2023-09-20 01:07:10
前言:
本篇博客要采集的站点:【看历史,通天下-历史剧网】
目标数据是该站点下的热门历史事件,列表页分页规则如下所示:
http://www.lishiju.net/hotevents/p0
http://www.lishiju.net/hotevents/p1
http://www.lishiju.net/hotevents/p2
首先我们通过普通的多线程,对该数据进行采集,由于本文主要目的是学习如何控制并发数,所以每页仅输出历史事件的标题内容。
普通的多线程代码:
import threading
import time
import requests
from bs4 import BeautifulSoup
class MyThread(threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.__url = url
def run(self):
res = requests.get(url=self.__url)
soup = BeautifulSoup(res.text, 'html.parser')
title_tags = soup.find_all(attrs={'class': 'item-title'})
event_names = [item.a.text for item in title_tags]
print(event_names)
print("")
if __name__ == "__main__":
start_time = time.perf_counter()
threads = []
for i in range(111): # 创建了110个线程。
threads.append(MyThread(url="http://www.lishiju.net/hotevents/p{}".format(i)))
for t in threads:
t.start() # 启动了110个线程。
for t in threads:
t.join() # 等待线程结束
print("累计耗时:", time.perf_counter() - start_time)
# 累计耗时: 1.537718624
上述代码同时开启所有线程,累计耗时 1.5 秒,程序采集结束。
多线程之信号量
python 信号量(Semaphore)用来控制线程并发数,信号量管理一个内置的计数器。 信号量对象每次调用其 acquire()
方法时,信号量计数器执行 -1 操作,调用 release()
方法,计数器执行 +1 操作,当计数器等于 0 时,acquire()
方法会阻塞线程,一直等到其它线程调用 release()
后,计数器重新 +1,线程的阻塞才会解除。
使用 threading.Semaphore()
创建一个信号量对象。
修改上述并发代码:
import threading
import time
import requests
from bs4 import BeautifulSoup
class MyThread(threading.Thread):
def __init__(self, url):
threading.Thread.__init__(self)
self.__url = url
def run(self):
if semaphore.acquire(): # 计数器 -1
print("正在采集:", self.__url)
res = requests.get(url=self.__url)
soup = BeautifulSoup(res.text, 'html.parser')
title_tags = soup.find_all(attrs={'class': 'item-title'})
event_names = [item.a.text for item in title_tags]
print(event_names)
print("")
semaphore.release() # 计数器 +1
if __name__ == "__main__":
semaphore = threading.Semaphore(5) # 控制每次最多执行 5 个线程
start_time = time.perf_counter()
threads = []
for i in range(111): # 创建了110个线程。
threads.append(MyThread(url="http://www.lishiju.net/hotevents/p{}".format(i)))
for t in threads:
t.start() # 启动了110个线程。
for t in threads:
t.join() # 等待线程结束
print("累计耗时:", time.perf_counter() - start_time)
# 累计耗时: 2.8005530640000003
当控制并发线程数量之后,累计耗时变多。
补充知识点之 GIL:
GIL
是 python 里面的全局解释器锁(互斥锁),在同一进程,同一时间下,只能运行一个线程,这就导致了同一个进程下多个线程,只能实现并发而不能实现并行。
需要注意 python 语言并没有全局解释锁,只是因为历史的原因,在 CPython
解析器中,无法移除 GIL
,所以使用 CPython
解析器,是会受到互斥锁影响的。
还有一点是在编写爬虫程序时,多线程比单线程性能是有所提升的,因为遇到 I/O 阻塞会自动释放 GIL
锁。
协程中使用信号量控制并发
下面将信号量管理并发数,应用到协程代码中,在正式编写前,使用协程写法重构上述代码。
import time
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def get_title(url):
print("正在采集:", url)
async with aiohttp.request('GET', url) as res:
html = await res.text()
soup = BeautifulSoup(html, 'html.parser')
title_tags = soup.find_all(attrs={'class': 'item-title'})
event_names = [item.a.text for item in title_tags]
print(event_names)
async def main():
tasks = [asyncio.ensure_future(get_title("http://www.lishiju.net/hotevents/p{}".format(i))) for i in range(111)]
dones, pendings = await asyncio.wait(tasks)
# for task in dones:
# print(len(task.result()))
if __name__ == '__main__':
start_time = time.perf_counter()
asyncio.run(main())
print("代码运行时间为:", time.perf_counter() - start_time)
# 代码运行时间为: 1.6422313430000002
代码一次性并发 110 个协程,耗时 1.6 秒执行完毕,接下来就对上述代码,增加信号量管理代码。
核心代码是 semaphore = asyncio.Semaphore(10)
,控制事件循环中并发的协程数量。
import time
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def get_title(semaphore, url):
async with semaphore:
print("正在采集:", url)
async with aiohttp.request('GET', url) as res:
html = await res.text()
soup = BeautifulSoup(html, 'html.parser')
title_tags = soup.find_all(attrs={'class': 'item-title'})
event_names = [item.a.text for item in title_tags]
print(event_names)
async def main():
semaphore = asyncio.Semaphore(10) # 控制每次最多执行 10 个线程
tasks = [asyncio.ensure_future(get_title(semaphore, "http://www.lishiju.net/hotevents/p{}".format(i))) for i in
range(111)]
dones, pendings = await asyncio.wait(tasks)
# for task in dones:
# print(len(task.result()))
if __name__ == '__main__':
start_time = time.perf_counter()
asyncio.run(main())
print("代码运行时间为:", time.perf_counter() - start_time)
# 代码运行时间为: 2.227831242
aiohttp 中 TCPConnector 连接池
既然上述代码已经用到了 aiohttp
模块,该模块下通过限制同时连接数,也可以控制线程并发数量,不过这个不是很好验证,所以从数据上进行验证,先设置控制并发数为 2,测试代码运行时间为 5.56
秒,然后修改并发数为 10,得到的时间为 1.4
秒,与协程信号量控制并发数得到的时间一致。所以使用 TCPConnector
连接池控制并发数也是有效的。
import time
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def get_title(session, url):
async with session.get(url) as res:
print("正在采集:", url)
html = await res.text()
soup = BeautifulSoup(html, 'html.parser')
title_tags = soup.find_all(attrs={'class': 'item-title'})
event_names = [item.a.text for item in title_tags]
print(event_names)
async def main():
connector = aiohttp.TCPConnector(limit=1) # 限制同时连接数
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [asyncio.ensure_future(get_title(session, "http://www.lishiju.net/hotevents/p{}".format(i))) for i in
range(111)]
await asyncio.wait(tasks)
if __name__ == '__main__':
start_time = time.perf_counter()
asyncio.run(main())
print("代码运行时间为:", time.perf_counter() - start_time)
来源:https://juejin.cn/post/7068824518902906916
猜你喜欢
- 什么是CSS裸奔节?CSS裸奔节就是将这整站的css样式都去掉,这样所有的布局,颜色,背景什么的就都没有了(除非你使用table布局),只剩
- 可以实现,下面我们就来做一个检测一个字符串在另一个字符串当中出现几次的函数:入口参数:TheChar="要检测的字符串"
- 数学是优美的. 听上去有点奇怪? 当我第一次开始设计的时候,我确信如此。数学如此刻板乏味。你可能会惊讶的发现,最美观的设计,艺术作品,物体,
- 熟悉js的朋友很多都遇到过js的数组与字符串相互转换的情况,本文就此作一简单介绍,示例如下:一、数组转字符串需要将数组元素用某个字符连接成字
- 本文介绍了4个asp数据库管理中常用到的access数据库操作程序,一般的网站管理后台都提供了这个功能,方便管理员对数据库数据的管理维护。1
- 我们大家都知道CSS功能的强大,而有关CSS基本的排版控制虽然已有详细的使用说明和参考教程,但还有许多丰富的CSS排版能力,是很少能查到的。
- “用户体验”作为舶来品在国内风靡已经有几个年头了,而且从目前情况来看仍旧会继续风靡一段时间。当某产品发布会上,发言人张口闭口就
- PHP simplexml_load_file() 函数实例转换 XML 文件为 SimpleXMLElement 对象,然后输出对象的键和
- 解决方法:1。 改表法。可能是你的帐号不允许从远程登陆,只能在localhost。这个时候只要在localhost的那台电脑,登入mysql
- 在编程时你一定碰到过时间触发的事件,在VB中有timer控件,而ASP中没有,假如你要不停地查询数据库来等待一个返回结果的话,我想你一定知道
- Oracle的执行计划一句话命令:set autotrace on
- Oracle 背景资料 在介绍 Oracle9i 之前我们先介绍一些关于Oracle 公司的资料,让各位朋友更多了解 Oracle。 197
- Windows环境下一、开启 Imagick 扩展1、安装PHP扩展:Imagick,下载地址 https://pecl.php.net/p
- QQ登录Banner增加了剧情的概念之后,已经推出了春节和情人节两期。这之后设想能围绕Banner做的更加丰富,对传统文化的体现也能更为深入
- 下面都是我收集的一些比较常用的正则表达式,因为平常可能在表单验证的时候,用到的比较多。特发出来,让各位朋友共同使用。呵呵。匹配中文字符的正则
- 在select语句中可以使用groupby子句将行划分成较小的组,然后,使用聚组函数返回每一个组的汇总信息,另外,可以使用having子句限
- 在开发和调试基于XML的应用的时候,程序员往往为找不到合适的快速桌面XPATH测试软件发愁。诚然,市面上有成套的XML编辑软件,但是它们往往
- 当我们的文章表中没有对于文章的评论数字段时,我们该这么写sql语句来显示出评论最多的文章呢?下面本站给大家收集了几种方法,仅供参考:1.se
- 首先我们有这么一种需求,就是我在一个列表中点击了某个item,跳转到详情界面,那么我就需要把item的实体数据从列表页面传递到详情页面,那么
- 在现代LOGO设计当中,叶子的形状被视做好的创意。或者说,是一种变革的想法。在网页中他们大多被用于轻量级的解决方案、干净的不抽像的设计。在实