前言
hello兄弟们。欢迎回到无聊的网友频道。最近不晓得干什么,天天除了上课也就只有下棋了(垃圾魔盗团,狗都不玩)。
所以我决定痛改前非,接着磨炼技术。今天来给小伙伴们上点才艺,下载点电视电影看看。当然我们也只敢去一些免费网站搞搞事情了。
今天我们就去往一个神秘的小网站云播TV:https://www.ahsnxc.com/
网站分析
当然了,我们不打没有准备的仗。第一步还是要扒光它的底裤,让它“赤裸裸”的让我们轻松爬取。
上图就是我们要爬取的网站首页,资源还是比较丰富的。我们随便点击一个电影或电视进去。我这里就搜索全职高手,拿第一部为例子。
可以看到该动漫url以及分集供我们选择。那么我们就选择第一集。注意我们最好在这个时候就要打开开发者工具了,查看其网络模块。仔细查看网页的变化
可以看到网页请求到了两个比较显眼的index.m3u8文件以及不断缓存的ts文件。那么我们首先得明白这两个文件是什么意思。我给大家简单的介绍一下。由于视频所占用的缓存过大,极易给网站带来负担,所以现在的网站都会将视频文件切片成一个个的小片段也就是一个个ts文件,每一片都有其独立的url地址。网站会将ts文件的地址按照顺序保存在m3u8文件中。所以现在网站播放视频的流程一般都是先请求到m3u8文件,通过保存的ts地址加载ts文件,然后播放资源。
所以我们设计代码时就可以按照这个顺序获得视频的资源。所以第一步就是要获取到m3u8文件的下载地址。我们可以发现上图有两个m3u8文件。一一打开查看其请求数据。
可以看到除了请求头中的url不一样外好像并没有什么区别,接着查看其得到的响应。首先看看第一个m3u8文件
细心的盆友应该很容易发现第一个文件获得的响应虽然不是我们想要的带有ts路径。但确实第二个m3u8文件的路径。而查看第二个m3u8文件发现才是我们要的ts路径
所以我们正确的思路应该是先获取到第一个m3u8文件,获取其内容请求到第二个m3u8文件。然后在请求下载ts文件。
那么我们接着对网页分析,看看m3u8文件的下载地址有没有保存在页面上。
查看页面源码,查找index.m3u8
可以看到在页面源码中保存着两个m3u8文件。其实看其key值名就可以看出url才是本集的m3u8,而url_next应该就是下一集对应的m3u8文件。和前面吞对比发现确实是第一个才是本集的m3u8文件。那么我们就可以开始设计代码了。
代码设计
import requests
# 定义main函数作为函数运行主体
def main():
url = 'https://www.ahsnxc.com/vodplay/31584-1-1.html' # 自己想要下载的视频url
second_m3u8_url = get_first_m3u8(url) # 获取第一个m3u8文件,并提取出第二层m3u8文件的地址
print(second_m3u8_url) # 测试代码
if __name__ == '__main__':
main()
我们先把大框架做下来,如上述代码,然后定义get_first_m3u8()函数
# 定义函数获取第一层m3u8文件
def get_first_m3u8(url):
resp = requests.get(url).text
return resp
先运行代码查看返回到的信息是什么
可以看到返回的是要下载的视频页的源码。下一步就是通过xpath定位到我们需要提取的信息处,接着对get_first_m3u8()函数修改
def get_first_m3u8(url):
resp = requests.get(url).text
tree = etree.HTML(resp)
dic_str = tree.xpath('//*[@id="bofang_box"]/script[1]/text()')[0].strip("var player_aaaa=") # 提取script中的所有内容,此时为字符串形式
dic = json.loads(dic_str) # 将字符串转为字典形式便于获取url
first_m3u8_url = dic['url']
return first_m3u8_url
先运行看是否获取到url
ok获取成功,接着根据这个url获取到第二层。再次对get_first_m3u8函数修改
first_m3u8_url = dic['url']
sec_resp = requests.get(first_m3u8_url).text
return sec_resp
接着运行查看结果
可以看到对第一层m3u8文件发送请求得到的数据如上图,下一步就是根据获得的数据拼接得到第二层的路径。由于返回的数据是根据换行区别的字符串数据,那么我们就可以直接split得到最后一行的数据并根据urljoin对两个url拼接(这玩意确实巨好用,建议朋友们了解一下)。
first_m3u8_url = dic['url']
sec_resp = requests.get(first_m3u8_url).text
sec_url = urljoin(first_m3u8_url, sec_resp.split()[-1])
return sec_url
运行
成功获取到第二层m3u8文件的地址。下一步就是下载m3u8文件的内容。
定义下载函数
def down_sec_m3u8(url):
pass
def main():
down_sec_m3u8(second_m3u8_url)
完善下载函数
def down_sec_m3u8(url):
resp = requests.get(url).text
# 最好新建一个文件夹用来保存本次下载文件
with open('./download_video/全职高手第一集m3u8.txt', mode='w', encoding='utf-8') as f:
f.write(resp)
print('下载完成')
可以看到所有的ts信息全部保存在该文件中。到了这一步我们就成功了一大半了。接下来就是要利用协程的方式快速下载这类大量文件。在读取第二层m3u8文件的时候一定要注意ts文件有没有被加密处理。如果加密的话我们需要再读取时做解密处理。不过我没记错的话云播是没有加密的,小伙伴们在爬取其他网站时记得仔细查看哦。
定义特殊函数下载所有ts文件,已经定义特殊函数单个下载ts文件
async def down_one_ts():
pass
async def down_all_ts():
tasks = []
with open('./download_video/全职高手第一集m3u8.txt', mode='r', encoding='utf-8') as f:
for li in f:
if li.startswith('#'): # 观察文件发现#开头的都是特殊解释,不是路径,所以去除
continue
else:
task = asyncio.create_task(down_one_ts(li))
tasks.append(task)
await asyncio.wait(tasks)
接着对down_one_ts定义
async def down_one_ts(url):
while 1: # 写死循环不断请求
try:
filename = url.split('/')[-1].strip()
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
data = await resp.content.read()
async with aiofiles.open('./download_video/video_ts/'+filename, mode='wb') as f:
await f.write(data)
print(url + '下载成功') # 一个文件下载成功后直接终止循环
break
except:
print('下载出错,正在重新发送请求'+url)
await asyncio.sleep(2) # 进行适当睡眠让浏览器反应能增加成功率
ok到了这部分我们基本的下载模块就处理结束了。在这里先放上源码然后我们运行,测试下20多分钟的动漫需要多久下载完成
import asyncio
import requests
from lxml import etree
import json
from urllib.parse import urljoin
import aiohttp
import aiofiles
# 下载第二层m3u8文件
def down_sec_m3u8(url):
resp = requests.get(url).text
with open('./download_video/全职高手第一集m3u8.txt', mode='w', encoding='utf-8') as f:
f.write(resp)
print('下载完成')
# 定义函数获取第一层m3u8文件
def get_first_m3u8(url):
resp = requests.get(url).text
tree = etree.HTML(resp)
dic_str = tree.xpath('//*[@id="bofang_box"]/script[1]/text()')[0].strip("var player_aaaa=") # 提取script中的所有内容,此时为字符串形式
dic = json.loads(dic_str) # 将字符串转为字典形式便于获取url
first_m3u8_url = dic['url']
sec_resp = requests.get(first_m3u8_url).text
sec_url = urljoin(first_m3u8_url, sec_resp.split()[-1])
return sec_url
async def down_one_ts(url):
while 1: # 写死循环不断请求
try:
filename = url.split('/')[-1].strip()
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
data = await resp.content.read()
async with aiofiles.open('./download_video/video_ts/'+filename, mode='wb') as f:
await f.write(data)
print(url + '下载成功') # 一个文件下载成功后直接终止循环
break
except:
print('下载出错,正在重新发送请求'+url)
await asyncio.sleep(2) # 进行适当睡眠让浏览器反应能增加成功率
async def down_all_ts():
tasks = []
with open('./download_video/全职高手第一集m3u8.txt', mode='r', encoding='utf-8') as f:
for li in f:
if li.startswith('#'): # 观察文件发现#开头的都是特殊解释,不是路径,所以去除
continue
else:
task = asyncio.create_task(down_one_ts(li))
tasks.append(task)
await asyncio.wait(tasks)
# 定义main函数作为函数运行主体
def main():
url = 'https://www.ahsnxc.com/vodplay/31584-1-1.html' # 自己想要下载的视频url
second_m3u8_url = get_first_m3u8(url) # 获取第一个m3u8文件,并提取出第二层m3u8文件的地址
down_sec_m3u8(second_m3u8_url)
asyncio.run(down_all_ts())
if __name__ == '__main__':
start_time = time.time()
main()
end_time = time.time()
print('下载时间为:', end_time-start_time)
正常下载中,但是我很不满意。我的错xdm,这傻逼网站太慢了,估计有什么限流或者单纯的就是太垃圾了,兄弟们可以尝试下载但千万别等结束。我一个20分钟的视频下载了快要一小时。学会方法就行了兄弟们,这网站太傻比了。xdm晚安。过几天在写怎么拼接这些零碎的视频文件。