上传文件至「lib」
This commit is contained in:
+15158
File diff suppressed because it is too large
Load Diff
+1027
@@ -0,0 +1,1027 @@
|
||||
/**
|
||||
* 统一 HTML/JSON 解析器
|
||||
* 结合轻量级HTML解析器(lite)和完整HTML解析器(cheerio版本)
|
||||
* 不依赖 cheerio,使用纯正则表达式实现,同时支持JSONPath解析
|
||||
*
|
||||
* 支持的选择器语法:
|
||||
* - 标签名: div, p, a, img
|
||||
* - 类选择器: .class-name
|
||||
* - ID选择器: #id-name
|
||||
* - 属性选择器: [attr], [attr=value]
|
||||
* - 组合选择器: div.class, div#id, img:last-of-type
|
||||
* - 后代选择器: div p (空格分隔)
|
||||
* - 伪选择器: :eq(n), :first, :last, :eq(-1), :last-of-type, :first-of-type
|
||||
* - :has(selector) - 匹配包含指定子元素的元素
|
||||
* - :contains(text) - 匹配包含指定文本的元素
|
||||
* - 排除语法: p--a (获取p标签内容但排除a标签)
|
||||
* - 海阔视界语法: && 分隔, Text, Html
|
||||
*/
|
||||
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
// ============================================
|
||||
// 常量定义
|
||||
// ============================================
|
||||
var URLJOIN_ATTR = /(url|src|href|-original|-src|-play|-url|style)$|^(data-|url-|src-)/;
|
||||
var SPECIAL_URL = /^(ftp|magnet|thunder|ws):/;
|
||||
var SELF_CLOSING_TAGS = /^(img|br|hr|input|meta|link|area|base|col|embed|param|source|track|wbr)$/i;
|
||||
|
||||
// 解析缓存开关
|
||||
var PARSE_CACHE = true;
|
||||
// 不自动加eq下标索引的选择器
|
||||
var NOADD_INDEX = ':eq|:lt|:gt|:first|:last|:not|:even|:odd|:has|:contains|:matches|:empty|^body$|^#';
|
||||
|
||||
// ============================================
|
||||
// HTML 元素类
|
||||
// ============================================
|
||||
function HtmlElement(tagName, attributes, innerHTML, outerHTML) {
|
||||
this.tagName = (tagName || '').toLowerCase();
|
||||
this.attributes = attributes || {};
|
||||
this.innerHTML = innerHTML || '';
|
||||
this.outerHTML = outerHTML || '';
|
||||
}
|
||||
|
||||
HtmlElement.prototype.attr = function(name) {
|
||||
if (!name) return '';
|
||||
var lowerName = name.toLowerCase();
|
||||
return this.attributes[name] || this.attributes[lowerName] || '';
|
||||
};
|
||||
|
||||
HtmlElement.prototype.text = function() {
|
||||
return this.innerHTML
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/&#(\d+);/g, function(m, c) { return String.fromCharCode(c); })
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
};
|
||||
|
||||
HtmlElement.prototype.html = function() {
|
||||
return this.innerHTML;
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 工具函数
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 解析 HTML 标签的属性
|
||||
*/
|
||||
function parseAttributes(attrString) {
|
||||
var attrs = {};
|
||||
if (!attrString) return attrs;
|
||||
|
||||
var attrRegex = /([\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
||||
var match;
|
||||
|
||||
while ((match = attrRegex.exec(attrString)) !== null) {
|
||||
var name = match[1].toLowerCase();
|
||||
var value = match[2] !== undefined ? match[2] :
|
||||
match[3] !== undefined ? match[3] :
|
||||
match[4] !== undefined ? match[4] : '';
|
||||
attrs[name] = value;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 拼接
|
||||
*/
|
||||
function urlJoin(base, path) {
|
||||
if (!path) return base || '';
|
||||
if (!base) return path;
|
||||
if (/^https?:\/\//i.test(path)) return path;
|
||||
if (path.indexOf('//') === 0) {
|
||||
return (base.indexOf('https://') === 0 ? 'https:' : 'http:') + path;
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
var m = base.match(/^(https?:\/\/[^\/]+)/i);
|
||||
return m ? m[1] + path : path;
|
||||
}
|
||||
|
||||
var baseUrl = base.split('?')[0];
|
||||
if (baseUrl.charAt(baseUrl.length - 1) !== '/') {
|
||||
var idx = baseUrl.lastIndexOf('/');
|
||||
baseUrl = idx > 8 ? baseUrl.substring(0, idx + 1) : baseUrl + '/';
|
||||
}
|
||||
|
||||
while (path.indexOf('../') === 0) {
|
||||
path = path.substring(3);
|
||||
var idx2 = baseUrl.lastIndexOf('/', baseUrl.length - 2);
|
||||
if (idx2 > 8) baseUrl = baseUrl.substring(0, idx2 + 1);
|
||||
}
|
||||
|
||||
if (path.indexOf('./') === 0) path = path.substring(2);
|
||||
return baseUrl + path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理文本
|
||||
*/
|
||||
function cleanText(text) {
|
||||
if (!text) return '';
|
||||
return text.replace(/[\s]+/g, ' ').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则测试
|
||||
*/
|
||||
function regexTest(pattern, text) {
|
||||
if (!pattern || !text) return false;
|
||||
var regex = new RegExp(pattern, 'i');
|
||||
return regex.test(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否包含指定内容
|
||||
*/
|
||||
function contains(text, match) {
|
||||
return text && text.indexOf(match) !== -1;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 选择器解析
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 解析选择器,提取标签名、类名、ID、属性和伪选择器
|
||||
*/
|
||||
function parseSelector(selector) {
|
||||
var result = {
|
||||
tagName: null,
|
||||
classNames: [],
|
||||
idName: null,
|
||||
attrs: {},
|
||||
pseudo: null,
|
||||
pseudoArg: null,
|
||||
hasSelector: null
|
||||
};
|
||||
|
||||
if (!selector) return result;
|
||||
selector = selector.trim();
|
||||
|
||||
// 解析 :has() 伪选择器
|
||||
var hasMatch = selector.match(/:has\(([^)]+)\)/);
|
||||
if (hasMatch) {
|
||||
result.hasSelector = hasMatch[1].trim();
|
||||
selector = selector.replace(/:has\([^)]+\)/, '');
|
||||
}
|
||||
|
||||
// 解析 :contains() 伪选择器
|
||||
var containsMatch = selector.match(/:contains\(([^)]+)\)/);
|
||||
if (containsMatch) {
|
||||
result.pseudo = 'contains';
|
||||
result.pseudoArg = containsMatch[1].replace(/^['"]|['"]$/g, '');
|
||||
selector = selector.replace(/:contains\([^)]+\)/, '');
|
||||
}
|
||||
|
||||
// 解析伪选择器
|
||||
var pseudoMatch = selector.match(/:(\w+(?:-\w+)*)(?:\((-?\d+)\))?$/);
|
||||
if (pseudoMatch && !result.pseudo) {
|
||||
result.pseudo = pseudoMatch[1];
|
||||
result.pseudoArg = pseudoMatch[2] !== undefined ? parseInt(pseudoMatch[2]) : null;
|
||||
selector = selector.replace(/:[\w-]+(?:\(-?\d+\))?$/, '');
|
||||
}
|
||||
|
||||
// 解析属性选择器
|
||||
var attrRegex = /\[([\w-]+)(?:([*^$~]?)=["']?([^"'\]]+)["']?)?\]/g;
|
||||
var attrMatch;
|
||||
while ((attrMatch = attrRegex.exec(selector)) !== null) {
|
||||
var attrName = attrMatch[1].toLowerCase();
|
||||
var matchMode = attrMatch[2] || '';
|
||||
var attrValue = attrMatch[3];
|
||||
|
||||
if (attrValue === undefined) {
|
||||
result.attrs[attrName] = { mode: 'exists', value: true };
|
||||
} else {
|
||||
result.attrs[attrName] = { mode: matchMode || 'exact', value: attrValue };
|
||||
}
|
||||
}
|
||||
selector = selector.replace(/\[[\w-]+(?:[*^$~]?=["']?[^"'\]]+["']?)?\]/g, '');
|
||||
|
||||
// 解析 #id
|
||||
var idMatch = selector.match(/#([\w-]+)/);
|
||||
if (idMatch) {
|
||||
result.idName = idMatch[1];
|
||||
selector = selector.replace(/#[\w-]+/, '');
|
||||
}
|
||||
|
||||
// 解析 .class
|
||||
var classRegex = /\.([\w-]+)/g;
|
||||
var classMatch;
|
||||
while ((classMatch = classRegex.exec(selector)) !== null) {
|
||||
result.classNames.push(classMatch[1]);
|
||||
}
|
||||
selector = selector.replace(/\.[\w-]+/g, '');
|
||||
|
||||
// 剩余的是标签名
|
||||
selector = selector.trim();
|
||||
if (selector && selector !== '*') {
|
||||
result.tagName = selector.toLowerCase();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查元素是否匹配选择器条件
|
||||
*/
|
||||
function matchesSelector(element, selectorInfo) {
|
||||
if (selectorInfo.tagName && element.tagName !== selectorInfo.tagName) return false;
|
||||
if (selectorInfo.idName && element.attributes.id !== selectorInfo.idName) return false;
|
||||
|
||||
if (selectorInfo.classNames.length > 0) {
|
||||
var elementClasses = (element.attributes.class || '').split(/\s+/);
|
||||
for (var i = 0; i < selectorInfo.classNames.length; i++) {
|
||||
if (elementClasses.indexOf(selectorInfo.classNames[i]) === -1) return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var attrKey in selectorInfo.attrs) {
|
||||
var attrVal = element.attributes[attrKey];
|
||||
var attrRule = selectorInfo.attrs[attrKey];
|
||||
|
||||
if (typeof attrRule !== 'object') {
|
||||
attrRule = { mode: attrRule === true ? 'exists' : 'exact', value: attrRule };
|
||||
}
|
||||
|
||||
if (attrVal === undefined) return false;
|
||||
|
||||
switch (attrRule.mode) {
|
||||
case 'exists': break;
|
||||
case 'exact': case '': if (attrVal !== attrRule.value) return false; break;
|
||||
case '*': if (attrVal.indexOf(attrRule.value) === -1) return false; break;
|
||||
case '^': if (attrVal.indexOf(attrRule.value) !== 0) return false; break;
|
||||
case '$': if (attrVal.indexOf(attrRule.value) !== attrVal.length - attrRule.value.length) return false; break;
|
||||
case '~':
|
||||
var words = attrVal.split(/\s+/);
|
||||
if (words.indexOf(attrRule.value) === -1) return false;
|
||||
break;
|
||||
default: if (attrVal !== attrRule.value) return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectorInfo.hasSelector) {
|
||||
var innerHtml = element.innerHTML || element.outerHTML;
|
||||
var hasElements = findElements(innerHtml, selectorInfo.hasSelector);
|
||||
if (hasElements.length === 0) return false;
|
||||
}
|
||||
|
||||
if (selectorInfo.pseudo === 'contains' && selectorInfo.pseudoArg) {
|
||||
var text = element.text ? element.text() : '';
|
||||
if (text.indexOf(selectorInfo.pseudoArg) === -1) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用伪选择器过滤
|
||||
*/
|
||||
function applyPseudo(elements, pseudo, pseudoArg) {
|
||||
if (!pseudo || elements.length === 0) return elements;
|
||||
|
||||
switch (pseudo) {
|
||||
case 'eq':
|
||||
if (pseudoArg !== null) {
|
||||
var idx = pseudoArg < 0 ? elements.length + pseudoArg : pseudoArg;
|
||||
return (idx >= 0 && idx < elements.length) ? [elements[idx]] : [];
|
||||
}
|
||||
return elements;
|
||||
case 'first': return elements.length > 0 ? [elements[0]] : [];
|
||||
case 'last': return elements.length > 0 ? [elements[elements.length - 1]] : [];
|
||||
case 'first-of-type': return elements.length > 0 ? [elements[0]] : [];
|
||||
case 'last-of-type': return elements.length > 0 ? [elements[elements.length - 1]] : [];
|
||||
case 'lt': return pseudoArg !== null ? elements.slice(0, pseudoArg) : elements;
|
||||
case 'gt': return pseudoArg !== null ? elements.slice(pseudoArg + 1) : elements;
|
||||
case 'even': return elements.filter(function(_, i) { return i % 2 === 0; });
|
||||
case 'odd': return elements.filter(function(_, i) { return i % 2 === 1; });
|
||||
default: return elements;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 HTML 中查找所有匹配的元素
|
||||
*/
|
||||
function findElements(html, selector) {
|
||||
if (!html || !selector) return [];
|
||||
|
||||
var selectorInfo = parseSelector(selector);
|
||||
var results = [];
|
||||
|
||||
var tagToFind = selectorInfo.tagName || '[a-zA-Z][a-zA-Z0-9]*';
|
||||
var openTagRegex = new RegExp('<(' + tagToFind + ')(\\s[^>]*)?>|<(' + tagToFind + ')(\\s[^>]*)?/>', 'gi');
|
||||
var match;
|
||||
|
||||
while ((match = openTagRegex.exec(html)) !== null) {
|
||||
var matchedTag = (match[1] || match[3] || '').toLowerCase();
|
||||
var attrString = match[2] || match[4] || '';
|
||||
var startPos = match.index;
|
||||
var isSelfClosing = match[0].endsWith('/>') || SELF_CLOSING_TAGS.test(matchedTag);
|
||||
|
||||
var parsedAttrs = parseAttributes(attrString);
|
||||
var element = new HtmlElement(matchedTag, parsedAttrs, '', match[0]);
|
||||
|
||||
if (!matchesSelector(element, selectorInfo)) continue;
|
||||
|
||||
if (!isSelfClosing) {
|
||||
var depth = 1;
|
||||
var searchPos = startPos + match[0].length;
|
||||
var contentStart = searchPos;
|
||||
|
||||
var closeTagRegex = new RegExp('<(/?)(' + matchedTag + ')(\\s[^>]*)?>|<' + matchedTag + '(\\s[^>]*)?/>', 'gi');
|
||||
closeTagRegex.lastIndex = searchPos;
|
||||
|
||||
var tagMatch;
|
||||
while ((tagMatch = closeTagRegex.exec(html)) !== null) {
|
||||
if (tagMatch[0].endsWith('/>')) continue;
|
||||
if (tagMatch[1] === '/') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
element.innerHTML = html.substring(contentStart, tagMatch.index);
|
||||
element.outerHTML = html.substring(startPos, tagMatch.index + tagMatch[0].length);
|
||||
break;
|
||||
}
|
||||
} else if (tagMatch[2]) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.push(element);
|
||||
}
|
||||
|
||||
return applyPseudo(results, selectorInfo.pseudo, selectorInfo.pseudoArg);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 海阔视界语法转换
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 将海阔视界解析语法转换为选择器
|
||||
*/
|
||||
function parseHikerToJq(parse, first) {
|
||||
if (!parse) return parse;
|
||||
|
||||
if (contains(parse, '&&')) {
|
||||
var parses = parse.split('&&');
|
||||
var newParses = [];
|
||||
for (var i = 0; i < parses.length; i++) {
|
||||
var psList = parses[i].split(' ');
|
||||
var ps = psList[psList.length - 1];
|
||||
if (!regexTest(NOADD_INDEX, ps)) {
|
||||
if (!first && i >= parses.length - 1) {
|
||||
newParses.push(parses[i]);
|
||||
} else {
|
||||
newParses.push(parses[i] + ':eq(0)');
|
||||
}
|
||||
} else {
|
||||
newParses.push(parses[i]);
|
||||
}
|
||||
}
|
||||
parse = newParses.join(' ');
|
||||
} else {
|
||||
var psList = parse.split(' ');
|
||||
var ps = psList[psList.length - 1];
|
||||
if (!regexTest(NOADD_INDEX, ps) && first) {
|
||||
parse = parse + ':eq(0)';
|
||||
}
|
||||
}
|
||||
return parse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解析信息
|
||||
*/
|
||||
function getParseInfo(nparse) {
|
||||
var excludes = [];
|
||||
var nparseIndex = 0;
|
||||
var nparseRule = nparse;
|
||||
|
||||
if (contains(nparse, ':eq')) {
|
||||
nparseRule = nparse.split(':eq')[0];
|
||||
var nparsePos = nparse.split(':eq')[1];
|
||||
if (contains(nparseRule, '--')) {
|
||||
excludes = nparseRule.split('--').slice(1);
|
||||
nparseRule = nparseRule.split('--')[0];
|
||||
} else if (contains(nparsePos, '--')) {
|
||||
excludes = nparsePos.split('--').slice(1);
|
||||
nparsePos = nparsePos.split('--')[0];
|
||||
}
|
||||
try {
|
||||
nparseIndex = parseInt(nparsePos.split('(')[1].split(')')[0]);
|
||||
} catch(e) {}
|
||||
} else if (contains(nparse, '--')) {
|
||||
nparseRule = nparse.split('--')[0];
|
||||
excludes = nparse.split('--').slice(1);
|
||||
}
|
||||
|
||||
return { nparseRule: nparseRule, nparseIndex: nparseIndex, excludes: excludes };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重排相邻的 :gt 和 :lt 选择器
|
||||
*/
|
||||
function reorderAdjacentLtAndGt(selector) {
|
||||
var adjacentPattern = /:gt\((\d+)\):lt\((\d+)\)/;
|
||||
var match;
|
||||
while ((match = adjacentPattern.exec(selector)) !== null) {
|
||||
var replacement = ':lt(' + match[2] + '):gt(' + match[1] + ')';
|
||||
selector = selector.substring(0, match.index) + replacement + selector.substring(match.index + match[0].length);
|
||||
adjacentPattern.lastIndex = match.index;
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个规则
|
||||
*/
|
||||
function parseOneRule(doc, nparse, ret, findElementsFn) {
|
||||
var info = getParseInfo(nparse);
|
||||
var nparseRule = reorderAdjacentLtAndGt(info.nparseRule);
|
||||
|
||||
if (!ret) {
|
||||
ret = findElementsFn(doc, nparseRule);
|
||||
} else {
|
||||
// 在已有元素中查找
|
||||
var newRet = [];
|
||||
for (var i = 0; i < ret.length; i++) {
|
||||
var innerElements = findElementsFn(ret[i].innerHTML, nparseRule);
|
||||
newRet = newRet.concat(innerElements);
|
||||
}
|
||||
ret = newRet;
|
||||
}
|
||||
|
||||
if (contains(nparse, ':eq') && ret && ret.length > info.nparseIndex) {
|
||||
ret = [ret[info.nparseIndex]];
|
||||
}
|
||||
|
||||
if (info.excludes.length > 0 && ret && ret.length > 0) {
|
||||
// 排除指定标签
|
||||
var newRet = [];
|
||||
for (var j = 0; j < ret.length; j++) {
|
||||
var html = ret[j].innerHTML;
|
||||
for (var k = 0; k < info.excludes.length; k++) {
|
||||
var excludeRegex = new RegExp('<' + info.excludes[k] + '[^>]*>[\\s\\S]*?</' + info.excludes[k] + '>', 'gi');
|
||||
html = html.replace(excludeRegex, '');
|
||||
var selfCloseRegex = new RegExp('<' + info.excludes[k] + '[^>]*/>', 'gi');
|
||||
html = html.replace(selfCloseRegex, '');
|
||||
}
|
||||
var newElement = new HtmlElement(ret[j].tagName, ret[j].attributes, html, '');
|
||||
newRet.push(newElement);
|
||||
}
|
||||
ret = newRet;
|
||||
}
|
||||
|
||||
return ret || [];
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// JSONPath 支持
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* JSONPath 查询
|
||||
*/
|
||||
var jsonpath = {
|
||||
query: function(jsonObject, path) {
|
||||
if (typeof JSONPath !== 'undefined' && JSONPath.JSONPath) {
|
||||
return JSONPath.JSONPath({ path: path, json: jsonObject });
|
||||
}
|
||||
// 简易 JSONPath 实现(仅支持基础语法)
|
||||
try {
|
||||
if (!path.startsWith('$.')) path = '$.' + path;
|
||||
var keys = path.replace(/^\$\./, '').split('.');
|
||||
var result = jsonObject;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
if (key === '*') {
|
||||
if (Array.isArray(result)) return result;
|
||||
return [];
|
||||
}
|
||||
if (key.includes('[')) {
|
||||
var bracketMatch = key.match(/([^\[]+)?\[(\d+)\]/);
|
||||
if (bracketMatch) {
|
||||
var arrKey = bracketMatch[1];
|
||||
var idx = parseInt(bracketMatch[2]);
|
||||
if (arrKey) result = result[arrKey];
|
||||
result = Array.isArray(result) && result[idx] ? result[idx] : undefined;
|
||||
}
|
||||
} else {
|
||||
result = result ? result[key] : undefined;
|
||||
}
|
||||
if (result === undefined) return [];
|
||||
}
|
||||
return result !== undefined ? [result] : [];
|
||||
} catch(e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 导出 API
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* load - cheerio 兼容接口
|
||||
*/
|
||||
function load(html) {
|
||||
if (!html) html = '';
|
||||
|
||||
function createEmpty() {
|
||||
return {
|
||||
each: function() {},
|
||||
find: function() { return createEmpty(); },
|
||||
attr: function() { return ''; },
|
||||
text: function() { return ''; },
|
||||
html: function() { return ''; },
|
||||
toArray: function() { return []; }
|
||||
};
|
||||
}
|
||||
|
||||
function wrapElement(el) {
|
||||
if (!el) return createEmpty();
|
||||
return {
|
||||
find: function(selector) {
|
||||
var inner = findElements(el.innerHTML || el.outerHTML || '', selector);
|
||||
return inner.length > 0 ? wrapElement(inner[0]) : createEmpty();
|
||||
},
|
||||
attr: function(name) {
|
||||
return (el.attr && el.attr(name)) || (el.attributes && (el.attributes[name] || el.attributes[name.toLowerCase()])) || '';
|
||||
},
|
||||
text: function() {
|
||||
var t = (el.text && el.text()) || '';
|
||||
return typeof t === 'string' ? t.trim() : '';
|
||||
},
|
||||
html: function() {
|
||||
return (el.innerHTML || '');
|
||||
},
|
||||
toArray: function() {
|
||||
return [el];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function query(sel) {
|
||||
if (typeof sel === 'string') {
|
||||
var elements = findElements(html, sel);
|
||||
return {
|
||||
each: function(cb) {
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
cb(i, elements[i]);
|
||||
}
|
||||
},
|
||||
find: function(selector) {
|
||||
var first = elements[0];
|
||||
return first ? wrapElement(first).find(selector) : createEmpty();
|
||||
},
|
||||
attr: function(name) {
|
||||
var first = elements[0];
|
||||
return first ? wrapElement(first).attr(name) : '';
|
||||
},
|
||||
text: function() {
|
||||
var first = elements[0];
|
||||
return first ? wrapElement(first).text() : '';
|
||||
},
|
||||
html: function() {
|
||||
var first = elements[0];
|
||||
return first ? (first.innerHTML || '') : '';
|
||||
},
|
||||
toArray: function() {
|
||||
return elements;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (sel && (sel.innerHTML !== undefined || sel.outerHTML !== undefined)) {
|
||||
return wrapElement(sel);
|
||||
}
|
||||
return createEmpty();
|
||||
}
|
||||
|
||||
query.html = function() { return html; };
|
||||
query.text = function() { return cleanText(html.replace(/<[^>]+>/g, ' ')); };
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* pdfa - 解析 HTML 返回匹配选择器的所有元素数组
|
||||
*/
|
||||
function pdfa(html, parse) {
|
||||
if (!html || !parse) return [];
|
||||
|
||||
try {
|
||||
parse = parseHikerToJq(parse, false);
|
||||
var parts = parse.split(' ');
|
||||
var currentElements = [html];
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var selector = parts[i];
|
||||
var newElements = [];
|
||||
|
||||
for (var j = 0; j < currentElements.length; j++) {
|
||||
var elements;
|
||||
if (typeof currentElements[j] === 'string') {
|
||||
elements = findElements(currentElements[j], selector);
|
||||
} else if (currentElements[j].innerHTML !== undefined) {
|
||||
elements = findElements(currentElements[j].innerHTML, selector);
|
||||
} else {
|
||||
elements = findElements(currentElements[j], selector);
|
||||
}
|
||||
newElements = newElements.concat(elements);
|
||||
}
|
||||
|
||||
currentElements = newElements;
|
||||
if (currentElements.length === 0) break;
|
||||
}
|
||||
|
||||
return currentElements.map(function(el) {
|
||||
return el.outerHTML;
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('[pdfa] 异常: ' + e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pdfh - 解析 HTML 返回匹配选择器的第一个元素的文本/属性
|
||||
*/
|
||||
function pdfh(html, parse, baseUrl) {
|
||||
if (!html || !parse) return '';
|
||||
|
||||
try {
|
||||
if (parse === 'body&&Text' || parse === 'Text') {
|
||||
var tempEl = new HtmlElement('body', {}, html, html);
|
||||
return cleanText(tempEl.text());
|
||||
}
|
||||
if (parse === 'body&&Html' || parse === 'Html') {
|
||||
return html;
|
||||
}
|
||||
|
||||
var parts = parse.split('&&');
|
||||
var option = null;
|
||||
var excludeTag = null;
|
||||
var selectors = [];
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i].trim();
|
||||
if (!part) continue;
|
||||
if (part.toLowerCase() === 'body') continue;
|
||||
|
||||
if (part.indexOf('--') > -1) {
|
||||
var excludeParts = part.split('--');
|
||||
part = excludeParts[0];
|
||||
excludeTag = excludeParts[1];
|
||||
}
|
||||
|
||||
if (i === parts.length - 1) {
|
||||
var isAttr = (part === 'Text' || part === 'text' ||
|
||||
part === 'Html' || part === 'html' ||
|
||||
/^[\w-]+(\|\|[\w-]+)*$/.test(part));
|
||||
var commonTags = /^(div|span|p|a|img|ul|li|ol|h[1-6]|table|tr|td|th|tbody|thead|tfoot|body|head|html|section|article|nav|aside|header|footer|main|form|input|button|select|option|textarea|label|dl|dt|dd|figure|figcaption|video|audio|source|canvas|svg|iframe|script|style|link|meta|br|hr)$/i;
|
||||
|
||||
if (isAttr && !commonTags.test(part.split('||')[0].split(':')[0])) {
|
||||
option = part;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var subParts = part.split(/\s+/);
|
||||
for (var j = 0; j < subParts.length; j++) {
|
||||
if (subParts[j]) selectors.push(subParts[j]);
|
||||
}
|
||||
}
|
||||
|
||||
var currentHtml = html;
|
||||
var element = null;
|
||||
|
||||
for (var k = 0; k < selectors.length; k++) {
|
||||
var elements = findElements(currentHtml, selectors[k]);
|
||||
if (elements.length === 0) return '';
|
||||
element = elements[0];
|
||||
currentHtml = element.innerHTML;
|
||||
}
|
||||
|
||||
if (!element) return '';
|
||||
|
||||
var processedHtml = element.innerHTML;
|
||||
if (excludeTag) {
|
||||
var excludeRegex = new RegExp('<' + excludeTag + '[^>]*>[\\s\\S]*?</' + excludeTag + '>', 'gi');
|
||||
processedHtml = processedHtml.replace(excludeRegex, '');
|
||||
var selfCloseRegex = new RegExp('<' + excludeTag + '[^>]*/>', 'gi');
|
||||
processedHtml = processedHtml.replace(selfCloseRegex, '');
|
||||
}
|
||||
|
||||
if (option) {
|
||||
if (option === 'Text' || option === 'text') {
|
||||
var tempElement = new HtmlElement(element.tagName, element.attributes, processedHtml, '');
|
||||
return cleanText(tempElement.text());
|
||||
}
|
||||
if (option === 'Html' || option === 'html') {
|
||||
return processedHtml;
|
||||
}
|
||||
|
||||
var attrOptions = option.split('||');
|
||||
var attrVal = '';
|
||||
|
||||
for (var m = 0; m < attrOptions.length; m++) {
|
||||
var opt = attrOptions[m].trim();
|
||||
attrVal = element.attr(opt);
|
||||
|
||||
if (/style/i.test(opt) && attrVal && attrVal.indexOf('url(') > -1) {
|
||||
var urlMatch = attrVal.match(/url\((['"]?)([^)]+)\1\)/);
|
||||
if (urlMatch) attrVal = urlMatch[2];
|
||||
}
|
||||
|
||||
if (attrVal && baseUrl && regexTest(URLJOIN_ATTR.source, opt) && !regexTest(SPECIAL_URL.source, attrVal)) {
|
||||
attrVal = attrVal.indexOf('http') > -1 ?
|
||||
attrVal.slice(attrVal.indexOf('http')) :
|
||||
urlJoin(baseUrl, attrVal);
|
||||
}
|
||||
|
||||
if (attrVal) break;
|
||||
}
|
||||
|
||||
return attrVal;
|
||||
}
|
||||
|
||||
return element.innerHTML;
|
||||
} catch (e) {
|
||||
console.log('[pdfh] 异常: ' + e.message);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pd - 增强版的 pdfh,自动处理URL拼接并解码HTML实体
|
||||
*/
|
||||
function pd(html, parse, baseUrl) {
|
||||
if (!baseUrl) {
|
||||
baseUrl = (typeof MY_URL !== 'undefined' && MY_URL) ? MY_URL :
|
||||
(typeof HOST !== 'undefined' && HOST) ? HOST : '';
|
||||
}
|
||||
|
||||
var result = pdfh(html, parse, baseUrl);
|
||||
|
||||
if (result) {
|
||||
result = result.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&#(\d+);/g, function(match, dec) {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
|
||||
if (!result && parse && parse.includes('&&')) {
|
||||
var parts = parse.split('&&');
|
||||
var lastPart = parts[parts.length - 1];
|
||||
|
||||
if (/data-original|data-src|src|original/.test(lastPart)) {
|
||||
var possibleAttrs = ['data-original', 'data-src', 'src', 'original'];
|
||||
for (var i = 0; i < possibleAttrs.length; i++) {
|
||||
var attr = possibleAttrs[i];
|
||||
var newParse = parts.slice(0, -1).concat(attr).join('&&');
|
||||
var tempResult = pdfh(html, newParse, baseUrl);
|
||||
if (tempResult) {
|
||||
tempResult = tempResult.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/&#(\d+);/g, function(match, dec) {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
result = tempResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result && baseUrl && regexTest(URLJOIN_ATTR.source, parse) && !regexTest(SPECIAL_URL.source, result)) {
|
||||
if (result.indexOf('http') > -1) {
|
||||
result = result.slice(result.indexOf('http'));
|
||||
} else {
|
||||
result = urlJoin(baseUrl, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* pdfl - 解析HTML获取列表数据
|
||||
*/
|
||||
function pdfl(html, parse, listText, listUrl, baseUrl) {
|
||||
if (!html || !parse) return [];
|
||||
|
||||
var elements = pdfa(html, parse);
|
||||
var results = [];
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var itemHtml = elements[i];
|
||||
var title = pdfh(itemHtml, listText, baseUrl);
|
||||
var url = pd(itemHtml, listUrl, baseUrl);
|
||||
if (title || url) {
|
||||
results.push(title + '$' + url);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* pjfh - 解析JSON获取单个值
|
||||
*/
|
||||
function pjfh(json, parse, addUrl, baseUrl) {
|
||||
if (!json || !parse) return '';
|
||||
|
||||
try {
|
||||
if (typeof json === 'string') {
|
||||
json = JSON.parse(json);
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('[pjfh] JSON解析失败: ' + e.message);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!parse.startsWith('$.')) parse = '$.' + parse;
|
||||
|
||||
var result = '';
|
||||
var paths = parse.split('||');
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
var queryResult = jsonpath.query(json, path);
|
||||
result = Array.isArray(queryResult) ? (queryResult[0] || '') : (queryResult || '');
|
||||
if (addUrl && result && baseUrl) {
|
||||
result = urlJoin(baseUrl, result);
|
||||
}
|
||||
if (result) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* pj - 解析JSON并自动拼接URL
|
||||
*/
|
||||
function pj(json, parse, baseUrl) {
|
||||
if (!baseUrl) {
|
||||
baseUrl = (typeof MY_URL !== 'undefined' && MY_URL) ? MY_URL :
|
||||
(typeof HOST !== 'undefined' && HOST) ? HOST : '';
|
||||
}
|
||||
return pjfh(json, parse, true, baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* pjfa - 解析JSON获取数组结果
|
||||
*/
|
||||
function pjfa(json, parse) {
|
||||
if (!json || !parse) return [];
|
||||
|
||||
try {
|
||||
if (typeof json === 'string') {
|
||||
json = JSON.parse(json);
|
||||
}
|
||||
} catch(e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!parse.startsWith('$.')) parse = '$.' + parse;
|
||||
|
||||
var result = jsonpath.query(json, parse);
|
||||
if (Array.isArray(result) && Array.isArray(result[0]) && result.length === 1) {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
return result || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Jsoup 类 - 兼容原有接口
|
||||
*/
|
||||
function Jsoup(MY_URL) {
|
||||
this.MY_URL = MY_URL || '';
|
||||
this.pdfh_html = '';
|
||||
this.pdfa_html = '';
|
||||
}
|
||||
|
||||
Jsoup.prototype = {
|
||||
test: function(pattern, text) {
|
||||
return regexTest(pattern, text);
|
||||
},
|
||||
contains: function(text, match) {
|
||||
return contains(text, match);
|
||||
},
|
||||
parseHikerToJq: parseHikerToJq,
|
||||
getParseInfo: getParseInfo,
|
||||
reorderAdjacentLtAndGt: reorderAdjacentLtAndGt,
|
||||
parseText: function(text) {
|
||||
return cleanText(text);
|
||||
},
|
||||
pdfa: function(html, parse) {
|
||||
return pdfa(html, parse);
|
||||
},
|
||||
pdfl: function(html, parse, listText, listUrl, MY_URL) {
|
||||
return pdfl(html, parse, listText, listUrl, MY_URL || this.MY_URL);
|
||||
},
|
||||
pdfh: function(html, parse, baseUrl) {
|
||||
return pdfh(html, parse, baseUrl || this.MY_URL);
|
||||
},
|
||||
pd: function(html, parse, baseUrl) {
|
||||
return pd(html, parse, baseUrl || this.MY_URL);
|
||||
},
|
||||
pjfh: function(json, parse, addUrl) {
|
||||
return pjfh(json, parse, addUrl, this.MY_URL);
|
||||
},
|
||||
pj: function(json, parse) {
|
||||
return pj(json, parse, this.MY_URL);
|
||||
},
|
||||
pjfa: function(json, parse) {
|
||||
return pjfa(json, parse);
|
||||
},
|
||||
pq: function(html) {
|
||||
return load(html);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 导出到全局
|
||||
// ============================================
|
||||
|
||||
var exports = {
|
||||
// HTML解析
|
||||
pdfa: pdfa,
|
||||
pdfh: pdfh,
|
||||
pd: pd,
|
||||
pdfl: pdfl,
|
||||
load: load,
|
||||
|
||||
// JSON解析
|
||||
pjfh: pjfh,
|
||||
pj: pj,
|
||||
pjfa: pjfa,
|
||||
jsonpath: jsonpath,
|
||||
|
||||
// 工具函数
|
||||
urlJoin: urlJoin,
|
||||
cleanText: cleanText,
|
||||
|
||||
// 类
|
||||
Jsoup: Jsoup,
|
||||
HtmlElement: HtmlElement,
|
||||
|
||||
// 兼容别名
|
||||
pdfa_advanced: pdfa,
|
||||
pdfh_advanced: pdfh,
|
||||
pd_advanced: pd,
|
||||
pdfa_lite: pdfa,
|
||||
pdfh_lite: pdfh,
|
||||
pd_lite: pd
|
||||
};
|
||||
|
||||
// 创建 jsp 对象
|
||||
var jsp = {
|
||||
pdfa: pdfa,
|
||||
pdfh: pdfh,
|
||||
pd: pd,
|
||||
pdfl: pdfl,
|
||||
pjfh: pjfh,
|
||||
pj: pj,
|
||||
pjfa: pjfa,
|
||||
Jsoup: Jsoup
|
||||
};
|
||||
|
||||
// 设置到全局
|
||||
for (var key in exports) {
|
||||
global[key] = exports[key];
|
||||
}
|
||||
|
||||
global.jsp = jsp;
|
||||
global.cheerio = { load: load };
|
||||
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
for (var key in exports) {
|
||||
globalThis[key] = exports[key];
|
||||
}
|
||||
globalThis.jsp = jsp;
|
||||
globalThis.cheerio = { load: load };
|
||||
}
|
||||
|
||||
console.log('[统一解析器] HTML/JSON 解析器已加载');
|
||||
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : this));
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 网盘服务模块集合
|
||||
* 统一导入和导出各种网盘服务提供商的实现
|
||||
*
|
||||
* 支持的网盘服务:
|
||||
* - Ali: 阿里云盘服务
|
||||
* - Baidu: 百度网盘服务
|
||||
* - Baidu2: 百度网盘服务(第二版本)
|
||||
* - Cloud: 天翼云盘服务
|
||||
* - Pan: 123网盘服务
|
||||
* - Quark: 夸克网盘服务
|
||||
* - UC: UC网盘服务
|
||||
* - Yun: 115网盘服务
|
||||
*
|
||||
* @example
|
||||
* import pans from './pans.js';
|
||||
* const aliPan = new pans.Ali(config);
|
||||
* const files = await aliPan.getFileList();
|
||||
*/
|
||||
//import "./cookie.js"; // 网盘ck服务
|
||||
|
||||
// 导入各种网盘服务实现
|
||||
//import {Ali} from './pan/ali.js'; // 阿里云盘服务
|
||||
import {Baidu} from "./pan/baidu.js"; // 百度网盘服务
|
||||
//import {Baidu2} from "./pan/baidu2.js"; // 百度网盘服务(第二版本)
|
||||
//import {Cloud} from "./pan/cloud.js"; // 天翼云盘服务
|
||||
//import {Pan} from "./pan/pan123.js"; // 123网盘服务
|
||||
import {Quark} from "./pan/quark.js"; // 夸克网盘服务
|
||||
import {UC} from "./pan/uc.js"; // UC网盘服务
|
||||
//import {Yun} from "./pan/yun.js"; // 115网盘服务
|
||||
|
||||
// 统一导出所有网盘服务
|
||||
//export default {Ali, Baidu, Baidu2, Cloud, Pan, Quark, UC, Yun}
|
||||
export { Quark, Baidu, UC}
|
||||
// export default { Quark, Baidu}
|
||||
|
||||
Reference in New Issue
Block a user