今天下午和师姐聊天,聊到了在帝都买房,她和她对象年初刚刚在三环边上买了套小两居,虽然也借了一点,东拼西凑加贷款,也非常令人羡慕了,我说压力不小吧,她说,嗯,但是hold住,她们两个人一个月加起来到手5万左右。。。。。。。
尼玛,税后啊,对于我这种刚毕业一年多的穷逼来说,这绝对高收入,人比人气死人啊。。。羡慕,羡慕,我赶紧写篇文章压压惊。。。
正题,这段时间,发现头条的视频分享,变了。。。
以前是这种,直接的给出网页端的地址:
https://www.365yg.com/a6563953907100290317
福利视频哦
现在么。。。变成这样的了
http://m.toutiaoimg.cn/group/6563953907100290317
打开后,这个地址会进行URL的重定向,跳转到第一个地址,有人可能会问,这有啥区别么。。。我刚开始也是这么认为,直到,我用retrofit在手机上直接请求第二个地址的时候,郁闷的事情发生了,特么返回的网页源代码是几个html的标签,像这样的。。
一看就是天杀的头条,采取了反爬虫的措施。。。奈何我爬虫技术实在是渣渣的很,各种加headers,就是绕不过去,只能曲线救国了。。。(在这里求各位道友指导一下如何绕过去)
分析一波分享视频的地址的特点:
西瓜视频分享的链接:http://m.toutiaoimg.cn/a6564149606324634116
打开后的链接:https://www.365yg.com/a6564149606324634116
抖音小视频分享的链接:http://m.toutiaoimg.cn/group/6563953907100290317
打开后的链接:http://www.365yg.com/a6563953907100290317
可以看到,西瓜视频的重定向地址为http://m.toutiaoimg.cn/ +分享链接cn/
后面带的一个字符串
抖音小视频的的重定向地址为http://m.toutiaoimg.cn/ +a
+分享链接group/后面的一个字符串
那就可以通过正则表达式来拼出重定向的url了~~
- 首先是http打头的正则,如果分享链接中不含有http,直接提示用户非分享链接
private Pattern httpPattern = Pattern.compile("(http.+)");
- 抖音小视频的正则,如果检测到分享链接中有group/xxx,那么把xxx出去来
private Pattern douYinPattern = Pattern.compile("group/(.+?)/");
- 如果非抖音小视频正则,再去判断是否是西瓜视频的链接,也就是cn/xxx,同样把xxx取出来
private Pattern xiGuaPattern = Pattern.compile("cn/(.+?)/");
到目前位置,已经能获取到分享视频的重定向url了~也就是类似这种
http://m.toutiaoimg.cn/a6563953907100290317
下面就可以去爬去找个界面的源码,把视频的真实id给匹配出来,先查看一下id的格式吧,方便我们去写正则。。。ctrl+u ,ctrl+f,输入videoid,搜一下源码,找到了唯一一处匹配:
playerInfo: {
videoId: 'v0300ce70000bcbtiitehjiein95b090'
},
我们要的就是中间的v0300ce70000bcbtiitehjiein95b090
部分,写正则的时候,注意一下冒号和单引号之间是有一个空格
的
private Pattern videoPattern = Pattern.compile("(?<=videoId: ').+(?=')");
具体如何拼接视频真实地址的请求地址,已有大神解决
今日头条的视频地址解析方法
有了方法我们要做的就是用java 代码实现他,这里我用的是RxJava(Rx大法好)
public class VideoUrlAnalysis {
/**
* 视频网页的正则
*/
private Pattern httpPattern = Pattern.compile("(http.+)");
/**
* 抖音小视频的正则
*/
private Pattern douYinPattern = Pattern.compile("group/(.+?)/");
/**
* 西瓜视频的正则
*/
private Pattern xiGuaPattern = Pattern.compile("cn/(.+?)/");
/**
* 视频id的正则
*/
private Pattern videoPattern = Pattern.compile("(?<=videoId: ').+(?=')");
/**
* 视频播放网页的base url
*/
private static final String BASE_365_URL = "https://www.365yg.com/";
/**
* 随机数的位数
*/
private static final int RANDOM_COUNT = 16;
/**
* 获取视频真实地址
*
* @param originalUrl 分享的链接
* @return 真实地址的列表
*/
public Observable getDownloadObservable(final String originalUrl) {
return Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter e) throws Exception {
//是否是分享链接
Matcher matcher = httpPattern.matcher(originalUrl);
if (matcher.find()) {
String shareUrl = matcher.group(1);
e.onNext(shareUrl);
} else {
throw new ApiException("不是分享链接");
}
}
}).map(new Function() {
@Override
public String apply(String s) throws Exception {
Matcher douYinMatcher = douYinPattern.matcher(s);
Matcher xiGuaMatcher = xiGuaPattern.matcher(s);
if (douYinMatcher.find()) {
return BASE_365_URL + "a" + douYinMatcher.group(1);
} else if (xiGuaMatcher.find()) {
return BASE_365_URL + xiGuaMatcher.group(1);
} else {
throw new ApiException("不是抖音或者西瓜视频链接!");
}
}
}).flatMap(new Function>() {
@Override
public ObservableSource apply(String s) throws Exception {
return RetrofitFactory.getVideoApi().getVideoHtml(s);
}
}).flatMap(new Function>() {
@Override
public ObservableSource apply(String s) throws Exception {
Matcher matcher = videoPattern.matcher(s);
if (matcher.find()) {
String videoId = matcher.group(0);
//1.将/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()},进行crc32加密。
Random random = new Random();
StringBuilder randomResult = new StringBuilder();
for (int i = 0; i < RANDOM_COUNT; i++) {
randomResult.append(random.nextInt(10));
}
CRC32 crc32 = new CRC32();
String videoUrl = "/video/urls/v/1/toutiao/mp4/%s?r=%s";
String format = String.format(videoUrl, videoId, randomResult.toString());
crc32.update(format.getBytes());
//2.访问http://i.snssdk.com/video/urls/v/1/toutiao/mp4/{videoid}?r={Math.random()}&s={crc32值}
String crcString = String.valueOf(crc32.getValue());
String videoAddressUrl = HttpConstants.VIDEO_URL + format + "&s=" + crcString;
return RetrofitFactory.getVideoApi().getVideoAddress(videoAddressUrl);
} else {
throw new ApiException("解析错误!");
}
}
}).map(new Function() {
@Override
public VideoAddressBean.DataBean.VideoListBean apply(VideoAddressBean videoAddressBean) throws Exception {
if (videoAddressBean.getMessage().equals(HttpConstants.MESSAGE_SUCCESS)) {
VideoAddressBean.DataBean.VideoListBean videoList = videoAddressBean.getData().getVideo_list();
return decodeVideoAddress(videoList);
} else {
throw new ApiException("未包含视频地址!");
}
}
/**
* 转换videoList
* @param originalList 初始化list
* @return 变换后的list
*/
private VideoAddressBean.DataBean.VideoListBean decodeVideoAddress(VideoAddressBean.DataBean.VideoListBean originalList) {
if (originalList.getVideo_1() != null) {
originalList.getVideo_1().setMain_url(getRealPath(originalList.getVideo_1().getMain_url()));
}
if (originalList.getVideo_2() != null) {
originalList.getVideo_2().setMain_url(getRealPath(originalList.getVideo_2().getMain_url()));
}
if (originalList.getVideo_3() != null) {
originalList.getVideo_3().setMain_url(getRealPath(originalList.getVideo_3().getMain_url()));
}
return originalList;
}
/**
* 解码
* @param base64 base64的原string
* @return 解码后的
*/
private String getRealPath(String base64) {
return new String(Base64.decode(base64.getBytes(), Base64.DEFAULT));
}
}
).compose(TransformUtil.defaultSchedulers());
}
}
实例化这个类后,调用getDownloadObservable方法,把分享链接传进去,就能获取真实地址啦~
new VideoUrlAnalysis().getDownloadObservable(shareUrl)
.subscribe(new Consumer() {
@Override
public void accept(VideoAddressBean.DataBean.VideoListBean videoListBean) throws Exception {
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
}
});
以上就是核心代码部分,最近正在做一款头条视频助手类的app,这部分代码会包含在里面,完成后会放出地址~
话说,RxJava真是越用越好用~