根据 ZSAIm 大神帖子 《XX视频H5解析分析过程》
进行如下请求头 ==构造②== ,
GET
/jp/dash?
得到启发,Network中搜索dash可得:
在Response 选项卡可看到如下内容:
{"data":{"dm3u8":"https:\/\/cache.m.i???i.com\/dc\/dt\/","st":101,"…………
右键 Copy Response,到[JSON格式化网站] (https://tool.lu/js/) 对内容格式化:
在尾部可看到视频分段信息,将格式化后的代码复制到记事本,并搜索ts相关信息,找到最后一个,可以看到:
其中含有该视频对应的TS片段和URL参数,其中start, end 分别为片段起始时间戳和结束时间戳,找到最大的end 时间戳后,将其对应的URL start改为0 即可使用该URL获取完整TS文件。
编写一个TamperMonkey脚本,尝试一下看能否通过 HOOK WebRequest 方式拦截该 XHR 请求:
代码如下:
// ==UserScript==
// @name HOOK AJAX XMLRequest 调试中
// @namespace NAMESPACE
// @version 0.1
// @description HOOK AJAX XMLRequest
// @author anonymous
// @match https://www.i????.com/*
// @grant none
// ==/UserScript==
function addXMLRequestCallback(callback){
var oldSend, i;
if( XMLHttpRequest.callbacks ) {
// we've already overridden send() so just add the callback
XMLHttpRequest.callbacks.push( callback );
} else {
// create a callback queue
XMLHttpRequest.callbacks = [callback];
// store the native send()
oldSend = XMLHttpRequest.prototype.send;
// override the native send()
XMLHttpRequest.prototype.send = function(){
// process the callback queue
// the xhr instance is passed into each callback but seems pretty useless
// you can't tell what its destination is or call abort() without an error
// so only really good for logging that a request has happened
// I could be wrong, I hope so...
// EDIT: I suppose you could override the onreadystatechange handler though
for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
XMLHttpRequest.callbacks[i]( this );
}
// call the native send()
oldSend.apply(this, arguments);
}
}
}
(function () {
'use strict';
addXMLRequestCallback( function( xhr ) {
xhr.addEventListener("load", function(){
if ( xhr.readyState == 4 && xhr.status == 200 ) {
console.log("HOOK > " + xhr.responseURL );
//if ( xhr.responseURL.includes("url") ) {
// console.log(xhr);
// //do something!
//}
}
});
});
})();
注意:uBlockOrigin 之类的扩展会干扰 TamperMonkey 脚本的正常运行,在使用油猴脚本调试网站时需要关闭 uBlockOrigin。
通过 https://oktools.net/json 这个在线格式化网站格式化如下URL:https://cache.video.i???i.com/dash?tvid=113292... 得到如下结果:
可以看到
object-->data-->program-->video[2] -->m3u8
即为我们想要的内容(m3u8):
可以通过在 Tamper Monkey 脚本中加入 debugger 然后在中断后的 console 中输入如下代码验证:
var k38=JSON.parse(xhr.responseText);
console.log(k38.data.program.video[2].m3u8);
经实测,其中video数组中,下标并不固定,需要编写一段代码搜索实际含有m3u8属性的video[?]
以下地址:
https://data.video.i???i.com/videos/vts/20200103/86/25/6222*536de.ts?start=0&end=639952&contentlength=639952&sd=0&qdv=1&qd_uid=17&
qd_tvid=1127691800&qd_vip=1&
qd_src=01010031010000000000&qd_tm=1579526642916&qd_ip=0&qd_p=0&qd_k=663&ve=&sgti=14_ddf2b06babfed178086ed9ebd16a5b9a_15
79526641369&dfp=&qd_sc=7d1c8771adf248c5a28f61677209564f&pv=0.1&cross-domain=1&stauto=1
返回JSON内容为:
{"t":"CT|??Xi-113.17.102.100","abs_speed":500,"mrc":"0","z":"qiniucdn_ct","h":"0","l":"https://qncdnct.inter.71???.com/videos/vts/20200103/86/25/6222d
e7dd4fd4.ts?key=0d4db529240&dis_k=f04f6db1759&dis_t=1579526643&dis_dz=CT-Xi&dis_st=49&src=i???.com&dis_hit=0&uuid=71116664-5e25a
9f3-25e&sgti=14_ddf2b06babfed178086ed9ebd16a5b9a_1579526641369&start=0&qd_uid=1&qd_tm=1579526642916&qdv=1&cross-domain=1&ve=&dfp=&contentlength=639952&qd_src=01010031010000000000&qd_p=0&sd=0&pv=0.1&qd_tvid=1127691800&qd_vip=1&qd_ip=
0&stauto=1&end=639952&qd_k=66553","e":"0"}
但其返回的end值并非最大的,导致使用该值下载的TS视频片段不完整,原因待查。所以,还是只有前面的方法才可以做到下载完整TS文件。
经试验,确认可以HOOK到相应的XHR请求,下步就是进一步修改脚本,使得其能够处理上述JSON并输出下载的URL:
// ==UserScript==
// @name HOOK AJAX XMLRequest 调试中
// @namespace NAMESPACE
// @version 0.1
// @description HOOK AJAX XMLRequest
// @author anonymous
// @match https://www.i???i.com/*
// @grant none
// ==/UserScript==
function addXMLRequestCallback(callback){
var oldSend, i;
if( XMLHttpRequest.callbacks ) {
// we've already overridden send() so just add the callback
XMLHttpRequest.callbacks.push( callback );
} else {
// create a callback queue
XMLHttpRequest.callbacks = [callback];
// store the native send()
oldSend = XMLHttpRequest.prototype.send;
// override the native send()
XMLHttpRequest.prototype.send = function(){
// process the callback queue
// the xhr instance is passed into each callback but seems pretty useless
// you can't tell what its destination is or call abort() without an error
// so only really good for logging that a request has happened
// I could be wrong, I hope so...
// EDIT: I suppose you could override the onreadystatechange handler though
for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
XMLHttpRequest.callbacks[i]( this );
}
// call the native send()
oldSend.apply(this, arguments);
}
}
}
(function () {
'use strict';
addXMLRequestCallback( function( xhr ) {
xhr.addEventListener("load", function(){
if ( xhr.readyState == 4 && xhr.status == 200 ) {
console.log("HOOK > " + xhr.responseURL );
if( xhr.responseURL.includes("dash") ){
//console.log(xhr.responseText);
var k38=JSON.parse(xhr.responseText);
var video=k38.data.program.video;
var i,vlen=video.length;
for(i=0;i<vlen;i++){
if( typeof(video[i].m3u8) != "undefined" ){
console.log(video[i].m3u8);
}
}
//debugger;
//do something!
}
}
});
});
})();
打开开发者工具,在 console 窗口中找到最后一个链接,将 start改为0即可获取对应的完整 TS 片段。