Java 模板方法设计模式 实战 ——获取腾讯视频真实地址

背景:

最近想做一个资讯发布的小功能,涉及图文,视频和音频三种类型,由于各大视频平台的接口都是需要vip资格,非rmb不能玩,所以尝试着爬取网页视频信息。

说明:

本示例采用模板方法设计模式,利用抽象接口,模板方法,模板实现,将各大平台的视频信息抓取方式进行整合。本示例只实现了腾讯视频的获取,其他的等以后再完善吧。

代码示例:
抽象接口
/**
 * 在线视频真实地址解析接口
 */
public interface VideoService {

    // 初始页面解析获取原始页面的json数据
    String getJsonFromHtml(String htmlUri) throws IOException;

    // 获取全部真实视频数据
    List<Video> analyseVideo(String htmlUri);

    // 获取单个默认的真实视频数据
    Video analyseVideoInfo(String htmlUri);

}
模板方法
/**
 * 在线视频真实地址解析模板
 */
public abstract class VideoTemplate implements VideoService {

    public static String KEY_270P = "270p";
    public static String KEY_480P = "480p";
    public static String KEY_720P = "720p";


    // 视频的标准ID
    public String vid;
    // 在线视频页面
    public String htmlUri;
    // 解析在线视频页面获取的基础json对象
    public JSONObject baseJson;

    public VideoTemplate( String htmlUri) {
        this.htmlUri = htmlUri;
    }

    public VideoTemplate() {  }

    /**
     * 根据视频页面url模拟请求获取页面document数据
     * @param htmlUri
     * @return
     * @throws IOException
     */
    @Override
    public String getJsonFromHtml(String htmlUri) throws IOException {
        // 组装获取初始信息的连接
        String infoUrl = this.change(htmlUri);
        if(infoUrl == null){
            return null;
        }
        return getURLConJsonStr(infoUrl);
    }

    /**
     * 根据页面解析的document字符串获取全部视频信息
     * @param jsonStr
     * @return
     */
    @Override
    public List<Video> analyseVideo(String jsonStr) {
        if(StringUtils.isEmpty(jsonStr)){
            return null;
        }
        // 检验jsonStr
        jsonStr = this.checkJsonStr(jsonStr);
        // 将json字符串转换为json对象
        this.baseJson= JSON.parseObject(jsonStr);
        return convertVideo(baseJson);
    }

    /**
     * 根据页面解析的document字符串获取单个视频默认信息
     * @param jsonStr
     * @return
     */
    @Override
    public Video analyseVideoInfo(String jsonStr) {
        if(StringUtils.isEmpty(jsonStr)){
            return null;
        }
        // 检验jsonStr
        jsonStr = this.checkJsonStr(jsonStr);
        // 将json字符串转换为json对象
        this.baseJson= JSON.parseObject(jsonStr);
        return convertVideoInfo(baseJson);
    }
    
    // 模拟发送get请求获取数据
    public String getURLConJsonStr(String infoUrl)throws IOException{
        String line = "";
        StringBuffer sb = new StringBuffer();
        URL url = new URL(infoUrl);
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        int responseCode = urlConnection.getResponseCode();
        if (responseCode == 200) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
            while ((line = reader.readLine()) != null) {
                sb.append(line);// 网页传回的只有一行
            }
            return sb.toString();
        }
        return "";
    }

    /**
     * 检查校验json串格式,去掉无用的字符
     * @param jsonStr
     * @return
     */
    protected abstract String checkJsonStr(String jsonStr);

    /**
     * 组装获取视频基础信息地址:
     *    视频平台不同,获取视频基础信息的格式不同;
     *    子类根据需求自己实现
     */
    abstract String change(String htmlUri);

    /**
     * 组装获取视频信息集合:
     *    视频平台不同,获取视频集合的格式不同,不同视频具有的清晰度也不同;
     *    子类根据需求自己实现
     */
    abstract List<Video> convertVideo( JSONObject obj);

    /**
     * 组装获取标清(270P)视频信息:
     *    视频平台不同,获取视频的格式不同;
     *    子类根据需求自己实现
     */
    abstract Video convert270P(int videoId);


    /**
     * 组装获取高清(480P)视频信息:
     *    视频平台不同,获取视频的格式不同;
     *    子类根据需求自己实现
     */
    abstract Video convert480P(int videoId);


    /**
     * 组装获取超清(720P)视频信息:
     *    视频平台不同,获取视频的格式不同;
     *    子类根据需求自己实现
     */
    abstract Video convert720P(int videoId);



    /**
     * 获取视频id集合
     *    子类根据需求自己实现
     */
    protected abstract Map<String,Integer> getVideoIds(JSONObject fl);

    /**
     * 根据id获取key
     *    子类根据需求自己实现
     */
    protected abstract String getVideoKey(int id);

    /**
     * 根据key 和 videId 获取视频信息
     *    视频平台不同,获取视频的格式不同;
     *    子类根据需求自己实现
     */
    protected abstract Video getVideoInfo(String key, Integer id);

    /**
     * 获取单个默认视频信息
     */
    protected abstract Video convertVideoInfo( JSONObject obj);
}
模板实现(腾讯视频)
/**
*  腾讯在线视频网页解析
*/
public class TencentVideoImpl extends VideoTemplate {

   // 初始页面路由
   private String baseInfoUri = "http://vv.video.qq.com/getinfo?vids=vid.DATA&platform=101001&charge=0&otype=json&defn=shd";

   // 最终的页面路由
   private String videoPath ="http://ugcws.video.gtimg.com/fn.DATA?vkey=vKey.DATA" ;

   public TencentVideoImpl( String htmlUri) {
       super( htmlUri);
   }


   /**
    * 获取视频基础信息地址
    * 页面地址示例: https://v.qq.com/x/page/f08302y6rof.html//
    * 转换格式: http://vv.video.qq.com/getinfo?vids=x0164ytbgov&platform=101001&charge=0&otype=json&defn=shd
    * @param htmlUri
    * @return
    */
   @Override
   String change(String htmlUri) {//定义从页面播放地址获取vid转换到后台接口地址的方法
       if(htmlUri.indexOf("page/")==-1||htmlUri.indexOf(".html")==-1||htmlUri.indexOf(".html")<htmlUri.indexOf("page/")){
           return null;
       }
       this.vid = htmlUri.substring(htmlUri.indexOf("page/")+5,htmlUri.indexOf(".html"));
       return baseInfoUri.replace("vid.DATA",this.vid);
   }

   /**
    * 组装获取视频信息集合:
    *    视频平台不同,获取视频集合的格式不同,不同视频具有的清晰度也不同;
    *    子类根据需求自己实现
    */
   @Override
   List<Video> convertVideo(JSONObject obj) {
       List<Video> videos = new ArrayList<>();
       // 1. 获取视频播放时长
       int videoTime = (int)obj.get("preview");
       // 2. 获取视频共有多少个清晰度
       JSONObject fl = obj.getJSONObject("fl");
       Map<String,Integer> videosMap = getVideoIds(fl);
       // 3. 遍历清晰度集合获取对应的视频信息
       for (String key :videosMap.keySet()) {
          Video  video = this.getVideoInfo(key,videosMap.get(key));
          if(video!=null){
              video.setTime(videoTime);
          }
           videos.add(video);
       }
       return videos;
   }

   // 获取视频id集合
   @Override
   protected Map<String,Integer> getVideoIds(JSONObject fl){
       //保证有序
       Map<String,Integer> videosMap = new LinkedHashMap<>();
       JSONArray jArray= fl.getJSONArray("fi");
       for(int i=0;i<jArray.size();i++){
           JSONObject video = jArray.getJSONObject(i);
           int id = (int) video.get("id");
           String key = this.getVideoKey(id);
           videosMap.put(key,id);
       }
       return videosMap;
   }

   // 根据id获取key(比720P更清晰的暂时不考虑)
   @Override
   protected String getVideoKey(int id){
       String key = null;
       switch (id){
           case 100701:
               key = KEY_270P;
               break;
           case 2:
               key = KEY_480P;
               break;
           default:
               key = KEY_720P;
               break;
       }
       return key;
   }

   // 根据key 和 id 获取视频信息
   @Override
   protected Video getVideoInfo(String key, Integer id) {
       Video video = null;
       switch (id){
           case 100701:
               video = convert270P(id);
               break;
           case 2:
               video = convert480P(id);
               break;
           default:
               video = convert720P(id);
               break;
       }
       if( video!=null){
           video.setClarity(key);
       }
       return video;
   }

   /**
    * 组装获取标清(270P)视频信息:
    *    视频平台不同,获取视频的格式不同;
    *    子类根据需求自己实现
    */
   @Override
   Video convert270P(int videoId) {
       JSONArray jArray = this.baseJson.getJSONObject("vl").getJSONArray("vi");
       JSONObject obj = jArray.getJSONObject(0);
       String vKey = (String)obj.get("fvkey");
       String fn = (String)obj.get("fn");
       videoPath = videoPath.replace("fn.DATA",fn).replace("vKey.DATA",vKey);
       Video video = new Video();
       video.setPath(videoPath);
       return video;
   }

   /**
    * 组装获取高清(480P)视频信息:
    *    视频平台不同,获取视频的格式不同;
    *    子类根据需求自己实现
    */
   @Override
   Video convert480P(int videoId) {
       String uri = "http://vv.video.qq.com/getkey?format="+videoId+"&otype=json&vt=150&vid="+this.vid+"&ran=0\\%2E9477521511726081\\\\&charge=0&filename="+this.vid+".mp4&platform=11";
       return getVideo(uri);
   }

   /**
    * 组装获取超清(720P)视频信息:
    *    视频平台不同,获取视频的格式不同;
    *    子类根据需求自己实现
    */
   @Override
   Video convert720P(int videoId) {
       String uri = "http://vv.video.qq.com/getkey?format="+videoId+"&otype=json&vt=150&vid="+this.vid+"&ran=0\\%2E9477521511726081\\\\&charge=0&filename="+this.vid+".p701.1.mp4&platform=11";
       return getVideo(uri);
   }

   // 根据视频加密连接获取视频信息
   private Video getVideo(String uri){
       try {
           String jsonStr = getURLConJsonStr(uri);
           jsonStr = jsonStr.replace("QZOutputJson=","");
           if(jsonStr.lastIndexOf(";")==jsonStr.length()-1){
               jsonStr = jsonStr.substring(0,jsonStr.length()-1);
           }
           JSONObject jsonObject = JSON.parseObject(jsonStr);
           String vKey = (String)jsonObject.get("key");
           String fn = (String)jsonObject.get("filename");
           videoPath = videoPath.replace("fn.DATA",fn).replace("vKey.DATA",vKey);
           Video video = new Video();
           video.setPath(videoPath);
           return video;
       } catch (IOException e) {
           e.printStackTrace();
       }
       return null;
   }

   /**
    * 校验json字符串的格式
    * 腾讯的json 返回格式:
    *  QZOutputJson={"em":60,"exem":3,"ip":"113.140.7.18","msg":"check vid\u0026filename failed","s":"f"};
    *  需要去掉 头部:QZOutputJson=
    *           尾部:;
    */
    @Override
   protected String checkJsonStr(String jsonStr) {
       jsonStr = jsonStr.replace("QZOutputJson=","");
       if(jsonStr.lastIndexOf(";")==jsonStr.length()-1){
           jsonStr = jsonStr.substring(0,jsonStr.length()-1);
       }
       System.out.println(jsonStr);
       return jsonStr;
   }

   /**
    * 获取单个默认的视频信息
    * @return
    */
   @Override
   protected Video convertVideoInfo( JSONObject obj) {
       // 1. 获取视频播放时长
       int videoTime = (int)obj.get("preview");
       // 2. 获取视频第一个清晰度
       JSONObject fl = obj.getJSONObject("fl");
       JSONArray jArray= fl.getJSONArray("fi");
       JSONObject videObj = jArray.getJSONObject(0);
       int id = (int) videObj.get("id");
       String key = this.getVideoKey(id);
       // 3. 获取视频信息
       Video video = this.getVideoInfo(key,id);
       if(video!=null){
           video.setTime(videoTime);
       }
       return video;
   }
   
   // 测试
   public static void main(String[] args) throws IOException {
       String html = "https://v.qq.com/x/page/z0839gt346w.html";
       TencentVideoImpl s = new TencentVideoImpl(html);
       List<Video> list = s.analyseVideo(s.getJsonFromHtml(s.htmlUri));
       if(list!=null){
           for (Video video :list ) {
               System.out.println("时长:"+video.getTime());
               System.out.println("urI:"+video.getPath());
               System.out.println("清晰度:"+video.getClarity());
               System.out.println("*******************************************");
           }
       }
       System.out.println("*******************单个************************");
       Video video = s.analyseVideoInfo(s.getJsonFromHtml(s.htmlUri));
       if(video!=null){
           System.out.println("时长:"+ video.getTime());
           System.out.println("urI:"+ video.getPath());
           System.out.println("清晰度:"+ video.getClarity());
       }
   }
}

到此就完成解析了,测试也OK,开森。

参考文献

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
uniapp腾讯地图地址解析接口是指在uniapp开发中使用腾讯地图SDK提供的接口来解析地址信息。可以通过调用该接口将地址转化为行政区划省市县区的具体地址信息。 在开发中,为了提高地址解析的准确性,可以采取一些方法来处理无法解析地址的情况。首先,要对地址进行格式化,确保地址符合腾讯地图SDK要求的格式。其次,可以使用地理编码接口来进行地址解析,通过更新SDK版本也可以解决一些地址解析问题。 需要注意的是,腾讯地图SDK对地址解析有一定的限制,需要按照一定的格式来输入地址。如果没有进行地址的格式化,可能会导致解析失败。因此,在使用腾讯地图地址解析接口时,需要对地址进行正确的格式化处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [腾讯地图WebService地址解析接口](https://download.csdn.net/download/linhaiyun_ytdx/10933467)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Uniapp腾讯地图无法解析地址怎么解决](https://blog.csdn.net/qq_29701691/article/details/130202434)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值