【Python爬虫】下载b站视频。超详细。

目录

  • 1、资源获取
    • **1.1 获得bvid**
    • **1.2获取cid**
    • **1.3获取视频url**
    • **1.4下载视频**
  • 2、其它函数
    • main.py
    • 用来创建文件夹的函数creatdir.py
    • 用来保存文件的函数savefile.py
  • 结语

自己练习网上抓东西。


实现功能:检索、下载b站非会员视频,会员的得要会员cookie。

核心部分:对b站公共api的使用,也让我对什么是api有了一定的了解。

程序设计思路:获得bvid→获得cid→获得视频url→下载flv

感想:不知道为什么,感觉这都是一些表面的东西,没什么实质的技术含量,也许是自己现在还比较菜,也算是基础入门吧。

难点: 正则表达式的使用,这也是我觉得比较难的地方吧。我也想过用beautifulsoup4,尝试了一下放弃了,因为bs4还是要用到正则(主要是我还没学透),


  之后我会更多的投入到大学基础课程的学习。啊!俺的高数,啊!俺的English,俺来啦~

正文:写这篇文章主要是想总结一下,一些难点,心得。

感谢:Bilibili ORZ

1、资源获取

 b站对视频资源设计是有了bvid(avid)视频ID和cvid视频分段ID(就是“自古2p没人看的‘p’ ”)就能从b站api获取视频url,用这个url就能从b站服务器下载视频。

1.1 获得bvid

从b站的search服务器网站’https://search.bilibili.com/all?keyword=’

'keyword='在后面加上想要搜索的名称

就能得到索引html文档。用正则表达式提取文档中bvid、视频名字和该视频的url(以后headers头中referer要用,防止反爬)

pattern = compile(')# 获取Bvid、视频名字
data = re.findall(patten, html)# 正则匹配 

list = []# 用来保存由 序号、名字、bvid、referer构成的字典1
        i = 0 
        for ul, name in data:
            dict = {'number': '', 'name': '', 'bvid': '', 'referer': ''}
            i += 1
            referer = 'http://www.bilibili.com' + ul
            bvid = re.findall('/([a-zA-Z0-9]*)\?', ul)[0]
            dict['number'] = i
            dict['name'] = name
            dict['bvid'] = bvid
            dict['referer'] = referer
            list.append(dict)
        path = savefile.save(name=s_name, dirpath=root_dir, content=list, suffix='json')
        # 我为了更好的测试,将每一步的结果都保存了起来,这个savefile函数是我自己写的。

1.2获取cid

从b站的api"https://api.bilibili.com/x/player/pagelist?bvid=BV1C7411k7D3&jsonp=jsonp"获得cid

'bvid='后面加上获得的bvid就可以了。

def getcid(name, bvid, referer, dirpath):
    Host = 'api.bilibili.com'
    url = 'https://api.bilibili.com/x/player/pagelist?'+'bvid=' + bvid +'&jsonp=jsonp'
    path = getHTML.get(url=url, name=name, dirpath=dirpath, referer=referer, Host=Host, suffix='txt')
    return path

getHTML是我自己写的用于爬取的函数,因为要经过不止一次爬取

内容是:(这才是真正意义上的爬取)

from urllib import request
import savefile


def get(url, name, dirpath=None, referer=None, Host=None, suffix=None):
    headers = {
    'Accept': 'text/html, application/xhtml+xml, application/xml; q=0.9, */*; q=0.8',
    'Accept-Language': 'zh-Hans-CN, zh-Hans; q=0.5',
    'DNT': '1',
    'Host': 'search.bilibili.com',
    'Origin': 'https://www.bilibili.com',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': '写上自己的useragent,一个网页右键检查元素,network网络里随便点一个包request headers请求头里',

    }# 刚开始尝试的时候headers写的少,被b站大大封了几次ip,还好b站大大仁慈,cookie可以不加,如果是浏览器无痕模式一般是不会将cookie加入头的,加上'DNT':1
    if referer != None:
        headers['referer'] = referer
    if Host != None:
        headers['Host'] = Host
    req = request.Request(url, headers=headers)
    print('开始抓取')
    response = request.urlopen(req)
    print('抓取{}成功!'.format(url))
    html = response.read()
    html = html.decode('utf-8')
    path = savefile.save(name=name, dirpath=dirpath, content=html,suffix=suffix )
    return path

if __name__ == '__main__':

获取的cid数据是json格式的

用json模块读取,可以进行一系列字典、列表操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4huHqqu-1588317947898)(cid.png)]
如果直接用瞅json文本结构简单还好,结构复杂能瞅瞎眼。我用vscode里的json tool插件,安装并重启后按ctrl + alt + m即可进行结构化展示,注意格式化前不要选中(就是浏览时用光标一直按住左键选取,文字地下会出现阴影)任何文本,不然会失败。

import json

with open(cid_file, mode='r', encoding='utf-8') as f:
    f = json.load(f)

f['data'][0]['cid']

值就是145508985。
注意data这个键对应的值是个列表,列表里的元素又是很多个(这里只有一个)字典。

有一些视频里不止有一个p,一个p对应一个cid。

1.3获取视频url

现在已经取得了视频的bvid,cid就能利用这两个参数从b站api

'https://api.bilibili.com/x/player/playurl?' + 'bvid=' + bvid +'&cid=' + str(cid) + '&qn=64&type=&otype=json'

里获得url,qu=64是视频分辨率1080p,32是720p。

这里获取资源的时候headers里Host值换成 api.bilibili.com

referer值换成之前已经获取的referer,一般结构是:“http://www.bilibili.com/video/BV1C7411k7D3?from=search”,

爬取后的文件格式依旧是json

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nXkAPk6g-1588317947901)(2.png)]
要有耐心滴一层一层剥开(我愿一层一层一层一层剥开你的心~)

import json

with open(cid_file, mode='r', encoding='utf-8') as f:
    f = json.load(f)
    f['data']['durl'][0]['url']# 这个就是主要的那个url,backup是备份的意思。

durl的值是个列表,如果是个多p视频,列表里的元素就会有好多个。

1.4下载视频

import requests
def download_video(url, referer, name, p_dir):
    headers = {
        'origin': 'https://www.bilibili.com',
        'referer': '还是之前那个referer',
        'user-agent': '还是那个useragent'
    }
    headers['referer'] = referer

    r = requests.get(url, stream=True, headers=headers) # 这里是下载 

    length = float(r.headers['content-length'])
    f = open(p_dir + '\\' + '{}.{}'.format(name, 'flv'), 'wb')
    count = 0
    count_tmp = 0
    time1 = time.time()
    print(url)
    print('开始下载{}'.format(url))
    for chunk in r.iter_content(chunk_size=10240):# 这里开始是进行分块保存,并计时
        if chunk:
            f.write(chunk)
            count += len(chunk)
            if time.time() - time1 > 2:
                p = count / length * 100
                speed = (count - count_tmp) / 1024 / 1024 / 2
                count_tmp = count
                print(name + ': ' + '{:.2f}'.format(p) + '%' + ' Speed: ' + '{:.2f}'.format(speed) + 'M/S')
                time1 = time.time()
        else:
            print('{}保存失败!'.format(name))
    f.close()
    print("{0}.{1}保存成功!".format(name, 'flv'))

以上部分就能实现从搜索到下载视频了

2、其它函数

main.py

有些命名时给我自己看的,看不懂没关系,1.4以上已经都实现了。

from urllib import parse
import getHTML
import creatdir
import getPATH
import getBvidUrlName
import printSelectList
import getCid
import getPlayurl
import download
import title


title.content()
d_path = getPATH.GetDesktopPath()
name = input('请输入你想下载的视频名称: ')
print('桌面路径为:{}'.format(d_path))
path = input('输入你想保存的文件夹的绝对位置(输入p跳过此步骤,默认路径为桌面):')
# 编码
parse_name = parse.quote(name)
search_url = 'https://search.bilibili.com/all?keyword=' + parse_name
# 创建根文件夹
if path != 'p':
    root_dir = creatdir.creat_a_dir(name, path)
else:
    root_dir = creatdir.creat_a_dir(name, d_path)
# 获取HTML
html_file = getHTML.get(url=search_url, name=name, dirpath=root_dir, suffix='txt')
# 抽取符合条件的列单
video_list = getBvidUrlName.getlist(html_file, name, root_dir=root_dir)
# 打印列单,让用户选择,获得选择视频的bvid,名字,和网址
g_name, bvid, referer = printSelectList.select_list(video_list)

# 创建用户选择视频的文件夹
g_dir = creatdir.creat_a_dir(g_name, root_dir)
# 获得cid
cid_file = getCid.getcid(g_name, bvid, referer, g_dir)
# 获取被选中的所有视频
p_name_list, p_dir_list = getPlayurl.getURL(cid_file=cid_file, bvid=bvid, referer=referer, g_dir=g_dir)
# 下载视频
download.get_video(g_dir=g_dir, p_name_list=p_name_list, p_dir_list=p_dir_list, referer=referer)



用来创建文件夹的函数creatdir.py

import os
import time


def creat_a_dir(sname, spath=r'C:\Users\PAN\Desktop'):
    '''创建位于spath的名为sname+当地时间的文件夹。默认在桌面,返回文件夹的目录'''
    local_time = time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime())
    dir_path = spath + '\\' + sname + '-' + local_time
    os.mkdir(dir_path)
    print('创建 {} 成功!'.format(dir_path))
    return dir_path


if __name__ == '__main__':
    a = creat_a_dir('小甲鱼')
    print(a)



用来保存文件的函数savefile.py

import json


def save(name, dirpath, content, suffix=None):
    '''name 文件名字, dirpath 保存在的文件夹目录,content 保存内容, suffix 后缀'''

    if suffix == 'json':
        suffix = '.' + suffix
        path = dirpath + '\\' + name + suffix
        with open(path, mode='a', encoding='utf-8') as f0:
            json.dump(content, f0)
    else:
        suffix = '.' + 'txt'
        path = dirpath + '\\' + name + suffix
        with open(path, mode='a', encoding='utf-8') as f1:
            f1.write(content)
    print('保存 {} 成功!'.format(path))
    return path

其实json是一种带格式的文本文件。保存成txt格式,用json格式化读取,结果是一样的。

我折腾来折腾去算明白了,只要是文本,统一保存txt就没错,文本的内容格式不用导来导去。关键在于编码要搞清楚。

结语

很清楚,有很多地方可以改进。
如果有时间的话会继续做。
优秀的程序,也要有优秀的异常处理,我这里并没有搞什么异常处理。程序可执行,但是比较脆弱。
欢迎到我的个人博客评论,大一新生,一起进步。(希望有大神能教教我怎么下会员的flv手动滑稽)
https://tominochick.github.io/

你可能感兴趣的:(Python自学笔记)