0
点赞
收藏
分享

微信扫一扫

手把手教你使用线程池爬取动态加载的短视频

@TOC

1 获取短视频页面信息

本次爬取的短视频网站是梨视频生活区,网址为https://www.pearvideo.com/category_5打开之后的页面是:
image.png我是使用xpath数据解析工具,用这个原始页面初始化一个etree对象。要注意的是爬取梨视频的时候要使用session对象进行爬取。

url = 'https://www.pearvideo.com/category_5'
session = requests.Session()
page_text = session.get(url = url, headers = headers).text
tree = etree.HTML(page_text)

之后就可以根据xpath路径获取每个视频的网址,就可以对视频详情页进行请求发送,这里以最热的视频为例:
image.png可以发现每个视频均在li标签下,那就可以获取ul标签下的所有li标签:

li_list = tree.xpath('//*[@id="listvideoListUl"]/li')

li标签下的a标签得到视频详情页的网址,但要注意的是那个网址并不完整,所以要自行补充完整。
image.png

detail_url = 'https://www.pearvideo.com/' + li.xpath('.//a/@href')[0]
name = li.xpath('.//a/div[2]/text()')[0] + '.mp4'
#对详情页发起请求
detail_page_text = session.get(url = detail_url, headers = headers).text

至此短视频的页面数据获取完成。
image.png

2 获取视频地址

获得短视频页面数据之后,按照常规的步骤,接下来应该从具体标签中获取视频的地址。
image.png
但是我们会发现得到的列表是空的,这是因为该网站的视频是动态加载出来的,我们可以进一步检验,打开详情页源码中的network下的Fetch/XHR选项然后对网址进行重新加载,就会看到抓包工具所抓取的数据。
tempsnip.png对于动态加载的数据我们对请求方法进行传参,所需要传的参数在Payload栏进行获取。
image.png其中第一个参数是详情页网址后面的数字字符串,第二个参数是一个随机数。然后就可以对动态数据发送请求,要注意的是此时的url是动态数据的url,且得到的数据是json类型数据。
image.png

ajax_url = "https://www.pearvideo.com/videoStatus.jsp?"
params = {
    'contId': detail_url.split('_')[-1],
    'mrd': random.random()
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36',
    'Referer' : detail_url #表示一个来源
}
json_data = session.get(url = ajax_url, headers = headers, params = params).json()

因为返回json类型数据是一个字典,所以我们可以以字典的方式来获取视频地址。
image.png至此,我们都认为得到了视频地址,但其不然,梨视频网站还对视频的地址进行了加密,也就是我们从json响应数据得到的视频地址和在详情页标签中得到的地址不一样。但是两者之间的差距是有规律的。例如下面:

#正确的网址
src = 'https://video.pearvideo.com/mp4/third/20220223/cont-1752582-13293812-085442-hd.mp4'
#加密后的网址
src = 'https://video.pearvideo.com/mp4/third/20220223/1645596094850-13293812-085442-hd.mp4'

可以分析知道只有后面的部分有差异,我们就可以利用字符串的拆分拼接和正则表达式进行破解。

rel = 'cont-' + params['contId']
lst = json_data['videoInfo']['videos']['srcUrl'].split("/")
false_src_part = lst[-1].split("-")[0]
real_vedio_src = re.sub(false_src_part, rel, json_data['videoInfo']['videos']['srcUrl'])

以字典形式保存所有视频的名称url

vedio_info = {'Name' : name, 'Url' : real_vedio_src}
urls.append(vedio_info)

3 使用线程池对视频进行爬取

初始化线程池,因为最热板块只有四个视频,所以传参为4.

pool = Pool(4)

利用map()方法进行线程池抓取操作,然后对数据进行持久化存储。

#对视频链接发起请求获取视频的二进制数据,然后将数据进行返回
def get_vedio_data(vedio_info) :
    url = vedio_info['Url']
    data = requests.get(url = url, headers = headers).content
    #持久化存储
    with open('Vedio/' + vedio_info['Name'], 'wb') as fp :
        fp.write(data)
        print(vedio_info['Name'],'下载成功!')
pool.map(get_vedio_data, urls)

关闭线程池。

pool.close()
pool.join()

4 完整代码

import requests
import random
import re
import os
from multiprocessing.dummy import Pool
from lxml import etree

def get_vedio_info(headers) :
    url = 'https://www.pearvideo.com/category_5'
    session = requests.Session()
    page_text = session.get(url = url, headers = headers).text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//*[@id="listvideoListUl"]/li')

    urls = [] #存储视频的网址
    for li in li_list :
        detail_url = 'https://www.pearvideo.com/' + li.xpath('.//a/@href')[0]
        name = li.xpath('.//a/div[2]/text()')[0] + '.mp4'
        #对详情页发起请求
        detail_page_text = session.get(url = detail_url, headers = headers).text
        #注意这里,url要使用动态url
        ajax_url = "https://www.pearvideo.com/videoStatus.jsp?"
        params = {
            'contId': detail_url.split('_')[-1],
            'mrd': random.random()
        }

        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36',
            'Referer' : detail_url #表示一个来源
        }
        json_data = session.get(url = ajax_url, headers = headers, params = params).json()
        #寻找规则,将伪装网址替换成真视频网址
        # src = 'https://video.pearvideo.com/mp4/third/20220223/cont-1752582-13293812-085442-hd.mp4'
        # src = 'https://video.pearvideo.com/mp4/third/20220223/1645596094850-13293812-085442-hd.mp4'
        rel = 'cont-' + params['contId']
        lst = json_data['videoInfo']['videos']['srcUrl'].split("/")
        false_src_part = lst[-1].split("-")[0]
        real_vedio_src = re.sub(false_src_part, rel, json_data['videoInfo']['videos']['srcUrl'])

        vedio_info = {'Name' : name, 'Url' : real_vedio_src}
        urls.append(vedio_info)
    return urls

#对视频链接发起请求获取视频的二进制数据,然后将数据进行返回
def get_vedio_data(vedio_info) :
    url = vedio_info['Url']
    data = requests.get(url = url, headers = headers).content
    #持久化存储
    with open('Vedio/' + vedio_info['Name'], 'wb') as fp :
        fp.write(data)
        print(vedio_info['Name'],'下载成功!')

if __name__ == "__main__" :
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
    }
    if not os.path.exists('Vedio') :
        os.mkdir('Vedio')
    urls = get_vedio_info(headers)
    #使用线程池对视频数据进行请求(较为耗时的阻塞数据)
    pool = Pool(4)
    pool.map(get_vedio_data, urls)

    pool.close()
    pool.join()
举报

相关推荐

0 条评论