获取腾讯视频真实播放地址及资源地址

背景

这是一个大数据时代,有时候有些程序想要在网站上爬取主流网站的视频信息并记录下来做数据分析,甚至想要通过程序自动拿到各大视频网站的视频资源,这个时候就出现了很多去研究这个的程序员了~
我研究的主要是移动端的视频播放,所以接下来讲的所有,都是和移动端的页面相关的。

知识储备

  1. 首先我们要了解怎样去抓取网页信息,这个可以参考我前面的一篇文章Jsoup初探
  2. 之所以博主选择腾讯视频有两个很重要的原因,第一,开始说到了我做的是移动端,视频播放格式一般是采用m3u8或者采用最简单的mp4格式,而m3u8一般作为直播的流媒体存储格式,里面放的是若干个切片的ts文件,大多数视频供应商(youku)在生成m3u8的时候就已经把广告的ts切片放在里面了,我们用程序剔出来还是比较麻烦的,而mp4就比较好了,最简单的格式,跨平台性能好,在Chrome上也可以调试,而腾讯视频在移动端就是统一转码(H264)后的mp4文件,这一点相当nice,用起来也很是方便。第二呢,腾讯视频会识别电脑操作系统,网页的播放的时候不是无脑的直接上flash,这让我的Mac不会烧机然我有了好感,所以,就觉得去爬你了。

方案和过程

做过这方面工作的同志大概会了解,腾讯视频在播放列表中给出的播放链接和真实播放链接是不同的,进入真实播放链接后爬取到的视频源文件名和真实源文件名也是不同的,它是通过js中的方法访问某个servlet给出加密vkey才可以播放。
博客给大家做一个展示,决定爬取一下国产经典葫芦娃片子,将把重要步骤和代码为大家展现:
播放列表链接:葫芦兄弟

1.png

评分还是杠杠的。我们的第一个目标就是拿下这十三集的播放链接。

2.png

看了我前面文章的都应该会觉得so easy了。
然而,调试了一下选择器body > div:nth-child(2) > div.site_container.container_main > div.container_inner > div > div.wrapper_main > div._playsrc_series > span > div > div.mod_bd._episodes._bd_container > div > span > a发现并获取不到a标签的资源,那就很可能是动态加载的列表了,看一下网页源代码

3.png

果然是空的,那我们就去抓下包看看是哪个servlet获取的

4.png

果然,这种都是比较好抓到的,顺便访问一下这个链接,看到了列表信息

5.png

那就直接去抓这个网页,然后把链接拿下来好了。

package com.videoqq.jsoup;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class BlogSpider {

    public static void main(String[] args) {
        try {
            String requestUrl = "http://s.video.qq.com/get_playsource?id=2iqrhqekbtgwp1s&plat=2&type=1&data_type=2&video_type=3&plname=&otype=json&num_mod_cnt=20&callback=_jsonp_0_6914&_t=1474533056089";
            URL obj = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
            conn.setReadTimeout(5000);
            conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
            conn.addRequestProperty("User-Agent", "Mozilla");
            conn.addRequestProperty("Referer", "google.com");

            boolean redirect = false;

            int status = conn.getResponseCode();
            if (status != HttpURLConnection.HTTP_OK) {
                if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM
                        || status == HttpURLConnection.HTTP_SEE_OTHER)
                    redirect = true;
            }

            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            //拿到整个html
            StringBuffer html = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                html.append(inputLine);
            }
            in.close();
            //分词拿下所有URL
            String[] param = html.toString().split("playUrl");
            //第一个不含播放链接,舍弃
            for (int i = 1; i < param.length; i++) {
                param[i] = param[i].substring(3,63);
                System.out.println(param[i]);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

打印链接看看:

6.png

随便打开一个链接,以移动端的模式打开,会发现跳转了一个加密的链接,这就是真实的播放地址。
我打开的是

http://v.qq.com/cover/2/2iqrhqekbtgwp1s.html?vid=c01350046ds

出现了这个链接

https://m.v.qq.com/x/cover/2/2iqrhqekbtgwp1s.html?vid=c01350046ds&ptag=v_qq_com%23v.play.adaptor%233
7.png

很幸运这个两个链接的区别并不是很大,我们多打开几个会发现规律都一样,就是把前缀改成了https://m.v.qq.com/x

,所以基本不用做什么处理,但不是所有链接都是这样的,就算变化比较大,也可能找到其中的规律,一般是在js中文件关键字的位置做了个混淆,仔细分析一下还是可以得到这个真实播放地址的。

https://m.v.qq.com/x/cover/2/2iqrhqekbtgwp1s.html?vid=c01350046ds

后面的参数可以不管了。
审查这个页面,很容易会发现该视频的播放地址。

8.png

在打开这个链接也是有效的。

9.png

但不能开心的太早,这并不是记录在静态页面中的。看看静态页面,视频源一直是
http://183.61.13.21/vhot2.qqvideo.tc.qq.com/w01243snzdg.m701.mp4?vkey=C60965F33D298C4C8FB5971288084B312AAAE216717CB5CF9EB857647A4719794B79AFBDABA7FB67&br=29&platform=2&fmt=msd&level=3&sdtfrom=v5010

而真正的视频源是
http://117.21.186.22/vhot2.qqvideo.tc.qq.com/c01350046ds.mp4?vkey=84364C00B98ABAD0E68D30245657B2B9F8A1F66D6126DAA006C92EAAF4FBBCB90272855F8A64D103CC79C1A7D2BD53FF1B65AFFCFAF0069D256E30A55D1C8F51082BA0FB50E24F0F795881683BF88F18ED167F96B807F6FE&br=60&platform=2&fmt=auto&level=0&sdtfrom=v3010

10.png

这说明我们还需要找到它的加载地址,也是通过某个servlet来传过来的,也就是需要去抓包看看到底哪个请求做了这个事呢,并且这个请求归根到底就是刷新了地址的vkey,获取到更新后的vkey,后面的参数都是无关紧要的。
然而,你在这个页面抓取所有的包都找不到一个可以去post vid而获取key的servlet,这就是大家止步不前的地方了。中间有很多尝试的方法,最后都失败了,讲讲我最后的方案。
腾讯把所有mp4播放的入口都给加密的很深,我就去尝试看看大众版,就是flash的swf文件,例如访问

http://static.video.qq.com/TPout.swf?vid=c01350046ds&auto=0

,这个通用版只是vid改一下就可以,其他的都不变就可以访问swf的文件,然后尝试抓包(因为是swf,只能在pc模式下抓),结果还真抓到个有用的东西

11.png

访问进去看看
http://vv.video.qq.com/getvkey?format=10702&ran=0%2E07930605486035347&charge=0&guid=9A37E7B5F6FF9CD2E7A35CE8E0BE7F21AAAF5423&lnk=c01350046ds&otype=xml&cKey=c%5FqOzzRtImWjUF1YdsMK3acsNS6aQqOoDE9XuU%2D1UJK1oStL%5FVcckXyceCRhghj1Jxgh8NuJiF3VMziCx81gn%5F4B1JoUSZ9oyMy8%2D4rKNzaExTsDdQBApJsy9mhwIsjKGz3gqx6kiotkIOGchZjp7ScwgMaO3SS8Ev0tlqfQYBwNJaoVnJsQbIQjGn3MWLF2JsHwssvHOrgur28g&filename=c01350046ds%2Ep702%2E1%2Emp4&encryptVer=5%2E4&ehost=&linkver=2&appver=3%2E2%2E19%2E358&platform=70902&vid=c01350046ds&vt=200

12.png

多么美好的结果.
我们分析一下这个request不难发现里面有&vid=c01350046ds,是不是所有与这个id相同的三处换成其他视频的vid也是可以的呢?

http://vv.video.qq.com/getvkey?format=10702&ran=0%2E07930605486035347&charge=0&guid=9A37E7B5F6FF9CD2E7A35CE8E0BE7F21AAAF5423&lnk=m01356kzefd&otype=xml&cKey=c%5FqOzzRtImWjUF1YdsMK3acsNS6aQqOoDE9XuU%2D1UJK1oStL%5FVcckXyceCRhghj1Jxgh8NuJiF3VMziCx81gn%5F4B1JoUSZ9oyMy8%2D4rKNzaExTsDdQBApJsy9mhwIsjKGz3gqx6kiotkIOGchZjp7ScwgMaO3SS8Ev0tlqfQYBwNJaoVnJsQbIQjGn3MWLF2JsHwssvHOrgur28g&filename=m01356kzefd%2Ep702%2E1%2Emp4&encryptVer=5%2E4&ehost=&linkver=2&appver=3%2E2%2E19%2E358&platform=70902&vid=m01356kzefd&vt=200

尝试了一下并没有得到想要的结果,毕竟传了这么一大堆参数,特别看到那个奇怪的文件名后面加了一堆的转译字符,同样我们继续抓包看到一个
http://vv.video.qq.com/getvinfo?guid=9A37E7B5F6FF9CD2E7A35CE8E0BE7F21AAAF5423&ran=0%2E5591048267669976&platform=70902&speed=0&charge=0&newplatform=70902&vids=c01350046ds&fp2p=1&pid=70A01B432FFB78BBF0D0A26654A3756B8A7E98A9&defnpayver=1&cKey=ildvKc3Qw4McaCUlxHmeaflPX2h%5Fk8qyDOH2Js0OMgqPBp3KkkWMxH4h8p89Jm3fQsF0xWNnyk6cYm48RFTCfvgBj1tRdVTvcS3MMSM8MKyPBmRRJBnA2nYhNSvx0WwcfXIFZ7wZunSSUTVtsuMiJkARqxZDGV1VqBGgJfAS7nES%5FMBAveqW0Lq5ebdszHJFfLvVR6VfNYg5EFRf&otype=xml&encryptVer=5%2E4&dtype=3&utype=%2D1&ip=59%2E53%2E183%2E83&linkver=2&appver=3%2E2%2E19%2E358&fhdswitch=0&vid=c01350046ds&ehost=

同样里面也带有vkey参数,按照上面的方法也试一下看。结果也是让人挺失望的。并不能拿到它的值。
最后,又发现了一个通用版的访问链接,在iPhone6模式下去抓包,发现一个同样传输vkey的request,过程和上面差不多就不赘述了。
URL:

http://v.qq.com/iframe/player.html?vid=c01350046ds&tiny=0&auto=0

request:
http://h5vv.video.qq.com/getinfo?callback=tvp_request_getinfo_callback_340380&platform=11001&charge=0&otype=json&ehost=http%3A%2F%2Fv.qq.com&sphls=0&sb=1&nocache=0&_rnd=1474896074003&vids=c01350046ds&defaultfmt=auto&&_qv_rmt=CTWS8OLbA17867igt=&_qv_rmt2=x6oMojAw144904luQ=&sdtfrom=v3010&callback=tvp_request_getinfo_callback_340380

14.png

经测试发现,只要修改request中的vids就都可以拿到一个一段时长有效的vkey.所以呢,想要程序自动爬取这些播放源文件就不是难事了。

package com.videoqq.jsoup;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

import com.asiainfo.util.DateFormatUtil;

public class BlogSpider {

    public static void main(String[] args) {
        try {
            String requestUrl = "http://s.video.qq.com/get_playsource?id=2iqrhqekbtgwp1s&plat=2&type=1&data_type=2&video_type=3&plname=&otype=json&num_mod_cnt=20&callback=_jsonp_0_6914&_t=1474533056089";
            URL obj = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
            conn.setReadTimeout(5000);
            conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
            conn.addRequestProperty("User-Agent", "Mozilla");
            conn.addRequestProperty("Referer", "google.com");

            boolean redirect = false;

            int status = conn.getResponseCode();
            if (status != HttpURLConnection.HTTP_OK) {
                if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM
                        || status == HttpURLConnection.HTTP_SEE_OTHER)
                    redirect = true;
            }

            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            // 拿到整个html
            StringBuffer html = new StringBuffer();

            while ((inputLine = in.readLine()) != null) {
                html.append(inputLine);
            }
            in.close();
            // 分词拿下所有URL
            String[] param = html.toString().split("playUrl");
            String[] vid = new String[param.length];
            // 第一个不含播放链接,舍弃
            for (int i = 1; i < param.length; i++) {
                param[i] = param[i].substring(3, 63);
                // System.out.println(param[i]);
                vid[i] = param[i].substring(49);
                // System.out.println(vid[i]);
            }

            // 真正播放地址
            for (int i = 1; i < vid.length; i++) {
                param[i] = "https://m.v.qq.com/x" + param[i].substring(15);
                // System.out.println(param[i]);
            }

            // 手动拿到的播放源,丢弃除vkey以外的参数
            String sourceMp4 = "http://36.250.4.15/vhot2.qqvideo.tc.qq.com/y0135k2wa6w.mp4?"
                    + "vkey=0AA1C91314C9B82107F68C14832A8C9C2061397B3D0A7FE65D97AD7A8981849864FD71B933E290D3B737BB040F7DDAB570A057A1EA836F5DE95ADBA6D6A098274B8460F9261CB01B904BF2DAFC6012362B8AFF2443D00D50";
            // 首次需要手动拿到每个视频的播放源文件地址,因为存储没有规律,腾讯是分散服务器存的,我这边只演示一个
            String[] sourceMp4Group = { sourceMp4 };
            for (int i = 0; i < sourceMp4Group.length; i++) {
                // 执行更新替换vkey
                requestUrl = "http://h5vv.video.qq.com/getinfo?callback=tvp_request_getinfo_callback_340380&platform=11001&charge=0&otype=json&ehost=http%3A%2F%2Fv.qq.com&sphls=0&sb=1&nocache=0&_rnd=1474896074003&"
                        + "vids=" + vid[i + 1]
                        + "&defaultfmt=auto&&_qv_rmt=CTWS8OLbA17867igt=&_qv_rmt2=x6oMojAw144904luQ=&sdtfrom=v3010&callback=tvp_request_getinfo_callback_"
                        + DateFormatUtil.get6num();
                obj = new URL(requestUrl);
                conn = (HttpURLConnection) obj.openConnection();
                conn.setReadTimeout(5000);
                conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
                conn.addRequestProperty("User-Agent", "Mozilla");
                conn.addRequestProperty("Referer", "google.com");

                redirect = false;

                status = conn.getResponseCode();
                if (status != HttpURLConnection.HTTP_OK) {
                    if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM
                            || status == HttpURLConnection.HTTP_SEE_OTHER)
                        redirect = true;
                }

                in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                html = new StringBuffer();

                while ((inputLine = in.readLine()) != null) {
                    html.append(inputLine);
                }
                in.close();

                param = html.toString().split(",");

//              for (int j = 0; j < param.length; j++) {
//                  System.out.println(param[j]);
//              }
                String fvkey = param[38].split(":")[1];
                fvkey = fvkey.substring(1, fvkey.length() - 1);
                sourceMp4 += "?vkey=" + fvkey;
                System.out.println(sourceMp4);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
Pic16.png
15.png

总结

  1. 因为每个视频的播放源文件存储在的服务器地址不一样(同一系列视频也是),所以需要第一手动拿一下播放mp4地址,审查一下元素就可以,很快。

  2. 我写了一个每五分钟访问链接测试vkey有效时常的告警,结果发现差不多六小时才会失效,所以设个TimeTask一般四小时刷新没问题的。

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.TimerTask;
    
    public class DeadTimeMonitorTask extends TimerTask {
        public void doVisit() {
            try {
                String url = "http://36.250.4.15/vhot2.qqvideo.tc.qq.com/y0135k2wa6w.mp4?vkey=0AA1C91314C9B82107F68C14832A8C9C2061397B3D0A7FE65D97AD7A8981849864FD71B933E290D3B737BB040F7DDAB570A057A1EA836F5DE95ADBA6D6A098274B8460F9261CB01B904BF2DAFC6012362B8AFF2443D00D50?vkey=A812D55B7E0FD9E40737D9C8E245C50A52B28D5F23F6036E28E275A644D88A4E433EC673B867AC25452CC3ACBAED1452144FA57890541CB63C250F7A52F9675BC8CABA9588EEFEA53AAA0854CB6AFF3A4ABB57CE06935DF8";
    
                URL obj = new URL(url);
                HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
                conn.setReadTimeout(5000);
                conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
                conn.addRequestProperty("User-Agent", "Mozilla");
                conn.addRequestProperty("Referer", "google.com");
    
                System.out.println("Request URL ... " + url);
    
                boolean redirect = false;
    
                int status = conn.getResponseCode();
                if (status != HttpURLConnection.HTTP_OK) {
                    if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM
                            || status == HttpURLConnection.HTTP_SEE_OTHER)
                        redirect = true;
                }
    
                System.out.println("Response Code ... " + status);
    
                if (status == 403) {
                    SendMail.sendMailfunc("检测失效时间:" + DateFormatUtil.getCurrentTime(),
                            "失效结束时间:" + DateFormatUtil.getCurrentTime(), "wentao_wanna@126.com");
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            //doVisit();
        }
    
        @Override
        public void run() {
            doVisit();
        }
    
    }
    
  3. 知其然还需要知其所以然,大家可以去探究一下获取vkey的request参数,经发现是这个js

https://vm.gtimg.cn/tencentvideo_v1/tvp/js/tvp.player_v2.js

放到解密js的网站美化一下,然后搜索参数关键词,你会看懂的~

  1. 此文章只用于大家技术研究,请不要用于违法的地方,转载请联系我!!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容