背景
这是一个大数据时代,有时候有些程序想要在网站上爬取主流网站的视频信息并记录下来做数据分析,甚至想要通过程序自动拿到各大视频网站的视频资源,这个时候就出现了很多去研究这个的程序员了~
我研究的主要是移动端的视频播放,所以接下来讲的所有,都是和移动端的页面相关的。
知识储备
- 首先我们要了解怎样去抓取网页信息,这个可以参考我前面的一篇文章Jsoup初探
- 之所以博主选择腾讯视频有两个很重要的原因,第一,开始说到了我做的是移动端,视频播放格式一般是采用
m3u8
或者采用最简单的mp4
格式,而m3u8一般作为直播的流媒体存储格式,里面放的是若干个切片的ts文件,大多数视频供应商(youku)在生成m3u8的时候就已经把广告的ts切片放在里面了,我们用程序剔出来还是比较麻烦的,而mp4就比较好了,最简单的格式,跨平台性能好,在Chrome上也可以调试,而腾讯视频在移动端就是统一转码(H264)后的mp4文件,这一点相当nice,用起来也很是方便。第二呢,腾讯视频会识别电脑操作系统,网页的播放的时候不是无脑的直接上flash,这让我的Mac不会烧机然我有了好感,所以,就觉得去爬你了。
方案和过程
做过这方面工作的同志大概会了解,腾讯视频在播放列表中给出的播放链接和真实播放链接是不同的,进入真实播放链接后爬取到的视频源文件名和真实源文件名也是不同的,它是通过js中的方法访问某个servlet给出加密vkey才可以播放。
博客给大家做一个展示,决定爬取一下国产经典葫芦娃片子,将把重要步骤和代码为大家展现:
播放列表链接:葫芦兄弟
评分还是杠杠的。我们的第一个目标就是拿下这十三集的播放链接。
看了我前面文章的都应该会觉得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标签的资源,那就很可能是动态加载的列表了,看一下网页源代码
果然是空的,那我们就去抓下包看看是哪个servlet获取的
果然,这种都是比较好抓到的,顺便访问一下这个链接,看到了列表信息
那就直接去抓这个网页,然后把链接拿下来好了。
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();
}
}
}
打印链接看看:
随便打开一个链接,以移动端的模式打开,会发现跳转了一个加密的链接,这就是真实的播放地址。
我打开的是
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
很幸运这个两个链接的区别并不是很大,我们多打开几个会发现规律都一样,就是把前缀改成了https://m.v.qq.com/x
,所以基本不用做什么处理,但不是所有链接都是这样的,就算变化比较大,也可能找到其中的规律,一般是在js中文件关键字的位置做了个混淆,仔细分析一下还是可以得到这个真实播放地址的。
https://m.v.qq.com/x/cover/2/2iqrhqekbtgwp1s.html?vid=c01350046ds
后面的参数可以不管了。
审查这个页面,很容易会发现该视频的播放地址。
在打开这个链接也是有效的。
但不能开心的太早,这并不是记录在静态页面中的。看看静态页面,视频源一直是
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
这说明我们还需要找到它的加载地址,也是通过某个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模式下抓),结果还真抓到个有用的东西
多么美好的结果.
我们分析一下这个request不难发现里面有&vid=c01350046ds
,是不是所有与这个id相同的三处换成其他视频的vid也是可以的呢?
同样里面也带有vkey参数,按照上面的方法也试一下看。结果也是让人挺失望的。并不能拿到它的值。
最后,又发现了一个通用版的访问链接,在iPhone6模式下去抓包,发现一个同样传输vkey的request,过程和上面差不多就不赘述了。
URL:
http://v.qq.com/iframe/player.html?vid=c01350046ds&tiny=0&auto=0
经测试发现,只要修改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();
}
}
}
总结
因为每个视频的播放源文件存储在的服务器地址不一样(同一系列视频也是),所以需要第一手动拿一下播放mp4地址,审查一下元素就可以,很快。
-
我写了一个每五分钟访问链接测试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(); } }
知其然还需要知其所以然,大家可以去探究一下获取vkey的request参数,经发现是这个
js
https://vm.gtimg.cn/tencentvideo_v1/tvp/js/tvp.player_v2.js
放到解密js的网站美化一下,然后搜索参数关键词,你会看懂的~
- 此文章只用于大家技术研究,请不要用于违法的地方,转载请联系我!!