干活干活, 区区懒癌已经阻挡不了澎湃的洪荒之力了......
运行环境: Windows 基于 python3.6
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ------------------
抓取视频时遇到 M3U8 的确挺烦人的, 去年年底实习, 由于项目需求所以和一个同事主攻蟒蛇爬虫, 抓取含有清晰人脸的图片和视频, 在爬取一些视频网站和直播网站时就被 "它" 糊了一脸, 作为一只, 呃, 不对, 是两只刚入爬虫坑的菜鸟, 在视频加载播放时找不到啥关于的. mp4,.MKV 的链接, 反而出现不少. TS 的链接, 然后爬取找度娘, 重点如下:
M3U8 是苹果公司推出一种视频播放标准, 是一种文件检索格式, 将视频切割成一小段一小段的 TS 格式的视频文件, 然后存在服务器中(现在为了减少 I / O 访问次数, 一般存在服务器的内存中), 通过 M3U8 解析出来路径, 然后去请求, 是现在比较流行的一种加载方式, 诸如腾讯视频之类大多都是切割成 TS 流进行加载.
但当时还是晕, 理论和实际处理是两回事, 那时候关于如何爬取 M3U8 的博客不是很多而且很少说到重点 (给俺下载代码, 理论改天再补) 样么是其它语言写的更懵, 近来倒是越来越多了 (可惜当时没赶上), 最后找到篇关于下载的却发现是 python2 的代码, 因为那时候刚开始使用蟒之前学的是 Java 中, 所以调了好一会还是运行失败, 但是也收获了不少, 之后和同事研究了两天试验了几个方案最终确定采用边下边合的方式进行处理, 上个月底从公司离职了, 由于公司管理较为严格禁止向外网(公司局域网以外) 发送消息或拷贝文件,(嗯, 拍照也不行, 就似这么严)所以这几天晚上下班回来抽空写写改改又另起了炉灶, 代码如下, 随意写的所以异常处理和模拟浏览器啥的就没加了(懒)!
注: 仅限窗口下使用, 如果要在 Linux 的上使用需要修改合并命令, 嗯, 或者等几天我再来篇兼容的
- # !/user/bin/env python
- # -*- coding: utf-8 -*-
- # au: caopeiya
- # 20180808
- import os, shutil
- import urllib.request, urllib.error, requests
- # 打开并读取网页内容
- def getUrlData(url):
- try:
- urlData = urllib.request.urlopen(url, timeout=20) # .read().decode('utf-8', 'ignore')
- return urlData
- except Exception as err:
- print(f'err getUrlData({url})\n', err)
- return -1
- # 下载文件 - urllib.request
- def getDown_urllib(url, file_path):
- try:
- urllib.request.urlretrieve(url, filename=file_path)
- return True
- except urllib.error.URLError as e:
- # hasttr(e, 'code'), 判断 e 是否有. code 属性, 因为不确定是不是 HTTPError 错误, URLError 包含 HTTPError, 但是 HTTPError 以外的错误是不返回错误码 (状态码) 的
- if hasattr(e, 'code'):
- print(e.code) # 打印服务器返回的错误码(状态码), 如 403,404,501 之类的
- elif hasattr(e, 'reason'):
- print(e.reason) # 打印错误原因
- def getVideo_urllib(url_m3u8, path, videoName):
- print('begin run ~~\n')
- # urlData = getUrlData(url_m3u8).readlines()
- urlData = getUrlData(url_m3u8)
- num = 0
- tempName_video = os.path.join(path, f'{videoName}.ts') # f'{}' 相当于'{}'.format() 或 '%s'%videoName
- # print(urlData)
- for line in urlData:
- # 解码, 由于是直接使用了所抓取的链接内容, 所以需要按行解码, 如果提前解码则不能使用直接进行 for 循环, 会报错
- # 改用上面的 readlines()或 readline()也可以, 但更繁琐些, 同样需要按行解码, 效率更低
- url_ts = line.decode('utf-8')
- tempName_ts = os.path.join(path, f'{num}.ts') # f'{}' 相当于'{}'.format()
- if not '.ts' in url_ts:
- continue
- else:
- if not url_ts.startswith('http'): # 判断字符串是否以'http'开头, 如果不是则说明 url 链接不完整, 需要拼接
- # 拼接 ts 流视频的 url
- url_ts = url_m3u8.replace(url_m3u8.split('/')[-1], url_ts)
- print(url_ts)
- getDown_urllib(url_ts, tempName_ts) # 下载视频流
- if num == 0:
- # 重命名, 已存在则自动覆盖
- shutil.move(tempName_ts, tempName_video)
- num += 1
- continue
- cmd = f'copy /b {tempName_video}+{tempName_ts} {tempName_video}'
- res = os.system(cmd)
- if res == 0:
- os.system(f'del {tempName_ts}')
- if num == 20: # 限制下载的 ts 流个数, 这个视频挺长有四百多个. ts 文件, 所以限制一下
- break
- num += 1
- continue
- print(f'Wrong, copy {num}.ts-->{videoName}.ts failure')
- return False
- os.system(f'del {path}/*.ts') # 调用 windows 命令行 (即 cmd) 工具, 运行命令
- filename = os.path.join(path, f'{videoName}.mp4')
- shutil.move(tempName_video, filename)
- print(f'{videoName}.mp4 finish down!')
- if __name__ == '__main__':
- url_m3u8 = 'http://wscdn.alhls.xiaoka.tv/201886/2f5/75a/HoHdTc1LjUaBjZbJ/index.m3u8'
- path = r'D:\videos'
- videoName = url_m3u8.split('/')[-2]
- getVideo_urllib(url_m3u8, path, videoName)
注: 修改文件名时, 特意选择 shutil 模块 (可以看作操作系统的高级版) 的移动方法, 虽然移动主要是用来移动文件的, 重命名算是附带的, 不过强制覆盖的特点在这里很有用, 避免中断后重新下载时重命名产生异常.
PS: 说来有趣, 7 月 31 号, 也就是离职的那天上午, 灵感突显, 利用请求下载文件的写入特点, 彻底解决了调用命令行导致的不兼容的窗口以外环境的问题, 哈哈, 所以下一篇就它了.
来源: https://www.cnblogs.com/guyuecanwu/p/9502230.html