前两天忽然间脑子抽风想要用python来爬一下视频网站,获取视频。一开始无从下手,在网上搜了不少相关的博客,然而也并未找到一个理想的解决方案,可是好在最终可以将视频网站的视频给爬下来,尽管吃相难看了点。特此将整个过程以及思考给记录下来。html
个人目标是爬取腾讯视频的视频内容,在网上搜索出来的结果是利用第三方解析网站对视频进行解析,而后在爬取,这是最简单的解决方案。因而乎也就照搬照作了。详细过程以下:python
打开:http://jx.618g.com/?url=这个第三方解析网站,将待解析的视频url加在后面就好了。如:https://jx.618g.com/?url=https://v.qq.com/x/cover/c949qjcugx9a7gh.htmlapi
这个时候对https://jx.618g.com/?url=https://v.qq.com/x/cover/c949qjcugx9a7gh.html进行抓包。会发现有不少的.ts的文件,后来由查看了不少相关的博客知道了原来这些.ts的文件就是咱们要抓去的对象。关于详细的介绍.m3u8以及.ts文件推荐一篇博客给你们,若是不懂的话能够去看看http://www.javashuo.com/article/p-xuctieys-mp.html。缓存
仔细的查看下这些ts文件发现并无须要携带的其余参数直接访问其url即可实现下载,固然了下载下来的也只是一小段视频片断。网络
按照网上的一种作法直接请求这些url将其下载下来而后在合并成一个完整的视频片断。这种作法的代码我先贴出来以供参考。多线程
1 import time 2 import requests 3 4 5 def loder(i): 6 """直接请求ts文件的url而后在写入到本地""" 7 url = 'https://doubanzyv3.tyswmp.com:888/2018/12/12/UEtWtHwTc0UniIDQ/out%03d.ts' % i # %03d 左边补0方式 8 html = requests.get(url).content 9 10 with open(r"D:\txsp_test\%s%03d.ts" % ("a", i), "wb") as f: 11 f.write(html) 12 13 14 if __name__ == "__main__": 15 pool.map(loder, range(400)) 16 pool.join() 17 pool.close()
这里你可使用多进程或者多线程来进行优化,我在这里就不将代码贴出来了。经过这种方式下载下来的ts文件极可能会由于网络的问题出现漏下,少下的状况,为此我也是尝试了各类方法都没有找到一个最优解。我尝试了一个方法是为进程加锁,以信号量的形式对文件进行下载,确保ts文件的完整,同时也能保证异步、并发。可是这样作的话就至关于开启了400个进程。你的内存必定会溢出。(有兴趣的能够试下线程锁)并发
1 import time 2 import requests 3 4 5 def loder(i, sem): 6 """直接请求ts文件的url而后在写入到本地""" 7 sem.acquire() # 获取钥匙 8 url = 'https://doubanzyv3.tyswmp.com:888/2018/12/12/UEtWtHwTc0UniIDQ/out%03d.ts' % i # %03d 左边补0方式 9 html = requests.get(url).content 10 11 with open(r"D:\txsp_test\%s%03d.ts" % ("a", i), "wb") as f: 12 f.write(html) 13 sem.release() 14 15 16 if __name__ == "__main__": 17 start_time = time.time() 18 print(start_time) 19 sem = Semaphore(5) # 规定锁的个数 20 # pool = Pool(5) 21 p_l = [] 22 for i in range(400): 23 p = Process(target=loder, args=(i, sem)) 24 p.start() 25 p_l.append(p) 26 for i in p_l: 27 i.join() 28 print(time.time()-start_time)
而后在对下载好的文件进行合成app
1 file_dir = r"D:\txsp_test" # 文件的保存路径 2 new_file = u"%s\out.ts" % file_dir # 合并以后的视频 3 f = open(new_file, 'wb+') # 二进制文件写操做 4 5 for i in range(0, 338): 6 file_path = r"D:\txsp_test\%s%03d.ts" % ("a", i) # 视频片断名称 7 print(file_path) 8 for line in open(file_path, "rb"): 9 f.write(line) 10 f.flush() 11 12 f.close()
ok上面的是一种作法,很显然能够将视频的下载与合并发在一块儿。我也不贴出来了。接下来就是另一种作法。异步
这种作法相对来讲更加的合理,就是先找到.m3u8的文件。scrapy
而后向其发送get请求,获得的响应结果就是一段段的.ts集合。(不知道我在说什么的请看上面的博客,或者本身动手requests.get()试下)这时能够经过正则匹配出.ts的文件而后在下载下来。最后合并成完整的视频。
代码以下:(采用了多线程对.ts文件进行下载)
1 import re 2 import os 3 import shutil 4 from concurrent.futures import ThreadPoolExecutor 5 from urllib.request import urlretrieve 6 7 import requests 8 from scrapy import Selector 9 10 11 class VideoDownLoader(object): 12 def __init__(self, url): 13 self.api = 'https://jx.618g.com' 14 self.get_url = 'https://jx.618g.com/?url=' + url 15 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' 16 'Chrome/63.0.3239.132 Safari/537.36'} 17 18 self.thread_num = 32 19 self.i = 0 20 html = self.get_page(self.get_url) 21 if html: 22 self.parse_page(html) 23 24 def get_page(self, get_url): 25 """获取网页""" 26 try: 27 print('正在请求目标网页....', get_url) 28 response = requests.get(get_url, headers=self.headers) 29 if response.status_code == 200: 30 # print(response.text) 31 print('请求目标网页完成....\n准备解析....') 32 self.headers['referer'] = get_url 33 return response.text 34 except Exception: 35 print('请求目标网页失败,请检查错误重试') 36 return None 37 38 def parse_page(self, html): 39 """解析网页""" 40 print('目标信息正在解析........') 41 selector = Selector(text=html) 42 self.title = selector.xpath("//head/title/text()").extract_first() # 获取标题(电影名称) 43 print(self.title) 44 m3u8_url = selector.xpath("//div[@id='a1']/iframe/@src").extract_first()[14:] # 获取视频地址(m3u8) 45 self.ts_list = self.get_ts(m3u8_url) # 获得一个包含ts文件的列表 46 print('解析完成,下载ts文件.........') 47 self.pool() 48 49 def get_ts(self, m3u8_url): 50 """解析m3u8文件获取ts文件""" 51 try: 52 response = requests.get(m3u8_url, headers=self.headers) 53 html = response.text 54 print('获取ts文件成功,准备提取信息') 55 ret_list = re.findall("(out.*?ts)+", html) # 匹配.ts的字段 56 ts_list = [] 57 for ret in ret_list: 58 ts_url = m3u8_url[:-13] + ret 59 ts_list.append(ts_url) 60 return ts_list 61 except Exception: 62 print('缓存文件请求错误1,请检查错误') 63 64 def pool(self): 65 print('经计算须要下载%d个文件' % len(self.ts_list)) 66 if self.title not in os.listdir(): 67 os.mkdir(r"D:" + self.title) # 新建视频目录 68 print('正在下载...所需时间较长,请耐心等待..') 69 # 开启多进程下载 70 pool = pool = ThreadPoolExecutor(max_workers=16) # 多线程下载 71 pool.map(self.save_ts, self.ts_list) 72 pool.shutdown() 73 print('下载完成') 74 self.ts_to_mp4() 75 76 def ts_to_mp4(self): 77 print('ts文件正在进行转录mp4......') 78 str = 'copy /b ' + self.title+'\*.ts ' + self.title + '.mp4' # copy /b 命令 79 os.system(str) 80 filename = self.title + '.mp4' 81 if os.path.isfile(filename): 82 print('转换完成,祝你观影愉快') 83 shutil.rmtree(self.title) 84 85 def save_ts(self, ts_list): 86 print(self.title) 87 self.i += 1 88 print('当前进度%d' % self.i) 89 urlretrieve(url=ts_list, filename=r"D:" + self.title + '\{}'.format(ts_list[-9:])) 90 91 92 if __name__ == '__main__': 93 url = "https://v.qq.com/x/cover/c949qjcugx9a7gh.html" # 视频url 94 video_down_loader = VideoDownLoader(url)
运行代码,喝一杯coffee等待10来分钟视频就自动下载好了。可是这里依然会存在这下载下来的.ts文件不完整的状况,博客写到这里我脑海里面又想到了一种解决方法,明天试一试把。哦对了,关于视频的文件下载多线程,多进程我分别都试过,二者的下载速度区别并不大,由于这涉及到了网络的请求以及文件的读写等IO操做。因此采用多线程/进程没啥区别,建议仍是用多线程来。结果以下所示:
1 # 下载400个.ts文件测试线程、进程的性能 2 3 # >>> multiprocessing ——> 196.01457595825195 默认开启4个进程 4 # >>> multiprocessing ——> 196.01457595825195 强制开启16个进程,实际上5个 5 # >>> threading ——> 174.57704424858093 默认开启4个线程 6 # >>> threading ——> 202.30066895484924 默认开启40个线程(网络卡顿) 7 # >>> threading ——> 155.5946135520935 默认开启16个线程
测试的结果表名,线程开启的速度确实比进程开启速度快。然并卵!