├── README.md ├── bilibili.py └── merge_flv.py /README.md: -------------------------------------------------------------------------------- 1 | # Bilibili 2 | 下载B站视频到本地 3 | 4 | ## 2018-11-2 5 | 由于原先使用多线程下载仍然感觉有点慢,且时常会卡住,因此修改为多进程版本进行下载,速度明显提升。 6 | 默认使用5个进程进行下载,可以根据需要下载的视频总数和电脑性能自行修改。 7 | 下载过程可能偶尔会出现卡住现象,按回车键即可唤醒进程。 8 | 9 | ## 2018-11-8 10 | 增加合并flv片段为mp4文件的方法 11 | 12 | 13 | ## 代码完整的运行方法: 14 | 1. 修改bilibili.py中的 aid 参数为你想要下载的视频编号,视频编号可在对应视频url尾部找到, 15 | 如 [https://www.bilibili.com/video/av6538245](https://www.bilibili.com/video/av6538245) 16 | 对应的视频编号为6538245。 17 | 18 | 2. 运行 bilibili.py 文件,该文件会在当前文件夹下创建对应的视频存储目录,并且将aid对应的 19 | 视频的所有视频片段下载到本地。默认启用5个进程进行下载,可以修改bilibili.py文件中的process_count 20 | 参数进行调整。 21 | 22 | 3. 待所有视频都已下载完成之后,修改merge_flv.py文件中的 root_dir 参数为你刚刚下载视频时自动 23 | 创建的那个根目录文件名,然后运行merge_flv.py文件。该文件会把同为一个视频的flv视频片段文件合并 24 | 成mp4文件并保存在同一文件夹下。 -------------------------------------------------------------------------------- /bilibili.py: -------------------------------------------------------------------------------- 1 | """ 2 | 使用多进程 3 | 下载B站指定视频 4 | """ 5 | 6 | from contextlib import closing 7 | import requests 8 | import sys,os 9 | import re 10 | import json 11 | import shutil 12 | import re 13 | import threading 14 | from multiprocessing import Pool 15 | from pprint import pprint 16 | 17 | # 下载地址的镜像格式部分的可能替换值 18 | video_mode = [ 'mirrorcos.', 'mirrorkodo.', 'mirrorks3.', 'mirrorbos.', 'mirrorks3u.', ] 19 | 20 | # 主线程 21 | main_thread = threading.current_thread() 22 | 23 | def make_path(p): 24 | """ 25 | 判断文件夹是否存在 26 | 存在则清空 27 | 不存在则创建 28 | """ 29 | #if os.path.exists(p): # 判断文件夹是否存在 30 | # shutil.rmtree(p) # 删除文件夹 31 | if not os.path.exists(p): 32 | os.mkdir(p) # 创建文件夹 33 | 34 | 35 | 36 | headers = { 37 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36', 38 | 'Accept': '*/*', 39 | 'Accept-Encoding': 'gzip, deflate, br', 40 | 'Accept-Language': 'zh-CN,zh;q=0.9', 41 | 'Referer': 'https://www.bilibili.com' 42 | } 43 | 44 | sess = requests.Session() 45 | #下载的根目录 46 | root_dir = '.' 47 | 48 | def download_video(video_url, dir_, video_name, index, count): 49 | ''' 50 | 下载一个视频片段 51 | ''' 52 | size = 0 53 | session = requests.Session() 54 | mirror = re.findall('mirror.*?\.', video_url) 55 | # 链接中是否带mirror字符 56 | isMirror = len(mirror) > 0 57 | chunk_size = 1024 #每次1KB 58 | video_path = os.path.join(dir_, video_name, str(index) + '.flv') 59 | for i,mode in enumerate(video_mode): 60 | video_url = re.sub('mirror.*?\.', mode, video_url) 61 | response = session.get(video_url, headers=headers, stream=True, verify=False) 62 | 63 | if response.status_code == 200: 64 | content_size = int(response.headers['content-length']) 65 | print("进程ID%d: %s 第%d/%d个片段, [文件大小]:%0.2f MB"% (os.getpid(), video_name, index, count, content_size / 1024 / 1024)) 66 | with open(video_path, 'wb') as file: 67 | j = 0 68 | for data in response.iter_content(chunk_size = chunk_size): 69 | file.write(data) 70 | size += len(data) 71 | file.flush() 72 | if j > 0 and j % 1000 == 0: 73 | print("进程ID%d: %s 第%d/%d个片段, [下载进度]:%.2f%%"% (os.getpid(), video_name, index, count, float(size / content_size * 100))) 74 | j += 1 75 | return 76 | 77 | else: 78 | 79 | print('进程ID%d:链接异常,尝试更换链接' %os.getpid()) 80 | 81 | if not isMirror or i == len(video_mode) - 1: 82 | print('进程ID%d:%s 第%d/%d个片段无法下载' %(os.getpid(), video_name, index, count)) 83 | return 84 | 85 | def download_videos(dir_, video_urls, video_name): 86 | """ 87 | 下载一个完整视频的所有视频片段 88 | """ 89 | make_path(os.path.join(dir_, video_name)) 90 | print('进程ID%d: %s 开始下载' %(os.getpid(), video_name)) 91 | for i, video_url in enumerate(video_urls): 92 | download_video(video_url, dir_, video_name, i+1, len(video_urls)) 93 | print('进程ID%d: %s 下载完成' %(os.getpid(), video_name)) 94 | 95 | def get_download_urls(arcurl): 96 | req = sess.get(url=arcurl, headers = headers, verify=False) 97 | pattern = '.__playinfo__=(.*)