HiveNetWebUtils.parser.html 源代码

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# Copyright 2019 黎慧剑
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
html代码解析器

@module html_parser
@file html_parser.py
"""

import sys
import os
import lxml.etree as ET
from HiveNetCore.xml_hivenet import SimpleXml, EnumXmlObjType
# 根据当前文件路径将包路径纳入, 在非安装的情况下可以引用到
sys.path.append(os.path.abspath(
    os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
)


__MOUDLE__ = 'html'  # 模块名
__DESCRIPT__ = u'html代码解析器'  # 模块描述
__VERSION__ = '0.1.0'  # 版本
__AUTHOR__ = u'黎慧剑'  # 作者
__PUBLISH__ = '2021.07.15'  # 发布日期


[文档]class HtmlElement(object): """ 自己封装的Html元素, 方法与 selenium 的 WebElement 一致 """
[文档] def __init__(self, elemnet: ET._Element): """ Html元素 @param {lxml.etree.Element} elemnet - lxml获取到的元素 """ self.element = elemnet
############################# # 属性 ############################# @property def id(self): """ 获取元素的id @property {str} - id, 不存在返回None """ return self.element.get('id', None) @property def tag_name(self): """ 获取元素标签名 @property {str} """ return self.element.tag @property def text(self): """ 获取元素文本 @property {str} """ return self.element.text @property def parent(self): """ 获取元素的父元素 @property {HtmlElement} - 如果找不到返回None """ _parent = self.element.getparent() if _parent is None: return None else: return HtmlElement(_parent) @property def position(self) -> int: """ 获取当前节点在父节点下的位置 @property {int} - 返回位置, 如果是顶层的情况返回None """ _parent = self.parent if _parent is None: return None else: return _parent.element.index(self.element) ############################# # 公共函数 #############################
[文档] def get_property(self, name: str): """ 获取属性值 @param {str} name - 获取对象属性值 @returns {Any} - 返回属性值, 不存在返回None """ return self.element.get(name, None)
[文档] def get_attribute(self, name: str): """ 获取属性值 @param {str} name - 获取对象属性值 @returns {Any} - 返回属性值, 不存在返回None """ return self.element.get(name, None)
[文档] def is_selected(self): """ 获取元素是否被选中 注: checkbox或radio @returns {bool} - 是否被选中 """ if self.element.tag.lower() in ('radio', 'checkbox'): return self.element.get('selected', None) is not None else: return self.element.get('selected', default='false') == 'true'
[文档] def is_enabled(self): """ 判断元素是否启用 @returns {bool} - 是否启用 """ return self.element.get('enabled', default='true') == 'true'
[文档] def is_same_with(self, el, with_path: bool = True) -> bool: """ 比较当前节点和另一个节点是否同一个 @param {HtmlElement} element - 要比较的元素 @param {bool} with_path=True - 是否比较路径 @returns {bool} - 比较结果 """ _a = self.element _b = el.element # 比较属性 for attrib in _a.attrib: if _a.get(attrib) != _b.get(attrib): return False # 比较其他值 if _a.text != _b.text: return False if _a.tag != _b.tag: return False if _a.prefix != _b.prefix: return False if _a.tail != _b.tail: return False if _a.values() != _b.values(): # may be redundant to the attrib matching return False if _a.keys() != _b.keys(): # may also be redundant to the attrib matching return False # 比较路径 _pa = self _pb = el while with_path: # 比较位置 _pos_a = _pa.position _pos_b = _pb.position if _pos_a is None and _pos_b is None: # 已经是顶层 break if _pos_a != _pos_b: return False # 比较父节点, 只需比较tag _pa = _pa.parent _pb = _pb.parent if _pa.tag_name != _pb.tag_name: return False return True
[文档]class HtmlParser(object): """ Html解析器 基于lxml实现的html文档的解析, 元素查找方式与 WebDriver 相似, 以便解析功能的兼容 """
[文档] def __init__(self, html: str, use_xpath2=False): """ html解析器 @param {str} html - 要解析的html代码 @param {bool} use_xpath2=False - 是否使用xpath2.0, 默认为False """ self.html_doc = SimpleXml( html, obj_type=EnumXmlObjType.String, parser='html', use_xpath2=use_xpath2 )
############################# # 元素查找 #############################
[文档] def find_elements(self, steps: list, parent: HtmlElement = None) -> list: """ 按步骤逐级查询对象 @param {list} steps - 要查询的步骤列表, 每一个步骤为一个操作列表, 第0个为操作类型, 后面为操作参数 [ ['pos': 0], # 获取当前清单中指定位置的元素 ['children'], # 获取当前清单元素中的所有子元素 ['id', 'myId'], # 通过id获取元素 ['xpath', '//img[@id="dracga" and @style]'], # 通过xpaht获取元素 ['name', 'myName'], # 通过元素的name属性获取 ['tag_name', 'img'], # 通过元素的标签名获取 ['class_name', 'styleClass'], # 通过元素的class名获取 ['css_selector', '#kw'], # 通过css选择器方式获取, id用#kw, class用.s_ipt, 与css的简写方式相同 ['link_text', 'hao123'], # 通过超文本链接上的文字信息来定位元素 ['partial_link_text', 'hao'] # 通过超文本连接上的文字的一部分关键字信息来定位元素 ] @param {HtmlElement} parent=None - 开始的父节点, 如果不传代表重新开始 @returns {list} - 返回查找到的对象列表 注: 对象类型为 HtmlElement """ # 转换为底层类型 _parent = None if parent is not None: _parent = parent.element # 查找第一个步骤 _elements = self._find_elements_step(steps[0], parent=_parent) # 循环处理后面每个步骤(从第2个开始) for _i in range(1, len(steps)): if len(_elements) == 0: # 没有找到任何元素, 直接退出循环 break # 执行子元素的查找处理 _step = steps[_i] _op = _step[0] _new_elements = list() if _op == 'pos': # 获取当前列表中指定位置的元素 _new_elements.append(_elements[_step[1]]) else: # 遍历每个元素进行查找 for _element in _elements: _new_elements.extend( self._find_elements_step(_step, parent=_element) ) # 更新清单 _elements = _new_elements # 转换为标准类型并返回结果 _trans_list = list() for _element in _elements: _trans_list.append(HtmlElement(_element)) return _trans_list
############################# # 内部函数 ############################# def _find_elements_step(self, step: list, parent: ET._Element = None) -> list: """ 单步查找元素 @param {list} step - 执行步骤参数, 参数数组中的第0个是查找指令, 其他数组对象是查找参数 ['children'] # 获取当前清单元素中的所有子元素 ['id', 'myId'] # 通过id获取元素 ['xpath', '//img[@id="dracga" and @style]'] # 通过xpaht获取元素 ['name', 'myName'] # 通过元素的name属性获取 ['tag_name', 'img'] # 通过元素的标签名获取 ['class_name', 'styleClass'] # 通过元素的class名获取 ['css_selector', '#kw'] # 通过css选择器方式获取, id用#kw, class用.s_ipt, 与css的简写方式相同 注: 暂不支持 ['link_text', 'hao123'] # 通过超文本链接上的文字信息来定位元素 ['partial_link_text', 'hao'] # 通过超文本连接上的文字的一部分关键字信息来定位元素 @param {lxml.etree._Element} parent=None - 父节点, 如果不传代表全局搜索 @returns {list} - 返回查找到的对象列表 注: 对象类型为 lxml.etree._Element """ # 处理查找操作 _elements = list() _op = step[0] # 查找指令 if _op == 'children': # 获取所有子元素 _elements = self._find_elements_by_xpath('.//*', parent=parent) elif _op == 'id': # 通过id查找 _elements = self._find_elements_by_xpath('.//*[@id="%s"]' % step[1], parent=parent) elif _op == 'xpath': _elements = self._find_elements_by_xpath(step[1], parent=parent) elif _op == 'name': _elements = self._find_elements_by_xpath('.//*[@name="%s"]' % step[1], parent=parent) elif _op == 'tag_name': _elements = self._find_elements_by_xpath('.//%s' % step[1], parent=parent) elif _op == 'class_name': _elements = self._find_elements_by_xpath( './/*[@class="{0}" or starts-with(@class, "{0} ") or contains(@class, " {0} ") or ends-with(@class, " {0}")]'.format(step[1]), parent=parent ) elif _op == 'link_text': _elements = self._find_elements_by_xpath( './/*[@href and text()="%s"]' % step[1], parent=parent ) elif _op == 'partial_link_text': _elements = self._find_elements_by_xpath( './/*[@href and contains(string(), "%s")]' % step[1], parent=parent ) elif _op == 'css_selector': if parent is None: parent = self.html_doc.root _elements = parent.cssselect(step[1]) else: # 没有匹配到类型 raise KeyError('not support find elements operator [%s]' % _op) return _elements def _find_elements_by_xpath(self, xpath: str, parent: ET._Element = None) -> list: """ 通过XPath获取元素 @param {str} xpath - xpath字符串 @param {lxml.etree.Element} parent=None - 父节点 @returns {list} - 返回查找到的对象列表 注: 对象类型为 lxml.etree.Element """ if parent is None: # 全局查找 return self.html_doc.get_nodes(xpath) else: # 查找子节点 return self.html_doc.get_childnodes_on_node(parent, xpath)
if __name__ == '__main__': # 当程序自己独立运行时执行的操作 # 打印版本信息 print(('模块名: %s - %s\n' '作者: %s\n' '发布日期: %s\n' '版本: %s' % (__MOUDLE__, __DESCRIPT__, __AUTHOR__, __PUBLISH__, __VERSION__)))