目的
在爬虫中使用异步实现高性能的数据爬取操作
异步爬虫的方法
-
多线程,多进程(不建议)
- 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
- 弊端:无法无限制的开启多线程或者多进程
-
线程池,进程池(适当使用)
- 好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销
- 弊端:池中线程或者进程的数量是有上限。
- 实例:爬出梨视频数据
from multiprocessing import Pool
import requests
from lxml import etree
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
}
#对下述url发起请求解析出视频详情页的url和视频名称
url = 'https://www.pearvideo.com/category_5'
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
urls_list = [] #视频url地址和名称
for li in li_list:
detai_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
name = li.xpath('./div/a/div[2]/text()')[0]
contId =li.xpath('./div/a/@href')[0]
contId = contId.split('_')[1]
#发起请求获得视频的地址
new_url = 'https://www.pearvideo.com/videoStatus.jsp'
param = {
'contId':contId
}
headers = {
'Referer':detai_url
}
response = requests.get(url=new_url,headers=headers,params=param)
response = response.json()
video_url = response['videoInfo']['videos']['srcUrl']
dic = {
'name':name,
'url':video_url
}
urls_list.append(dic)
#对视频链接发起请求获取视频的二进制数据,然后将视频进行返回
def get_video_data(dic):
url = dic['url']
print(url)
data = requests.get(url=url,headers=headers).content
video_path = dic['name'] + '.mp4'
with open(video_path,'wb') as f:
f.write(data)
#使用线程池对视频数据进行请求
if __name__ == '__main__':
pool = Pool(4)
pool.map(get_video_data,urls_list)
- 单线程+异步协程(推荐)
-
event_Loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
-
coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用,我们可以使用 async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
-
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
-
future:代表将来执行或还没有执行的任务,实际上和task没有本质区别
-
async:定义一个协程
-
await:用来挂起阻塞方法的执行。
import asyncio
async def request(url):
print('正在请求:',url)
print('请求成功',url)
return url
#async修饰的函数,调用之后返回的一个协程对象
c = request('www.baidu.com')
# #创建一个时间循环对象
# loop = asyncio.get_event_loop()
# #将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)
# #task的使用
# loop = asyncio.get_event_loop()
# #基于loop创建一个task对象
# task = loop.create_task(c)
# print(task) #打印协程状态
# #将task注册到循环事件中
# loop.run_until_complete(task)
# print(task)
#future的使用
# loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(c)
# print(task)
# loop.run_until_complete(task)
# print(task)
#假设该函数为一个回调函数
def callback_func(task):
#result返回的就是任务对象中封装的协程对象对应函数的返回值
print(task.result())
#绑定回调
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
#将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)
多任务协程
import asyncio
import time
async def request(url):
print('正在下载',url)
#在异步协程中如果出现同步模块相关的代码,那么就无法实现异步
# time.sleep(2) 属于同步代码
#当在saycio中遇到阻塞操作必须进行手动挂起
await asyncio.sleep(2)
print('下载完成',url)
start = time.time()
urls = [
'www.baidu.com',
'www.sougou.com',
'www.bandoujia.com'
]
#任务列表:多放多个任务对象
stasks = []
for url in urls:
c = request(url)
task = asyncio.ensure_future(c)
stasks.append(task)
loop = asyncio.get_event_loop()
#需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(stasks))
print(time.time()-start)
单线程异步爬虫练习
##############服务端代码#############
from flask import Flask
import time
app = Flask(__name__)
@app.route('/bobo')
def index_bobo():
time.sleep(2)
return 'hello bobo'
@app.route('/jay')
def index_jay():
time.sleep(2)
return 'hello jay'
@app.route('/tom')
def index_tom():
time.sleep(2)
return 'hello tom'
if __name__ == '__main__':
app.run(threaded=True)
#####################运行代码######################
import requests
import asyncio
import time
import aiohttp
start = time.time()
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom'
]
async def get_page(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
#test()返回字符串形式的响应数据
#read()返回二进制形式的响应数据
#json()返回json形式的响应数据
#注意:获取响应数据操作之前一定要使用await进行手动挂起
page_text = await response.text()
print(page_text)
# async def get_page(url):
# print('正在下载',url)
# # response = requests.get(url=url) #request.get是基于同步的,必须使用基于异步的网络请求模块进行指定url发送
# #aiohttp:基于异步网络请求的模块
# print('下载完成',response.text)
tasks = []
for url in urls:
c = get_page(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
endtime = time.time()
print('总耗时:',endtime-start)
###############################结果#########################
hello jay
hello bobo
hello tom
总耗时: 2.011115074157715
Process finished with exit code 0