逆向分析pptv,利用python實現下載藍光畫質視頻(Vip)


前言

前幾天我想看一個番劇, 正好搜索到了 PP視頻,我才知道PP視頻就是PPTV聚力,我想把番劇下載下來,結果發現視頻竟然不是m3u8格式,而是多段mp4,所以簡單的寫了個腳本,可以在不登錄的情況下直接下載vip和付費視頻藍光畫質(只能說明這個視頻網站做的不行)

開始分析

經過調試我首先得到了視頻地址的url

https://10314.vcdn.pplive.cn/0/0/1/2de8f8694a79c437a11b09941fb1cb0a.mp4?h5vod.ver=2.1.3&k=0b6a74e50374776203bd05d55a090fba-e800-1588284812&type=mhpptv

當然,我當時拿到的url 是很長的一段還有其他參數,但是我發現那些參數都是無用的影響訪問的參數只有這幾個
h5vod.ver : 固定值
type : 固定值
k : 變值

說明 k 是一個接口得到的, 所以我需要找到對應的接口就可以了

但是現在還有一個問題就是 這個只是視頻的一段, 其他視頻分段地址又是什么樣的呢?
經過我反復調試發現 url 中的

https://10314.vcdn.pplive.cn/a/b/c/

(對應位置我用字母來代替數字了,為了描述方便)
a 分段視頻的第幾段
b 默認為0 就可以(具體表示什么不清楚,但是瞎填肯定是不行的)
c 這個數字可以隨意瞎填,因為后台解析沒用到這個參數,但是要有這個參數
a 為 0 第一段視頻, a 為 1 為第二段視頻

所以接下來的問題就是找到那個接口了
經過調試發現了一個接口

https://web-play.pptv.com/webplay3-0-12407631.xml?o=0&version=6&type=mhpptv&appid=pptv.web.h5&appplt=web&appver=4.0.7&cb=a

這個接口參數只有一個變量 那就是12407631,也就是id,每一集都有唯一的 id ,而這個id 我們也可以通過訪問對應視頻播放界面的url后返回的html源碼中查看到

 


返回的不只是一集,而是全劇的每一集id都可以通過一個url來獲取到

那么我們來看看 這個接口返回了哪些內容

 


幾種畫質的 mp4,知道這些那么我們還差一個 k參數就可以構造視頻的url了
經過我查找發現了k值, 一個視頻的一種清晰度對應唯一一個k值

 


這里用了url編碼看起來亂七八糟,用python解碼一下

from urllib.parse import unquote
print(unquote("4e6a8848fb388e09708a132bf4caeb4e-5775-1588285899%26bppcataid%3D17"))

 

輸出:

4e6a8848fb388e09708a132bf4caeb4e-5775-1588285899&bppcataid=17

可以看到 & 符號前面的就是k ,所以到時候我們可以用& 進行分割然后取出 k

到此看起來問題全部解決了我們也可以構造視頻url 了,但是還有一個問題, 那就是視頻到底分多少段我們還不知道, 畢竟我們不能遍歷去訪問直到訪問不到數據(那樣太傻了)

其實接口里面也存在這樣的數據了

 


畫質下面mp4 字段 childNodes 是一個列表,這個列表的長度減1 就是不同畫質的分段數, 減1 是因為最后一個元素識別的內容不是描述視頻分段信息的, 不得不說這個接口返回的數據構造的真是及其混亂, 我解析的時候都費了很大勁!

到此為止問題全部解決

python代碼

import requests
import re
import json
import os
from threading import Thread
import sys

import time

requests.packages.urllib3.disable_warnings()

def progress():
    width=30
    while True:
        global all_num
        global now_num
        percent = now_num/all_num * 100
        left = int(width * percent // 100)
        right = width - left
        print('\r[', '#' * left, ' ' * right, ']',f' {percent:.0f}%',sep='', end='', flush=True)
        if all_num == now_num:
            break
        time.sleep(1)

def hecheng(sh,path):
    # 判斷是否只是一段視頻
    if len(os.listdir(path)) <= 2:
        os.remove(path+os.sep+"1.txt")
    else:
        os.system(sh)
        for i in os.listdir(path):
            if i != 'output.mp4':
                os.remove(path+os.sep+i)

def download(url,id_,name):
    global now_num
    data = requests.get(url,headers={"Range": "bytes=0-"},stream=True).content
    with open(name+os.sep+str(id_)+'.mp4','wb') as f:
        f.write(data)
    now_num += 1

def api_get(id_):
    global all_num   
    global now_num
    all_num = 0
    now_num = 0

    api_url = f"https://web-play.pptv.com/webplay3-0-{id_}.xml?o=0&version=6&type=mhpptv&appid=pptv.web.h5&appplt=web&appver=4.0.7&cb=a"
    s = requests.get(api_url,verify=False,headers=headers)
    data = json.loads(s.text[2:-4])
    try:
        name = data['childNodes'][2]['nm']
    except Exception:
        name = data['childNodes'][0]['nm']
    print("正在下載:"+name)

    rid = data['childNodes'][-4]['rid']
    all_num = len(data['childNodes'][-4]['childNodes']) -1
    kk = data['childNodes'][-5]['childNodes'][-1]['childNodes'][0].split('%26')[0]
    if not os.path.exists(name):
        os.makedirs(name)

    # 合成命令
    a1 = os.path.abspath(name+os.sep+'1.txt')
    a2 = os.path.abspath(name+os.sep+'output.mp4')
    sh = f'ffmpeg -f concat -safe 0 -i "{a1}" -c copy "{a2}" -loglevel error'
    path = os.path.dirname(a1)

    # 啟動進度條線程
    progress_t = Thread(target=progress)
    progress_t.start()

    t_list = []
    t_list.append(progress_t)

    f = open(name+os.sep+'1.txt','w')
    for i in range(all_num):
        video_url = f"https://10314.vcdn.pplive.cn/{i}/0/1/{rid}?h5vod.ver=2.1.3&k={kk}&type=mhpptv"
        f.write("file "+ os.path.abspath(name+os.sep+f'{i}.mp4')+"\n")

        t = Thread(target=download,args=(video_url,i,name))
        t_list.append(t)
        t.start()

        if not mul_t:
            t.join()

    f.close()   
    for t in t_list:
        t.join()

    # print(sh)
    print("\n"+name+" 正在合成...")
    hecheng(sh,path)
    print(name+" 合並成功")

def start(url,is_all,s,e,mul_t):
    response = requests.get(url,verify=False,headers=headers)
    if response.status_code == 200:
        result = re.findall("var webcfg = (.*?);",response.text)
        data = json.loads(result[0])

        if is_all:
        # 下載劇集
            try:
                for item in data['playList']['data']['list'][s-1:e]:
                    api_get(item['id'])
            except Exception:
                for item in data['playList']['data']['list']:
                    api_get(item['id'])
        else:
        # 下載單集
            api_get(data['id'])

if __name__ == "__main__":
    all_num = 0
    now_num = 0
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
    }

    url = input("輸入地址:")
    is_all = input("是否下載全劇(默認否):")
    if is_all:
        r = input("請輸入下載劇集(例如1-5,默認全部):")
        if r:
            s,e = r.split("-")    
            s = int(s)
            if e:
                e = int(e)
            else:
                e = None
        else:
            s = None
            e = None

    mul_t = input("是否啟用多線程下載(默認否):")
    start(url,is_all,s,e,mul_t)

 

也是做了一個簡單的命令行工具,功能有限

  1. 通過一次輸入可以下載全集的藍光畫質視頻
  2. 下載vip 視頻
  3. 下載付費視頻
  4. 下載完分段視頻自動調用 ffmpeg 命令進行合成
  5. 選擇性 下載 m-n 集視頻
  6. 多線程下載(下載電影的時候最好不要開啟)

使用方法: 視頻播放界面的url輸入進去,然后回車, 剩下的參數不寫直接回車相當於默認, 隨意寫什么相當於是

其他注意 : windows 使用調用 ffmpeg先要把其加入系統環境變量里面,另外 第 81 行代碼應改為

f.write("file "+ (os.path.abspath(name+os.sep+f'{i}.mp4')+"\n").replace("\\","\\\\"))

 

這個主要是windows 腦殘的文件路徑問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM