Add files via upload

This commit is contained in:
gzlinz2
2022-10-15 16:39:13 +08:00
committed by GitHub
parent 9bfe99eef6
commit cb6be9d91f
@@ -0,0 +1,1935 @@
package com.github.catvod.spider;
import android.content.Context;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Pair;
import com.github.catvod.crawler.Spider;
import com.github.catvod.crawler.SpiderDebug;
import com.github.catvod.utils.Misc;
import com.github.catvod.utils.okhttp.OKCallBack;
import com.github.catvod.utils.okhttp.OkHttpUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Call;
public class XBiu extends Spider {
protected String ext = null;
public JSONObject rule = null;
// 默认的视频类型
private ArrayList<String> videoFormatList = new ArrayList<>(Arrays.asList(".m3u8", ".mp4", ".mpeg", ".flv", ".mkv"));
protected final int base64Flag = Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP;
// 一定是正确的分类名称,用来帮助定位分类列表,猜cateManual
protected final ArrayList<String> cateManuals = new ArrayList<>(Arrays.asList("电影", "剧集", "电视剧", "连续剧", "综艺", "动漫"));
// 无效的分类名,彡来过滤cateManual
protected final ArrayList<String> invalidCateNames = new ArrayList<>(Arrays.asList("更多","下载", "首页", "资讯", "留言", "导航", "专题", "短视频", "热榜", "排行", "追剧","更新","APP", "直播", "label", "Netflix"));
// 详情页 影片信息相关字段,猜详情页信息时用
protected final ArrayList<String> detailItemNames = new ArrayList<>(Arrays.asList("导演", "主演", "演员", "地区", "类型", "年份", "年代"));
protected final ArrayList<String> detailItemKeys = new ArrayList<>(Arrays.asList("vod_director", "vod_actor", "vod_actor", "vod_area", "type_name", "vod_year", "vod_year"));
protected String splitFlag = ""; // 分段标志,猜cateManual时用
// html标签查找时用到的辅助类
protected class HtmlMatchInfo {
public String group0; // 正则表达式匹配到的字符串
/**
* 一般用来放href中的内容
*/
public String group1; //
public String group2; //
public String diff; // 两个匹配结果比较group1得到的不同部分
public int startPos; // 正则匹配到的起始位置
public int endPos; // 正则匹配到的结束位置
public ArrayList<Integer> uploads; // 祖先结节的索引
public int matchedUpNodePos = -1; // 与其他HtmlMatchInfo最匹配的祖先节点位置
public int diffStartIndex; // 不同那部分数据的开始位置
public int diffEndIndex; // 不同那部分数据的结束位置
public void init(Matcher m) {
this.group0 = m.group(0);
if (m.groupCount() > 0)
this.group1 = m.group(1);
if (m.groupCount() > 1)
this.group2 = m.group(2);
this.startPos = m.start(0);
this.endPos = m.end(0);
}
// 通过比较两个group1的不同部分,不同部分的内容以splitFlag中的字符为开始或结束位置
public boolean findDiffStr(HtmlMatchInfo rhs, String splitFlag) {
int len = Math.min(group1.length(), rhs.group1.length());
// 找不同字符的开始位置
for (int i =0; i < len; ++i){
char a = group1.charAt(i);
char b = rhs.group1.charAt(i);
if(a== b && splitFlag.indexOf (a) != -1) {
diffStartIndex = i+1;
rhs.diffStartIndex = i+1;
}
if(a != b) break;
}
// 找不同字符的结束位置
diffEndIndex = group1.length();
rhs.diffEndIndex = rhs.group1.length();
for (int i =1; i < len; ++i){
char a = group1.charAt(group1.length()-i);
char b = rhs.group1.charAt(rhs.group1.length()-i);
if(a== b && splitFlag.indexOf (a) != -1) {
diffEndIndex = group1.length()-i;
rhs.diffEndIndex = rhs.group1.length()-i;
}
if(a != b) break;
}
if(this.diff == null || this.diff.isEmpty() && diffStartIndex < diffEndIndex) {
diff = group1.substring(diffStartIndex, diffEndIndex);
}else{
if( diffEndIndex < diffStartIndex || !diff.equals(group1.substring(diffStartIndex, diffEndIndex))){
return false;
}
}
if(rhs.diffStartIndex < rhs.diffEndIndex) {
rhs.diff = rhs.group1.substring(rhs.diffStartIndex, rhs.diffEndIndex);
}
return true;
}
// 判断 rhs 与当前对象是有相同的祖先节点
boolean hasSameUpNode(HtmlMatchInfo rhs) {
if (rhs.uploads.size() != this.uploads.size()) return false;
for (int i = 0; i < uploads.size(); ++i) {
if (uploads.get(i).intValue() != rhs.uploads.get(i).intValue()) continue;
if (matchedUpNodePos == -1 || uploads.get(i).intValue() == matchedUpNodePos) {
matchedUpNodePos = uploads.get(i).intValue();
rhs.matchedUpNodePos = uploads.get(i).intValue();
return true;
}
return false;
}
return false;
}
}
public void init(Context context, String extend) {
super.init(context, extend);
this.ext = extend;
}
// 初始化抓虫规则
protected void fetchRule() {
if (rule == null) {
if (ext != null) {
try {
if (ext.startsWith("http")) {
if(ext.indexOf("{cateId}") != -1 || ext.indexOf("{catePg}") !=-1){
rule = new JSONObject();
rule.put("homeUrl", ext);
}else{
String json = OkHttpUtil.string(ext, null);
rule = new JSONObject(json);
}
} else {
rule = new JSONObject(ext);
}
if (!rule.has("list")) {
rule.put("list", new JSONObject());
}
JSONObject list = rule.getJSONObject("list");
// 初始化homeUrl,list.url
String homeUrl= rule.getString("homeUrl");
if(homeUrl.indexOf("{cateId}") != -1){
URL r = new URL(homeUrl);
String path = r.getPath();
// 更新解析出来的homeUrl
rule.put("homeUrl", homeUrl.substring(0, homeUrl.indexOf(path)));
if(!list.has("url")){
list.put("url", homeUrl);
}
}
// 初始化截断标志
String listUrl = list.getString("url");
if(listUrl.indexOf("/") !=-1) splitFlag+='/';
if(listUrl.indexOf(".") !=-1) splitFlag+='.';
if(listUrl.indexOf("-") !=-1) splitFlag+='-';
if (!rule.has("detail")) {
rule.put("detail", new JSONObject());
}
if (!rule.has("playlist")) {
rule.put("playlist", new JSONObject());
}
// 如果没有search,则生成一个默认的search规则,大部分网站的search规则都一样
// 省掉一个search json
if (!rule.has("search")) {
String url = addHttpPrefix("index.php/ajax/suggest?mid=1&wd=阿凡达");
try {
// 尝试访问这个json接口,如果返回了正确的json格式,就认为支持json搜索
JSONObject result = new JSONObject( OkHttpUtil.string(url, getHeaders(url)));
JSONObject search = new JSONObject();
search.put("vod_id", "id");
search.put("vod_name", "name");
search.put("vod_pic", "pic");
search.put("url", addHttpPrefix("index.php/ajax/suggest?mid=1&wd={wd}"));
rule.put("search", search);
}
catch (Exception e){
}
}
// 部分网站的播放页上直接就有 播放地址,基本上就是一样的格式,可以尝试在playerContent中直接拿直链
if (!rule.has("play")) {
JSONObject play = new JSONObject();
JSONArray region = new JSONArray();
region.put("var player_aaaa=");
region.put(0);
JSONArray vod_url = new JSONArray();
vod_url.put("\"url\":\"");
vod_url.put("\"");
play.put("region", region);
play.put("vod_url", vod_url);
rule.put("play", play);
}
// play字段中可以填写播放连接的关键字用来帮助识别嗅探结果,
// 一般奇葩的网站会用到
if (rule.has("play")) { // 自定义嗅探关键字
JSONObject play = rule.getJSONObject("play");
JSONArray keywords = play.optJSONArray("keywords");
if (keywords != null) {
videoFormatList.clear();
for (int i = 0; i < keywords.length(); ++i) {
videoFormatList.add(keywords.getString(i));
}
}
}
// 猜cateManaul
JSONObject cateManual = rule.optJSONObject("cateManual");
String body = "";
if (cateManual == null) {
// 重建 cateManaul规则
body = this.fetchUrl(rule.getString("homeUrl"), rule.optJSONObject("header"));
if(body.length() > 32*1024) { body = body.substring(0, 32 * 1024); }
cateManual = this.guess_rule_cateManual(body);
if(cateManual != null){
rule.put("cateManual", cateManual);
}
}
// 猜list.vod_id
if (!list.has("vod_id")) {
if(body.isEmpty()){
body = this.fetchUrl(rule.getString("homeUrl"), rule.optJSONObject("header"));
if(body.length() > 32*1024) { body = body.substring(0, 32 * 1024); }
}
JSONArray listvodid = this.guess_rule_vod_id(body);
list.put("vod_id", listvodid);
}
// 如果没有json搜索接口,那么尝试在主页上找search 的接口 url
if (!rule.has("search")) {
if(body.isEmpty()){
body = this.fetchUrl(rule.getString("homeUrl"), rule.optJSONObject("header"));
if(body.length() > 32*1024) { body = body.substring(0, 32 * 1024); }
}
String url = this.guess_rule_search_url(body);
if(!url.isEmpty()){
JSONObject search = new JSONObject();
search.put("url", url);
rule.put("search", search);
}
}
SpiderDebug.log(String.format("默认rule: %s", rule.toString()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public String addHttpPrefix(String url) {
try {
if (url.isEmpty()) return "";
if (url.startsWith("http")) return url;
String result = rule.getString("homeUrl");
if (result.endsWith("/")) {
result = result.substring(0, result.length() - 1);
}
if (url.startsWith("/")) {
result += url;
} else {
result += "/" + url;
}
return result;
} catch (JSONException e) {
e.printStackTrace();
}
return url;
}
protected HashMap<String, String> getHeaders(String url) {
HashMap<String, String> headers = new HashMap<>();
try {
if (rule.has("header")) {
JSONObject header = rule.getJSONObject("header");
Iterator<String> iter = header.keys();
while (iter.hasNext()) {
String key = iter.next();
headers.put(key, header.getString(key));
}
}
} catch (JSONException e) {
e.printStackTrace();
}
if (!headers.containsKey("User-Agent")) {
headers.put("User-Agent", Misc.UaWinChrome);
}
return headers;
}
public static class HtmlNodeHlper{
// 非正常配对的html标签,进行html层级查找时要用到
protected static ArrayList<String> notPairedTag = new ArrayList<>(Arrays.asList("img", "br", "meta", "!--")); // <!---->为注释
// 判断当前html标签是否为正常的标签
public static boolean isPairedHtmlTag(String str, int startPos) {
String tmp = str.substring(startPos, Math.min(str.length(), startPos + 10));
for (String p : notPairedTag) {
if (tmp.indexOf(p) != -1) { // 找到了
// 找 > 如果匹配了 /> 则认为是配对的
for (int i = startPos + 1; i < str.length(); ++i) {
String sm = str.substring(i);
if (str.charAt(i) == '>') {
if (str.charAt(i - 1) == '/') {
return true;
} else {
return false;
}
}
}
return false;
}
}
return true;
}
// 查找当前标签的html代码 pos必须是标签的开始位置 <
public static String nodeString(String str, int pos) {
if (pos < 0 || pos >= str.length() || str.charAt(pos) != '<') return str;
int isRightNode = 0;
for (int i = pos; i < str.length() - 1; ++i) {
// String sm = str.substring(i, i + 400);
switch (str.charAt(i)) {
// 遇到 / 那么这个位置有可能是xml的结束标识,这种情况下再遇到<则不是当前节点的上级节点
case '/': {
if (str.charAt(i + 1) == '>') { // "/>" 认为是标签的结束位置
isRightNode--;
} else if (str.charAt(i - 1) == '<') { // "</" 认为是标签的结束位置
isRightNode--;
}
break;
}
case '>': {
if (isRightNode == 0) {
return str.substring(pos, i + 1);
}
break;
}
case '<': {
if (str.charAt(i + 1) != '/' && isPairedHtmlTag(str, i)) { // 不是 "</" 则认为是html标签的开始位置
++isRightNode;
}
break;
}
default:
break;
}
}
return str.substring(pos);
}
// lookback 回溯层级, 一般能找到共同的祖先结节就可以了
public static ArrayList<Integer> findUpNodes(String str, int pos, int lookback) {
ArrayList<Integer> nodes = new ArrayList<>();
ArrayList<String> urls = new ArrayList<>();
if (pos == -1) return nodes;
int isUpNode = 0;
for (int i = pos; i >= 0; --i) {
switch (str.charAt(i)) {
// 遇到 / 那么这个位置有可能是xml的结束标识,这种情况下再遇到<则不是当前节点的上级节点
case '/': {
if (str.charAt(i + 1) == '>') {
isUpNode++;
// SpiderDebug.log(String.format("not xml %s", str.substring(i, i + 20)));
} else if (str.charAt(i - 1) == '<') {
isUpNode++;
--i;
// SpiderDebug.log(String.format("not xml %s", str.substring(i, i + 20)));
}
break;
}
case '<': {
if (isUpNode == 0) {
// SpiderDebug.log(String.format("find up node %d %s", i, str.substring(i, i + 30)));
urls.add(String.format("%5d", i));
nodes.add(i);
} else if (isPairedHtmlTag(str, i)) {
isUpNode--;
if (isUpNode < 0) isUpNode = 0;
// SpiderDebug.log(String.format("%s", str.substring(i, i + 30)));
}
break;
}
default:
break;
}
if (nodes.size() >= lookback) {
break;
}
}
return nodes;
}
// 获取当前节点的所有子节点
public static ArrayList<String> getChildNodes(String str) {
ArrayList<String> arr = new ArrayList<>();
int pos = 0;
if (pos < 0 || pos >= str.length() || str.charAt(pos) != '<') return arr;
++pos;
while (pos > -1 && pos < str.length()) {
pos = str.indexOf('<', pos);
String p = nodeString(str, pos);
if (p.isEmpty()) {
break;
}
arr.add(p);
pos += p.length();
}
return arr;
}
// 移除字符串的html标签
public static String trimHtmlString(String str, String r) {
String ret = str.replace("\r\n", "")
.replace("\n", "")
.replaceAll("<.+?>", r)
// .replace(" ", "")
.replaceAll("\\s+", " ")
.replace("&nbsp;", "")
.replace("&emsp;", "")
.trim();
return ret;
}
public static String trimHtmlString(String str) {
return trimHtmlString(str, "");
}
}
public static class Utils{
// 查找列表块的起始位置,取最靠近共同祖先节点的位置
public static int findBlockPos(ArrayList<Integer> a, ArrayList<Integer> b) {
int len = a.size() > b.size() ? b.size() : a.size();
if(len ==1 ) return b.get(0);
for (int i = 0; i < len; ++i) {
if (a.get(i).intValue() == b.get(i).intValue()) {
return b.get(i - 1);
}
}
return b.get(len - 1);
}
// 查找两个字符串之间的子串
// keys 字段说明
// 0 prefix 1 suffix 2 找到子串后左边index的偏移量 3 找到子串后右边index的偏移量
public static String findSubString(String str, int startPos, JSONArray keys, String defaultVal) {
try {
if (keys == null) return defaultVal;
String prefix = keys.getString(0);
String suffix = keys.getString(1);
int offsetl = 0; // 左边的偏移量
int offsetr = 0; // 右边的偏移量
if (keys.length() > 2) {
offsetl = keys.getInt(2);
}
if (keys.length() > 3) {
offsetr = keys.getInt(3);
}
int a = str.indexOf(prefix, startPos) + prefix.length();
if (a < prefix.length()) return defaultVal;
int b = str.indexOf(suffix, a);
if (b < a) return defaultVal;
return HtmlNodeHlper.trimHtmlString(str.substring(a + offsetl, b + offsetr));
} catch (JSONException e) {
e.printStackTrace();
}
return defaultVal;
}
public static String findSubString(String str, int startPos, JSONArray keys) {
return findSubString(str, startPos, keys, "");
}
// 获取回看层数
public static int getLookbackCount(JSONArray keys) {
try {
if (keys != null && keys.length() > 4) return keys.getInt(4);
} catch (Exception e) {
//e.printStackTrace();
}
return 0;
}
// 遍历JSONObect中的JSONArray查找回看的层数可用的规则
public static JSONArray getLookbackArray(JSONObject obj) {
try {
Iterator iter = obj.keys();
while (iter.hasNext()) {
String key = (String) iter.next();
Object val = obj.get(key);
if (val.getClass().getSimpleName().equals("JSONArray")) {
int c = getLookbackCount((JSONArray) val);
if (c > 0) return (JSONArray) val;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 统计子串个数
public static int getSubStringCount(String str, String sub){
int pos =0;
int count =0;
while (pos < str.length()){
pos = str.indexOf(sub, pos);
if(pos == -1) break;
pos += sub.length();
++count;
}
return count;
}
// 获取指定区间的字符串
public static String getRegion(String str, JSONObject obj) {
try {
if (obj == null) return str;
JSONArray region = obj.optJSONArray("region");
if (region == null) return str;
String prefix = region.getString(0);
int a = str.indexOf(prefix);
if (a == -1) return str;
int b = str.length();
if (region.length() > 1) {
b = str.indexOf(region.getString(1), a + prefix.length());
if (b == -1) b = str.length();
}
return str.substring(a, b);
} catch (JSONException e) {
e.printStackTrace();
}
return str;
}
}
// 猜测分类列表的html区间代码
protected String guessCateManualHtmlString(String body) {
String regx = String.format("<a.+?href=\"(.+?)\".*?<", TextUtils.join("|", cateManuals));
Pattern pattern = Pattern.compile(regx);
Matcher m = pattern.matcher(body);
ArrayList<HtmlMatchInfo> list = new ArrayList<>();
int mcount = 0;
while (m.find() ) {
++mcount;
if(mcount >30 && !list.isEmpty()){
break;
}
HtmlMatchInfo cate = new HtmlMatchInfo();
cate.init(m);
cate.group2 = HtmlNodeHlper.trimHtmlString(HtmlNodeHlper.nodeString(body, cate.startPos));
if (cate.group2.isEmpty()) continue;
boolean bOk = false;
for (String v : cateManuals){
if(cate.group2.indexOf(v) !=-1) {
bOk = true;
break;
}
}
if(!bOk) continue;
cate.uploads = HtmlNodeHlper.findUpNodes(body, cate.startPos, 3);
if (!list.isEmpty()) {
boolean b = list.get(0).hasSameUpNode(cate);
if (!b) { // 当前找到的info和list中的匹配
if (list.size() > 1) { // 如果list中的数据大于1 则认为找到了类型列表
return HtmlNodeHlper.nodeString(body, list.get(0).matchedUpNodePos);
}
list.clear();
}
}
list.add(cate);
}
if (list.size() > 1) { // 如果list中的数据大于1 则认为找到了类型列表
return HtmlNodeHlper.nodeString(body, list.get(0).matchedUpNodePos);
} else {
return "";
}
}
// 从html代码中猜测分类名和分类ID cateManual规则
protected JSONObject guess_rule_cateManual(String body) {
try {
String str = this.guessCateManualHtmlString(body);
if (str.isEmpty()) return new JSONObject();
String regx = String.format("<a.+?href=\"(.+?)\".*?[\"|>](\\s*?\\S+?\\s*?)(\"|<)", TextUtils.join("|", cateManuals));
Pattern pattern = Pattern.compile(regx, Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(str);
ArrayList<HtmlMatchInfo> list = new ArrayList<>();
while (m.find()) {
// HtmlMatchInfo 字段映射
// HtmlMatchInfo.group1 -> href
// HtmlMatchInfo.group2 -> name
// HtmlMatchInfo.diff -> id 分类ID
HtmlMatchInfo cate = new HtmlMatchInfo();
cate.init(m);
if (cate.group1.length() < 5) continue;
cate.group2 = HtmlNodeHlper.trimHtmlString(HtmlNodeHlper.nodeString(str, cate.startPos));
if(cate.group2.isEmpty()) continue;
// 判断是否为正常的分类名
boolean validCateName = true;
for (int j = 0; j < invalidCateNames.size(); ++j) {
if (cate.group2.indexOf(invalidCateNames.get(j)) != -1) {
SpiderDebug.log(String.format("排除无效分类:%s --> %s", cate.group1, cate.group2));
validCateName = false;
break;
}
}
if(!validCateName) continue;
if (!list.isEmpty()) {
if (!list.get(0).findDiffStr(cate, splitFlag)) {
SpiderDebug.log(String.format("排除可能无效的分类 %s <--> %s", cate.group1, cate.group2));
continue;
}
}
list.add(cate);
}
ArrayList<Integer> baseInfoIndexs = new ArrayList<>();
// 找到最可能是正确的导航item
for (int i =0; i < list.size(); ++i){
list.get(i).diff = null;
for (String v : cateManuals){
if(list.get(i).group2.indexOf(v) !=-1) {
baseInfoIndexs.add(i);
break;
}
}
}
// 以找到的导航item为基准重建分类ID
int baseInfoIndex=0;
for (int i =1; i < baseInfoIndexs.size(); ++i){
baseInfoIndex=baseInfoIndexs.get(0).intValue();
list.get(baseInfoIndex).findDiffStr(list.get(baseInfoIndexs.get(i).intValue()), splitFlag);
}
JSONObject cateManual = new JSONObject();
for (int i = 0; i < list.size(); ++i) {
if(list.get(i).diff == null || list.get(i).diff.isEmpty()) {
if(!list.get(baseInfoIndex).findDiffStr(list.get(i), splitFlag)){
SpiderDebug.log(String.format("排除可能无效的分类 : %s", list.get(i).group0));
continue;
}
}
boolean validCateName = true;
String name = list.get(i).group2;
String id = list.get(i).diff;
if (id == null || id.isEmpty()) continue;
if (name == null || name.isEmpty()) continue;
for (int k =0; k <id.length(); ++k){
if(splitFlag.indexOf(id.charAt(k)) != -1){
SpiderDebug.log(String.format("跳过无效的分类ID :%s", id));
continue;
}
}
for (int j = 0; j < invalidCateNames.size(); ++j) {
if (name.indexOf(invalidCateNames.get(j)) != -1) {
validCateName = false;
break;
}
}
if (validCateName && !cateManual.has(name)) {
cateManual.put(name, id);
}
}
rule.put("cateManual", cateManual);
return cateManual;
} catch (Exception e) {
e.printStackTrace();
}
return new JSONObject();
}
// 猜测搜索页的url规则
protected String guess_rule_search_url(String body){
// 找搜索页url的逻辑,不处理其他情情况
// 1. 找包含 name input标签
// 2. 找到后往上找三层,找action="" 如果有的话就找到了
String regex = "<input.+?name=\"(.+?)\"";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(body);
if(m.find()){
String str = m.group(0);
String wd = m.group(1);
for (int i =1; i < 4; ++i){
ArrayList<Integer> arr = HtmlNodeHlper.findUpNodes(body,m.start(0), i);
String r = HtmlNodeHlper.nodeString(body, arr.get(arr.size()-1));
String regex2 = "action=\"(.+?)\"";
Pattern pattern2 = Pattern.compile(regex2, Pattern.CASE_INSENSITIVE);
Matcher m2 = pattern2.matcher(r);
if(m2.find()){
String url = m2.group(1);
char ch = url.indexOf('?') ==-1 ? '?' : '&';
url = addHttpPrefix(url + ch + wd + "={wd}");
return url;
}
}
}
return "";
}
// 猜测列表数据的 vod_id 规则
public JSONArray guess_rule_vod_id(String body) {
try {
String regx = "<a.+?href=\"(.+?)\"";
Pattern pattern = Pattern.compile(regx);
Matcher m = pattern.matcher(body);
HashMap<String, JSONArray> founds = new HashMap<>();
ArrayList<HtmlMatchInfo> list = new ArrayList<>();
while (m.find()) {
HtmlMatchInfo cate = new HtmlMatchInfo();
cate.init(m);
cate.uploads = HtmlNodeHlper.findUpNodes(body, cate.startPos, 4);
// String ms = this.findNodeString(body, cate.uploads.get(cate.uploads.size()-1));
if (!list.isEmpty()) {
if(cate.group1.equals( list.get(list.size()-1).group1)) continue;
boolean b = list.get(list.size()-1).hasSameUpNode(cate);
if (!b) { // 当前找到的info和list中的匹配
if (list.size() > 1) {
HtmlMatchInfo info = list.get(0);
info.findDiffStr(list.get(1), splitFlag);
int id = 0;
boolean isNumberID = false;
try { id = Integer.valueOf(info.diff).intValue(); isNumberID = true; }catch (Exception e){}
if(id > 100 ){ // cateID一般都是小于100的
String url = (info.group1.replace(list.get(0).diff, "{vid}"));
JSONArray arr = new JSONArray();
String prefix = url.substring(0, url.indexOf("{vid}"));
String suffix = url.substring(prefix.length() + "{vid}".length());
int lookback = info.uploads.indexOf(info.matchedUpNodePos) - 1;
if (lookback < 1) lookback = 1;
arr.put(prefix);
arr.put(suffix);
arr.put(0);
arr.put(0);
arr.put(lookback);
arr.put(list.size());
if (!founds.containsKey(url)) {
founds.put(url, arr);
} else {
int nlen = founds.get(url).getInt(5) + list.size();
arr.put(5, nlen);
founds.put(url, arr);
if(nlen >= 30){
list.clear();
break;
}
}
}
}
list.clear();
}
}
list.add(cate);
if(list.size()>30){
break;
}
}
if (list.size() > 5 || (list.size()>1 && founds.isEmpty())) { // 如果list中的数据大于1 则认为找到了类型列表
HtmlMatchInfo info = list.get(0);
info.findDiffStr(list.get(1), splitFlag);
int id = 0;
boolean isNumberID = false;
try { id = Integer.valueOf(info.diff).intValue(); isNumberID = true; }catch (Exception e){}
if(id > 100 ){ // cateID一般都是小于100的
String url = (info.group1.replace(list.get(0).diff, "{vid}"));
JSONArray arr = new JSONArray();
String prefix = url.substring(0, url.indexOf("{vid}"));
String suffix = url.substring(prefix.length() + "{vid}".length());
int lookback = info.uploads.indexOf(info.matchedUpNodePos) - 1;
if (lookback < 1) lookback = 1;
arr.put(prefix);
arr.put(suffix);
arr.put(0);
arr.put(0);
arr.put(lookback);
arr.put(list.size());
if (!founds.containsKey(url)) {
founds.put(url, arr);
} else {
int nlen = founds.get(url).getInt(5) + list.size();
arr.put(5, nlen);
founds.put(url, arr);
}
}
}
JSONArray c = null;
for (String key : founds.keySet()) {
JSONArray v = founds.get(key);
if(c == null || c.getInt(5) < v.getInt(5)) c = v;
}
return c;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 猜播放列表
public JSONArray guess_rule_vod_play_url(String str, String vid) {
String regex = "href=\"(/.+?)\"";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(str);
HtmlMatchInfo info = new HtmlMatchInfo();
ArrayList<String> vec = new ArrayList<>();
boolean p0__ = false;
while (m.find()){
String sb = m.group(1);
// 太长的url认为是错误的播放地址
if(sb.length() > 100) continue;
if(sb.indexOf(vid) == -1) continue;
// 如果当前url的长度比上一个url的长度短也认为是无效的播放地址(带上了vod_id一般短一点的可能是详情页的地址)
// 一般来讲 999-1-1.html 这种格式是播放页的地址,不排除这种地址
boolean is__html = (sb.indexOf(vid+"-") != -1);
if(!is__html && vec.size() > 0 && vec.get(vec.size()-1).length() > sb.length()) continue;
if(is__html && !p0__ ){ // 找到了准确度最高的播放连接格式,如果检查到列表头不是这种格式的话,清空列表从头开始
vec.clear();
}
// 如果列表里面装了标准的连接格式,不标准的就不要了
if(p0__ && !is__html) continue;
info.init(m);
if(vec.isEmpty()) p0__ = is__html;
vec.add(m.group(1));
if(vec.size() > 10 && vec.get(vec.size()-2).length() == sb.length()) {
break;
}
}
if(info.group0 != null){
for (int i =1;i < 4; ++i){
ArrayList<Integer> nodes = HtmlNodeHlper.findUpNodes(str, info.startPos, i);
int startPos = nodes.get(nodes.size()-1).intValue();
//String smd = str.substring(startPos, startPos+10);
String smd = HtmlNodeHlper.nodeString(str, startPos);
if(smd.indexOf("<ul") ==0 || smd.indexOf("<div") ==0 || i == 3){ // 最多退三层,找不到就算了
// found 播放列表的 根节节点
String prefix = info.group1.substring(0, info.group1.indexOf(vid));
String suffix = "\"";
JSONArray arr = new JSONArray();
arr.put(prefix);
arr.put(suffix);
arr.put(0 - prefix.length());
arr.put(0);
arr.put(i);
return arr;
}
}
}
return null;
}
//
public String guess_value_vod_name(String nd, int startPos) {
try {
JSONArray vec = new JSONArray();
vec.put("alt=\"");
vec.put("\"");
String val = Utils.findSubString(nd, startPos, vec);
if (val.isEmpty()) {
vec.put(0, "\" title=\"");
val = Utils.findSubString(nd, startPos, vec);
}
if (val.isEmpty()) { // 如果没有通过title找到视频名,则取整个node的文本内容,取出现次数最多的项
String all = HtmlNodeHlper.trimHtmlString(nd, "!!!!");
String[] words = all.split("!!!!");
HashMap<String, Integer> map = new HashMap<String, Integer>();
for (int i = 0; i < words.length; ++i) {
words[i] =words[i].trim();
if (!words[i].isEmpty() && words[i].indexOf("更新") ==-1) {
int c = 1;
if (map.containsKey(words[i])) {
c = 1 + map.get(words[i]).intValue();
;//[words[i]]
}
map.put(words[i], Integer.valueOf(c));
}
}
String s = "";
int c = 0;
for (String key : map.keySet()) {
int v = map.get(key).intValue();
if (v > c) {
c = v;
s = key;
}
}
val = s;
}
return val.replace("在线", "")
.replace("立即", "")
.replace("观看", "")
.replace("点播", "")
.replace("影片", "")
.replace("信息", "")
.replace("播放", "")
.trim();
//return val;
} catch (Exception e) {
}
return "";
}
public String guess_value_vod_remarks(String nd, int startPos, String vod_name) {
try {
String all = HtmlNodeHlper.trimHtmlString(nd, "!!!!");
String[] words = all.split("!!!!");
String val = "";
for (int i = 0; i < words.length; ++i) {
String wd = words[i].trim();
if (!wd.isEmpty() && wd.indexOf(vod_name) == -1) {
String dot = (!val.isEmpty()) ? "," : "";// val += ",";
String tmp = val + dot + wd;
if (tmp.length() > 20) {
break;
}
val = tmp;
}
}
return val;
} catch (Exception e) {
}
return "";
}
public String guess_value_vod_pic(String nd, int startPos) {
try {
JSONArray vec = new JSONArray();
vec.put("data-original=\"");
vec.put("\"");
String val = Utils.findSubString(nd, startPos, vec);
if (val.isEmpty()) {
vec.put(0, "data-src=\"");
val = Utils.findSubString(nd, startPos, vec);
}
if (val.isEmpty()) {
vec.put(0, "src=\"");
val = Utils.findSubString(nd, startPos, vec);
}
if (val.isEmpty()) {
vec.put(0, "data-bg=\"");
val = Utils.findSubString(nd, startPos, vec);
}
if (val.isEmpty()) {
//TODO: 直接在nd中找个.jpg .png 之类的当图片
}
return addHttpPrefix(val);
} catch (Exception e) {
}
return "";
}
@Override
public boolean isVideoFormat(String url) {
url = url.toLowerCase();
if (url.contains("=http") || url.contains("=https") || url.contains("=https%3a%2f") || url.contains("=http%3a%2f")) {
return false;
}
for (String format : videoFormatList) {
if (url.contains(format)) {
return true;
}
}
return false;
}
// 让当前爬虫自己判断是否为可播放的地址
@Override
public boolean manualVideoCheck() {
return true;
}
@Override
public String homeContent(boolean z) {
try {
fetchRule();
JSONObject result = new JSONObject();
JSONArray classes = new JSONArray();
JSONObject cateManual = rule.optJSONObject("cateManual");
Iterator<String> keys = cateManual.keys();
while (keys.hasNext()) {
String key = keys.next();
JSONObject jsonObject = new JSONObject();
jsonObject.put("type_name", key);
jsonObject.put("type_id", cateManual.getString(key));
classes.put(jsonObject);
}
result.put("class", classes);
if (z && rule.has("filter")) {
result.put("filters", rule.getJSONObject("filter"));
}
return result.toString();
} catch (Exception e) {
SpiderDebug.log(e);
}
return "";
}
// from xpath 加入过滤条件
protected String categoryUrl(String tid, String pg, boolean filter, HashMap<String, String> extend) {
try {
JSONObject list = this.rule.getJSONObject("list");
String cateUrl = list.optString(pg, "");
if(cateUrl.isEmpty())
cateUrl = list.getString("url");
if (filter && extend != null && extend.size() > 0) {
for (Iterator<String> it = extend.keySet().iterator(); it.hasNext(); ) {
String key = it.next();
String value = extend.get(key);
if (value.length() > 0) {
cateUrl = cateUrl.replace("{" + key + "}", URLEncoder.encode(value));
}
}
}
cateUrl = cateUrl.replace("{cateId}", tid).replace("{catePg}", pg);
Matcher m = Pattern.compile("\\{(.*?)\\}").matcher(cateUrl);
while (m.find()) {
String n = m.group(0).replace("{", "").replace("}", "");
cateUrl = cateUrl.replace(m.group(0), "").replace("/" + n + "/", "");
}
return cateUrl;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
@Override
public String categoryContent(String tid, String pg, boolean filter, HashMap<String, String> extend) {
try {
JSONObject list = this.rule.getJSONObject("list");
String url = categoryUrl(tid, pg, filter, extend);
String body = fetchUrl(url, list.optJSONObject("header"));
String str = Utils.getRegion(body, list);
JSONArray videos = new JSONArray();
JSONArray lookback = Utils.getLookbackArray(list);
Set<String> set = new HashSet<String>();
int pos = 0;
ArrayList<Integer> urlnodes = null;
int lookup = -1;
while (lookback != null) {
pos = str.indexOf(lookback.getString(0), pos);
if (pos == -1) break;
ArrayList<Integer> arr = null;
int blockPos = 0;
String nd ="";
do {
arr = HtmlNodeHlper.findUpNodes(str, pos - 1, lookback.getInt(4));
if (urlnodes == null) {
urlnodes = arr;
blockPos = arr.get(arr.size() - 1);
} else {
blockPos = Utils.findBlockPos(urlnodes, arr);
}
nd = HtmlNodeHlper.nodeString(str, blockPos);
// 检查是否回看层数过多,如果回看导数过多会导致加载不出来数据或一页只加载一条数据,需要进行修正
if(lookup < 0){
int count = Utils.getSubStringCount(nd, lookback.getString(0));
if(count > 3 && lookback.getInt(4)>1){
lookback.put(4, lookback.getInt(4)-1);
urlnodes = null;
blockPos=0;
nd="";
SpiderDebug.log(String.format("找到过多的url匹配项(%d),降低匹配层级为%d", count, lookback.getInt(4)));
}else if(lookup == -1){
String pic = guess_value_vod_pic(nd,0); //尝试找一下图片,如果没找到的话增加一级
String vName = guess_value_vod_name(nd,0);
if(pic.isEmpty()||vName.isEmpty()){
lookback.put(4, lookback.getInt(4)+1);
urlnodes = null;
blockPos=0;
nd="";
lookup = -2; // 只退一次
SpiderDebug.log(String.format("当前层级未找到(%s),增加匹配层级为%d", pic.isEmpty()? "图片": "标题", lookback.getInt(4)));
}else{
lookup = lookback.getInt(4);
}
}else{
lookup = lookback.getInt(4);
}
}
}while (lookup < 0 );
pos += nd.length();
blockPos = 0;
String vod_id = Utils.findSubString(nd, blockPos, list.getJSONArray("vod_id"));
if (!set.contains(vod_id)) { // 排除重复数据
set.add(vod_id);
JSONObject v = new JSONObject();
v.put("vod_id", vod_id);
v.put("vod_name", Utils.findSubString(nd, blockPos, list.optJSONArray("vod_name")));
v.put("vod_pic", addHttpPrefix(Utils.findSubString(nd, blockPos, list.optJSONArray("vod_pic"))));
v.put("vod_remarks", Utils.findSubString(nd, blockPos, list.optJSONArray("vod_remarks")));
if (v.getString("vod_name").isEmpty()) {
v.put("vod_name", guess_value_vod_name(nd, 0));
}
if (v.getString("vod_pic").isEmpty()) {
v.put("vod_pic", guess_value_vod_pic(nd, 0));
}
// 随便整点remark
if (v.getString("vod_remarks").isEmpty()) {
String vod_name = v.getString("vod_name");
v.put("vod_remarks", guess_value_vod_remarks(nd, 0, vod_name));
}
v.put("vod_id", Base64.encodeToString(v.toString().getBytes(Misc.CharsetUTF8), base64Flag));
videos.put(v);
}
// pos += vod_id.length();
}
JSONObject result = new JSONObject();
result.put("page", pg);
result.put("pagecount", Integer.MAX_VALUE);
result.put("limit", 90);
result.put("total", Integer.MAX_VALUE);
result.put("list", videos);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
// 生成播放的名称
public ArrayList<String> makeVodPlayFrom(int sz) {
ArrayList<String> vec = new ArrayList<String>();
for (int i = 1; i <= sz; ++i) {
vec.add("播放列表" + i);
}
return vec;
}
// 查找播放列表名
public ArrayList<String> findVodPlayFrom(String str, int sz) {
try {
ArrayList<Integer> urlnodes = null;
JSONObject playlist = this.rule.getJSONObject("playlist");
if (!playlist.has("vod_play_from")) {
return makeVodPlayFrom(sz);
}
ArrayList<Pair<Integer, String>> vod_play_from = new ArrayList<Pair<Integer, String>>();
JSONArray rule_vod_play_from = playlist.getJSONArray("vod_play_from");
for (int i = 0; i < rule_vod_play_from.length(); ++i) {
String s = rule_vod_play_from.get(i).getClass().getSimpleName();
String key = "";
String alias = "";
if (s.equals("String")) {
key = alias = rule_vod_play_from.getString(i);
} else if (s.equals("JSONArray")) {
JSONArray item = rule_vod_play_from.getJSONArray(i);
key = alias = item.getString(0);
if (item.length() > 1) {
alias = item.getString(1);
}
} else {
return makeVodPlayFrom(sz);
}
int pos = str.indexOf(key);
if (pos == -1) continue;
vod_play_from.add(new Pair<>(pos, alias));
}
// 找到的名称与实际需要的数量不匹配,返回默认的名称
if (vod_play_from.size() != sz) {
return makeVodPlayFrom(sz);
}
// 排序
Collections.sort(vod_play_from, new Comparator<Pair<Integer, String>>() {
@Override
public int compare(Pair<Integer, String> a, Pair<Integer, String> b) {
return a.first.intValue() - b.first.intValue();
}
});
ArrayList<String> vec = new ArrayList<String>();
for (int i = 0; i < vod_play_from.size(); ++i) {
vec.add(vod_play_from.get(i).second);
}
return vec;
} catch (Exception e) {
e.printStackTrace();
}
return makeVodPlayFrom(sz);
}
// 查找播放列表
public ArrayList<String> findVodPlayUrl(String str) {
ArrayList<String> tmp_vod_play_url = new ArrayList<String>();
ArrayList<String> vod_play_url = new ArrayList<String>();
try {
int pos = 0;
ArrayList<Integer> urlnodes = null;
JSONObject playlist = this.rule.getJSONObject("playlist");
int sort = playlist.optInt("sort", 0); // 如果这个值是0。表示要倒序播放列表
HashMap<String, Integer> map = new HashMap<String, Integer>();
Set<Integer> rmset = new HashSet<Integer>();
// String tmp = "";
ArrayList<String> tmp = new ArrayList<String>();
JSONArray lookback = Utils.getLookbackArray(playlist);
while (lookback != null) {
JSONArray rule_vod_play_url = playlist.getJSONArray("vod_play_url");
pos = str.indexOf(rule_vod_play_url.getString(0), pos);
if (pos == -1) break;
ArrayList<Integer> arr = HtmlNodeHlper.findUpNodes(str, pos - 1, lookback.getInt(4));
int blockPos = 0;
if (urlnodes == null || arr.size() != urlnodes.size() || arr.get(arr.size() - 1).intValue() != urlnodes.get(urlnodes.size() - 1).intValue()) {
urlnodes = arr;
blockPos = arr.get(Math.max(0, arr.size() - 2));
// 如果上级节点不同,说明当前播放列表已经结束,可以
if (!tmp.isEmpty()) {
SpiderDebug.log("change play list ");
if (sort != 0) {
Collections.reverse(tmp);
}
tmp_vod_play_url.add(TextUtils.join("#", tmp));
tmp = new ArrayList<String>();
}
} else {
blockPos = Utils.findBlockPos(urlnodes, arr);
}
// String bs = str.substring(blockPos, blockPos + 1000);
// String b = this.findNodeString(str, blockPos);
// 处理play_url 为空的情况
String play_url = addHttpPrefix(Utils.findSubString(str, blockPos, playlist.getJSONArray("vod_play_url")));
if (map.containsKey(play_url)) { // 如果以经找过当前播放的url,那么认为之前找到的都是垃圾数据,清空之间的成果
SpiderDebug.log("发现重复播放连接,清空已解析到的播放列表");
rmset.add(map.get(play_url)); // 添加移除标志
}
map.put(play_url, tmp_vod_play_url.size());
String play_url_title = Utils.findSubString(str, blockPos, playlist.optJSONArray("vod_play_url_title"));
if (play_url_title.isEmpty()) {
play_url_title = HtmlNodeHlper.trimHtmlString(HtmlNodeHlper.nodeString(str, blockPos));
int dd = 3;
}
tmp.add(play_url_title + "$" + play_url);
// SpiderDebug.log(String.format("%s$%s", play_url_title, play_url));
pos += play_url.length();
}
if (!tmp.isEmpty()) {
if (sort != 0) {
Collections.reverse(tmp);
}
tmp_vod_play_url.add(TextUtils.join("#", tmp));
}
for (int i = 0; i < tmp_vod_play_url.size(); ++i) {
if (!rmset.contains(i)) {
vod_play_url.add(tmp_vod_play_url.get(i));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return vod_play_url;
}
// 猜测详情数据的html区间
protected String guessDetailContentRegion(String body) {
String regx = String.format(">\\s*?(%s)|(%s)", TextUtils.join("|", detailItemNames), TextUtils.join("|", detailItemNames));
Pattern pattern = Pattern.compile(regx, Pattern.CASE_INSENSITIVE);
Matcher m = pattern.matcher(body);
ArrayList<HtmlMatchInfo> list = new ArrayList<>();
while (m.find()) {
HtmlMatchInfo cate = new HtmlMatchInfo();
cate.init(m);
cate.uploads = HtmlNodeHlper.findUpNodes(body, cate.startPos, 5);
if (!list.isEmpty()) {
boolean b = list.get(0).hasSameUpNode(cate);
if (!b) { // 当前找到的info和list中的匹配
if (list.size() > 1) {
boolean found = false;
for (int i = 0; i < list.size(); ++i) {
if (list.get(i).group0.indexOf("导演") != -1) {
found = true;
}
}
if (found) {
return HtmlNodeHlper.nodeString(body, list.get(0).matchedUpNodePos);
} else {
list.clear();
}
}
list.clear();
}
}
list.add(cate);
}
if (list.size() > 1) { // 如果list中的数据大于1 则认为找到了类型列表
return HtmlNodeHlper.nodeString(body, list.get(0).matchedUpNodePos);
} else {
return "";
}
}
@Override
public String detailContent(List<String> ids) {
try {
fetchRule();
JSONObject vinfo = new JSONObject(new String(Base64.decode(ids.get(0), base64Flag), "UTF-8"));
JSONObject detail = rule.optJSONObject("detail");
if (detail == null) return "";
// 如果不存在url,则使用list中的vod_id来生成url
if (!detail.has("url")) {
JSONObject list = rule.getJSONObject("list");
JSONArray tmp = list.getJSONArray("vod_id");
String u = addHttpPrefix(tmp.getString(0) + "{vid}" + tmp.getString(1));
detail.put("url", u);
}
String url = detail.getString("url").replace("{vid}", vinfo.getString("vod_id"));
String body = fetchUrl(url, detail.optJSONObject("header"));
String str = Utils.getRegion(body, detail);
int startPos = 0;
String nodeString = "";
// 圈定 详情数据的范围
JSONArray lookback = Utils.getLookbackArray(detail);
if (lookback != null) {
int pos = str.indexOf(lookback.getString(0), 0);
if (pos != -1) {
ArrayList<Integer> arr = HtmlNodeHlper.findUpNodes(str, pos - 1, lookback.getInt(4));
if (arr.size() > 0) {
startPos = arr.get(arr.size() - 1);
nodeString = HtmlNodeHlper.nodeString(str, startPos); // 精确详情数据的范围
}
}
}
// 没有指定详情数据范围则猜一个出来
if (nodeString.isEmpty()) {
nodeString = this.guessDetailContentRegion(body);
}
if (nodeString.length() != str.length()) {
str = nodeString;
startPos = 0;
}
///////////////////////////////////////////////////////////////////////////////////////
JSONObject vod = new JSONObject();
vod.put("vod_id", ids.get(0));
vod.put("vod_name", Utils.findSubString(str, startPos, detail.optJSONArray("vod_name")));
vod.put("vod_pic", addHttpPrefix(Utils.findSubString(str, startPos, detail.optJSONArray("vod_pic"))));
vod.put("type_name", Utils.findSubString(str, startPos, detail.optJSONArray("type_name")));
vod.put("vod_year", Utils.findSubString(str, startPos, detail.optJSONArray("vod_year")));
vod.put("vod_area", Utils.findSubString(str, startPos, detail.optJSONArray("vod_area")));
vod.put("vod_remarks", Utils.findSubString(str, startPos, detail.optJSONArray("vod_remarks")));
vod.put("vod_actor", Utils.findSubString(str, startPos, detail.optJSONArray("vod_actor")));
vod.put("vod_director", Utils.findSubString(str, startPos, detail.optJSONArray("vod_director")));
vod.put("vod_content", Utils.findSubString(str, startPos, detail.optJSONArray("vod_content")));
////////////////////////////////////////////////////////////////////////////////////////
if (vod.getString("vod_name").isEmpty()) {
vod.put("vod_name", vinfo.optString("vod_name", ""));
}
// 从页面中猜个视频名称出来
if (vod.getString("vod_name").isEmpty()) {
vod.put("vod_name", guess_value_vod_name(str, startPos));
}
////////////////////////////////////////////////////////////////////////////////////////
if (vod.getString("vod_pic").isEmpty()) {
vod.put("vod_pic", vinfo.optString("vod_pic", ""));
}
if (vod.getString("vod_pic").isEmpty()) {
vod.put("vod_pic", guess_value_vod_pic(str, startPos));
}
////////////////////////////////////////////////////////////////////////////////////////
if (lookback != null && lookback.length() > 1) {
JSONArray key = new JSONArray();
String name = lookback.getString(0);
String skey = lookback.getString(0);
ArrayList<String> detailItems = new ArrayList<>(Arrays.asList("导演", "演员", "类型", "年份"));
for (String p : detailItems) {
if (name.indexOf(p) != -1) {
skey = p;
break;
}
}
key.put(name);
key.put(lookback.getString(1));
if (vod.getString("vod_director").isEmpty()) {
key.put(0, name.replace(skey, "导演"));
vod.put("vod_director", Utils.findSubString(str, startPos, key));
}
if (vod.getString("vod_actor").isEmpty()) {
key.put(0, name.replace(skey, "主演"));
vod.put("vod_actor", Utils.findSubString(str, startPos, key));
}
if (vod.getString("vod_content").isEmpty()) {
String all = HtmlNodeHlper.trimHtmlString(str, "!!!!");
String[] words = all.split("!!!!");
String v = "";
for (int i = 0; i < words.length; ++i) {
if (words[i].length() > v.length()) {
v = words[i];
}
}
vod.put("vod_content", HtmlNodeHlper.trimHtmlString(v));
}
} else { // 猜一下详情数据
if (vod.getString("vod_director").isEmpty()) {
ArrayList<String> arr = HtmlNodeHlper.getChildNodes(nodeString);
String content = "";
String f = TextUtils.join("|", detailItemNames);
String regex = String.format("%s",f);
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
for (int i = 0; i < arr.size(); ++i) {
String p = HtmlNodeHlper.trimHtmlString(arr.get(i), " ").replace("", "");
if (p.length() > content.length()) { content = p; }
String[] all = p.split(regex);
// split出来的可能存在空字符串,去除掉
ArrayList<String> items = new ArrayList<>();
for(String c: all){
if(c.isEmpty()) continue;
items.add(c);
}
Matcher m = pattern.matcher(p);
int index = 0;
while (m.find() && index < items.size()){
String s = m.group(0);
for (int j = 0; j < detailItemNames.size(); ++j) {
String name = detailItemNames.get(j);
String key = detailItemKeys.get(j);
if (s.indexOf(name) != -1) {
if (vod.getString(key).isEmpty()) {
vod.put(key, items.get(index).trim());
}
break;
}
}
++index;
}
}
if (vod.getString("vod_content").isEmpty()) {
vod.put("vod_content", content);
}
}
}
playlistContent(ids, vod, body);// 获取播放列表
JSONObject result = new JSONObject();
JSONArray list = new JSONArray();
list.put(vod);
result.put("list", list);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
// 播放页 str 为 detailContent 函数中http返回值
protected void playlistContent(List<String> ids, JSONObject vod, String body) {
try {
fetchRule();
JSONObject vinfo = new JSONObject(new String(Base64.decode(ids.get(0), base64Flag), "UTF-8"));
JSONObject playlist = rule.optJSONObject("playlist");
if (playlist == null) return;
if (playlist.has("url")) {
String detailUrl = rule.getJSONObject("detail").optString("url");
String playListUrl = playlist.getString("url");
if(!detailUrl.equals(playListUrl)){
String url = playlist.getString("url").replace("{vid}", vinfo.getString("vod_id"));
body = fetchUrl(url, playlist.optJSONObject("header"));
}
}
String str = Utils.getRegion(body, playlist);
ArrayList<String> vod_play_url = null;
if (!playlist.has("vod_play_url")) {
// 猜vod_play_url的查找规则
JSONArray vod_play_url_rule = this.guess_rule_vod_play_url(str, vinfo.getString("vod_id"));
if(vod_play_url_rule != null){
playlist.put("vod_play_url", vod_play_url_rule);
}
}
vod_play_url = this.findVodPlayUrl(str);
ArrayList<String> vod_play_from = this.findVodPlayFrom(str, vod_play_url.size());
// 如果有说明播放源的名称,那么对播放源进行排序
String f1 = TextUtils.join("$$$", vod_play_from);
String f2 = TextUtils.join("$$$", makeVodPlayFrom(vod_play_url.size()));
if (!f1.equals(f2)) {
ArrayList<String> urls = new ArrayList<>();
ArrayList<String> froms = new ArrayList<>();
JSONArray rule_vod_play_from = playlist.getJSONArray("vod_play_from");
for (int i = 0; i < rule_vod_play_from.length(); ++i) {
String s = rule_vod_play_from.get(i).getClass().getSimpleName();
String alias = "";
if (s.equals("String")) {
alias = rule_vod_play_from.getString(i);
} else if (s.equals("JSONArray")) {
JSONArray item = rule_vod_play_from.getJSONArray(i);
alias = item.getString(0);
if (item.length() > 1) {
alias = item.getString(1);
}
}
for (int j = 0; j < vod_play_from.size(); ++j) {
if (vod_play_from.get(j).equals(alias)) {
urls.add(vod_play_url.get(j));
froms.add(vod_play_from.get(j));
}
}
}
vod_play_url = urls;
vod_play_from = froms;
}
vod.put("vod_play_url", TextUtils.join("$$$", vod_play_url));
vod.put("vod_play_from", TextUtils.join("$$$", vod_play_from));
} catch (Exception e) {
e.printStackTrace();
}
}
// 尝试从播放页中找播放url
protected String parsePlayUrl(String str, String str2, List<String> list) {
try {
JSONObject play = rule.optJSONObject("play");
if (play == null) {
return "";
}
String tmp = fetchUrl(str2, play.optJSONObject("header"));
String body = Utils.getRegion(tmp, play);
int startPos = 0;
JSONArray lookback = Utils.getLookbackArray(play);
if (lookback != null) {
int pos = body.indexOf(lookback.getString(0), 0);
if (pos != -1) {
ArrayList<Integer> arr = HtmlNodeHlper.findUpNodes(body, pos - 1, lookback.getInt(4));
if (arr.size() > 0) {
startPos = arr.get(arr.size() - 1);
} else {
startPos = pos;
}
}
}
String vod_url = Utils.findSubString(body, startPos, play.optJSONArray("vod_url"));
vod_url = vod_url.replace("\\/", "/");
if (vod_url.isEmpty() || !isVideoFormat(vod_url)) return "";
JSONObject result = new JSONObject();
result.put("parse", 0);
result.put("playUrl", "");
result.put("url", vod_url);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
@Override
public String playerContent(String str, String str2, List<String> list) {
try {
fetchRule();
// 先判断是否可以拿到直链
String ret = parsePlayUrl(str, str2, list);
if (!ret.isEmpty()) return ret;
// 直接将网页地址返回回去进行嗅探
JSONObject result = new JSONObject();
result.put("parse", 1);
result.put("playUrl", "");
result.put("url", str2);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
protected Object parseJsonSearchResult(Object obj) {
try {
if (obj == null) return null;
JSONObject search = rule.optJSONObject("search");
if (search == null) return null;
String key_vod_id = search.getString("vod_id");
String key_vod_name = search.getString("vod_name");
String type = obj.getClass().getSimpleName();
if (type.equals("JSONObject")) {
JSONObject object = (JSONObject) obj;
if (object.has(key_vod_id) && object.has(key_vod_name)) return object;
for (Iterator<String> iter = object.keys(); iter.hasNext(); ) {
String k = iter.next();
Object r = parseJsonSearchResult(object.get(k));
if (r != null) {
return r;
}
}
} else if (type.equals("JSONArray")) {
JSONArray array = (JSONArray) obj;
for (int i = 0; i < array.length(); ++i) {
if (parseJsonSearchResult(array.get(i)) != null) {
return array;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected String parseSearchResult(String body) {
try {
JSONObject obj = new JSONObject(body);
Object info = parseJsonSearchResult(obj);
if (info == null) return "";
JSONArray arr = new JSONArray();
if (info.getClass().getSimpleName().equals("JSONObject")) {
arr.put(info);
} else {
arr = (JSONArray) info;
}
JSONObject search = rule.optJSONObject("search");
JSONArray videos = new JSONArray();
for (int i = 0; i < arr.length(); ++i) {
JSONObject v = new JSONObject();
JSONObject o = arr.getJSONObject(i);
if (search.has("vod_id") && o.has(search.getString("vod_id"))) {
v.put("vod_id", o.get(search.getString("vod_id")).toString());
} else {
continue;
}
if (search.has("vod_name") && o.has(search.getString("vod_name"))) {
v.put("vod_name", o.get(search.getString("vod_name")).toString());
} else {
v.put("vod_name", "未知");
}
if (search.has("vod_pic") && o.has(search.getString("vod_pic"))) {
v.put("vod_pic", o.get(search.getString("vod_pic")).toString());
} else {
v.put("vod_pic", "");
}
if (search.has("vod_remarks") && o.has(search.getString("vod_remarks"))) {
v.put("vod_remarks", o.get(search.getString("vod_remarks")).toString());
} else {
v.put("vod_remarks", "");
}
v.put("vod_id", Base64.encodeToString(v.toString().getBytes(Misc.CharsetUTF8), base64Flag));
videos.put(v);
}
JSONObject result = new JSONObject();
result.put("list", videos);
return result.toString();
} catch (Exception e) {
// e.printStackTrace();
}
return "";
}
// post 搜索,只做了简单的支持
protected String postSearch(String wd, boolean z) {
try {
JSONObject search = rule.optJSONObject("search");
if (search == null) return "";
String url = search.getString("url");
JSONObject params = search.getJSONObject("post"); // 查询参数
HashMap<String, String> reqpayload = new HashMap<>();
Iterator<String> iter = params.keys();
while (iter.hasNext()) {
String key = iter.next();
String value = params.getString(key).replace("{wd}", wd);
reqpayload.put(key, value);
}
final String[] retval = {""};
HashMap<String, String> header = getHeaders(url);
header.put("content-type", "application/x-www-form-urlencoded");
OkHttpUtil.post(OkHttpUtil.defaultClient(), url, reqpayload, header, new OKCallBack.OKCallBackString() {
@Override
protected void onFailure(Call call, Exception e) {
}
@Override
protected void onResponse(String response) {
retval[0] = response;
}
});
return retval[0];
} catch (Exception e) {
}
return "";
}
@Override
public String searchContent(String wd, boolean z) {
try {
fetchRule();
JSONObject search = rule.optJSONObject("search");
if (search == null) return "";
String str = "";
if (search.has("post")) {
str = postSearch(wd, z);
} else {
String url = search.getString("url").replace("{wd}", wd);
str = fetchUrl(url, search.optJSONObject("header"));
}
str = Utils.getRegion(str, search);
// 先当JSON解析试试
String r = parseSearchResult(str);
if (r != null && !r.isEmpty()) {
return r;
}
if (!search.has("vod_id")) {
JSONObject list = rule.getJSONObject("list");
search.put("vod_id", list.getJSONArray("vod_id"));
}
JSONArray videos = new JSONArray();
Set<String> set = new HashSet<String>();
int pos = 0;
ArrayList<Integer> urlnodes = null;
JSONArray lookback = Utils.getLookbackArray(search);
int lookup = -1;
while (lookback != null) {
pos = str.indexOf(lookback.getString(0), pos);
if (pos == -1) break;
ArrayList<Integer> arr = null;
int blockPos = 0;
String nd ="";
do {
arr = HtmlNodeHlper.findUpNodes(str, pos - 1, lookback.getInt(4));
if (urlnodes == null) {
urlnodes = arr;
blockPos = arr.get(arr.size() - 1);
} else {
blockPos = Utils.findBlockPos(urlnodes, arr);
}
nd = HtmlNodeHlper.nodeString(str, blockPos);
// 检查是否回看层数过多,如果回看导数过多会导致加载不出来数据或一页只加载一条数据,需要进行修正
if(lookup < 0){
int count = Utils.getSubStringCount(nd, lookback.getString(0));
if(count > 3 && lookback.getInt(4)>1){
lookback.put(4, lookback.getInt(4)-1);
urlnodes = null;
blockPos=0;
nd="";
SpiderDebug.log(String.format("找到过多的url匹配项(%d),降低匹配层级为%d", count, lookback.get(4)));
}
else if(lookup == -1){
String pic = guess_value_vod_pic(nd,0); //尝试找一下图片,如果没找到的话增加一级
String vName = guess_value_vod_name(nd,0);
if(pic.isEmpty()||vName.isEmpty()){
lookback.put(4, lookback.getInt(4)+1);
urlnodes = null;
blockPos=0;
nd="";
lookup = -2; // 只退一次
SpiderDebug.log(String.format("当前层级未找到(%s),增加匹配层级为%d", pic.isEmpty()? "图片": "标题", lookback.getInt(4)));
}else{
lookup = lookback.getInt(4);
}
}else{
lookup = lookback.getInt(4);
}
}
}while (lookup < 0);
pos += nd.length();
blockPos = 0;
String vod_id = Utils.findSubString(nd, blockPos, search.getJSONArray("vod_id"));
if (!set.contains(vod_id)) {
set.add(vod_id);
JSONObject v = new JSONObject();
v.put("vod_id", vod_id);
v.put("vod_name", Utils.findSubString(nd, blockPos, search.optJSONArray("vod_name")));
v.put("vod_pic", addHttpPrefix(Utils.findSubString(nd, blockPos, search.optJSONArray("vod_pic"))));
v.put("vod_remarks", Utils.findSubString(nd, blockPos, search.optJSONArray("vod_remarks")));
if (v.getString("vod_name").isEmpty()) {
v.put("vod_name", guess_value_vod_name(nd, 0));
}
if (v.getString("vod_pic").isEmpty()) {
v.put("vod_pic", guess_value_vod_pic(nd, 0));
}
// 随便整点remark
if (v.getString("vod_remarks").isEmpty()) {
String vod_name = v.getString("vod_name");
v.put("vod_remarks", guess_value_vod_remarks(nd, 0, vod_name));
}
v.put("vod_id", Base64.encodeToString(v.toString().getBytes(Misc.CharsetUTF8), base64Flag));
videos.put(v);
}
}
JSONObject result = new JSONObject();
result.put("list", videos);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
protected String fetchUrl(String url, JSONObject h) {
String html = OkHttpUtil.string(url, getHeaders(url));
html = this.jumpbtwaf(url, html, h);
return html.replaceAll("<!--.+?-->", "").replace("\r\n","").replace("\n",""); // 移除注释
}
protected String jumpbtwaf(String webUrl, String html, JSONObject h) {
try {
// 没有配置btwaf不执行下面的代码
if (!rule.optBoolean("btwaf", false)) {
return html;
}
if (html.contains("检测中") && html.contains("跳转中") && html.contains("btwaf")) {
JSONArray keys = new JSONArray();
keys.put("btwaf=");
keys.put("\"");
String btwaf = Utils.findSubString(html, 0, keys);
String bturl = webUrl + "?btwaf=" + btwaf;
Map<String, List<String>> cookies = new HashMap<>();
OkHttpUtil.string(bturl, getHeaders(webUrl), cookies);
for (Map.Entry<String, List<String>> entry : cookies.entrySet()) {
if (entry.getKey().equals("set-cookie") || entry.getKey().equals("Set-Cookie")) {
String btcookie = TextUtils.join(";", entry.getValue());
if (!rule.has("header")) {
rule.put("header", new JSONObject());
}
rule.getJSONObject("header").put("cookie", btcookie);
break;
}
}
html = fetchUrl(webUrl, h);
}
if (!html.contains("检测中") && !html.contains("btwaf")) {
return html;
}
} catch (Exception e) {
e.printStackTrace();
}
return html;
}
}