爬虫 | 多线程、多进程、协程
进程和线程
进程就相当于各个部门,线程就相当于各个部门里的干事员们
"""
进程中至少有一条线程,线程和进程的开启非常类似,这里就展示线程
"""
from threading import Thread
from multiprocessing import Process
# 第一种开启的方法就是直接def
def func(name):
for i in range(100):
print(name, i)
if __name__ == '__main__':
t = Thread(target=func, args=('T1',)) # 创建线程,布置任务,传入参数(注意这里的参数必须是元素,里面加逗号)
t.start() # 告诉线程可以开始工作了
# 下面是主任务
for n in range(100):
print('主任务', n)
# 第二种方法是类继承
class MyThread(Thread):
def __init__(self, name): # 这里就是通过构造函数来传入参数name
Thread.__init__(self) # >>> 等价于super().__init__(),先继承父类的东西
self.name = name
def run(self):
for i in range(100):
print(self.name, i)
if __name__ == '__main__':
t = MyThread("T1") # 创建一个实例对象,并传参T1
t.start() # 告诉线程可以开始工作了,这里如果是t.run()是不对的,这是在调用方法,实际上是在主线程中同步执行的,而非并发
# 下面是主任务
for n in range(100):
print('主任务', n)
进程池和线程池
就是一次性开很多个进程或者线程
from concurrent.futures import ThreadPoolExecutor
def fn(name):
for i in range(1000):
print(name, i)
# main
# 创建线程池,并指定线程池大小为50workers
with ThreadPoolExecutor(max_workers=50) as t:
for i in range(100): # 提交100个任务到线程池,这50个workers会共同完成这100个任务。
t.submit(fn, name=f"线程{i}") # >>>> 这里是用submit提交,而不是start
# 提交完所有任务后,线程池会在with块结束时自动关闭
print("123")
协程(单线程下实现并发)
电脑cpu在运行时,遇到input等交互、sleep等休息、或者网络发送get等IO操作(输入输出)时,是在等待状态中的,即阻塞状态,这个时候cpu是没事儿干闲着的,协程就是让它在一闲下来的时候,选择性的切换到其他任务上继续工作。
常见的阻塞操作:同步IO操作,time.sleep,request.get()等
协程在微观和宏观上
微观上:一个任务接一个任务的切换,切换条件一般就是IO操作
宏观上:我们看到的其实是多个任务一起在执行
也就是多任务异步操作
这是程序来切换的,不是操作系统来切换
# 其实有很多种写法,这里就展示主流的一种,在def前面加async
async def func():
print("你好啊,小明")
if __name__ == '__main__':
g = func() # 创建协程对象。此时的函数是异步协程函数,此时函数执行得到的是一个协程对象,而不会直接执行
asyncio.run(g) # 运行协程对象(但不能直接运行多个)
"""async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行。(不然整个界面有很多异步函数,大家都各自并发执行了,就完全没有顺序科研了,await就是来确保要fun1完成后,再执行func2)"""
运行多个协程对象
async def func1():
print("你好啊,小明")
# time.sleep(3) # time.sleep有阻塞性质,是一种同步操作
await asyncio.sleep(3) # 如果这里想发挥time.sleep的功能,只能用模块中的功能asyncio.sleep
print("你好啊,小明")
async def func2():
print("你好啊,塞尔达")
# time.sleep(2)
await asyncio.sleep(2)
print("你好啊,塞尔达")
async def func3():
print("你好啊,林克")
# time.sleep(4)
await asyncio.sleep(4)
print("你好啊,林克")
async def main():
f1 = func1() # 创建协程对象
f2 = func2()
f3 = func3()
tasks = [
asyncio.create_task(f1), # 派发任务:告诉程序,这就是需要处理的协程对象
asyncio.create_task(f2),
asyncio.create_task(f3)
]
await asyncio.wait(tasks) # await只能放在异步函数内
await asyncio.gather(*tasks) # 是另一种返回方法,返回的信息简单一点
print(await asyncio.wait(tasks)) # 返回了很丰富的信息,包括任务名、函数名、函数所在的文件路径、函数返回值
print(await asyncio.gather(*tasks)) # 只返回了函数的返回值(这里都是none)
if __name__ == '__main__':
# 一次性启动多个任务(协程)需要用wait来处理列表
t1 = time.time()
asyncio.run(main())
t2 = time.time()
print(t2 - t1)
爬虫异步模板
async def download(url):
print('准备开始下载')
await asyncio.sleep(1) # 这一步是在模拟异步中的网络请求,不能是request.get,这个不适用于异步操作。要使用aiohttp里的方法
print('下载完成')
async def main():
urls = [
'https://www.bilibili.com',
'https://www.baidu.com',
'https://www.wechat.com'
]
tasks = []
for url in urls:
task = asyncio.create_task(download(url))
tasks.append(task)
await asyncio.wait(tasks) # await我理解是,挂起,告诉程序这一步可以挂在这里进行协程,一边等下载,一边cpu继续去干其他的
if __name__ == '__main__':
asyncio.run(main()) # run这个异步函数返回的协程对象(异步函数返回值自动就是协程对象)
关于报错:coroutine 'func1' was never awaited
# 过去的写法是:
import asyncio
import time
async def func1():
print("你好啊,我是1")
await asyncio.sleep(3)
print("你好啊,我是1")
async def func2():
print("你好啊,我是2")
await asyncio.sleep(2)
print("你好啊,我是2")
async def func3():
print("你好啊,我是3")
await asyncio.sleep(4)
print("你好啊,我是3")
if __name__ == '__main__':
f1 = func1() # 此时的函数是异步协程函数。此时函数执行得到的是一个协程对象
f2 = func2()
f3 = func3()
# asyncio.run(f1) #协程程序运行需要asyncio模块的支持
tasks = [
f1, f2, f3
]
t1 = time.time()
# 一次性启动多个任务(协程)
asyncio.run(asyncio.wait(tasks))
t2 = time.time()
print(t2 - t1)
"""由于python 3.8以后的一些调整,这么写无法运行,方法就是要包成task对象,也就是create_task。可参考:https://fishc.com.cn/thread-224340-1-1.html"""
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· Qt个人项目总结 —— MySQL数据库查询与断言