首先是私信和评论,我不是很常用CSDN,一般我都是发在我的个人博客网站上,所以你们的私信和评论我都没回复上,不过现在我打算CSDN也搞起来,所以如果你还有问题的话,可以重新私信我一下
然后就是这个请一定一定一定不要用于商业用途!
最后一次更新于2020/04/09
移动端的bilibili客户端是可以直接下载视频的,不过有一些版权限制的视频无法下载(虽然你可以观看
所以说这个时候你想要下载欣赏一下怎么办呢,那就只好给爷爬了(
其实在以前和现在都有很多可以在网页上直接下载B站视频的网站和浏览器插件,但是随着版本更新,有些失效了,更新起来比较麻烦,所以就自己搞一波
首先B站是一个大型网站,随着时间的推移,势必会进行修改,无论是前端还是后端的修改,对爬虫的打击都是致命的,截止到 2020/04/09,记录一下几次B站我认为比较大的改版:
这里坑还是比较多的
首先打开Fiddler4,再网站上看几个视频,抓一下包,可以看到能抓到一段段的视频流和音频流,有相应的下载地址,那这个网址肯定对应的是某个清晰度的视频
继续分析的话,首先这个网址很长很迷幻,肯定不能通过猜测网址规律来得到所有下载地址,那就猜一猜获取这个网址的接口是什么,看了一圈抓包列表,最后发现似乎没有调用这种接口的请求,那么答案就只能有一个了,这个网址是跟网页HTML一起发过来的
因为直接把网址放在HTML的标签里不太可能,那么就先看一下 js 文件,先赌一波对应的 js 没有混淆,直接文本搜索对应的下载地址,找到了相应的 js 文件(话说竟然真的没有混淆),里面对应了不同清晰度的网址
这个时候 第一个坑 就是:B站近几年的视频是音频和视频分离传输的(我们管它称作1类视频),所以会有两种网址,一种audio音频,一种video视频,但其实B站2017年之前的视频都是 flv格式(我们管它称作2类视频),也就只有一个网址,也就是说你需要写两份爬取方式来检测这个视频属于哪类
然后看一波清晰度的id标识,会发现有 [16, 32, 64, 80, 112] 等,对应了不同清晰度,前缀标识 300 代表视频,302 和 32 代表音频,那就开爬!
#清晰度ID标识字典
{'30112':'高清 1080P+', '30080':'高清 1080P', '30064':'高清 720P', '30032':'清晰 480P', '30016':'流畅 360P', '30116':'1080P60', '30074':'720P60'}
{'32112':'极高音质', '30280':'高音质', '30264':'中音质', '30232':'低音质', '30216':'极低音质'}
写好爬虫以后,你会发现总是报错,这个时候 第二个坑 就来了:尽管他给出了很多清晰度,但发给你的下载网址却只有那么几个。这个是为什么呢,很好理解,因为bilibili有大会员限制,要恰饭的,怎么可能一口气把所有清晰度都给你,而且就算是非大会员的清晰度下载网址,有时候也会莫名其妙的消失,这个时候就要想方法骗后端把所有的下载地址都吐出来
那我们继续抓包,发现请求网页HTML的时候没有特别的附加值,那就只能是 Cookies 的问题,用 Fiddler4 把Cookies 读取出来,这里涉及我的账号信息就不贴图了,反正你会发现值非常的多,但没关系,我们通过名称和不断地改 Cookies 发假请求可以知道一些值的作用,首先是账号认证信息,这个很复杂,可以说破解不了,只能复制你的 Cooikes 值来模拟你的账号去请求,这个时候会不会给你大会员清晰度的网址就取决于你的用户信息了
但其他值我们可以随便改,比较重要的就是 CURRENT_QUALITY,这个值决定了默认加载的清晰度,如果你是大会员,此时有 CURRENT_QUALITY = 112 ,此时就会发给你大会员专享的那个清晰度,但你不是的话,他会忽略这一项,所以说我们的抓取策略如下:
这个时候就把所有的下载地址得到了,可以下载了!但是这里有 第三个坑:bilibili肯定是有防盗链的,所以你需要修改下载请求的 Referer 值才行,一般修改成 “https://www.bilibili.com/” 就可以了,但大部分下载器不支持修改Referer,目前我知道的支持修改的就是我顶上说的 IDM 和 aria2 ,IDM 里对应的叫参见,而 aria2 中就是修改请求头加上Referer
最后的最后,如果你爬的是 1类视频 就只需要把音频和视频用软件混流在一起看就可以了(可能会有点慢,如果是 2类视频 ,那可以把 flv 格式转码成你想要的格式来看
from lxml import etree
import requests
import pathlib
import msvcrt
import time
import json
import os
import re
Path = os.path.abspath('.')
quality_dic = {'112':'高清 1080P+','80':'高清 1080P','64':'高清 720P','32':'清晰 480P','16':'流畅 360P','116':'1080P60','74':'720P60'}
Lista = {'32112':'极高音质','30280':'高音质','30264':'中音质','30232':'低音质','30216':'极低音质'}
cookies = {}
headers = {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Upgrade-Insecure-Requests": "1",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "Keep-Alive"
}
#读取cookies值,目标文件manual_cookies.txt
def cookies_made():
manual_cookies = {}
with open('manual_cookies.txt', 'r', encoding = 'utf-8') as frcookie:
cookies_txt = frcookie.read().strip(';')
for item in cookies_txt.split(';'):
name,value = item.strip().split('=',1)
manual_cookies[name] = value
return manual_cookies
def deal_with_new(text):
string = ''
pattern = re.compile(r'"accept_quality":\[(.*?)\]')
accept_quality = pattern.findall(text)[0].split(',')
print(accept_quality)
#视频(无音频)
for i in accept_quality:
pattern = re.compile('"id":' + i + ',"baseUrl":"' + r'(.*?)' + '"')
url = pattern.findall(text)
string += quality_dic[i] + ' 视频(无音频): '
if len(url) != 0:
string += url[0] + '\n'
else:
string += 'null\n'
string += '\n'
accept_quality = ['32112','30280','30264','30232','30216']
#音频
for i in accept_quality:
pattern = re.compile(r'"audio":\[\{"id":' + i + ',"baseUrl":"(.*?)"')
url = pattern.findall(text)
if len(url) != 0:
string += Lista[i] + ' 音频: ' + url[0] + '\n'
return string
def deal_with_old(Url, text):
string = ''
pattern = re.compile(r'"accept_quality":\[(.*?)\]')
accept_quality = pattern.findall(text)[0].split(',')
print(accept_quality)
for i in accept_quality:
cookies['CURRENT_QUALITY'] = i
try:
response = requests.get(Url, headers = headers, cookies = cookies)
text = response.content.decode("utf-8")
pattern = re.compile(r'')
text = pattern.findall(text)[0]
except:
print('no')
pattern = re.compile('"url":"(.*?)",')
url = pattern.findall(text)
string += quality_dic[i] + ' 视频flv: '
if len(url) != 0:
string += url[0] + '\n'
else:
string += 'null\n'
return string
def deal_with_newep(text):
quality_bool = {'112':0,'80':0,'64':0,'32':0,'16':0,'116':0,'74':0}
string = ''
pattern = re.compile(r'"accept_quality":\[(.*?)\]')
accept_quality = pattern.findall(text)[0].split(',')
print(accept_quality)
pattern = re.compile('"baseUrl":(.*?)id":(.*?),')
bet = pattern.findall(text)
for i in bet:
pattern = re.compile('"(.*)","size"')
url = pattern.findall(i[0])
if len(i[1]) <= 3:
if len(url) != 0 and quality_bool[i[1]] == 0:
quality_bool[i[1]] = 1
string += quality_dic[i[1]] + ' 番剧视频(无音频): ' + url[0] + '\n'
else:
if len(url) != 0:
string += Lista[i[1]] + ' 番剧音频: ' + url[0] + '\n'
return string
def Main(name):
global cookies
string = ''
cookies = cookies_made() #检查cookies值
if name[:2] == 'ep':
url = 'https://www.bilibili.com/bangumi/play/' + name
else:
url = 'https://www.bilibili.com/video/' + name
try:
response = requests.get(url, headers = headers, cookies = cookies)
text = response.content.decode("utf-8")
except:
print("请求失败,请检查引索格式或Cookies值")
return -1
print(name)
Selector = etree.HTML(text)
title = Selector.xpath('//div[@class="video-info report-wrap-module report-scroll-module"]/h1/@title')
titlep = Selector.xpath('//div[@class="media-right"]/a/@title')
pattern = re.compile(r'')
text = pattern.findall(text)[0]
pattern = re.compile('"baseUrl":"' + r'(.*?)' + '"')
bet = pattern.findall(text)
if name[:2] == 'ep':
string += titlep[0] + '\n' + deal_with_newep(text)
else:
if len(bet) == 0:
string += title[0] + '\n' + deal_with_old(url, text)
else:
string += title[0] + '\n' + deal_with_new(text)
return string
#调用Main()就行,最后会返回一个字符串,里面是所有的下载地址信息,参数是av号,不用加av前缀,另外这个番剧和电影也可以爬,不过需要ep号,ep号可以通过对应番剧的网址后面看,这个需要加ep前缀
#print(Main("BV1vg4y187Zz")) 爬取bv号BV1vg4y187Zz输出,传入的是字符串
#print(Main("ep12347")) 爬取对应ep为12347的番剧或电影输出
#manual_cookies.txt 文件存你的Cookies值
还有一个下载器版本,虽然有暂停和恢复下载功能,但有点慢说实话,尽管用了多线程还是很慢,这里就不放了
另外如果你懒得做 manual_cookies.txt 文件,你可以直接在该py文件同目录下新建一个 manual_cookies.txt ,写入如下信息即可,没有这个文件的话,是会报错的
CURRENT_FNVAL=16; CURRENT_QUALITY=112;