QuiryRain 发表于 2023-1-10 18:08

用Python下载小破站视频

本帖最后由 QuiryRain 于 2023-2-15 22:14 编辑

原因:前两天一朋友问有没有小破站的视频下载工具。然后就想着,最近学了点python是不是可以实现。

新增:
    1. 视频以当前最高清晰度进行下载,在带上cookie的前提下,可下载大会员视频。
    2. 增加音轨、视频轨下载进度条

原理: 下载视频轨道文件、和音频轨道文件,最后用ffmpeg进行视频轨和音轨的合成

目前下载工具可以在 win/linux/mac上运行。其实就是执行python

# Linux/Mac OS X
在linux/Mac OS X系统中可能需要修改下文件权限

### Linux
> cd /path/to/BiliDown
> chmod +x tools/linux/ffmpeg

### Mac
> cd /path/to/BiliDown
> chmod +x tools/mac/ffmpeg


### 教程
```Text
pip install -r reqirements.txt

编辑 download.py 文件,在最后 url 中填上你需要下载的B站链接,
如果需要换 cookie, 在Cookies中填写你浏览器中的cookie内容
最后用python执行就行了

最终需要的视频会在当前目录下的 Output 中生成
```

代码:
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import os
import sys
import re
import time
import platform
import json
import traceback
import requests
import subprocess

requests.packages.urllib3.disable_warnings()

sess = requests.Session()
sess.headers.update({
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',

})

def CMD(command):
    p = subprocess.Popen(command, shell=True, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE, encoding='utf8')
    stdout, stderr = p.communicate(timeout=10)
    stdout = stdout.strip()
    return stdout, stderr

def download_file(filename, url):
    resp = sess.get(url, verify=False, stream=True)
    content_size = int(resp.headers['content-length'])
    if resp.status_code != 200:
      print('请求失败')
      sys.exit()
    size = 0
    start_time = time.time()
    print('Downloading ... File Size: {size:.2f} MB'.format(size=content_size/1024/1024.0))
    with open(filename, 'wb') as f:
      for chunk in resp.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)
                size += len(chunk)
                print(
                  '\r[下载进度]: %s %.2f%%' % (
                        '>'*int(size*50 / content_size),
                        float(size / content_size * 100)),
                  end=' ')
    print('\nDownload completed!,times: {}秒'.format(int(time.time() - start_time)))
    return filename


def delete_cache_file(path):
    try:
      os.remove(path)
    except Exception as e:
      print(e)


def get_video_and_audio_url_from_bilibili(url):
    re_playinfo = re.compile(r'window\.__playinfo__=(\{.*?)</script>', re.I)
    re_initial_state = re.compile(r'window\.__INITIAL_STATE__=(\{.*?);\(function\(\)', re.I)

    sess.headers.update({'referer': url})
    resp = sess.get(url, verify=False, timeout=(8, 8))
    html = resp.text
    # print(html)

    _initial_state = re_initial_state.search(html)
    _playinfo = re_playinfo.search(html)
    if not all():
      print('请求失败')
      sys.exit()

    playinfo = _playinfo.group(1)
    playinfo_json = json.loads(playinfo)
    initial_state = _initial_state.group(1)
    initial_state_json = json.loads(initial_state)
    dash = playinfo_json.get('data', {}).get('dash', {})
    video_urls = []
    for video in dash.get('video', []):
      base_url = video.get('baseUrl', '')
      if not base_url:
            continue
      video_urls.append(base_url)

    audio_urls = []
    for audio in dash.get('audio', []):
      base_url = audio.get('baseUrl')
      if not base_url:
            continue
      audio_urls.append(base_url)

    title = (initial_state_json.get('h1Title')
             or initial_state_json.get('videoData', {}).get('title')
             or get_bvid(url))
    return video_urls, audio_urls, title


def create_cache_directory():
    if not os.path.exists('BVcache'):
      os.mkdir('BVcache')

    if not os.path.exists('Output'):
      os.mkdir('Output')


def get_bvid(url):
    create_cache_directory()
    return url.split('?').rsplit('/', maxsplit=1)


def main(url):
    bvid = get_bvid(url)

    video_urls, audio_urls, video_name = get_video_and_audio_url_from_bilibili(url)
    # print(video_urls, audio_urls)

    video_filename = f'BVcache/video_{bvid}.m4s'
    audio_filename = f'BVcache/audio_{bvid}.m4s'
    print(f'{video_name}: {bvid} 开始下载视频轨道数据...')
    for video_url in video_urls:
      try:
            download_file(video_filename, video_url)
            break
      except Exception as e:
            print('Video Error. {}'.format(e))
            print(traceback.format_exc())
    print(f'{video_name}: {bvid} 视频轨道下载完毕')
    print(f'{video_name}: {bvid} 开始下载音频轨道数据...')
    for audio_url in audio_urls:
      try:
            download_file(audio_filename, audio_url)
            break
      except Exception as e:
            print('Audio Error. {}'.format(e))
    print(f'{video_name}: {bvid} 音频轨道下载完毕!')

    system = (platform.platform()).lower()
    if 'windows' in system:
      ffmpeg_tool = r'.\tools\win\ffmpeg.exe'
    elif 'macOS' in system:
      ffmpeg_tool = './tools/mac/ffmpeg'
    else:
      ffmpeg_tool = './tools/linux/ffmpeg'
    filename = video_name.replace(" ", "-") or bvid
    ffmpeg_command = (
      ffmpeg_tool + f' -i {video_filename} -i {audio_filename} -codec '
                      f'copy Output/{filename}.mp4'
    )
    # print(ffmpeg_command)

    CMD(ffmpeg_command)

    delete_cache_file(video_filename)
    delete_cache_file(audio_filename)

    print(f'{bvid} 视频mp4文件已经生成,请在当前运行路径中的"Output"文件夹中查看,'
          f'生成的文件名称为: {filename}.mp4')


if __name__ == '__main__':
    # url = 'https://www.bilibili.com/video/BV1yr4y1r7f6?spm_id_from=333.851.b_7265636f6d6d656e64.5'

    url = "https://www.bilibili.com/bangumi/play/ep706667?spm_id_from=333.337.0.0"
    # url = "https://www.bilibili.com/video/BV1Px4y1G7Ua"
    # url = 'https://www.bilibili.com/bangumi/play/ss44096?theme=movie&from_spmid=666.7.hotlist.2'
    url = 'https://www.bilibili.com/bangumi/play/ss41936?from_spmid=666.23.0.0'
    url = 'https://www.bilibili.com/bangumi/play/ep468228?from_spmid=666.25.episode.0&from_outer_spmid=666.24.0.0'
    sess.headers.update({
      # 'cookie': ("Your Cookie")
    })
    main(url)

    """
    运行环境: Linux、python
    安装环境包:pip install requests
    将上面的url修改为要下载的视频地址。
    运行:python download.py
    输出:视频mp4文件会存放到当前目录中的Output文件夹中
    """


下载链接放上:https://quiryrain.lanzoue.com/iR3zM0notoja密码:52pj

QuiryRain 发表于 2023-1-13 18:47

aa2923821a 发表于 2023-1-12 08:43
小破站缓存导出不行吗,我之前导歌mv就这么干

有些缓存不了,不过用音轨文件加视频轨文件,可以合成视频,方法通用

臀臀怪 发表于 2023-1-12 20:32

aa2923821a 发表于 2023-1-12 08:43
小破站缓存导出不行吗,我之前导歌mv就这么干

小伙子你还年轻,有些是没办法缓存的

aa2923821a 发表于 2023-1-12 08:43

小破站缓存导出不行吗,我之前导歌mv就这么干

shamoul 发表于 2023-1-12 16:56

很有用,学到了

hxywbrlzl 发表于 2023-1-12 17:24

又是一个新的下载思路,收藏学习了。之前用的都是现成的软件

whoami233 发表于 2023-1-13 22:25

我一般用you-get
pip install you-get

windgod489 发表于 2023-1-19 07:53

小破站知道被冠名不知咋想

yyn233 发表于 2023-1-25 23:01

画质默认是多少

Nuyoah0824 发表于 2023-1-28 14:20

看起来不错,可以试试
页: [1] 2
查看完整版本: 用Python下载小破站视频