搜索
查看: 2668|回复: 13

[网络] 一次分析下载广场舞视频的过程附源码......

[复制链接]

1

主题

4

帖子

47

积分

新手入门

Rank: 1

积分
47
发表于 2018-6-10 22:45:10 | 显示全部楼层 |阅读模式
前言:
她说要减肥,要下载广场舞在电视上放来学,本以为很简单的事情,网上随便搜索下载就是,于是随口答应。。。
结果,辛苦搜索半天基本都没什么能下载的,逼我动用神器:AARDIO

先找到一个播放网址:
http://www.boosj.com/drama_43355_29.html

页面上没有下载按钮,于是看了下网页源码,也没发现什么直接的视频地址,只找到了这个一段视频信息配置


既然没有直接写在网页源码里面,那就F12分析看看,在等待了几十秒的广告后,开始播放了,发现播放的不是一个完整的MP4或FLV等视频文件,而是多个TS文件



根据经验,这个的视频文件一般有个分段视频配置文件,于是往上找,找到了这样一个网址:
http://v10.hls.boosj.com/v/2016-12/29/2016-1229SVNE123110511031020818.mp4/index.m3u8?t=tKZEMKlur-P-JdRlzymCPg&m=1528634372

果然这里面保存的就是完整的视频地址


在来找找这个配置文件的网址是哪里来的,继续往上翻,找到了这个一个网址:
http://gslb.boosj.com/f_hls/?_id=6888231&t=99155818ca3a229879eff66e9f4955a8



挺顺利啊,继续找这个网址,咦,貌似不需要继续找了,网页源码里面貌似就有这个id,在第一张截图里面的 vid 参数 和这个 _id参数一致,模拟GET这个视频播放网址,提取出 vid参数,直接就能构建出这个 http://gslb.boosj.com/f_hls/?_id=视频编号&t=99155818ca3a229879eff66e9f4955a8

OK,祭出神器【aardio】,开工测试一下上面这些流程
  1. import console;
  2. import web.json
  3. import inet.http
  4. http = inet.http()
  5. html = http.get( "http://www.boosj.com/drama_43355_29.html" )
  6. 视频编号 = string.match( html, '<@vid:"@>([0-9]+)' )
  7. hls = "http://gslb.boosj.com/f_hls/?_id="+视频编号+"&t=99155818ca3a229879eff66e9f4955a8"
  8. html = http.get( hls )
  9. json = web.json.tryParse( html )
  10. m3u8 = json.url ++ "?" ++ json.t
  11. html = http.get( m3u8 )
  12. for ts in ..string.gmatch( html, "(http.*?\.ts)" ) {
  13.         console.log( ts )
  14. }
  15. console.pause(true);
复制代码
运行结果


视频地址全出来了,继续测试其他的播放页面看看,把代码里面的网址换成:
http://www.boosj.com/drama_43355_36.html

结果报错,json.t 不存在,打印出来瞧瞧:


看来网址 "http://gslb.boosj.com/f_hls/?_id="+视频编号+"&t=99155818ca3a229879eff66e9f4955a8" 里面的 参数t 不是固定的

于是删除t 随机t 各种尝试,始终报错,看来不能直接拼接 "http://gslb.boosj.com/f_hls/?_id="+视频编号+"&t=99155818ca3a229879eff66e9f4955a8"这个网址

那就继续分析哪里生成的这个 t 参数,结果分析了半天,F12里面看了个遍,都没找到哪里有生成这个 t 参数,也没找到有 f_hls 关键字的内容,没道理啊
继续在调试器里面东看西看,冥思苦想,看到个v_4.3.swf播放器文件,难道是这个v_4.3.swf文件生成的t参数,下载下来分析下:
http://static.boosj.com/v/swf/v_4.3.swf

找了个自己的百宝箱,不知道我的FLASH反编译工具跑哪里去了,只有一个 URL Action Editor6.0 ,就你了,先看看吧


哈哈,果然藏在这里,看你往哪儿跑,赶紧下载个反编译工具JPEXS Free Flash Decompiler,找到这个怎么生成的


可以看出这个 t = md5(视频编号+01136c5948d353b1bg2)   或者 t = md5(视频编号+01136c5948d353b1)
从前面步骤的网址可以看出我们需要的t应该是用  版本HLS的 01136c5948d353b1bg2 但是还是测试一下比较保险
在前面有这个一个地址:http://gslb.boosj.com/f_hls/?_id ... 29879eff66e9f4955a8
我们来测试看看,结果确实是一致的,这里使用这个固定的01136c5948d353b1bg2


现在问题解决,再测试几个网址,也都正常了,可以愉快的下载了
不过控制台窗口确实不好操作,优化一下搞个稍微方便点的小工具吧


源码:(代码有点乱,将就看,也就是个思路)
  1. import win.ui;
  2. /*DSG{{*/
  3. winform = win.form(text="广场舞下载";right=807;bottom=639)
  4. winform.add(
  5. button={cls="button";text="抓呀";left=717;top=11;right=794;bottom=47;z=4};
  6. listview={cls="listview";left=14;top=57;right=795;bottom=455;edge=1;fullRow=1;gridLines=1;hscroll=1;vscroll=1;z=3};
  7. log={cls="edit";left=14;top=469;right=795;bottom=630;bgcolor=0;color=65280;edge=1;multiline=1;vscroll=1;z=5};
  8. static={cls="static";text="播放网址";left=8;top=19;right=64;bottom=41;align="right";transparent=1;z=2};
  9. url={cls="edit";left=69;top=18;right=705;bottom=40;edge=1;multiline=1;z=1}
  10. )
  11. /*}}*/

  12. winform.listview.insertColumn("名称",200);
  13. winform.listview.insertColumn("文件数",100);
  14. winform.listview.insertColumn("已下载",100);
  15. winform.listview.insertColumn("状态",100);
  16. winform.listview.adjust = function(cx,cy){
  17.     winform.listview.fillParent(1);
  18. }
  19. import process
  20. import fsys
  21. import fsys.dlg
  22. import crypt
  23. import mouse;
  24. import win.util.tray;
  25. import win.ui.menu;
  26. import crypt
  27. import thread.manage
  28. manage = thread.manage()
  29. import thread.command
  30. cmd = thread.command()
  31. thread.set("cmdhwnd", cmd.hwnd)
  32. thread.set("listviewHwnd",winform.listview.hwnd)

  33. 日志 = function(...){
  34.         winform.log.print(...)
  35. }
  36. cmd.subscribe("日志",日志)

  37. 解析 = function(网址){
  38.         import win
  39.         import crypt
  40.         import web.json
  41.         import fsys
  42.         import thread.command;
  43.         cmd = thread.command.bind(thread.get("cmdhwnd"));
  44.         import win.ui.ctrl.listview
  45.         listview = win.ui.ctrl.listview()
  46.         listview.hwnd = thread.get("listviewHwnd")
  47.         import inet.http
  48.         import inet.httpFile
  49.         http = inet.http("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0")
  50.         
  51.         cmd.post("日志", "解析网址", 网址)

  52.         //第一步 获取视频基本信息
  53.         var html = http.get(网址)
  54.         if(!html or !#html){
  55.                 cmd.post("日志", "打开网址失败")
  56.                 return ;
  57.         }
  58.         
  59.         var 视频编号 = string.match(html,'<@vid:"@>([0-9]+)')
  60.         var 视频名称 = string.match(html,"videoName\s*?\:\s*?\'(.*?)\'")
  61.                  视频名称 = string.replace(视频名称,"\s+","_")
  62.         if(!视频编号){
  63.                 cmd.post("日志", "视频编号获取失败")
  64.                 return ;
  65.         }
  66.         if(!视频名称){
  67.                 视频名称 = crypt.md5(网址)
  68.         }
  69.         cmd.post("日志", "视频编号",视频编号)
  70.         cmd.post("日志", "视频名称",视频名称)
  71.         
  72.         
  73.         //第二步 找到分段视频配置文件m3u8
  74.         var t = crypt.md5(视频编号++"01136c5948d353b1bg2",false);
  75.         var url2 = "http://gslb.boosj.com/f_hls/?_id="++视频编号++"&t="++t;
  76.         var html2 = http.get(url2)
  77.         if(!html2 or !#html2){
  78.                 cmd.post("日志", "获取分段视频配置文件失败",url2)
  79.                 return ;
  80.         }
  81.         var m3u8 = web.json.tryParse(html2)
  82.         if(!m3u8 or m3u8.error!=200 or !m3u8.url or !m3u8.t){
  83.                 cmd.post("日志", "m3u8错误",html2)
  84.                 return ;
  85.         }
  86.         
  87.         //第三步 提取分段视频网址
  88.         var url3 = m3u8.url ++ "?" ++ m3u8.t
  89.         var html3 = http.get(url3)
  90.         if(!html3 or !#html3){
  91.                 cmd.post("日志", "分段视频网址获取失败",url3)
  92.                 return ;
  93.         }
  94.         var 下载地址 = {}
  95.         for 视频地址 in ..string.gmatch( html3,"(http.*?\.ts)") {
  96.                 table.push(下载地址, 视频地址)
  97.         }
  98.         
  99.         var 已完成 = 0;
  100.         var 文件数 = #下载地址;
  101.         if(!文件数){
  102.                 cmd.post("日志", "未获取到分段视频下载地址",html3)
  103.                 return ;
  104.         }
  105.         cmd.post("日志", "分段视频个数",文件数)
  106.         
  107.         //第四步 开始下载
  108.         down = function(网址,目录){
  109.                 var dw = inet.httpFile(网址,目录)
  110.                 if( dw.test() ){
  111.                         return true ;
  112.                 }
  113.                 var ok,err,fileSize = dw.download()
  114.                 return ok,err,fileSize;
  115.         }
  116.     var id = listview.addItem( 视频名称 );
  117.     listview.setItemText(  文件数, id, 2 );
  118.    
  119.         var x = 10
  120.         for(i=1;#tostring(文件数);1){
  121.                 x *= 10
  122.         }
  123.    
  124.     var GUID = crypt.md5(视频名称)
  125.     var 下载目录 = io.fullpath(""++GUID)
  126.         for(k,v in 下载地址){
  127.                 var ok,err = down(v, 下载目录++""++(x+k)++".ts")
  128.                 if(ok){
  129.                         已完成++
  130.                         listview.setItemText(  已完成, id, 3 );
  131.                 }
  132.         }
  133.         if(已完成==文件数){
  134.                 listview.setItemText(  "下载完成", id, 4 );
  135.         }else {
  136.                 已完成 = 0
  137.                 var ok,err = down(v, "/"+视频名称+"/")
  138.                 if(ok){
  139.                         已完成++
  140.                         listview.setItemText(  已完成, id, 3 );
  141.                 }
  142.         }
  143.         
  144.         if(已完成==文件数){
  145.                 listview.setItemText(  "下载完成", id, 4 );
  146.                 var 完成文件 = io.fullpath(下载目录++"/"++视频名称++".ts")
  147.                 if(!io.exist("/视频/")){
  148.                         fsys.createDir("/视频/")
  149.                 }
  150.                 var 合并命令 = "copy/b "++GUID+'\\'+"*.ts "++'视频\\'+视频名称++".ts"
  151.                 合并命令 = string.fromto(合并命令,65001,0)
  152.                 execute(合并命令)
  153.                 cmd.post("日志", "任务结束","/视频/"+视频名称+".ts")
  154.         }else {
  155.                 listview.setItemText(  "部分完成", id, 4 );
  156.                 cmd.post("日志", "任务结束","下载未全部完成,暂未合并")
  157.         }
  158.         return 1;
  159. }

  160. winform.button.oncommand = function(id,event){
  161.         var 网址 = string.trim( winform.url.text )
  162.         if(!#网址){
  163.                 winform.msgboxErr("请输入视频网址")
  164.                 return ;
  165.         }
  166.         if(string.sub(网址,0,4)!="http"){
  167.                 网址 = "http://"++网址
  168.         }

  169.         manage.create(解析, 网址)

  170. }

  171. zpop = win.ui.popmenu(winform);
  172. zpop.add('打开视频目录',function(id){
  173.         var 视频名称 = winform.listview.getItemText(winform.listview.selIndex,1)
  174.         if(#视频名称){
  175.                 if(!io.exist("/视频/")){
  176.                         fsys.createDir("/视频/")
  177.                         process.explore("/视频/"++视频名称++".ts")
  178.                 }else {
  179.                         process.exploreSelect("/视频/"++视频名称++".ts")
  180.                 }
  181.         }
  182. });
  183. zpop.add()
  184. zpop.add('打开下载目录',function(id){
  185.         var 视频名称 = winform.listview.getItemText(winform.listview.selIndex,1)
  186.         if(#视频名称){
  187.                 var GUID = crypt.md5(视频名称)
  188.                 if(!io.exist("/"+GUID+"/")){
  189.                         fsys.createDir("/"+GUID+"/")
  190.                 }
  191.                 process.explore("/"+GUID)
  192.         }
  193. });
  194. zpop.add()
  195. zpop.add('播放',function(id){
  196.         var 视频名称 = winform.listview.getItemText(winform.listview.selIndex,1)
  197.         if(#视频名称){
  198.                 if(!io.exist("/视频/"++视频名称++".ts")){
  199.                         winform.msgboxErr("视频文件不存在")
  200.                         return ;
  201.                 }
  202.                 process.execute("/视频/"++视频名称++".ts")
  203.         }
  204. });
  205. zpop.add()
  206. zpop.add('合并下载目录ts文件',function(id){
  207.         var 视频名称 = winform.listview.getItemText(winform.listview.selIndex,1)
  208.         if(#视频名称){
  209.                 var GUID = crypt.md5(视频名称)
  210.                 if(!io.exist("/"+GUID+"/")){
  211.                         winform.msgboxErr("分段视频目录不存在")
  212.                         return ;
  213.                 }
  214.                 var 合并命令 = "copy/b "++GUID+'\\'+"*.ts "++'视频\\'+视频名称++".ts"
  215.                 合并命令 = string.fromto(合并命令,65001,0)
  216.                 execute(合并命令)
  217.                
  218.                 var 视频文件 = "/视频/"++视频名称++".ts"
  219.                 if(!io.exist(视频文件)){
  220.                         日志("合并失败",GUID++"\*.ts")
  221.                         winform.msgboxErr("请检查分段TS文件是否存在","合并失败")
  222.                         return ;
  223.                 }
  224.                 日志("合并成功",视频文件)
  225.                
  226.                 process.exploreSelect(视频文件)
  227.         }else {
  228.                 var 合并目录 = fsys.dlg.opendir(io._exedir,,"选择待合并的TS文件目录","该目录下的所有TS文件将合并成一个文件,请注意TS文件名称排序问题")
  229.                 if(合并目录){
  230.                         var 视频名称 = crypt.md5(合并目录)
  231.                         var 合并命令 = "copy/b "++合并目录+'\\'+"*.ts "++'视频\\'+视频名称++".ts"
  232.                         合并命令 = string.fromto(合并命令,65001,0)
  233.                         execute(合并命令)
  234.                         
  235.                         var 视频文件 = "/视频/"++视频名称++".ts"
  236.                         if(!io.exist(视频文件)){
  237.                                 日志("合并失败",合并目录++"\*.ts")
  238.                                 winform.msgboxErr("请检查分段TS文件是否存在","合并失败")
  239.                                 return ;
  240.                         }
  241.                         日志("合并成功",视频文件)
  242.                 }
  243.         }
  244.         
  245. });
  246. zpop.add()
  247. zpop.add('移除',function(id){
  248.     winform.listview.delItem( winform.listview.selIndex )
  249. });

  250. winform.listview.onnotify = function(id,code,ptr){
  251.     select(code) {
  252.             case 0xFFFFFFFB/*_NM_RCLICK*/ {
  253.                     var x,y = mouse.getPos()
  254.                     zpop.popup(x,y,true);//弹出菜单
  255.             }
  256.             case 0xFFFFFFFD/*_NM_DBLCLK*/ {
  257.                         var 名称 = winform.listview.getItemText(winform.listview.selIndex,1)
  258.                         if(#名称){
  259.                                 if(!io.exist("/视频/")){
  260.                                         fsys.createDir("/视频/")
  261.                                 }
  262.                                 process.exploreSelect("/视频/"++名称++".ts")
  263.                         }else {
  264.                                 process.explore("/视频/")
  265.                         }
  266.             }
  267.     }
  268. }


  269. winform.onClose = function(hwnd,message,wParam,lParam){
  270.     winform.text = "正在等待关闭";
  271.     manage.quitMessage();
  272. }

  273. winform.enableDpiScaling();
  274. winform.show();

  275. return win.loopMessage();
复制代码

OK,到此结束!感谢神器AARDIO

回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2018-6-14 15:26:21 | 显示全部楼层
感谢分享
回复

使用道具 举报

2

主题

57

帖子

1433

积分

新手入门

积分
1433
发表于 2018-6-14 17:10:39 | 显示全部楼层
感谢分享,非常棒!
也可以用mb直接读取
mb.onRequestBegin = function(hRequest,url){
    if string.indexOf(url,".m3u8") console.log(url)
}
回复

使用道具 举报

1

主题

4

帖子

47

积分

新手入门

Rank: 1

积分
47
 楼主| 发表于 2018-6-14 21:46:54 | 显示全部楼层
松江 发表于 2018-6-14 17:10
感谢分享,非常棒!
也可以用mb直接读取
mb.onRequestBegin = function(hRequest,url){

额,还不知道这个



论坛编辑器自动把斜杠搞丢了。。。
var ok,err = down(v, 下载目录++""++(x+k)++".ts")
需要加上斜杠
var ok,err = down(v, 下载目录++"\"++(x+k)++".ts")

其他地方自己看着办。。。
回复

使用道具 举报

3

主题

7

帖子

66

积分

一级会员

Rank: 2

积分
66
发表于 2018-6-14 23:15:28 | 显示全部楼层
厉害厉害,记录了整个过程,学到了很多。测试了下效果很棒
回复

使用道具 举报

0

主题

6

帖子

42

积分

新手入门

Rank: 1

积分
42
发表于 2018-6-15 20:56:42 | 显示全部楼层
很好的编程范例,从需求分析,算法测试,到功能实现。学习了
回复

使用道具 举报

39

主题

174

帖子

1037

积分

荣誉会员

htmlayout.cn 我的学习笔记

Rank: 8Rank: 8

积分
1037
发表于 2018-6-15 22:41:27 来自手机 | 显示全部楼层
非常详细的写了整个思考的过程,谢谢分享
回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2018-6-15 23:54:36 | 显示全部楼层
感谢楼主分享,
很抱歉因为包含“减肥”这帖子被审核了许久(现在都没怎么看这些)。

这是难得一见的好教程,建议新手仔细看看。
我在看源码的时候,突然想拿 web.rest试一下,试的时候蹦出一点灵感火花,把web.rest针对这个帖子中的抓取过程进行了改进。主要是可以直接指定匹配一组键值对,然后把这个键值对又可以直接用于 web.rest中替换URL模板占位符的参数。

参考楼主的代码,用 web.rest 写了个测试代码如下:
import crypt;
import console;
import web.rest.jsonLiteClient;

//创建HTTP客户端
var http = web.rest.jsonLiteClient()

//第一步、声明获取视频基本信息的接口
var boosj = http.api("http://www.boosj.com/drama_43355_29.html",,{
    id =
'<@vid:"@>([0-9]+)';
    name =
"videoName\s*?\:\s*?\'(.*?)\'"
} )

//第二步、找到分段视频配置文件m3u8
data = boosj.get();
data.t = crypt.md5(data.id+
"01136c5948d353b1bg2",false);

var gslb = http.api("http://gslb.boosj.com/f_hls/?_id={id}&t={t}")
var m3u8 = gslb[ data ].get();

//第三步、提取分段视频网址
if( m3u8["error"] == 200 ){
   
var m3u8Urls = http.api(m3u8.url ++ "?" ++ m3u8.t,"GET",{"(http.*?\.ts)"})
   
var urls = m3u8Urls.get();
    console.dumpJson( urls )
}

console.pause(
true);

回复

使用道具 举报

1

主题

4

帖子

47

积分

新手入门

Rank: 1

积分
47
 楼主| 发表于 2018-6-16 14:28:11 | 显示全部楼层
Jacen.He 发表于 2018-6-15 23:54
感谢楼主分享,
很抱歉因为包含“减肥”这帖子被审核了许久(现在都没怎么看这些)。

学习了,这样简洁很多

但是我遇到个问题,第一次API匹配的时候会报错


http.api第三个参数不用数组就正常了


不知道什么问题...
回复

使用道具 举报

3

主题

50

帖子

421

积分

培训班

积分
421
发表于 2018-6-16 15:07:47 | 显示全部楼层
getpost 发表于 2018-6-16 14:28
学习了,这样简洁很多

但是我遇到个问题,第一次API匹配的时候会报错

怕是没更新版本?
键值对匹配是新版本的特性
aardio v17.120 更新   (2018/6/14):
---------------------------------------------------------------------------
1、给力的升级 web.rest,可以用键值对匹配一组返回结果。
可以在API对象的成员中使用一个表对象批量替换多个URL模板的占位符。
回复

使用道具 举报

1

主题

4

帖子

47

积分

新手入门

Rank: 1

积分
47
 楼主| 发表于 2018-6-16 16:34:08 | 显示全部楼层
nlysh007 发表于 2018-6-16 15:07
怕是没更新版本?
键值对匹配是新版本的特性





确实是没有..............................................................................................................
回复

使用道具 举报

2

主题

39

帖子

335

积分

二级会员

Rank: 3Rank: 3

积分
335
发表于 2018-6-17 20:50:12 | 显示全部楼层
完整的分析思路很好.不过这个有直接下载地址,就在视频信息配置里:murl:"http://m.boosj.com/drama_43355_29.html",然后可以直接找到下载地址:http://bsyvideo.boosj.com/v/2016-12/29/2016-1229SVNE123110511031020818.mp4?start=0&t=tHlWYer5wpSpA0kgWwZ0Qw&m=1529253269&c=m
回复

使用道具 举报

0

主题

19

帖子

331

积分

二级会员

Rank: 3Rank: 3

积分
331
发表于 2018-6-18 11:38:26 | 显示全部楼层
谢谢楼主的精彩分享,讲的太好了,也很详细,思路很清晰
回复

使用道具 举报

2

主题

22

帖子

171

积分

一级会员

Rank: 2

积分
171
发表于 2018-6-26 09:10:56 | 显示全部楼层
好清晰的思路,精彩~~~~~评论区更精彩,谢谢分享~~~~~~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

手机版|未经许可严禁引用或转载本站文章|站长邮箱|aardio.com|aardio官方社区 ( 皖ICP备09012014号 )

GMT+8, 2018-12-16 16:50 , Processed in 0.093750 second(s), 23 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表