1.Event事件
Event 事件的作用:
用来控制线程的执行。
由一些线程去控制另一些线程。
from threading import Event
调用Event 类实例化一个对象。 e = Event
若该方法出现在任务中,则为False,阻塞
e.wait() #False
若该方法出现在任务中,则将其他线程的Flase改为True,进入就绪态与运行态
e.set() # True
2.进程池与线程池
1)什么是进程池与线程池?
进程池与线程池是用来控制当前程序允许创建(进程/线程)的数量.
2)进程池与线程池的作用:
保证在硬件允许的范围内创建 (进程/线程) 的数量.
3)如何使用:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
# ProcessPoolExecutor(5) # 5代表只能开启5个进程
# ProcessPoolExecutor() # 默认以CPU的个数限制进程数
pool = ThreadPoolExecutor(5) # 5代表只能开启5个线程 -5 +1 -1 +1 -1
# ThreadPoolExecutor() # 默认以CPU个数 * 5 限制线程数
# t = Tread() # 异步提交
# t.start(0)
# pool.submit('传函数地址') # 异步提交任务
# def task():
# print('线程任务开始了...')
# time.sleep(1)
# print('线程任务结束了...')
#
#
# for line in range(5):
# pool.submit(task)
# 异步提交任务
# pool.submit('传函数地址').add_done_callback('回调函数地址')
def task():
print('线程任务开始了...')
time.sleep(1)
print('线程任务结束了...')
return 123
# 回调函数
def call_back(res):
print(type(res))
# 注意: 赋值操作不要与接收的res同名
res2 = res.result()
print(res2)
for line in range(2):
pool.submit(task).add_done_callback(call_back)
#
线程任务开始了...
线程任务开始了...
线程任务结束了...
<class 'concurrent.futures._base.Future'>
123
线程任务结束了...
<class 'concurrent.futures._base.Future'>
123
高性能爬取梨视频
网站主页:
https://www.pearvideo.com/
requests: 用户封装底层socket套接字
- 打开CMD
# 下载第三方模块
>> pip3 install requests
https://www.pearvideo.com/video_1614813
https://www.pearvideo.com/video_1615201
1.从主页中获取所有的视频ID号(1615201,1614813...)
- 拼接视频详情页url
https://www.pearvideo.com/video_ + '视频ID号'
2.在视频详情页中获取真实视频url
srcUrl="()"
3.往真实视频url地址发送请求获取 视频 二进制数据
4.最后把视频二进制数据保存到本地
## pip3 install requests #终端输入:pip3 install requests
from concurrent.futures import ThreadPoolExecutor
import requests
import re
import uuid
代码:
from concurrent.futures import ThreadPoolExecutor
import requests
import re
import uuid
pool = ThreadPoolExecutor(200)
# 1.发送请求函数
def get_page(url):
response = requests.get(url)
return response
# 2.解析主页获取视频ID号
def parse_index(response):
id_list = re.findall(
'<a href="video_(.*?)".*?>',
response.text,
re.S
)
return id_list
# 3.解析视频详情页获取真实 视频链接
def parse_detail(res):
response = res.result()
movie_detail_url = re.findall('srcUrl="(.*?)"', response.text, re.S)[0]
print(f'往视频链接: {movie_detail_url}发送请求...')
# 异步往视频详情页链接发送请求,把结果交给
pool.submit(get_page, movie_detail_url).add_done_callback(save_movie)
return movie_detail_url
# 4.往真实视频链接发送请求,获取数据并保存到本地
def save_movie(res):
movie_response = res.result()
# print(1111)
# movie_response = get_page(movie_detail_url)
# print(movie_response)
name = str(uuid.uuid4())
print(f'{name}.mp4视频开始保存...')
with open(f'{name}.mp4', 'wb') as f:
f.write(movie_response.content)
print('视频下载完毕!')
if __name__ == '__main__':
# 1.访问主页获取数据
index_response = get_page('https://www.pearvideo.com/')
# # 2.解析主页获取所有的视频id号
id_list = parse_index(index_response)
print(id_list)
# 3.循环对每个视频详情页链接进行拼接
for id in id_list:
print(id)
detail_url = 'https://www.pearvideo.com/video_' + id
# 异步提交爬取视频详情页,把返回的数据,交给parse_detail(回调函数)
pool.submit(get_page, detail_url).add_done_callback(parse_detail)
3.协程
进程:资源单位
线程:执行单位。
协程:在单线程下实现并发。
注意:
协程不是操作系统资源,他是程序取的名字,为让单线程能实现并发。
协程的目的:
操作系统:
躲到技术,切换+保存状态
1.遇到 IO
2.CPU执行时间长
协程意义:
通过手动模拟操作系统‘多道技术’,实现 切换+保存。
1.手动实现 遇到 IO却换,欺骗操作系统误以没有 IO操作。
单线程 遇到 IO ,切换 + 保存状态。
单线程 计算密集型。来回切换 + 保存状态,反而效果更低
优点:
在 IO密集型的情况下,会提高效率。
缺点:
若在计算密集型的情况下,来回切换,反而效率更低。
如何实现协程:切换 + 保存状态。
基于 yield 并发执行(验证计算密集型的情况下,反而效率更低)
并发:切换
# 串行执行(串行比协程并发性更高)
import time
def func1():
for i in range(10000000):
i+1
def func2():
for i in range(10000000):
i+1
start = time.time()
func1()
func2()
stop = time.time()
print(stop-start) #1.0149040222167969
# 验证计算密集型的情况下效果更低 # 1.2908563613891602 还比如串行
# 基于yield并发执行
import time
def func1():
while True:
10000000+1
yield
def func2():
# g 生成器对象
g = func1()
for i in range(10000000):
# time.sleep(100)# 模拟 IO,yield 并不会捕捉到并自动切换。
i+1
next(g) #每次执行next相当与切换到func1下面。
start = time.time()
func2()
stop = time.time()
print(stop-start) # 1.2908563613891602
# 两个都是计算密集型,但是通过手动实现 切换+保存执行效率会低一些(用的秒长一些)。
gevent
是一个第三方模块,可以帮你监听 IO操作,并切换。
需要下载:pip3 install gevent
使用gevent目的:
为了实现单行线程下,实现遇到 IO, 保存状态 +切换