目录
1)模拟网络请求,使用线程池方式执行 -- 多任务执行时,耗时少,但受限于线程数量
2)使用async 关键字定义方法,request方法被调用后,方法体中的内容不会被立即执行;
3)使用异步协程,将协程对象注册到事件循环中,并启动事件循环后,request方法会被执行;
4)将协程对象封装到task任务对象中,将task注册到事件循环中
5)将协程对象封装到future任务对象中,将future任务对象注册到事件循环中
6)绑定回调 -- task.add_done_callback(callback_func)
2)基于异步的多任务协程 -- 方法中的同步代码,改为异步代码
5、多任务异步协程爬虫 -- 基于异步的网络请求aiohttp模块
2)多任务异步协程--基于同步的网络请求的模块 requests.get(url)
3)多任务异步协程--基于异步的网络请求的模块 aiohttp.ClientSession()
异步爬虫
- 在爬虫中,使用异步实现高性能的数据爬取操作;如requests.get()是一个阻塞的方法;
- 异步爬虫的方式:
- 1)多线程,多进程:不推荐使用(无法对每个请求开辟一个进程或线程)
- 好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行;
- 弊端:无法无限制的开启多线程或多进程(耗费CPU资源,影响响应效率)
- 2)线程池,进程池:适当使用
- 好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销;
- 弊端:池中线程或进程的数量是有上限;(当阻塞操作大大高于线程池对象的时候,即待请求数远高于线程数量时,不适用;)
- 原则:不能把所有的阻塞操作,都交给线程池来处理,线程池应该处理的是阻塞且耗时的操作;
- 3)单线程+异步协程:推荐
- 4)多任务异步协程aiohttp:推荐
- 1)多线程,多进程:不推荐使用(无法对每个请求开辟一个进程或线程)
1、单线程爬虫
1)模拟网络请求,单线程下载 -- 多任务执行时,耗时多
- 使用单线程下载(下载4个,每个下载需要时间为2秒,总耗时8秒)
# 模拟网络请求下载的操作,每个下载需要时间为2秒;
# 使用单线程串行方式执行
import time
def get_page(str):
print('正在下载:', str)
time.sleep(2)
print('下载成功:', str)
name_list = ['xiaozi', 'aa', 'bb', 'cc']
start_time = time.time()
for i in range(len(name_list)):
get_page(name_list[i])
end_time = time.time()
print('下载耗时为:%s 秒' % (end_time - start_time))
输出结果:
正在下载: xiaozi
下载成功: xiaozi
正在下载: aa
下载成功: aa
正在下载: bb
下载成功: bb
正在下载: cc
下载成功: cc
下载耗时为:8.002454280853271 秒
2、线程池爬虫
1)模拟网络请求,使用线程池方式执行 -- 多任务执行时,耗时少,但受限于线程数量
- 使用线程池下载(下载4个,每个下载需要时间为2秒,总耗时2秒左右)
- 导入线程池模块对应的类,应用在阻塞操作中:from multiprocessing.dummy import Pool
- 实例化一个线程池对象,如:4个线程,pool = Pool(4)
- 使用线程池处理阻塞的方法,参数:阻塞的方法,可迭代序列,result_list = pool.map(get_page, name_list)
# 模拟网络请求下载的操作,每个下载需要时间为2秒;
# 使用线程池方式执行
import time
# 导入线程池模块对应的类,应用在阻塞操作中
from multiprocessing.dummy import Pool
def get_page(str):
print('正在下载:', str)
time.sleep(2)
print('下载成功:', str)
name_list = ['xiaozi', 'aa', 'bb', 'cc']
start_time = time.time()
# 实例化一个线程池对象,4个线程
pool = Pool(4)
# 使用线程池处理阻塞的方法,参数:阻塞的方法,可迭代序列
# 将列表中每一个列表元素传递给get_page进行处理
result_list = pool.map(get_page, name_list) # 阻塞方法get_page()的返回值会赋给result_list
end_time = time.time()
print('下载耗时为:%s 秒' % (end_time - start_time))
print(result_list) # [None, None, None, None]
print(type(result_list)) # <class 'list'>
输出结果:
正在下载:正在下载:正在下载: aa正在下载:bb
cc
xiaozi
下载成功:下载成功:下载成功: 下载成功:aa bbxiaozi
cc
下载耗时为:2.042710781097412 秒
[None, None, None, None]
<class 'list'>
2)线程池方式下载
- 线程池使用原则:线程池处理阻塞并且较为耗时的操作,如:耗时操作有视频下载,图片下载,mp3下载;文本解析,可以不使用线程池,耗时较少;
- 线程开多了,影响下载速度,可能会产生连接超时;
- 视频的下载地址,是在请求返回的response中,可以使用正则表达式re抽取response中的视频地址;
- src_url = re.findall('srcUrl":"(.*)"}', response.text)[0]
- 线程池方式下载视频:pool.map(get_content, video_info_list) # 参数是阻塞的方法,下载视频方法,和传入的视频下载地址,视频名称
# 爬取视频数据
# 线程池原则:线程池处理的是阻塞且耗时的操作
import requests
from fake_useragent import UserAgent
from lxml import etree
import re
import os.path
import os
import time
from multiprocessing.dummy import Pool
ua = UserAgent()
header = {
"User-Agent": ua.random,
"Referer": None
}
# 获取视频【世界】板块的新闻详情页url
def get_detail_url(url):
response = requests.get(url=url, headers=header)
response.encoding = 'utf-8'
info_dic_list = []
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.HTML(response.text, parser=parser)
div_list = tree.xpath('//div[@class="vervideo-bd"]')
for div in div_list:
a = 'https://www.pearvideo.com/' + div.xpath('./a/@href')[0]
video_name = div.xpath('.//div[@class="vervideo-title"]/text()')[0]
info_dic = {
"detail_url": a,
"video_name": video_name
}
info_dic_list.append(info_dic)
return info_dic_list
# 拼接新闻视频url
def get_video_url(detail_url):
# 通过详情页获取contID
cont_id = detail_url.rsplit(sep='_', maxsplit=1)[1]
get_url = f'https://www.pearvideo.com/videoStatus.jsp?contId={cont_id}' # ajax请求获取新闻video的url
header["Referer"] = f"https://www.pearvideo.com/video_{cont_id}"
session = requests.session()
session.headers = header
response = session.get(get_url)
# 替换srcUrl中部分数字串为contID
src_url = re.findall('srcUrl":"(.*)"}', response.text)[0]
url_list = src_url.rsplit(sep='/', maxsplit=1)
url_replace_list = url_list[1].split(sep='-', maxsplit=1)
url_replace_list[0] = "cont-" + cont_id
url_list[1