1. 多进程 多线程 (不建议)
2. 进程池 线程池 (适当)
3. 单线程 + 异步协程 (推荐)
参考: https://www.cnblogs.com/bobo-zhang/p/10735140.html
多线程
线程池爬取梨视频
import re
import random
import requests
from lxml import etree
from multiprocessing.dummy import Pool
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'
}
url = 'https://www.pearvideo.com/category_1'
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideonListUl"]/li')
# 实例化一个线程池对象
pool = Pool(4)
video_url_list = [] # 所有的视频连接
for li in li_list:
detail_url = 'https://www.peavideo.com' + li.xpath('./div/a/@href')[0]
detail_page_text = requests.get(url=detail_url, headers=headers).text
ex = 'srcUrl="(.*?)", vdoUrl='
video_url = re.findall(ex, detail_page_text, re.S)[0]
video_url_list.append(video_url)
def request_video(url):
return requests.get(url=url, headers=headers).content
def save_video(data):
name = str(random.randint(0, 999)) + '.mp4'
with open(name, 'wb') as f:
f.write(data)
# 异步获取4个视频的二进制文件
video_data_list = pool.map(request_video, video_url_list)
# 进行视频持久化存储
pool.map(save_video, video_data_list)
单线程 + 异步协程
asyncio 模块
asyncio 第三方模块, 可以实现异步网络操作, 并发和协程
官网描述: 当代码需要执行一个耗时的 I/O 操作的时候, 它只发出 I/O 的指令, 并不等待 I/O 的结果, 然后去执行其它的代码, 以提高效率。
关于 asyncio 的关键字:
1) event_loop 事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,函数就会被循环执行. 程序是按照设定的顺序从头执行到尾, 运行的次数也是完全按照设定. 当编写异步程序时, 其中有些部分程序的运行是耗时比较久, 需要先让出当前程序的控制权, 让其在背后运行, 而另一部分先运行起来, 当背后的运行的程序完成后, 也需要及时通知主程序已经完成任务可以进行下一步操作, 但这个过程时间并不确定, 需要主程序监听状态, 一旦检测完成, 就开始下一步, loop就是这个持续不断的监听器.
2) coroutine 协程:协程对象(一个特殊函数的返回值),指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
3) task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程对象进一步封装,其中包含了任务的各种状态
4) future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别
5) async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
aiohttp # 支持异步的请求模块
实例
import time
import asyncio
now = lambda : time.time()
# 非异步模块的代码:在此处如果存在非异步操作代码,则会彻底让asyncio失去异步的效果
async def do_some_work(x):
print("waiting:", x)
# await 后面就是调用耗时的操作(暂时挂起阻塞任务, 继续执行其他任务)
await asyncio.sleep(x) # 模拟了阻塞或者耗时操作
return "Done after {}s".format(x)
start = now()
# 这里是一个协程对象,这个时候do_some_work函数并没有执行
coroutine = do_some_work(2)
# 创建一个事件loop(循环)
loop = asyncio.get_event_loop()
# task对象是Future类的子类,保存了协程运行后的状态,用于未来获取协程的结果
# task = loop.create_future()
task = asyncio.ensure_future(coroutine)
# 将协程注册到事件循环loop中,并启动事件循环
loop.run_until_complete(task)
print("Time:",now()-start)
asyncio + aiohttp 实现协程
import time
import aiohttp
import asyncio
async def get_page(url):
async with aiohttp.ClientSession() as session: # 实例化session对象
async with await session.get(url=url) as response: # 发送get请求
text = await response.text()
print(text)
urls = [
'http://127.0.0.1:5000/xixi',
'http://127.0.0.1:5000/haha',
'http://127.0.0.1:5000/hehe',
]
start = time.time()
# 任务列表, 放置多个任务对象
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
cro = get_page(url)
task = asyncio.ensure_future(cro)
tasks.append(task)
# 将多个任务对象对应的列表注册到事件循环中
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)
回调函数用于解析数据
import time
import aiohttp
import asyncio
def callback(task):
print('this is callback')
# 获取响应数据
page_text = task.result()
print("在回调函数中, 实现数据解析")
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with await session.get(url=url) as response:
text = await response.text()
print(text)
urls = [
'http://127.0.0.1:5000/xixi',
'http://127.0.0.1:5000/haha',
'http://127.0.0.1:5000/hehe',
]
start = time.time()
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
cro = get_page(url)
task = asyncio.ensure_future(cro)
# 给任务对象绑定回调函数, 用于解析响应数据
task.add_done_callback(callback)
tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)
模拟服务器
from flask import Flask
import time
app = Flask(__name__)
@app.route('/xixi')
def func1():
time.sleep(2)
return 'xixi'
@app.route('/haha')
def func2():
time.sleep(2)
return 'haha'
@app.route('/hehe')
def func3():
time.sleep(2)
return 'hehe'
if __name__ == '__main__':
app.run(threaded=True)