异步 协程 爬虫
高性能的异步爬虫
初识异步爬虫方式
- 多线程,多进程(不建议):
- 优点:可以为相关堵塞的操作单独开启线程和进程,堵塞程序就会实现异步执行
- 缺点:无法限制多进程或多进程 - 线程池,进程池:
- 优点:降低系统对于线程和进程创建和销毁的频率,减小系统开销
- 缺点:池中线程和进程数量有上限
举个栗子直观地看一下线程的作用吧
import time
def get_text(char):
print("正在下载",char)
time.sleep(2)
print("成功加载",char)
return char
#单线程运行:
#运行文本
text=['a','b','c','d']
#记录开始时间
start_time=time.time()
for i in text:
get_text(i)
#记录结束时间
end_time=time.time()
print("单进程共需要时间:",end_time-start_time)
#多线程运行
#导库
from multiprocessing.dummy import Pool
#实例化4线程池
pool=Pool(4)
start_time=time.time()
text_list=pool.map(get_text#进行多线程的目标函数名,没有()
,text#传入数据的列表
)
#该函数返回值为函数return值组成的列表,顺序和传入列表相对应
end_time=time.time()
print("多进程共需要时间:",end_time-start_time)
print(text_list)
结果为:
正在下载 a
成功加载 a
正在下载 b
成功加载 b
正在下载 c
成功加载 c
正在下载 d
成功加载 d
单进程共需要时间:8.001578569412231
正在下载 a
正在下载 b
正在下载 c
正在下载 d
成功加载 b成功加载 d
成功加载 c
成功加载 a
多进程共需要时间:2.0006678104400635
['a', 'b', 'c', 'd']
我们发现:多线程输出的顺序结束时间是随机的,也证实了多线程之间运行并不会相互影响
来喽,爬取最新梨视频
代码和部分讲解
import os
from lxml import etree
import re
import requests
from multiprocessing.dummy import Pool
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
# 梨视频url
url = 'https://www.pearvideo.com/'
# cookie破解
session = requests.Session()
session.get(url=url, headers=header)
# 获取前台代码并解析目标url 讲解【1】
response = session.get(url=url, headers=header).text
tree = etree.HTML(response).xpath('/html/body/div[2]/div[9]/div[2]/div[2]/ul/li')
# 存储名字和地址的列表
content_name_loc = []
for each_tree in tree:
# each_tree.xpath('./div/a/@href')[0]解析地址不完整,要完整化 讲解【1】
each_loc = 'https://www.pearvideo.com/' + each_tree.xpath('./div/a/@href')[0]
# 确定视频存储名称 讲解【1】
each_name = each_tree.xpath('./div/a/div[2]/text()')[0] + ".mp4"
# url_2是each_loc进入界面内部的一个包,我们不能直接从前台界面直接获取包,所以要获取一个新的url
# 这个url_2是对于所有视频详情界面共有的地址,通过不同的params里的'contId'值实现不同视频的呈现
# 讲解【2】
url_2 = 'https://www.pearvideo.com/videoStatus.jsp?'
header_1 = {
# 讲解【2】
'Referer': each_loc
,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
# 我们发现'contId'参数并不是毫无规律,而是我们在主界面获取each_loc的后面的一串数字
# 栗子:
# 若each_loc=https://www.pearvideo.com/video_1733139
# 那么'contId'参数就是‘1733373’,所以我们通过‘_’来进行分割
params = {
# 这个参数位置在哪见讲解【2】
'contId': each_loc.split('_')[-1]
}
each_response = session.get(url=url_2, params=params, headers=header_1).json()
# 看下面讲解
str_1 = each_response["videoInfo"]["videos"]["srcUrl"]
"""
each_response的json字符串
{
"resultCode":"1",
"resultMsg":"success", "reqId":"9424322a-fe48-48a8-86fd-85070498a1e8",
"systemTime": "1624770918308",
"videoInfo":{"playSta":"1","video_image":"https://image1.pearvideo.com/cont/20210624/11429718-143425-1.png",
"videos":{"hdUrl":"","hdflvUrl":"","sdUrl":"","sdflvUrl":"","srcUrl":"https://video.pearvideo.com/mp4/third/20210624/1624770918308-11429718-143415-hd.mp4"}}
}
我们需要提取的是"https://video.pearvideo.com/mp4/third/20210624/1624770918308-11429718-143415-hd.mp4"
但是我们在网页上打开该网址发现显示:404 Not Found
这是为什么呢?
因为真正的网址是这个形式的'https://video.pearvideo.com/mp4/third/20210624/cont-1733139-11429718-143415-hd.mp4'
所以发现失败了,那么我们想,视频界面是ajax,不能解析出来,用抓包又不是我们想要的
但是在山重水复时,静下心来发现我们刚才提取的网址和视频网址那么相像
于是有了一个得到的想法:
分割已有的提取的网址来组成我们的目标网址!!!
each_loc='https://www.pearvideo.com/video_1733139'
str_1="https://video.pearvideo.com/mp4/third/20210624/1624770918308-11429718-143415-hd.mp4"
new_loc='https://video.pearvideo.com/mp4/third/20210624/cont-1733139-11429718-143415-hd.mp4'
new_loc就是我们的目标,怎么得到呢?
就是把str_1的 1624770918308换成cont-1733139
而cont我想就是参数名,每个视频相同
对于1733139就是each_loc的后面一串
于是也就有了下面这一段代码
那么我们开始一步步剖析它:
1. a = 'cont-' + each_loc.split('_')[-1]
each_loc.split('_')结果就是
['https://www.pearvideo.com/video''1733139']
each_loc.split('_')[-1]就是'1733139',在加上'cont-',就是我们想要的结果
2. re_1 = "(.*)/.*?-(.*)"
b = re.findall(re_1, str_1)
这两句就是使用正则表达式提取str_1中的
'https://video.pearvideo.com/mp4/third/20210624'(因为最后一个/结束,所以用的贪婪匹配)
和
'11429718-143415-hd.mp4'
3. new_loc = b[0][0] + "/" + a + "-" + b[0][1]
对结果进行拼接得到结果
不要忘了"/" 和"-"
这就得到了我们苦苦寻找的视频地址了
"""
a = 'cont-' + each_loc.split('_')[-1]
print(str_1)
re_1 = "(.*)/.*?-(.*)"
b = re.findall(re_1, str_1)
new_loc = b[0][0] + "/" + a + "-" + b[0][1]
print(new_loc)
# 到这里可能你已经忘了还有多线程的事,嘻嘻嘻
# 这里为了最后多线程爬取内容更加方便
# 我们还用字典的格式对数据视频名称each_name和new_loc放于content_name_loc中
content_name_loc.append({"name": each_name, "loc": new_loc})
# 在这里输出一下看看结果是否符合预期
print(content_name_loc)
# 创建文件夹
if not os.path.exists("./梨视频"):
os.mkdir("./梨视频")
# 分装线程池
def get_content(dic):
# 请求数据
video_data = session.get(url=dic['loc'], headers=header).content
# 打开文件并写入
fp = open("./梨视频/" + dic["name"], mode="wb")
fp.write(video_data)
fp.close()
# 计算时间(跟网速有关)
start = time.time()
# 实例化Pool,定个数
pool = Pool(len(content_name_loc))
# 多线程运行
pool.map(get_content, content_name_loc)
# 关闭线程
pool.close()
# 等待所有线程结束
pool.join()
end = time.time()
# 输出时间
print(end - start)
看结果:
(提前说明一下,这可不是博主喜欢的内容啊,完全是巧合,要信我,嘻嘻嘻~~~)
讲解
讲解【1】
简单的几条线就讲解完了???
对,没错!!!此处无声胜有声
讲解【2】
蓝色内容就是我们想获取的视频地址!
咦?这是什么吗?我直接爬不得了?还用你的分割?
但是你爬取后就会发现爬去数据里根本找不到这个网址
为什么呢?让我们刷新一下界面
那个网址就会消失
所以我们怀疑是ajxa,所以我们复制在包里寻找
结果无结果,在XHR中只有一个包。
这个包就是我们提到的合成new_loc的包
另有refer参数需要在headers中
该包的返回值代码中已经提到,这里不再展示
【持续更新ing】