无内鬼,爬B站视频来一波

首先是私信和评论,我不是很常用CSDN,一般我都是发在我的个人博客网站上,所以你们的私信和评论我都没回复上,不过现在我打算CSDN也搞起来,所以如果你还有问题的话,可以重新私信我一下
然后就是这个请一定一定一定不要用于商业用途!
最后一次更新于2020/04/09

前言:

移动端的bilibili客户端是可以直接下载视频的,不过有一些版权限制的视频无法下载(虽然你可以观看

所以说这个时候你想要下载欣赏一下怎么办呢,那就只好给爷爬了(

其实在以前和现在都有很多可以在网页上直接下载B站视频的网站和浏览器插件,但是随着版本更新,有些失效了,更新起来比较麻烦,所以就自己搞一波


用到的工具:
  1. Fiddler4,这个用来抓包bilibili网页来分析B站的后端和前端是怎么交互的,其他的也可以
  2. python,⑧说了,爬虫没有python就失去了灵魂好吧
  3. IDMaria2,这两个是下载器,虽然本文会去自制一个下载器,但是推荐还是用专业的下载器,速度快且稳定,为什么要推荐这两个下载器,首先 IDM 作为老牌下载器,还是🐮🍺的,用过的都说好;而 aria2 是我个人比较喜欢的下载器,虽然用起来不是很方便,但是真的很强大
  4. 格式工厂,用来给音频和纯视频混流,至于为什么要这么做之后会解释,其他能做类似工作的软件也可以

在开始之前:

首先B站是一个大型网站,随着时间的推移,势必会进行修改,无论是前端还是后端的修改,对爬虫的打击都是致命的,截止到 2020/04/09,记录一下几次B站我认为比较大的改版:

  1. 在2016年左右,B站开始使用HTML5播放器,取代了默认的flash播放器(不过现在flash播放器在设置里依旧可以用
  2. 在2017年中旬,B站的服务器存储视频开始从flv文件转变为mp3和mp4音频流和视频流,也就是说以前是传输一个flv文件,现在是传输一个音频流文件,一个视频流文件在浏览器播放
  3. 2020年3月末,B站开始使用BV号来代替AV号作为引索(这个其实对爬虫来说没有太大的变化),转换算法可以看这个文章:https://www.zhihu.com/question/381784377/answer/1099438784

爬取过程:

这里坑还是比较多的

首先打开Fiddler4,再网站上看几个视频,抓一下包,可以看到能抓到一段段的视频流和音频流,有相应的下载地址,那这个网址肯定对应的是某个清晰度的视频

a

继续分析的话,首先这个网址很长很迷幻,肯定不能通过猜测网址规律来得到所有下载地址,那就猜一猜获取这个网址的接口是什么,看了一圈抓包列表,最后发现似乎没有调用这种接口的请求,那么答案就只能有一个了,这个网址是跟网页HTML一起发过来的

因为直接把网址放在HTML的标签里不太可能,那么就先看一下 js 文件,先赌一波对应的 js 没有混淆,直接文本搜索对应的下载地址,找到了相应的 js 文件(话说竟然真的没有混淆),里面对应了不同清晰度的网址

position

这个时候 第一个坑 就是: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 ,此时就会发给你大会员专享的那个清晰度,但你不是的话,他会忽略这一项,所以说我们的抓取策略如下:

  1. 如果你的账号是大会员:提取Cookies,先检测你要爬的是 1类视频 还是 2类视频,如果是 1类视频,无脑修改 CURRENT_QUALITY = 112,他会把所有清晰度的都给你, 如果是 2类视频 比较特殊,这个时候他只会返回你 CURRENT_QUALITY 对应的那个网址,所以做个遍历,不断修改 CURRENT_QUALITY 去请求,把所有下载地址爬到
  2. 如果不是:退而求其次,提取Cookies,同样检测要爬的是哪类,1类视频 先发一个 CURRENT_QUALITY = 112,获得除大会员清晰的所有下载地址,2类视频 跟上面的相同,遍历一遍就行了

这个时候就把所有的下载地址得到了,可以下载了!但是这里有 第三个坑:bilibili肯定是有防盗链的,所以你需要修改下载请求的 Referer 值才行,一般修改成 “https://www.bilibili.com/” 就可以了,但大部分下载器不支持修改Referer,目前我知道的支持修改的就是我顶上说的 IDMaria2 ,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'<script>window.__playinfo__={(.*?)</script>')
            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'<script>window.__playinfo__={(.*?)</script>')
    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;
  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值