背景:
最近想做一个资讯发布的小功能,涉及图文,视频和音频三种类型,由于各大视频平台的接口都是需要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,开森。