python爬虫-异步爬虫
前言
异步爬虫的目的:提高数据爬取的性能和效率
异步爬虫的方式
- 多线程/多进程
为相关阻塞的操作单独开启线程或进程,使得阻塞操作可以异步执行。但是由于资源有限,我们并不能无限制的开启多线程或多进程。 - 线程池/进程池
可以降低系统为了创建和销毁线程或进程而产生的开销。但是池中的线程或进程仍然是有上限的。
样式
"""
使用进程池模拟异步爬虫
"""
import time
from multiprocessing.dummy import Pool
def get_page(url):
print('读取:', url)
time.sleep(3)
print('完成:', url)
if __name__ == '__main__':
urls = ['url1', 'url2', 'url3', 'url4']
# 实例化进程池
pool = Pool(2)
start_time = time.time()
# 使用进程池
pool.map(get_page, urls)
end_time = time.time()
print('second: %d' % (end_time - start_time))
读取: url1
读取: url2
完成: url1
读取:完成: url2
读取: url3url4
完成:完成: url3 url4
second: 6
- 单线程+异步协程
(这里涉及比较复杂我们在另外一篇里展开)
python版本要求:3.6
event_loop:事件循环。相当于一个无限循环,我们可以把一些函数注册到这个事件循环上。当满足某些条件时,函数会循环执行。
coroutine:协程对象。我们可以将协程对象注册到事件循环中,它会被事件循环调用。我么可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
task:任务。他是协程对象的进一步封装,包含了任务的各个状态。
future:表示将来执行或还未执行的任务,和 task没本质区别。
async:定义一个协程。
await:用来挂起阻塞方法的执行。
异步爬虫案例
进程池爬取梨视频音乐板块热门
"""
使用进程池爬取梨视频音乐栏目视频数据
注意:线程池处理的主要是阻塞且耗时的操作
"""
from multiprocessing.dummy import Pool # 导出进程池模块对应的类
import requests
from lxml import etree
import random
import os
def get_video_url(url):
# 在Network->XHR->Headers找到头部信息
headers = {
'Host': 'www.pearvideo.com',
'Referer': url,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/86.0.4240.198 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
ajax_url = "https://www.pearvideo.com/videoStatus.jsp?" # Headers里面的Request URL,后面跟的是可变参数
contId = url.split('/')[-1].split('video_')[1] # contId就是视频url编号
# print(contId)
mrd = random.random() # mrd就是个随机小数
params = {
'contId': contId,
'mrd': str(mrd)
}
try:
response = requests.get(url=ajax_url, headers=headers, params=params)
# print(response.headers['content-type'])
# print(response.encoding)
# print(response.text)
if response.status_code == 200:
json = response.json() # 因为MP4的视频是动态加载出来的,所以通过ajax请求获取视频的真实网址
if json:
fake_url = json['videoInfo']['videos']['srcUrl'] # 通过官网界面提取的url,并不是真正的url
# print("fake_url:", fake_url)
fake_url_list = fake_url.split('/')
# print("fake_url_list:", fake_url_list)
end = fake_url_list.pop() # 删除不必要的字符串
# print("end:", end)
end_list = end.split("-")
# print("end_list:", end_list)
end_url = "" # end_url是一个结尾字符串
for i in range(len(end_list) - 1):
end_url = end_url + "-" + end_list[i + 1]
# print("end_url:", end_url)
"""
通过分析发现,ajax请求获取的网址是一个伪网址,和真实网址有区别
伪地址:https://video.pearvideo.com/mp4/third/20210606/1623161815187-11724129-212335-ld.mp4
真地址:https://video.pearvideo.com/mp4/third/20210606/cont-1731450-11724129-212335-ld.mp4
"""
# 真实的地址,先用假地址,然后组合contId
real_url = ""
for element in fake_url_list:
real_url = real_url + element + "/"
real_url = real_url + "cont-" + str(contId) + end_url
print("real_url:", real_url)
return real_url
except requests.ConnectionError as e:
print("错误:", e.args)
def download_video(url_dict):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/86.0.4240.198 Safari/537.36'}
video_data = requests.get(url=url_dict['url'], headers=headers).content
print(url_dict['name'], "正在下载……")
filename = url_dict['name']
with open(filename, 'wb') as f:
f.write(video_data)
print(url_dict['name'], "下载成功!")
def main():
url = 'https://www.pearvideo.com/category_59' # 爬取梨视频“音乐版块”
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/86.0.4240.198 Safari/537.36'}
response = requests.get(url=url, headers=headers)
page_text = response.text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
video_urls = list() # 用于存储多个url的列表
for li in li_list:
detail_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
name = li.xpath('./div/a/div[@class="vervideo-title"]/text()')[0] + '.mp4'
# print(detail_url, name)
real_url = get_video_url(detail_url)
url_dict = {'name': name, 'url': real_url}
video_urls.append(url_dict)
try:
os.mkdir('LiShiPin') # 创建“LiShiPin”文件夹
except FileExistsError:
pass
os.chdir('LiShiPin')
pool = Pool(4)
pool.map(download_video, video_urls)
pool.close()
pool.join()
if __name__ == '__main__':
main()
浙公网安备 33010602011771号