鱼C论坛

 找回密码
 立即注册
查看: 3350|回复: 0

[作品展示] B站,番剧下载爬虫(VIP版)

[复制链接]
发表于 2020-11-19 21:47:42 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 兰竹皋 于 2020-11-19 21:49 编辑


现在b站番剧版权要求越来越严,越来越多的番剧不能直接下载了。
而我个人对于一些喜欢的番剧有收藏爱好,于是就写了这个小爬虫——通过番剧的ep号下载b站番剧(对于VIP账户而言)。
当然,为了方便而言我也就没有打包成GUI了。
下面展示一些爬取过程:
首先获取番剧的ep号,如图:
2.png
3.png
通常我们点入番剧后得到的并不是ep号,
此时只需要点击一下右方的任意一其他集,如图:
4.png
页面上方地址便出现了ep号。
当然,还有一些特殊番剧,只有一集,如:
16.png
无法通过上述方法获得,只能通过抓包(playurl开头的包),如图:
17.png


而同样,通常而言,一般番剧的ep号是连续数字,如图:
5.png
此时,执行程序时,只需要输入:
6.png

7.png

但同样,也总存在这样一些莫名其妙的番剧,如图:
8.png
9.png
番号并不连续。
此时,执行程序时,输入:
10.png
即可。

接着,输入下载画质,如图(分别对应( 12.png )):
13.png

最后,开始下载了:
14.png
但要注意,下载前命理窗口必须要足够大,且下载过程中别改变其大小,不然会出现排序混乱的情况。
(由于技术原因,上方命令窗口中显示的中文字符有些会缺失一半,如果有知道怎么回事的高手,还请告知一二,谢谢啦 (^_^) )


下方是源代码:
  1. import requests
  2. from lxml import etree
  3. import curses
  4. import time
  5. from multiprocessing.pool import ThreadPool
  6. import os
  7. import demjson

  8. requests.packages.urllib3.disable_warnings()


  9. def getVideoName(home_url):
  10.     headers = {
  11.         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
  12.         'Referer': home_url,
  13.     }

  14.     re = requests.get(home_url, headers=headers)
  15.     re.raise_for_status()
  16.     re.encoding = 'utf-8'
  17.     re_text = re.text

  18.     tree = etree.HTML(re_text)
  19.     name = tree.xpath('//div[@class="media-wrapper"]/h1/text()')
  20.     title = tree.xpath('//meta[@name="keywords"]/@content')
  21.     return name[0], title[0]


  22. def getBliVideo(home_url, each_episode, each_episode_i, session = requests.session()):
  23.     headers_info = {
  24.         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
  25.         'Referer': home_url,
  26.         'cookie': "*******************"      #注意,此处改成自己的cookie就行了
  27.     }
  28.     info_url = 'https://api.bilibili.com/pgc/player/web/playurl'
  29.     # qn : 16, 32, 64, 80, 112
  30.     params = {
  31.         'qn': qn,
  32.         'ep_id': each_episode,
  33.     }

  34.     video_info_re = session.get(url=info_url, headers=headers_info, params=params, verify=False)
  35.     video_info_re.raise_for_status()

  36.     video_info_dict = video_info_re.json()
  37.     video_size = video_info_dict['result']['durl'][0]['size']
  38.     # video_length = video_info_dict['result']['durl'][0]['length'] / 1000
  39.     video_url = video_info_dict['result']['durl'][0]['backup_url'][0]
  40.     video_name, video_title = getVideoName(home_url)

  41.     video_title = os.path.join(os.getcwd(), video_title)
  42.     if os.path.exists(video_title):
  43.         pass
  44.     else:
  45.         os.mkdir(video_title)

  46.     BliBliDownload(home_url=home_url, video_url=video_url, video_size=video_size, video_title=video_title, video_name=video_name, each_episode_i=each_episode_i)


  47. def BliBliDownload(home_url, video_url, video_size, video_title, video_name, each_episode_i, session=requests.session()):
  48.     def get_url(url, headers, timeout=5):
  49.         try:
  50.             return session.get(url=url, headers=headers, timeout=timeout, verify=False)
  51.         except requests.exceptions.Timeout:
  52.             return get_url(url, headers, timeout)

  53.     headers = {
  54.         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',
  55.         'Referer': home_url,
  56.     }
  57.     # options_re = session.options(url=video_url, headers=headers)
  58.     # print(options_re)
  59.     # options_re.raise_for_status()

  60.     # 进度条显示
  61.     total=int(video_size/1024/1024); ncols=50; nrow=(each_episode_i+1)*2-1; desc='Processing'; unit='Mb'
  62.     def get_bar(l, n, ncol):
  63.         r = ncol - l - 1
  64.         bar = ''.join(['#' for i in range(l)])
  65.         if l != ncol:
  66.             bar += f'{n}'
  67.         bar += ''.join([' ' for i in range(r)])
  68.         return '|'+bar+'|'

  69.     stdscr.addstr(nrow, 0, f'正 在 下 载  -- {" ".join(video_name)}:')
  70.     processing = f'{0:>3d}%'
  71.     bar = get_bar(0, 0, ncols)
  72.     mod = f'{desc} {each_episode_i+1:>2d}:  {bar} {processing:>4s}'
  73.     stdscr.addstr(nrow+1, 1, mod)
  74.     stdscr.refresh()
  75.    
  76.     start_time = time.time()

  77.     with open(video_title+'/'+video_name + '_Video.mp4', 'wb') as f:
  78.         # 每次下载 1M 数据
  79.         begin = 0
  80.         end = 1024 * 1024 - 1
  81.         flag = 0
  82.         for i in range(total):
  83.             sub_start_time = time.time()

  84.             # 内容处理
  85.             headers.update({'Range': f'bytes={begin}-{end}'})
  86.             re = get_url(url=video_url, headers=headers, timeout=5)
  87.             if re.status_code != 416:
  88.                 begin = end + 1
  89.                 end = end + 1024 * 1024
  90.             else:
  91.                 headers.update({'Range': f'bytes={end + 1}-'})
  92.                 re = get_url(url=video_url, headers=headers, timeout=5)
  93.             f.write(re.content)
  94.             f.flush()

  95.             sub_stop_time = time.time()
  96.             sub_delta_time = sub_stop_time - sub_start_time
  97.             delta_time = sub_stop_time - start_time
  98.             remain_time = (total - i - 1)*sub_delta_time
  99.             
  100.             temp = (i+1)/total
  101.             processing = f'{int(temp*100):>3d}%'
  102.             l = int(temp*ncols)
  103.             n = int(temp*ncols*10)%10
  104.             bar = get_bar(l, n, ncols)
  105.             mod = f'{desc} {each_episode_i+1:>2d}:  {bar} {processing:>4s} ({i+1} of {total}) [{int(delta_time//60):0>2d}:{int(delta_time%60):0>2d}<{int(remain_time//60):0>2d}:{int(remain_time%60):0>2d}, {1/sub_delta_time:.2f}{unit}/s]'
  106.             stdscr.addstr(nrow+1, 1, mod)
  107.             stdscr.refresh()

  108.     stdscr.addstr(nrow+1,0, f'{" ":>200s}')     # 清楚原行数据
  109.     bar='|Download completed|'
  110.     mod = f'{desc} {each_episode_i+1:>2d}:  {bar} {processing:>4s} ({i+1} of {total}) [{int(delta_time//60):0>2d}:{int(delta_time%60):0>2d}, {total/delta_time:.2f}{unit}/s]'
  111.     stdscr.addstr(nrow+1, 1, mod)
  112.     stdscr.refresh()

  113. def main(ep_id_list):
  114.     stdscr.addstr(0, 0, ' '.join('开始下载:'))
  115.     stdscr.refresh()
  116.     pool = ThreadPool(4)
  117.     each_episode_i = 0
  118.     for each_episode in ep_id_list:
  119.         home_url = f'https://www.bilibili.com/bangumi/play/ep{each_episode}'
  120.         pool.apply_async(getBliVideo, (home_url, each_episode, each_episode_i))
  121.         each_episode_i += 1
  122.         # print(getVideoName(home_url))
  123.     pool.close()
  124.     pool.join()
  125.     stdscr.addstr(2*ep_id_iter_n+1, 0, ' '.join('所有视频已下载完成 (^_^)...'))
  126.     stdscr.addstr(2*ep_id_iter_n+3, 0, 'Press any key to continue...')
  127.     stdscr.refresh()


  128. if __name__ == '__main__':
  129.     requests.packages.urllib3.disable_warnings()

  130.     ep_id = input('请输入视频epid(第一集), 或 epid列表[](不包括ep):')
  131.     try:
  132.         flag = 0
  133.         if ',' in ep_id.strip():
  134.             ep_id_list = demjson.decode(ep_id)
  135.             ep_id_iter_n = len(ep_id_list)
  136.             flag = 1
  137.         else:
  138.             ep_id_ns = input('请输入要下载的范围([从第几集,到第几集]), 如 [4,9]; 或 下载几集, 如 5; 或下载第几集, 如[5,]:')

  139.             ep_id_ns = demjson.decode(ep_id_ns)
  140.             if isinstance(ep_id_ns, int):
  141.                 ep_id_iter_start, ep_id_iter_n = int(ep_id), int(ep_id_ns)
  142.                 flag = 1
  143.             elif isinstance(ep_id_ns, list):
  144.                 if len(ep_id_ns) == 2:
  145.                     ep_id_iter_start, ep_id_iter_n = int(ep_id) + int(ep_id_ns[0]) - 1, int(ep_id_ns[1]) - int(ep_id_ns[0]) + 1
  146.                     flag = 1
  147.                 elif len(ep_id_ns) == 1:
  148.                     ep_id_iter_start, ep_id_iter_n = int(ep_id) + int(ep_id_ns[1]) - 1, 1
  149.                     flag = 1
  150.             ep_id_iter_end = ep_id_iter_start + ep_id_iter_n - 1
  151.             ep_id_list = range(ep_id_iter_start, ep_id_iter_end+1)
  152.     except:
  153.         raise ValueError('选集有错误哦~~~')
  154.     if flag == 0:
  155.         raise ValueError('选集有错误哦~~~')

  156.     qn = input('请输入下载视频画质(112,80,64,32,16):')

  157.     def get_ch_and_continue(stdscr):
  158.         stdscr.nodelay(0)
  159.         ch = stdscr.getch()
  160.         stdscr.nodelay(1)
  161.     try:
  162.         # 创建 curses
  163.         stdscr = curses.initscr()
  164.         curses.cbreak()
  165.         curses.noecho()
  166.         stdscr.keypad(1)

  167.         main(ep_id_list)
  168.         
  169.         get_ch_and_continue(stdscr)
  170.         # 关闭 curses
  171.         curses.nocbreak()
  172.         curses.echo()
  173.         stdscr.keypad(0)
  174.         curses.endwin()
  175.     except Exception as e:
  176.         curses.nocbreak()
  177.         curses.echo()
  178.         stdscr.keypad(0)
  179.         curses.endwin()
  180.         print(e)
复制代码

其中,cookie在自己登陆账号后,从F12中,network,任意一个包中,查看headers,复制粘贴过来就行了。

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-5-11 00:25

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表