始于2020.2.25
以https://www.xxxxxxx.com/vodplayhtml/27709.html为例
该网址可以先从网页源码中提取出一个m3u8的文件
https://dadi-bo.com/20190205/2s3wcjro/index.m3u8
但是没加载出来时只能提取到这个:
var vHLSurl = “https://”+CN1+"/20190205/2s3wcjro/index.m3u8";
也差不多
文件中是如下信息:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=720x406
/ppvod/4B8FA7A19F704F55A7AEF68E90B3B854.m3u8
然后,我们从捕获的XHR接口中可以发现这样一个文件
https://dadi-bo.com/ppvod/4B8FA7A19F704F55A7AEF68E90B3B854.m3u8
打开文件看,发现就是ts文件的播放列表
摘取部分如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:9
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:6.039,
/20190205/2s3wcjro/800kb/hls/hDdIBh38332000.ts
#EXTINF:6.34,
/20190205/2s3wcjro/800kb/hls/hDdIBh38332001.ts
其中ts文件就是视频的流文件
https://dadi-bo.com/20190205/2s3wcjro/800kb/hls/hDdIBh38332012.ts
使用时加上https://dadi-bo.com即可
这下我们就可以确定思路:
(1)抓取网页源码,解析出第一个m3u8文件和视频名称,保存下来
(2)从第一个m3u8文件中解析出第二个m3u8文件,把所有的ts信息都提取出来,保存在列表中
(3)先尝试单线程爬虫爬取部分ts数据,看是否有强烈的反爬措施,爬取时最好慢一点
(4)用os.makedirs来一次创建多层文件夹,将文件保存进去
(5)研究该网站反爬措施,然后在单线程爬虫的基础上,改造多线程爬虫
(6)导入某种Python库,将ts视频按m3u8文件整合成mp4,用ffmpeg来合并视频,博客https://blog.csdn.net/weixin_33826268/article/details/88039817
修订&更新:
2020.2.27:
背景:(1)~(5)的已经全部实现,网站未有强烈的反爬措施,但是下载视频时会有延迟,即有些响应会超时,导致一部分线程挂掉,还有就是爬取速度不稳定,导致网断,整个程序得重新再来,这样的程序太脆弱了,我们需要一个stronger程序!
初始方案:
(1)每次爬取时抓取下的ts链接,对应文件夹中是否有这个文件,如果有,直接跳过,如果没有,就下载
(2)每次从Queue里取出ts链接时,准确来说不要取出来,就访问头部元素,如果下载成功了再取出来,防止缺少链接
(3)在遇到响应超时或其他error时,抛出错误,并继续执行程序,而不是被搞掉了
(4)将error输出为日志
方案修订:
(1)第(2)点中,链接要取出来,否则多线程会一直爬取头部元素,导致效率低下以致崩溃,改进方法为将其再插入尾部,等待被再次取出
方案实现:
(1)用os方法里的os.path.isfile()来判断是否有该文件
(2)用try和except来实现重复爬取,用BaseException来就够了
(3)超时特判用在代码中get方法添加timeout参数来实现
代码:
import requests
import re
import time
import os
import sys
import threading
import logging
import subprocess
from queue import Queue
from bs4 import BeautifulSoup
# 设置日志参数,输出到log文件中
logging.basicConfig(filename='log/spider.log', level=logging.WARNING, format='%(asctime)s %(filename)s[line:%(lineno)d] %(message)s')
# 建立会话
s = requests.Session()
# 参数
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0',
'Host': 'www.52sehua.com',
}
download_list = []
def prepare_system():
path = os.getcwd().replace('\\', '/')
if not os.path.exists(path + '/log'):
os.makedirs(path + '/log')
path = path.rsplit('/', 1)[0]
if not os.path.exists(path + '/mp4'):
os.makedirs(path + '/mp4')
path = path + '/mp4'
if not os.path.exists(path + '/ppvod'):
os.makedirs(path + '/ppvod')
return path
class download_ts_thread(threading.Thread):
def __init__(self, name, queue_ts, ols):
super(download_ts_thread, self).__init__()
self.name = name
self.queue_ts = queue_ts
self.ols = ols
def fn(self, a):
ret = a.group()
return '0'+ret
def parse_ts(self, ts):
nls = len(ts)
if nls == self.ols:
pattern = re.compile(r'(\d{3}).ts')
ret = pattern.sub(self.fn, ts)
else:
ret = ts
return ret
def run(self):
print('%s启动...' % self.name)
while not self.queue_ts.empty():
ts = self.queue_ts.get()
file_ts = self.parse_ts(ts)
if os.path.isfile(file_path + file_ts):
continue
print('正在下载%s...' % ts)
ts_url = 'https://x.xxxxxx.com' + ts
try:
r = s.get(url=ts_url, headers=headers, timeout=80)
except BaseException as e:
logging.error(e)
self.queue_ts.put(ts)
print(e)
time.sleep(2)
continue
with open(file_path + file_ts, 'wb')as fp:
fp.write(r.content)
print('%s下载完成...' % file_ts)
time.sleep(2)
print('%s结束' % self.name)
def get_m3u8(recode, code, page):
print('正在获取第%d个m3u8文件...' % page)
url_m3u8 = 'https://x.xxxxxxxx.com{}'
# print(url_m3u8)
pattern = re.compile(r'' + recode, re.M)
filename_m3u8 = pattern.findall(code)[0]
if os.path.isfile(file_path + filename_m3u8):
with open(file_path + filename_m3u8, 'r')as fg:
m3u8 = fg.read()
print('检测到有本文件,结束!')
return m3u8
filepath = os.path.split(file_path + filename_m3u8)[0]
if not os.path.exists(filepath):
os.makedirs(filepath)
url_m3u8 = url_m3u8.format(filename_m3u8)
# print(url_m3u8)
while 1:
try:
m3u8 = s.get(url=url_m3u8, headers=headers, timeout=30)
m3u8.encoding = 'utf-8'
except BaseException as e:
logging.error(e)
print(e)
print('重新下载m3u8文件')
continue
break
print('第%d个m3u8文件获取完成,正在保存...' % page)
with open(file_path + filename_m3u8, 'w', encoding='utf-8')as fp:
fp.write(m3u8.text)
print('保存完成!')
return m3u8.text
def get_ts(code):
print('正在获取ts信息...')
pattern = re.compile(r'(.*?.ts)\n', re.M)
list_ts = pattern.findall(code)
print('ts信息获取完成!')
return list_ts
def creat_queue(list_ts):
q = Queue()
for ts in list_ts:
q.put(ts)
return q
def create_ts_thread(queue_ts, ols):
thread_list = []
for Thread in range(1, 71):
name = '下载' + str(Thread) + '号'
thread_list.append(name)
for name in thread_list:
tdownload = download_ts_thread(name, queue_ts, ols)
download_list.append(tdownload)
def create_dir(ts, title):
print('检验文件路径...')
path = os.path.split(ts)[0]
if not os.path.exists(file_path + path):
print('没有文件路径,创建文件路径!')
os.makedirs(file_path + path)
else:
print('已存在文件路径!')
save_title(file_path + path, title)
return path
def get_title(text):
soup = BeautifulSoup(text, 'html.parser')
# 这里不能用name="xxx"的形式,因为name是关键字,所有为了不起冲突,最好统一用字典来写
with open('htmlcode.txt', 'w', encoding='utf-8')as fp:
fp.write(text)
title = soup.find_all('meta', {'name': 'keywords'})[0]['content']
return title
def save_title(path, title):
with open(path + '/视频名称.txt', 'w', encoding='utf-8')as fp:
fp.write(title)
judge_exists_movie(path)
def combine_ts(title, path):
if not os.path.isfile(file_path + path + '/movie.mp4'):
path = 'mp4' + path
# 妈的这个我折腾了好久,淦,&&是连续执行的意思
subprocess.run("F:&&cd ..&&cd " + path + "&© /b *.ts movie.mp4", shell=True)
print('mp4文件转化完毕!')
else:
print('movie文件已存在!')
def judge_exists_movie(path):
print('检验是否已经下载过...')
path_movie = path+'/movie.mp4'
if os.path.isfile(path_movie):
print("当前movie已下载,无需再次下载!关闭程序!")
remove_ts(path)
sys.exit()
def remove_ts(path):
print('删除ts文件...')
files = os.listdir(path)
for file in files:
if os.path.splitext(file)[1]=='.ts':
os.remove(path + '/' + file)
print('删除%s中...' % file)
def main():
global file_path
file_path = prepare_system()
number = int(input('请输入即将下载的视频号码:'))
url = 'https://www.xxxxxx.com/vodplayhtml/{}.html'
url = url.format(number)
T1 = time.time()
print('正在下载页面信息...')
while 1:
try:
r = s.get(url=url, headers=headers, timeout=10)
r.encoding = 'utf-8'
except BaseException as e:
logging.error(e)
print(e)
print('重新下载页面信息...')
continue
break
print('页面信息下载完成!')
title = get_title(r.text)
print(title + '正在下载...')
# m3u8_1 = get_m3u8('\"https://\"\+CN\d+\+\"(.*)\"', r.text, 1)
m3u8_1 = get_m3u8('\"https://.*?(/.*)\"', r.text, 1)
m3u8_2 = get_m3u8('.*?.m3u8', m3u8_1, 2)
list_ts = []
list_ts = get_ts(m3u8_2)
ordinary_len_ts = len(list_ts[0])
path = create_dir(list_ts[0], title)
queue_ts = creat_queue(list_ts)
print('初始化下载...')
time.sleep(2)
create_ts_thread(queue_ts, ordinary_len_ts)
print('开始下载!!!')
for tdownload in download_list:
tdownload.start()
for tdownload in download_list:
tdownload.join()
T2 = time.time() - T1
print('下载完成!!!')
print('共用时%.3f秒' % T2)
print('开始转换成mp4文件...')
time.sleep(2)
combine_ts(title, path)
if __name__ == '__main__':
main()