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

今天下午和师姐聊天,聊到了在帝都买房,她和她对象年初刚刚在三环边上买了套小两居,虽然也借了一点,东拼西凑加贷款,也非常令人羡慕了,我说压力不小吧,她说,嗯,但是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真是越用越好用~

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

推荐阅读更多精彩内容