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

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


土豪、我的天.png

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

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

以前是这种,直接的给出网页端的地址:
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了~~

  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 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真是越用越好用~

你可能感兴趣的:(解析今日头条视频、抖音小视频地址)