美文网首页
解析今日头条视频、抖音小视频地址

解析今日头条视频、抖音小视频地址

作者: 言吾許 | 来源:发表于2018-06-08 14:25 被阅读0次

    今天下午和师姐聊天,聊到了在帝都买房,她和她对象年初刚刚在三环边上买了套小两居,虽然也借了一点,东拼西凑加贷款,也非常令人羡慕了,我说压力不小吧,她说,嗯,但是hold住,她们两个人一个月加起来到手5万左右。。。。。。。


    土豪、我的天.png

    尼玛,税后啊,对于我这种刚毕业一年多的穷逼来说,这绝对高收入,人比人气死人啊。。。羡慕,羡慕,我赶紧写篇文章压压惊。。。

    正题,这段时间,发现头条的视频分享,变了。。。

    以前是这种,直接的给出网页端的地址:
    https://www.365yg.com/a6563953907100290317福利视频哦
    现在么。。。变成这样的了
    http://m.toutiaoimg.cn/group/6563953907100290317
    打开后,这个地址会进行URL的重定向,跳转到第一个地址,有人可能会问,这有啥区别么。。。我刚开始也是这么认为,直到,我用retrofit在手机上直接请求第二个地址的时候,郁闷的事情发生了,特么返回的网页源代码是几个html的标签,像这样的。。
    <html><head></head><body></body></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了~~

    1. 首先是http打头的正则,如果分享链接中不含有http,直接提示用户非分享链接
        private Pattern httpPattern = Pattern.compile("(http.+)");
    
    1. 抖音小视频的正则,如果检测到分享链接中有group/xxx,那么把xxx出去来
    private Pattern douYinPattern = Pattern.compile("group/(.+?)/");
    
    1. 如果非抖音小视频正则,再去判断是否是西瓜视频的链接,也就是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<VideoAddressBean.DataBean.VideoListBean> getDownloadObservable(final String originalUrl) {
            return Observable.create(new ObservableOnSubscribe<String>() {
                @Override
                public void subscribe(ObservableEmitter<String> 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<String, String>() {
                @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<String, ObservableSource<String>>() {
                @Override
                public ObservableSource<String> apply(String s) throws Exception {
                    return RetrofitFactory.getVideoApi().getVideoHtml(s);
                }
            }).flatMap(new Function<String, ObservableSource<VideoAddressBean>>() {
                @Override
                public ObservableSource<VideoAddressBean> 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<VideoAddressBean, VideoAddressBean.DataBean.VideoListBean>() {
                       @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.<VideoAddressBean.DataBean.VideoListBean>defaultSchedulers());
        }
    }
    

    实例化这个类后,调用getDownloadObservable方法,把分享链接传进去,就能获取真实地址啦~

     new VideoUrlAnalysis().getDownloadObservable(shareUrl)
                    .subscribe(new Consumer<VideoAddressBean.DataBean.VideoListBean>() {
                        @Override
                        public void accept(VideoAddressBean.DataBean.VideoListBean videoListBean) throws Exception {
                         
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable throwable) throws Exception {
                         
                        }
                    });
    

    以上就是核心代码部分,最近正在做一款头条视频助手类的app,这部分代码会包含在里面,完成后会放出地址~
    话说,RxJava真是越用越好用~

    相关文章

      网友评论

          本文标题:解析今日头条视频、抖音小视频地址

          本文链接:https://www.haomeiwen.com/subject/utpnsftx.html