JavaScript:使用含有Iframe的Xpath定位网页元素(非跨域)
一、实现思路
1.1 在任意Document对象中使用Xpath定位
注:如果是Iframe,需要用[elem].contentWindow.document获取Document对象
function $x(str_xpath, doc=document) {
var xresult = doc.evaluate(str_xpath, doc, null, XPathResult.ANY_TYPE, null);
var xnodes = [], xres;
while (xres = xresult.iterateNext()) {
xnodes.push(xres);
}
return xnodes;
}
1.2 针对Xpath语句按照Iframe进行分段
情况1://iframe[@id=“test”]//input 拆分成 //iframe[@id=“test”] 和 //input
情况2://something::iframe//input 拆分成 //something::iframe 和 //input
注:因为可能存在多个Iframe层级,所以需要用递归进行拆分
// 将Xpath语句按照Iframe进行分段
function spilt_xpath_by_iframe(str_xpath) {
let iframeRegex = /(?:\/+iframe|\/\/.*?::iframe)(\[.*?\])?/;
let iframeMatch = str_xpath.match(iframeRegex);
if (!iframeMatch) return str_xpath ? [str_xpath] : [];
let iframeXpath = str_xpath.substring(0, str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
let innerXpath = str_xpath.substring(str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
return [iframeXpath].concat(spilt_xpath_by_iframe(innerXpath));
}
二、整合功能(适配多级Iframe结构的Xpath查找)
2.1 完整代码
// Xpath查找方法(默认在当前document查找)
function $x(str_xpath, doc=document) {
// 匹配Xpath中的Iframe节点
let iframeMatch = str_xpath.match(/(?:\/+iframe|\/\/.*?::iframe)(\[.*?\])?/);
// 实际执行的Xpath、递归执行的Xpath
let exec_xpath = str_xpath, inner_xpath = '';
// 判断是否匹配iframe
if (iframeMatch) {
exec_xpath = str_xpath.substring(0, str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
inner_xpath = str_xpath.substring(str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
}
// 如果执行的Xpath语句为空,则直接返回空数组
if (!doc || !exec_xpath) return [];
// 执行Xpath匹配
let xresult = doc.evaluate(exec_xpath, doc, null, XPathResult.ANY_TYPE, null);
// 迭代匹配结果
let xnodes = [], xres;
while (xres = xresult.iterateNext()) {
// 判断是否为Iframe节点,且需要继续匹配
if (inner_xpath && xres.contentWindow) {
// 以Iframe的contentWindow.document作为参数,递归进行匹配
xnodes = xnodes.concat($x(inner_xpath, xres.contentWindow.document));
} else {
// 保存匹配结果
xnodes.push(xres);
}
}
// 返回匹配结果
return xnodes;
}
三、实战案例(以获取QQ邮箱中收件列表为例)
3.1 编写定位Iframe元素的Xpath语句
通过Xpath语句定位Iframe://iframe[@id=‘mainFrame’]
3.2 编写定位所有邮件的Xpath语句
通过Xpath获取所有邮件://table[contains(@class, ‘i’) and not(contains(@class, ‘bold’))]
3.3 合并Xpath语句,并使用新封装的Xpath查找方法
完整Xpath://iframe[@id=‘mainFrame’]//table[contains(@class, ‘i’) and not(contains(@class, ‘bold’))]
// 使用Xpath获取所有邮件
$x("//iframe[@id='mainFrame']//table[contains(@class, 'i') and not(contains(@class, 'bold'))]");
// 封装获取邮件信息方法
function get_email_infos(){
var email_infos = [];
var emails = $x("//iframe[@id='mainFrame']//table[contains(@class, 'i') and not(contains(@class, 'bold'))]");
for(var k in emails){
var email_title = emails[k].getElementsByClassName("tt")[0].textContent;
var email_desc = emails[k].getElementsByClassName("no")[0].textContent;
email_infos.push({'title': email_title, 'desc': email_desc});
}
return email_infos;
}
3.4 完整案例(可直接在控制台运行)
// Xpath查找方法(默认在当前document查找)
function $x(str_xpath, doc=document) {
// 匹配Xpath中的Iframe节点
let iframeMatch = str_xpath.match(/(?:\/+iframe|\/\/.*?::iframe)(\[.*?\])?/);
// 实际执行的Xpath、递归执行的Xpath
let exec_xpath = str_xpath, inner_xpath = '';
// 判断是否匹配iframe
if (iframeMatch) {
exec_xpath = str_xpath.substring(0, str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
inner_xpath = str_xpath.substring(str_xpath.indexOf(iframeMatch[0]) + iframeMatch[0].length);
}
// 如果执行的Xpath语句为空,则直接返回空数组
if (!doc || !exec_xpath) return [];
// 执行Xpath匹配
let xresult = doc.evaluate(exec_xpath, doc, null, XPathResult.ANY_TYPE, null);
// 迭代匹配结果
let xnodes = [], xres;
while (xres = xresult.iterateNext()) {
// 判断是否为Iframe节点,且需要继续匹配
if (inner_xpath && xres.contentWindow) {
// 以Iframe的contentWindow.document作为参数,递归进行匹配
xnodes = xnodes.concat($x(inner_xpath, xres.contentWindow.document));
} else {
// 保存匹配结果
xnodes.push(xres);
}
}
// 返回匹配结果
return xnodes;
}
// 封装获取邮件信息方法
function get_email_infos(){
var email_infos = [];
var emails = $x("//iframe[@id='mainFrame']//table[contains(@class, 'i') and not(contains(@class, 'bold'))]");
for(var k in emails){
var email_title = emails[k].getElementsByClassName("tt")[0].textContent;
var email_desc = emails[k].getElementsByClassName("no")[0].textContent;
email_infos.push({'title': email_title, 'desc': email_desc});
}
return email_infos;
}
get_email_infos()