Python爬虫 之 异步 协程 爬虫

异步 协程 爬虫

高性能的异步爬虫

初识异步爬虫方式

  1. 多线程,多进程(不建议):
    - 优点:可以为相关堵塞的操作单独开启线程和进程,堵塞程序就会实现异步执行
    - 缺点:无法限制多进程或多进程
  2. 线程池,进程池:
    - 优点:降低系统对于线程和进程创建和销毁的频率,减小系统开销
    - 缺点:池中线程和进程数量有上限

举个栗子直观地看一下线程的作用吧

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)

看结果:
(提前说明一下,这可不是博主喜欢的内容啊,完全是巧合,要信我,嘻嘻嘻~~~)
Python爬虫 之 异步 协程 爬虫

讲解

讲解【1】
Python爬虫 之 异步 协程 爬虫
简单的几条线就讲解完了???
对,没错!!!此处无声胜有声
讲解【2】
Python爬虫 之 异步 协程 爬虫

蓝色内容就是我们想获取的视频地址!
咦?这是什么吗?我直接爬不得了?还用你的分割?
但是你爬取后就会发现爬去数据里根本找不到这个网址
为什么呢?让我们刷新一下界面

Python爬虫 之 异步 协程 爬虫

那个网址就会消失
所以我们怀疑是ajxa,所以我们复制在包里寻找
结果无结果,在XHR中只有一个包。
这个包就是我们提到的合成new_loc的包
另有refer参数需要在headers中
该包的返回值代码中已经提到,这里不再展示

Python爬虫 之 异步 协程 爬虫

【持续更新ing】
Python爬虫 之 异步 协程 爬虫

上一篇:Mac OS X 访问 Windows 共享文件夹


下一篇:phpmywind教程:关于日期函数调用整理