近日看了少歌#3的线上配信,想把留档的视频文件保存下来方便日后再演,奈何家里的宽带实在拉胯,录屏是没可能了,于是只能想办法把视频文件爬下来。
Eplus的反爬措施比较严格,每次建立会话时会生成两个key放在请求的cookie里,另外每隔一段时间会话的key就会失效,因此手动复制cookie的方法也不可行。无力分析混淆js代码的我最终还是选择了笨方法——直接用selenium下载。
在开始下载之前,我们还需要做一些准备工作:
视频作为流媒体 和众多流媒体平台一样,eplus传输视频文件的方式是将整个视频分割成多个小片段,加载视频时不断从服务器下载这些片段文件。片段文件的后缀名为ts,在这里就是一些长度只有几秒的短视频。所有这些片段文件的路径被存储在一个m3u8文件中。因此,我们首先需要找到网站加载视频所使用的m3u8文件:
使用F12开发者工具查看网站发出的网络请求,并筛选出所有m3u8文件,可以看到这里有不止一个文件。查看第一个m3u8文件的内容,发现其记录的是不同清晰度的m3u8文件的路径。为了保存用自然是要下载清晰度最高的1080p,查看下面对1080p的m3u8的请求。由于是get,直接把url复制出来新开个标签页就可以下载了。
保存下来m3u8文件之后可以看到里面存储的ts文件的路径都是相对路径,为了找到下载的url,再在开发者工具中查看对ts文件的请求,把url复制下来即可。
下载ts文件并拼接 这一部分比较简单(selenium做了所有复杂的部分),因为eplus回放甚至连登陆都不需要,只需要请求播放url后再挨个get每个ts文件的地址就可以了。不过缺点在于由于eplus不支持同时建立多个连接,因此没法进行多进程下载。另外需要说明的是我下载的时候隔一段时间就会报timeout的错,不知道是网络问题还是反爬虫机制的作用。好在分段传输方式天然地支持了断点续传,因此只需要重启一下就可以了,我们一直以来的努力不会全部木大。
这一部分的代码在下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import osfrom time import sleepfrom tqdm import tqdmfrom selenium import webdriverfrom ffmpy import FFmpegfrom typing import Iterabledef parse_m3u8 (filename ): ts_paths = [] with open (filename) as f: for line in f.readlines(): line = line.strip() if line.endswith('.ts' ): ts_paths.append(line) return ts_paths def download_ts (driver: webdriver.Chrome, ts_path: str , download_dir: str = '.' ): path = os.path.join(download_dir, ts_path) if os.path.exists(path): return False driver.get(f'{ts_host_url.rstrip("/" )} /{ts_path} ' ) return True def ep_download_all (download_dir: str ): options = webdriver.ChromeOptions() prefs = {'profile.default_content_settings.popups' : 0 , 'download.default_directory' : os.path.abspath(download_dir)} options.add_experimental_option('prefs' , prefs) options.add_argument("--disable-web-security" ) options.add_argument("--disable-site-isolation-trials" ) driver = webdriver.Chrome( executable_path='chromedriver.exe' , chrome_options=options) driver.get(view_url) counter = 0 for path in tqdm(ts_paths, desc='download progress' ): if download_ts(driver, path, download_dir): counter += 1 if counter == 30 : sleep(30 ) counter = 0 driver.quit() def merge_binary_files (files: Iterable[str ], outfile: str ): with open (outfile, 'wb' ) as outf: for file in tqdm(files, desc='merge progress' ): with open (file, 'rb' ) as f: outf.write(f.read()) outdir = 'ts_video' if not os.path.exists(outdir): os.mkdir(outdir) m3u8_path = 'index_4.m3u8' out_name = 'total' ts_paths = parse_m3u8(m3u8_path) view_url = 'https://live.eplus.jp/ex/player?ib=xxx' ts_host_url = 'https://vod.live.eplus.jp/out/v1/xxx/' while True : try : ep_download_all(outdir) break except Exception as e: print (f'Exception {e} occured, restarting...' ) for f in filter (lambda s: s.endswith('.crdownload' ), os.listdir(outdir)): os.remove(os.path.join(outdir, f)) sleep(100 ) print ('download complete, start merging...' )merge_binary_files([os.path.join(outdir, ts) for ts in ts_paths], f'{out_name} .ts' ) print ('converting merged ts file to mp4...' )ff = FFmpeg( inputs={f'{out_name} .ts' : None }, outputs={f'{out_name} .mp4' : '-vcodec copy -acodec copy' }, global_options='-hide_banner' ) ff.run() print ('convertion complete, clearing ts files...' )os.remove(f'{out_name} .ts' ) for ts in ts_paths: os.remove(os.path.join(outdir, ts)) try : os.removedirs(outdir) except OSError: pass
23.9.24更新:这段代码在下载完成全部ts文件后会自动将拼接成整个文件,转码成MP4格式并删除临时文件和目录。