diff --git a/DrissionPage/action_chains.py b/DrissionPage/action_chains.py index 1d938b3..5ada2ac 100644 --- a/DrissionPage/action_chains.py +++ b/DrissionPage/action_chains.py @@ -36,6 +36,8 @@ class ActionChains: ele_loc = ele_or_loc.location if offset_x or offset_y else ele_or_loc.midpoint lx = ele_loc['x'] + offset_x ly = ele_loc['y'] + offset_y + else: + raise TypeError('ele_or_loc参数只能接受坐标(x, y)或ChromiumElement对象。') if not _location_in_viewport(self.page, lx, ly): self.page.scroll.to_location(lx, ly) diff --git a/DrissionPage/chrome_element.py b/DrissionPage/chromium_element.py similarity index 94% rename from DrissionPage/chrome_element.py rename to DrissionPage/chromium_element.py index 62a0822..2a01094 100644 --- a/DrissionPage/chrome_element.py +++ b/DrissionPage/chromium_element.py @@ -17,7 +17,7 @@ from .base import DrissionElement, BaseElement from .common import make_absolute_link, get_loc, get_ele_txt, format_html, is_js_func, _location_in_viewport -class ChromeElement(DrissionElement): +class ChromiumElement(DrissionElement): """ChromePage页面对象中的元素对象""" def __init__(self, page, node_id: str = None, obj_id: str = None): @@ -46,7 +46,7 @@ class ChromeElement(DrissionElement): def __call__(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> Union['ChromeElement', str, None]: + timeout: float = None) -> Union['ChromiumElement', str, None]: """在内部查找元素 \n 例:ele2 = ele1('@id=ele_id') \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 @@ -98,7 +98,7 @@ class ChromeElement(DrissionElement): """返回未格式化处理的元素内文本""" return self.prop('innerText') - # -----------------driver独有属性------------------- + # -----------------d模式独有属性------------------- @property def obj_id(self) -> str: """返回js中的object id""" @@ -193,7 +193,7 @@ class ChromeElement(DrissionElement): self._scroll = ChromeScroll(self) return self._scroll - def parent(self, level_or_loc: Union[tuple, str, int] = 1) -> Union['ChromeElement', None]: + def parent(self, level_or_loc: Union[tuple, str, int] = 1) -> Union['ChromiumElement', None]: """返回上面某一级父元素,可指定层数或用查询语法定位 \n :param level_or_loc: 第几级父元素,或定位符 :return: 上级元素对象 @@ -203,7 +203,7 @@ class ChromeElement(DrissionElement): def prev(self, index: int = 1, filter_loc: Union[tuple, str] = '', - timeout: float = 0) -> Union['ChromeElement', str, None]: + timeout: float = 0) -> Union['ChromiumElement', str, None]: """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 \n :param index: 前面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -215,7 +215,7 @@ class ChromeElement(DrissionElement): def next(self, index: int = 1, filter_loc: Union[tuple, str] = '', - timeout: float = 0) -> Union['ChromeElement', str, None]: + timeout: float = 0) -> Union['ChromiumElement', str, None]: """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 \n :param index: 后面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -227,7 +227,7 @@ class ChromeElement(DrissionElement): def before(self, index: int = 1, filter_loc: Union[tuple, str] = '', - timeout: float = None) -> Union['ChromeElement', str, None]: + timeout: float = None) -> Union['ChromiumElement', str, None]: """返回当前元素前面的一个元素,可指定筛选条件和第几个。查找范围不限兄弟元素,而是整个DOM文档 \n :param index: 前面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -239,7 +239,7 @@ class ChromeElement(DrissionElement): def after(self, index: int = 1, filter_loc: Union[tuple, str] = '', - timeout: float = None) -> Union['ChromeElement', str, None]: + timeout: float = None) -> Union['ChromiumElement', str, None]: """返回当前元素后面的一个元素,可指定筛选条件和第几个。查找范围不限兄弟元素,而是整个DOM文档 \n :param index: 后面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -250,7 +250,7 @@ class ChromeElement(DrissionElement): def prevs(self, filter_loc: Union[tuple, str] = '', - timeout: float = 0) -> List[Union['ChromeElement', str]]: + timeout: float = 0) -> List[Union['ChromiumElement', str]]: """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 \n :param filter_loc: 用于筛选元素的查询语法 :param timeout: 查找元素的超时时间 @@ -260,7 +260,7 @@ class ChromeElement(DrissionElement): def nexts(self, filter_loc: Union[tuple, str] = '', - timeout: float = 0) -> List[Union['ChromeElement', str]]: + timeout: float = 0) -> List[Union['ChromiumElement', str]]: """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 \n :param filter_loc: 用于筛选元素的查询语法 :param timeout: 查找元素的超时时间 @@ -270,7 +270,7 @@ class ChromeElement(DrissionElement): def befores(self, filter_loc: Union[tuple, str] = '', - timeout: float = None) -> List[Union['ChromeElement', str]]: + timeout: float = None) -> List[Union['ChromiumElement', str]]: """返回当前元素后面符合条件的全部兄弟元素或节点组成的列表,可用查询语法筛选。查找范围不限兄弟元素,而是整个DOM文档 \n :param filter_loc: 用于筛选元素的查询语法 :param timeout: 查找元素的超时时间 @@ -279,7 +279,7 @@ class ChromeElement(DrissionElement): return super().befores(filter_loc, timeout) def wait_ele(self, - loc_or_ele: Union[str, tuple, 'ChromeElement'], + loc_or_ele: Union[str, tuple, 'ChromiumElement'], timeout: float = None) -> 'ChromeElementWaiter': """返回用于等待子元素到达某个状态的等待器对象 \n :param loc_or_ele: 可以是元素、查询字符串、loc元组 @@ -320,7 +320,7 @@ class ChromeElement(DrissionElement): def is_alive(self) -> bool: """返回元素是否仍在DOM中""" try: - self.attrs + d = self.attrs return True except Exception: return False @@ -362,6 +362,50 @@ class ChromeElement(DrissionElement): else: return attrs.get(attr, None) + def set_attr(self, attr: str, value: str) -> None: + """设置元素attribute属性 \n + :param attr: 属性名 + :param value: 属性值 + :return: None + """ + self.run_script(f'this.setAttribute(arguments[0], arguments[1]);', False, attr, str(value)) + + def remove_attr(self, attr: str) -> None: + """删除元素attribute属性 \n + :param attr: 属性名 + :return: None + """ + self.run_script(f'this.removeAttribute("{attr}");') + + def prop(self, prop: str) -> Union[str, int, None]: + """获取property属性值 \n + :param prop: 属性名 + :return: 属性值文本 + """ + p = self.page.driver.Runtime.getProperties(objectId=self._obj_id)['result'] + for i in p: + if i['name'] == prop: + if 'value' not in i or 'value' not in i['value']: + return None + + return format_html(i['value']['value']) + + def set_prop(self, prop: str, value: str) -> None: + """设置元素property属性 \n + :param prop: 属性名 + :param value: 属性值 + :return: None + """ + value = value.replace('"', r'\"') + self.run_script(f'this.{prop}="{value}";') + + def set_innerHTML(self, html: str) -> None: + """设置元素innerHTML \n + :param html: html文本 + :return: None + """ + self.set_prop('innerHTML', html) + def run_script(self, script: str, as_expr: bool = False, *args: Any) -> Any: """运行javascript代码 \n :param script: js文本 @@ -383,7 +427,7 @@ class ChromeElement(DrissionElement): def ele(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> Union['ChromeElement', str, None]: + timeout: float = None) -> Union['ChromiumElement', str, None]: """返回当前元素下级符合条件的第一个元素、属性或节点文本 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致 @@ -393,7 +437,7 @@ class ChromeElement(DrissionElement): def eles(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> List[Union['ChromeElement', str]]: + timeout: float = None) -> List[Union['ChromiumElement', str]]: """返回当前元素下级所有符合条件的子元素、属性或节点文本 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致 @@ -422,7 +466,7 @@ class ChromeElement(DrissionElement): def _ele(self, loc_or_str: Union[Tuple[str, str], str], timeout: float = None, - single: bool = True) -> Union['ChromeElement', str, None, List[Union['ChromeElement', str]]]: + single: bool = True) -> Union['ChromiumElement', str, None, List[Union['ChromiumElement', str]]]: """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间 @@ -431,43 +475,6 @@ class ChromeElement(DrissionElement): """ return make_chrome_ele(self, loc_or_str, single, timeout) - def prop(self, prop: str) -> Union[str, int, None]: - """获取property属性值 \n - :param prop: 属性名 - :return: 属性值文本 - """ - p = self.page.driver.Runtime.getProperties(objectId=self._obj_id)['result'] - for i in p: - if i['name'] == prop: - if 'value' not in i or 'value' not in i['value']: - return None - - return format_html(i['value']['value']) - - def set_prop(self, prop: str, value: str) -> None: - """设置元素property属性 \n - :param prop: 属性名 - :param value: 属性值 - :return: None - """ - value = value.replace("'", "\\'") - self.run_script(f'this.{prop}="{value}";') - - def set_attr(self, attr: str, value: str) -> None: - """设置元素attribute属性 \n - :param attr: 属性名 - :param value: 属性值 - :return: None - """ - self.run_script(f'this.setAttribute(arguments[0], arguments[1]);', False, attr, str(value)) - - def remove_attr(self, attr: str) -> None: - """删除元素attribute属性 \n - :param attr: 属性名 - :return: None - """ - self.run_script(f'this.removeAttribute("{attr}");') - def style(self, style: str, pseudo_ele: str = '') -> str: """返回元素样式属性值,可获取伪元素属性值 \n :param style: 样式属性名称 @@ -612,7 +619,7 @@ class ChromeElement(DrissionElement): if not by_js: self.page.scroll_to_see(self) - if self.is_in_view: + if self.is_in_viewport: midpoint = self.midpoint client_midpoint = self.client_midpoint client_x = client_midpoint['x'] @@ -698,7 +705,7 @@ class ChromeElement(DrissionElement): self.drag_to((offset_x, offset_y), speed, shake) def drag_to(self, - ele_or_loc: Union[tuple, 'ChromeElement'], + ele_or_loc: Union[tuple, 'ChromiumElement'], speed: int = 40, shake: bool = True) -> None: """拖拽当前元素,目标为另一个元素或坐标元组 \n @@ -708,7 +715,7 @@ class ChromeElement(DrissionElement): :return: None """ # x, y:目标点坐标 - if isinstance(ele_or_loc, ChromeElement): + if isinstance(ele_or_loc, ChromiumElement): midpoint = ele_or_loc.midpoint target_x = midpoint['x'] target_y = midpoint['y'] @@ -802,7 +809,7 @@ class ChromeElement(DrissionElement): class ChromeShadowRootElement(BaseElement): """ChromeShadowRootElement是用于处理ShadowRoot的类,使用方法和ChromeElement基本一致""" - def __init__(self, parent_ele: ChromeElement, obj_id: str): + def __init__(self, parent_ele: ChromiumElement, obj_id: str): super().__init__(parent_ele.page) self.parent_ele = parent_ele self._node_id = self._get_node_id(obj_id) @@ -813,7 +820,7 @@ class ChromeShadowRootElement(BaseElement): def __call__(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> Union[ChromeElement, None]: + timeout: float = None) -> Union[ChromiumElement, None]: """在内部查找元素 \n 例:ele2 = ele1('@id=ele_id') \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 @@ -880,7 +887,7 @@ class ChromeShadowRootElement(BaseElement): from threading import Thread Thread(target=_run_script, args=(self, script, as_expr, self.page.timeouts.script, args)).start() - def parent(self, level_or_loc: Union[str, int] = 1) -> ChromeElement: + def parent(self, level_or_loc: Union[str, int] = 1) -> ChromiumElement: """返回上面某一级父元素,可指定层数或用查询语法定位 \n :param level_or_loc: 第几级父元素,或定位符 :return: ChromeElement对象 @@ -903,7 +910,7 @@ class ChromeShadowRootElement(BaseElement): def next(self, index: int = 1, - filter_loc: Union[tuple, str] = '') -> Union[ChromeElement, str, None]: + filter_loc: Union[tuple, str] = '') -> Union[ChromiumElement, str, None]: """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 \n :param index: 第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -914,7 +921,7 @@ class ChromeShadowRootElement(BaseElement): def before(self, index: int = 1, - filter_loc: Union[tuple, str] = '') -> Union[ChromeElement, str, None]: + filter_loc: Union[tuple, str] = '') -> Union[ChromiumElement, str, None]: """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 \n :param index: 前面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -924,7 +931,7 @@ class ChromeShadowRootElement(BaseElement): return nodes[index - 1] if nodes else None def after(self, index: int = 1, - filter_loc: Union[tuple, str] = '') -> Union[ChromeElement, str, None]: + filter_loc: Union[tuple, str] = '') -> Union[ChromiumElement, str, None]: """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 \n :param index: 后面第几个查询结果元素 :param filter_loc: 用于筛选元素的查询语法 @@ -933,7 +940,7 @@ class ChromeShadowRootElement(BaseElement): nodes = self.afters(filter_loc=filter_loc) return nodes[index - 1] if nodes else None - def nexts(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromeElement, str]]: + def nexts(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromiumElement, str]]: """返回后面所有兄弟元素或节点组成的列表 \n :param filter_loc: 用于筛选元素的查询语法 :return: ChromeElement对象组成的列表 @@ -946,7 +953,7 @@ class ChromeShadowRootElement(BaseElement): xpath = f'xpath:./{loc}' return self.parent_ele.eles(xpath, timeout=0.1) - def befores(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromeElement, str]]: + def befores(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromiumElement, str]]: """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 \n :param filter_loc: 用于筛选元素的查询语法 :return: 本元素前面的元素或节点组成的列表 @@ -959,7 +966,7 @@ class ChromeShadowRootElement(BaseElement): xpath = f'xpath:./preceding::{loc}' return self.parent_ele.eles(xpath, timeout=0.1) - def afters(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromeElement, str]]: + def afters(self, filter_loc: Union[tuple, str] = '') -> List[Union[ChromiumElement, str]]: """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 \n :param filter_loc: 用于筛选元素的查询语法 :return: 本元素后面的元素或节点组成的列表 @@ -971,7 +978,7 @@ class ChromeShadowRootElement(BaseElement): def ele(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> Union[ChromeElement, None]: + timeout: float = None) -> Union[ChromiumElement, None]: """返回当前元素下级符合条件的第一个元素 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致 @@ -981,7 +988,7 @@ class ChromeShadowRootElement(BaseElement): def eles(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> List[ChromeElement]: + timeout: float = None) -> List[ChromiumElement]: """返回当前元素下级所有符合条件的子元素 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致 @@ -1006,7 +1013,7 @@ class ChromeShadowRootElement(BaseElement): def _ele(self, loc_or_str: Union[Tuple[str, str], str], timeout: float = None, - single: bool = True) -> Union['ChromeElement', None, List[ChromeElement]]: + single: bool = True) -> Union['ChromiumElement', None, List[ChromiumElement]]: """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间 @@ -1029,14 +1036,14 @@ class ChromeShadowRootElement(BaseElement): css_paths = [i.css_path[47:] for i in eles] if single: node_id = self.page.driver.DOM.querySelector(nodeId=self._node_id, selector=css_paths[0])['nodeId'] - return ChromeElement(self.page, node_id) if node_id else None + return ChromiumElement(self.page, node_id) if node_id else None else: results = [] for i in css_paths: node_id = self.page.driver.DOM.querySelector(nodeId=self._node_id, selector=i)['nodeId'] if node_id: - results.append(ChromeElement(self.page, node_id)) + results.append(ChromiumElement(self.page, node_id)) return results def _get_node_id(self, obj_id) -> str: @@ -1044,10 +1051,10 @@ class ChromeShadowRootElement(BaseElement): return self.page.driver.DOM.requestNode(objectId=obj_id)['nodeId'] -def make_chrome_ele(ele: ChromeElement, +def make_chrome_ele(ele: ChromiumElement, loc: Union[str, Tuple[str, str]], single: bool = True, - timeout: float = None) -> Union[ChromeElement, str, None, List[Union[ChromeElement, str]]]: + timeout: float = None) -> Union[ChromiumElement, str, None, List[Union[ChromiumElement, str]]]: """在chrome元素中查找 \n :param ele: ChromeElement对象 :param loc: 元素定位元组 @@ -1078,10 +1085,10 @@ def make_chrome_ele(ele: ChromeElement, return _find_by_css(ele, loc[1], single, timeout) -def _find_by_xpath(ele: ChromeElement, +def _find_by_xpath(ele: ChromiumElement, xpath: str, single: bool, - timeout: float) -> Union[ChromeElement, List[ChromeElement]]: + timeout: float) -> Union[ChromiumElement, List[ChromiumElement], None]: """执行用xpath在元素中查找元素 :param ele: 在此元素中查找 :param xpath: 查找语句 @@ -1119,22 +1126,22 @@ def _find_by_xpath(ele: ChromeElement, if r['result']['subtype'] == 'null': return None else: - return ChromeElement(ele.page, obj_id=r['result']['objectId']) + return ChromiumElement(ele.page, obj_id=r['result']['objectId']) else: if r['result']['description'] == 'NodeList(0)': return [] else: r = ele.page.driver.Runtime.getProperties(objectId=r['result']['objectId'], ownProperties=True)['result'] - return [ChromeElement(ele.page, obj_id=i['value']['objectId']) + return [ChromiumElement(ele.page, obj_id=i['value']['objectId']) if i['value']['type'] == 'object' else i['value']['value'] for i in r[:-1]] -def _find_by_css(ele: ChromeElement, +def _find_by_css(ele: ChromiumElement, selector: str, single: bool, - timeout: float) -> Union[ChromeElement, List[ChromeElement]]: + timeout: float) -> Union[ChromiumElement, List[ChromiumElement], None]: """执行用css selector在元素中查找元素 :param ele: 在此元素中查找 :param selector: 查找语句 @@ -1163,14 +1170,14 @@ def _find_by_css(ele: ChromeElement, if r['result']['subtype'] == 'null': return None else: - return ChromeElement(ele.page, obj_id=r['result']['objectId']) + return ChromiumElement(ele.page, obj_id=r['result']['objectId']) else: if r['result']['description'] == 'NodeList(0)': return [] else: r = ele.page.driver.Runtime.getProperties(objectId=r['result']['objectId'], ownProperties=True)['result'] - return [ChromeElement(ele.page, obj_id=i['value']['objectId']) for i in r] + return [ChromiumElement(ele.page, obj_id=i['value']['objectId']) for i in r] def _make_js_for_find_ele_by_xpath(xpath: str, type_txt: str, node_txt: str) -> str: @@ -1222,7 +1229,7 @@ def _run_script(page_or_ele, script: str, as_expr: bool = False, timeout: float :param args: 参数,按顺序在js文本中对应argument[0]、argument[2]... :return: js执行结果 """ - if isinstance(page_or_ele, (ChromeElement, ChromeShadowRootElement)): + if isinstance(page_or_ele, (ChromiumElement, ChromeShadowRootElement)): page = page_or_ele.page obj_id = page_or_ele.obj_id else: @@ -1271,7 +1278,7 @@ def _parse_js_result(page, ele, result: dict): if result['className'] == 'ShadowRoot': return ChromeShadowRootElement(ele, obj_id=result['objectId']) else: - return ChromeElement(page, obj_id=result['objectId']) + return ChromiumElement(page, obj_id=result['objectId']) elif sub_type == 'array': r = page.driver.Runtime.getProperties(objectId=result['result']['objectId'], ownProperties=True)['result'] @@ -1289,7 +1296,7 @@ def _parse_js_result(page, ele, result: dict): def _convert_argument(arg: Any) -> dict: """把参数转换成js能够接收的形式""" - if isinstance(arg, ChromeElement): + if isinstance(arg, ChromiumElement): return {'objectId': arg.obj_id} elif isinstance(arg, (int, float, str, bool)): @@ -1302,9 +1309,8 @@ def _convert_argument(arg: Any) -> dict: return {'unserializableValue': '-Infinity'} -def _send_enter(ele: ChromeElement) -> None: +def _send_enter(ele: ChromiumElement) -> None: """发送回车""" - # todo:windows系统回车是否不一样 data = {'type': 'keyDown', 'modifiers': 0, 'windowsVirtualKeyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r', 'autoRepeat': False, 'unmodifiedText': '\r', 'location': 0, 'isKeypad': False} @@ -1313,7 +1319,7 @@ def _send_enter(ele: ChromeElement) -> None: ele.page.run_cdp('Input.dispatchKeyEvent', **data) -def _send_key(ele: ChromeElement, modifier: int, key: str) -> None: +def _send_key(ele: ChromiumElement, modifier: int, key: str) -> None: """发送一个字,在键盘中的字符触发按键,其它直接发送文本""" if key not in _keyDefinitions: ele.page.run_cdp('Input.insertText', text=key) @@ -1365,7 +1371,7 @@ class ChromeScroll(object): """ :param page_or_ele: ChromePage或ChromeElement """ - if isinstance(page_or_ele, ChromeElement): + if isinstance(page_or_ele, ChromiumElement): self.t1 = self.t2 = 'this' self.obj_id = page_or_ele.obj_id self.page = page_or_ele.page @@ -1444,7 +1450,7 @@ class ChromeScroll(object): class ChromeSelect(object): """ChromeSelect 类专门用于处理 d 模式下 select 标签""" - def __init__(self, ele: ChromeElement): + def __init__(self, ele: ChromiumElement): """初始化 \n :param ele: select 元素对象 """ @@ -1470,12 +1476,12 @@ class ChromeSelect(object): return multi and multi.lower() != "false" @property - def options(self) -> List[ChromeElement]: + def options(self) -> List[ChromiumElement]: """返回所有选项元素组成的列表""" return self._ele.eles('tag:option') @property - def selected_option(self) -> Union[ChromeElement, None]: + def selected_option(self) -> Union[ChromiumElement, None]: """返回第一个被选中的option元素 \n :return: ChromeElement对象或None """ @@ -1483,7 +1489,7 @@ class ChromeSelect(object): return ele @property - def selected_options(self) -> List[ChromeElement]: + def selected_options(self) -> List[ChromiumElement]: """返回所有被选中的option元素列表 \n :return: ChromeElement对象组成的列表 """ @@ -1638,14 +1644,14 @@ class ChromeElementWaiter(object): def __init__(self, page_or_ele, - loc_or_ele: Union[str, tuple, ChromeElement], + loc_or_ele: Union[str, tuple, ChromiumElement], timeout: float = None): """等待元素在dom中某种状态,如删除、显示、隐藏 \n :param page_or_ele: 页面或父元素 :param loc_or_ele: 要等待的元素,可以是已有元素、定位符 :param timeout: 超时时间,默认读取页面超时时间 """ - if not isinstance(loc_or_ele, (str, tuple, ChromeElement)): + if not isinstance(loc_or_ele, (str, tuple, ChromiumElement)): raise TypeError('loc_or_ele只能接收定位符或元素对象。') self.driver = page_or_ele @@ -1653,11 +1659,11 @@ class ChromeElementWaiter(object): if timeout is not None: self.timeout = timeout else: - self.timeout = page_or_ele.page.timeout if isinstance(page_or_ele, ChromeElement) else page_or_ele.timeout + self.timeout = page_or_ele.page.timeout if isinstance(page_or_ele, ChromiumElement) else page_or_ele.timeout def delete(self) -> bool: """等待元素从dom删除""" - if isinstance(self.loc_or_ele, ChromeElement): + if isinstance(self.loc_or_ele, ChromiumElement): end_time = perf_counter() + self.timeout while perf_counter() < end_time: if not self.loc_or_ele.is_alive: diff --git a/DrissionPage/chrome_page.py b/DrissionPage/chromium_page.py similarity index 95% rename from DrissionPage/chrome_page.py rename to DrissionPage/chromium_page.py index 159b642..a5d4652 100644 --- a/DrissionPage/chrome_page.py +++ b/DrissionPage/chromium_page.py @@ -16,10 +16,10 @@ from .config import DriverOptions, _cookies_to_tuple from .base import BasePage from .common import get_loc from .drission import connect_chrome -from .chrome_element import ChromeElement, ChromeScroll, _run_script, ChromeElementWaiter +from .chromium_element import ChromiumElement, ChromeScroll, _run_script, ChromeElementWaiter -class ChromePage(BasePage): +class ChromiumPage(BasePage): """用于管理浏览器的类""" def __init__(self, Tab_or_Options: Union[Tab, DriverOptions] = None, @@ -67,14 +67,14 @@ class ChromePage(BasePage): self._driver.DOM.enable() self._driver.Page.enable() root = self._driver.DOM.getDocument() - self.root = ChromeElement(self, node_id=root['root']['nodeId']) + self.root = ChromiumElement(self, node_id=root['root']['nodeId']) self._alert = Alert() self.driver.Page.javascriptDialogOpening = self._on_alert_open self.driver.Page.javascriptDialogClosed = self._on_alert_close - def __call__(self, loc_or_str: Union[Tuple[str, str], str, 'ChromeElement'], - timeout: float = None) -> Union['ChromeElement', None]: + def __call__(self, loc_or_str: Union[Tuple[str, str], str, 'ChromiumElement'], + timeout: float = None) -> Union['ChromiumElement', None]: """在内部查找元素 \n 例:ele = page('@id=ele_id') \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 @@ -114,7 +114,7 @@ class ChromePage(BasePage): @property def tab_ids(self) -> list: """返回所有标签页id""" - self.driver + d = self.driver json = loads(requests_get(f'http://{self.address}/json').text) return [i['id'] for i in json if i['type'] == 'page'] @@ -141,7 +141,7 @@ class ChromePage(BasePage): return {'height': h, 'width': w} @property - def active_ele(self) -> ChromeElement: + def active_ele(self) -> ChromiumElement: """返回当前焦点所在元素""" return self.run_script('return document.activeElement;') @@ -267,8 +267,8 @@ class ChromePage(BasePage): self.driver.Network.setCookies(cookies=result_cookies) def ele(self, - loc_or_ele: Union[Tuple[str, str], str, ChromeElement], - timeout: float = None) -> Union[ChromeElement, None]: + loc_or_ele: Union[Tuple[str, str], str, ChromiumElement], + timeout: float = None) -> Union[ChromiumElement, None]: """获取第一个符合条件的元素对象 \n :param loc_or_ele: 定位符或元素对象 :param timeout: 查找超时时间 @@ -277,8 +277,8 @@ class ChromePage(BasePage): return self._ele(loc_or_ele, timeout=timeout) def eles(self, - loc_or_ele: Union[Tuple[str, str], str, ChromeElement], - timeout: float = None) -> List[ChromeElement]: + loc_or_ele: Union[Tuple[str, str], str, ChromiumElement], + timeout: float = None) -> List[ChromiumElement]: """获取所有符合条件的元素对象 \n :param loc_or_ele: 定位符或元素对象 :param timeout: 查找超时时间 @@ -286,12 +286,13 @@ class ChromePage(BasePage): """ return self._ele(loc_or_ele, timeout=timeout, single=False) - def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, ChromeElement] = None) -> Union[SessionElement, str, None]: + def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, ChromiumElement] = None) -> Union[ + SessionElement, str, None]: """查找第一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高 \n :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象或属性、文本 """ - if isinstance(loc_or_ele, ChromeElement): + if isinstance(loc_or_ele, ChromiumElement): return make_session_ele(loc_or_ele) else: return make_session_ele(self, loc_or_ele) @@ -304,9 +305,9 @@ class ChromePage(BasePage): return make_session_ele(self, loc_or_str, single=False) def _ele(self, - loc_or_ele: Union[Tuple[str, str], str, ChromeElement], + loc_or_ele: Union[Tuple[str, str], str, ChromiumElement], timeout: float = None, - single: bool = True) -> Union[ChromeElement, None, List[ChromeElement]]: + single: bool = True) -> Union[ChromiumElement, None, List[ChromiumElement]]: """执行元素查找 :param loc_or_ele: 定位符或元素对象 :param timeout: 查找超时时间 @@ -315,7 +316,7 @@ class ChromePage(BasePage): """ if isinstance(loc_or_ele, (str, tuple)): loc = get_loc(loc_or_ele)[1] - elif isinstance(loc_or_ele, ChromeElement): + elif isinstance(loc_or_ele, ChromiumElement): return loc_or_ele else: raise ValueError('loc_or_str参数只能是tuple、str、ChromeElement类型。') @@ -336,12 +337,12 @@ class ChromePage(BasePage): count = 1 if single else count nodeIds = self.driver.DOM.getSearchResults(searchId=search_result['searchId'], fromIndex=0, toIndex=count) if count == 1: - return ChromeElement(self, node_id=nodeIds['nodeIds'][0]) + return ChromiumElement(self, node_id=nodeIds['nodeIds'][0]) else: - return [ChromeElement(self, node_id=i) for i in nodeIds['nodeIds']] + return [ChromiumElement(self, node_id=i) for i in nodeIds['nodeIds']] def wait_ele(self, - loc_or_ele: Union[str, tuple, ChromeElement], + loc_or_ele: Union[str, tuple, ChromiumElement], timeout: float = None) -> ChromeElementWaiter: """返回用于等待元素到达某个状态的等待器对象 \n :param loc_or_ele: 可以是元素、查询字符串、loc元组 @@ -405,7 +406,7 @@ class ChromePage(BasePage): f.write(png) return str(path.absolute()) - def scroll_to_see(self, loc_or_ele: Union[str, tuple, ChromeElement]) -> None: + def scroll_to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement]) -> None: """滚动页面直到元素可见 \n :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串(详见ele函数注释) :return: None @@ -492,7 +493,7 @@ class ChromePage(BasePage): :param url: 新标签页跳转到的网址 :return: None """ - self.driver + d = self.driver url = f'?{url}' if url else '' requests_get(f'http://{self.address}/json/new{url}') @@ -517,7 +518,7 @@ class ChromePage(BasePage): def to_front(self) -> None: """激活当前标签页使其处于最前面""" - self.driver + d = self.driver requests_get(f'http://{self.address}/json/activate/{self.current_tab_id}') def close_tabs(self, num_or_ids: Union[int, str, list, tuple, set] = None, others: bool = False) -> None: @@ -700,7 +701,7 @@ class Alert(object): class Timeout(object): """用于保存d模式timeout信息的类""" - def __init__(self, page: ChromePage): + def __init__(self, page: ChromiumPage): self.page = page self.page_load = 30 self.script = 30 @@ -713,7 +714,7 @@ class Timeout(object): class WindowSizeSetter(object): """用于设置窗口大小的类""" - def __init__(self, page: ChromePage): + def __init__(self, page: ChromiumPage): self.driver = page.driver self.window_id = self._get_info()['windowId'] @@ -784,7 +785,7 @@ def _get_tabs(ids: list, num_or_ids: Union[int, str, list, tuple, set]) -> set: return set(i if isinstance(i, str) else ids[i] for i in num_or_ids) -def _show_or_hide_browser(page: ChromePage, hide: bool = True) -> None: +def _show_or_hide_browser(page: ChromiumPage, hide: bool = True) -> None: """执行显示或隐藏浏览器窗口 :param page: ChromePage对象 :param hide: 是否隐藏 diff --git a/DrissionPage/common.py b/DrissionPage/common.py index 99e0d27..bede592 100644 --- a/DrissionPage/common.py +++ b/DrissionPage/common.py @@ -611,10 +611,55 @@ def _get_running_args(opt: DriverOptions) -> list: result.append(ext) # ----------处理experimental_options------------- + prefs = opt.experimental_options.get('prefs', None) + if prefs and opt.user_data_path: + prefs_file = Path(opt.user_data_path) / 'Default' / 'Preferences' + if not prefs_file.exists(): + return result + + from json import load, dump + with open(prefs_file, "r") as f: + j = load(f) + + for pref in prefs: + value = prefs[pref] + pref = pref.split('.') + _make_leave_in_dict(j, pref, 0, len(pref)) + _set_value_to_dict(j, pref, value) + + with open(prefs_file, 'w') as f: + dump(j, f) return result +def _make_leave_in_dict(target_dict: dict, src: list, num: int, end: int) -> None: + """把prefs中a.b.c形式的属性转为a['b']['c']形式 + :param target_dict: 要处理的dict + :param src: 属性层级列表[a, b, c] + :param num: 当前处理第几个 + :param end: src长度 + :return: None + """ + if num == end: + return + if src[num] not in target_dict: + target_dict[src[num]] = {} + num += 1 + _make_leave_in_dict(target_dict[src[num - 1]], src, num, end) + + +def _set_value_to_dict(target_dict: dict, src: list, value) -> None: + """把a.b.c形式的属性的值赋值到a['b']['c']形式的字典中 + :param target_dict: 要处理的dict + :param src: 属性层级列表[a, b, c] + :param value: 属性值 + :return: None + """ + src = "']['".join(src) + src = f"target_dict['{src}']=value" + exec(src) + def _location_in_viewport(page, loc_x: int, loc_y: int) -> bool: """判断给定的坐标是否在视口中 |n diff --git a/DrissionPage/config.py b/DrissionPage/config.py index 28e6405..3de5abe 100644 --- a/DrissionPage/config.py +++ b/DrissionPage/config.py @@ -462,6 +462,7 @@ class DriverOptions(Options): """ super().__init__() self._driver_path = None + self._user_data_path = None self.ini_path = None if read_file: @@ -475,9 +476,13 @@ class DriverOptions(Options): self._extensions = options_dict.get('extensions', []) self._experimental_options = options_dict.get('experimental_options', {}) self._debugger_address = options_dict.get('debugger_address', None) - self.set_window_rect = options_dict.get('set_window_rect', None) self.page_load_strategy = options_dict.get('page_load_strategy', 'normal') + for arg in self._arguments: + if arg.startswith('--user-data-dir='): + self.set_paths(user_data_path=arg[16:]) + break + self.timeouts = options_dict.get('timeouts', {'implicit': 10, 'pageLoad': 30, 'script': 30}) self.timeouts['implicit'] *= 1000 self.timeouts['pageLoad'] *= 1000 @@ -496,6 +501,11 @@ class DriverOptions(Options): """浏览器启动文件路径""" return self.binary_location or 'chrome' + @property + def user_data_path(self) -> str: + """返回用户文件夹路径""" + return self._user_data_path + # -------------重写父类方法,实现链式操作------------- def add_argument(self, argument) -> 'DriverOptions': """添加一个配置项 \n @@ -727,10 +737,10 @@ class DriverOptions(Options): :return: 当前对象 """ if driver_path is not None: - self._driver_path = driver_path + self._driver_path = str(driver_path) if chrome_path is not None: - self.binary_location = chrome_path + self.binary_location = str(chrome_path) if local_port is not None: self.debugger_address = '' if local_port == '' else f'127.0.0.1:{local_port}' @@ -739,13 +749,14 @@ class DriverOptions(Options): self.debugger_address = debugger_address if download_path is not None: - self.experimental_options['prefs']['download.default_directory'] = download_path + self.experimental_options['prefs']['download.default_directory'] = str(download_path) if user_data_path is not None: - self.set_argument('--user-data-dir', user_data_path) + self.set_argument('--user-data-dir', str(user_data_path)) + self._user_data_path = user_data_path if cache_path is not None: - self.set_argument('--disk-cache-dir', cache_path) + self.set_argument('--disk-cache-dir', str(cache_path)) return self diff --git a/DrissionPage/configs.ini b/DrissionPage/configs.ini index e742857..763cf8b 100644 --- a/DrissionPage/configs.ini +++ b/DrissionPage/configs.ini @@ -5,11 +5,10 @@ tmp_path = [chrome_options] debugger_address = 127.0.0.1:9222 binary_location = chrome -arguments = ['--no-sandbox', '--disable-gpu', '--ignore-certificate-errors', '--disable-infobars', '--disable-popup-blocking'] +arguments = ['--no-first-run', '--no-sandbox', '--disable-gpu', '--ignore-certificate-errors', '--disable-infobars', '--disable-popup-blocking'] extensions = [] experimental_options = {'prefs': {'profile.default_content_settings.popups': 0, 'profile.default_content_setting_values': {'notifications': 2}, 'plugins.plugins_list': [{'enabled': False, 'name': 'Chrome PDF Viewer'}]}, 'useAutomationExtension': False, 'excludeSwitches': ['enable-automation']} timeouts = {'implicit': 10.0, 'pageLoad': 30.0, 'script': 30.0} -set_window_rect = None page_load_strategy = normal [session_options] diff --git a/DrissionPage/web_page.py b/DrissionPage/web_page.py index b408370..847321b 100644 --- a/DrissionPage/web_page.py +++ b/DrissionPage/web_page.py @@ -7,15 +7,15 @@ from requests import Session, Response from requests.structures import CaseInsensitiveDict from tldextract import extract -from .chrome_element import ChromeElement +from .chromium_element import ChromiumElement from .session_element import SessionElement from .base import BasePage from .config import DriverOptions, SessionOptions, _cookies_to_tuple -from .chrome_page import ChromePage +from .chromium_page import ChromiumPage from .session_page import SessionPage -class WebPage(SessionPage, ChromePage, BasePage): +class WebPage(SessionPage, ChromiumPage, BasePage): """整合浏览器和request的页面类""" def __init__(self, @@ -34,7 +34,7 @@ class WebPage(SessionPage, ChromePage, BasePage): if self._mode not in ('s', 'd'): raise ValueError('mode参数只能是s或d。') - super(ChromePage, self).__init__(timeout) # 调用Base的__init__() + super(ChromiumPage, self).__init__(timeout) # 调用Base的__init__() self._session = None self._driver = None self._set_session_options(session_or_options) @@ -44,11 +44,11 @@ class WebPage(SessionPage, ChromePage, BasePage): self._response = None if self._mode == 'd': - self.driver + d = self.driver def __call__(self, - loc_or_str: Union[Tuple[str, str], str, ChromeElement, SessionElement], - timeout: float = None) -> Union[ChromeElement, SessionElement, None]: + loc_or_str: Union[Tuple[str, str], str, ChromiumElement, SessionElement], + timeout: float = None) -> Union[ChromiumElement, SessionElement, None]: """在内部查找元素 \n 例:ele = page('@id=ele_id') \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 @@ -152,8 +152,8 @@ class WebPage(SessionPage, ChromePage, BasePage): return super().get(url, show_errmsg, retry, interval, timeout, **kwargs) def ele(self, - loc_or_ele: Union[Tuple[str, str], str, ChromeElement, SessionElement], - timeout: float = None) -> Union[ChromeElement, SessionElement, str, None]: + loc_or_ele: Union[Tuple[str, str], str, ChromiumElement, SessionElement], + timeout: float = None) -> Union[ChromiumElement, SessionElement, str, None]: """返回第一个符合条件的元素、属性或节点文本 \n :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与页面等待时间一致 @@ -166,7 +166,7 @@ class WebPage(SessionPage, ChromePage, BasePage): def eles(self, loc_or_str: Union[Tuple[str, str], str], - timeout: float = None) -> List[Union[ChromeElement, SessionElement, str]]: + timeout: float = None) -> List[Union[ChromiumElement, SessionElement, str]]: """返回页面中所有符合条件的元素、属性或节点文本 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :param timeout: 查找元素超时时间,默认与页面等待时间一致 @@ -177,7 +177,7 @@ class WebPage(SessionPage, ChromePage, BasePage): elif self._mode == 'd': return super(SessionPage, self).eles(loc_or_str, timeout=timeout) - def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, ChromeElement, SessionElement] = None) \ + def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, ChromiumElement, SessionElement] = None) \ -> Union[SessionElement, str, None]: """查找第一个符合条件的元素以SessionElement形式返回,d模式处理复杂页面时效率很高 \n :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 @@ -214,7 +214,7 @@ class WebPage(SessionPage, ChromePage, BasePage): # s模式转d模式 if self._mode == 'd': if not self._has_driver: - self.driver + d = self.driver self._url = None if not self._has_driver else super(SessionPage, self).url self._has_driver = True @@ -353,10 +353,10 @@ class WebPage(SessionPage, ChromePage, BasePage): return super().download def _ele(self, - loc_or_ele: Union[Tuple[str, str], str, ChromeElement, SessionElement], + loc_or_ele: Union[Tuple[str, str], str, ChromiumElement, SessionElement], timeout: float = None, single: bool = True) \ - -> Union[ChromeElement, SessionElement, str, None, List[Union[SessionElement, str]], List[ - Union[ChromeElement, str]]]: + -> Union[ChromiumElement, SessionElement, str, None, List[Union[SessionElement, str]], List[ + Union[ChromiumElement, str]]]: """返回页面中符合条件的元素、属性或节点文本,默认返回第一个 \n :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 :param timeout: 查找元素超时时间,d模式专用 diff --git a/README.md b/README.md index 66b69d7..7ff0dae 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ DrissionPage,即 driver 和 session 组合而成的 page。 是个基于 python 的 Web 自动化操作集成工具。 它用 POM 模式封装了页面和元素常用的方法, 自带一套简洁直观优雅的元素定位语法, -实现了 selenium 和 requests 之间的无缝切换, -可兼顾 selenium 的便利性和 requests 的高效率, +实现了浏览器和 requests 之间的无缝切换, +可兼顾浏览器自动化的便利性和 requests 的高效率, 更棒的是,它的使用方式非常简洁和人性化,代码量少,对新手友好。 **使用文档:** 📒[点击打开](http://g1879.gitee.io/drissionpage) @@ -17,10 +17,35 @@ DrissionPage,即 driver 和 session 组合而成的 page。 ## 📕 背景 用 requests 做数据采集面对要登录的网站时,要分析数据包、JS 源码,构造复杂的请求,往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高。若数据是由 JS 计算生成的,还须重现计算过程,体验不好,开发效率不高。 -使用 selenium,可以很大程度上绕过这些坑,但 selenium 效率不高。因此,这个库将 selenium 和 requests 合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 -除了合并两者,本库还以网页为单位封装了常用功能,简化了 selenium 的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 +使用浏览器,可以很大程度上绕过这些坑,但浏览器运行效率不高。因此,这个库将它们合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 +除了合并两者,本库还以网页为单位封装了常用功能,提供非常简便的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 一切从简,尽量提供简单直接的使用方法,对新手更友好。 +# 🔆 3.0 版隆重推出 + +以前的版本是对 selenium 进行重新封装实现的。从 3.0 开始,作者另起炉灶,对底层进行了重新开发,摆脱对 selenium 的依赖,增强了功能,提升了运行效率。 +3.0 全新开发的页面对象是`WebPage`,支持 chromium 内核的浏览器(如 chrome 和 edge)。除了保持之前的功能,比依赖 selenium 的`MixPage`有以下优点: + +- 无 webdriver 特征,不会被网站识别 + +- 无需为不同版本的浏览器下载不同的驱动 + +- 运行速度更快 + +- 可以跨 iframe 查找元素,无需切入切出 + +- 把 iframe 看作普通元素,获取后可直接在其中查找元素,逻辑更清晰 + +- 可以同时操作浏览器中的多个标签页,即使标签页为非激活状态 + +- 可以直接读取浏览器缓存来保持图片,无需用 GUI 点击保存 + +- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持) + +新版是自己实现的功能,开发不会受太多限制,以后将主要对`WebPage`进行更新。 + +3.0 版已经发布,目前正在测试,欢迎试用并提出意见,让我做得更好。 + # 💡 特性和亮点 作者踩过无数坑,总结出的经验全写到这个库里了。内置了 N 多实用功能,对常用功能作了整合和优化。 @@ -28,7 +53,7 @@ DrissionPage,即 driver 和 session 组合而成的 page。 ## 🎉 特性 - 代码高度集成,以简洁的代码为第一追求。 -- 页面对象可在 selenium 和 requests 模式间任意切换,保留登录状态。 +- 页面对象可在浏览器和 requests 间任意切换,保留登录状态。 - 极简单但强大的元素定位语法,支持链式操作,代码极其简洁。 - 两种模式提供一致的 API,使用体验一致。 - 人性化设计,集成众多实用功能,大大降低开发工作量。 @@ -160,8 +185,7 @@ ele.click() 🌿 用 xpath 直接获取属性或文本节点(返回文本) ```python -# 使用 selenium: -相当复杂 +# 使用 selenium 无此功能 # 使用 DrissionPage: class_name = element('xpath://div[@id="div_id"]/@class') @@ -178,7 +202,7 @@ page.hide_browser() # 让浏览器窗口消失 page.show_browser() # 重新显示浏览器窗口 ``` -注:本功能只支持 Windows,且须设置了`local_port`或`debugger_address`参数时才能生效。 +注:本功能只支持 Windows。 ☘️ **与 requests 代码对比** diff --git a/docs/README.md b/docs/README.md index 5d485e0..da00c16 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,12 +4,10 @@ DrissionPage,即 driver 和 session 组合而成的 page。 是个基于 python 的 Web 自动化操作集成工具。 它用 POM 模式封装了页面和元素常用的方法, 自带一套简洁直观优雅的元素定位语法, -实现了 selenium 和 requests 之间的无缝切换, -可兼顾 selenium 的便利性和 requests 的高效率, +实现了浏览器和 requests 之间的无缝切换, +可兼顾浏览器自动化的便利性和 requests 的高效率, 更棒的是,它的使用方式非常简洁和人性化,代码量少,对新手友好。 - - **交流QQ群:** 897838127 **联系邮箱:** g1879@qq.com @@ -17,11 +15,36 @@ DrissionPage,即 driver 和 session 组合而成的 page。 ## 📕 背景 用 requests 做数据采集面对要登录的网站时,要分析数据包、JS 源码,构造复杂的请求,往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高。若数据是由 JS 计算生成的,还须重现计算过程,体验不好,开发效率不高。 -使用 selenium,可以很大程度上绕过这些坑,但 selenium 效率不高。因此,这个库将 selenium 和 requests 合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 -除了合并两者,本库还以网页为单位封装了常用功能,简化了 selenium 的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 +使用浏览器,可以很大程度上绕过这些坑,但浏览器运行效率不高。因此,这个库将它们合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 +除了合并两者,本库还以网页为单位封装了常用功能,提供非常简便的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 一切从简,尽量提供简单直接的使用方法,对新手更友好。 -# 🍀 特性和亮点 +# 🔆 3.0 版隆重推出 + +以前的版本是对 selenium 进行重新封装实现的。从 3.0 开始,作者另起炉灶,对底层进行了重新开发,摆脱对 selenium 的依赖,增强了功能,提升了运行效率。 +3.0 全新开发的页面对象是`WebPage`,支持 chromium 内核的浏览器(如 chrome 和 edge)。除了保持之前的功能,比依赖 selenium 的`MixPage`有以下优点: + +- 无 webdriver 特征,不会被网站识别 + +- 无需为不同版本的浏览器下载不同的驱动 + +- 运行速度更快 + +- 可以跨 iframe 查找元素,无需切入切出 + +- 把 iframe 看作普通元素,获取后可直接在其中查找元素,逻辑更清晰 + +- 可以同时操作浏览器中的多个标签页,即使标签页为非激活状态 + +- 可以直接读取浏览器缓存来保持图片,无需用 GUI 点击保存 + +- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持) + +新版是自己实现的功能,开发不会受太多限制,以后将主要对`WebPage`进行更新。 + +3.0 版已经发布,目前正在测试,欢迎试用并提出意见,让我做得更好。 + +# 💡 特性和亮点 作者踩过无数坑,总结出的经验全写到这个库里了。内置了 N 多实用功能,对常用功能作了整合和优化。 diff --git a/docs/版本历史.md b/docs/版本历史.md index ee783fc..25dc62e 100644 --- a/docs/版本历史.md +++ b/docs/版本历史.md @@ -1,4 +1,34 @@ -# 2.7.3 +# v3.0.0 + +重大更新。推出`WebPage`,重新开发底层逻辑,摆脱对 selenium 的依赖,增强了功能,提升了运行效率。支持 chromium 内核的浏览器(如 chrome 和 edge)。比`MixPage`有以下优点: + +- 无 webdriver 特征,不会被网站识别 + +- 无需为不同版本的浏览器下载不同的驱动 + +- 运行速度更快 + +- 可以跨 iframe 查找元素,无需切入切出 + +- 把 iframe 看作普通元素,获取后可直接在其中查找元素,逻辑更清晰 + +- 可以同时操作浏览器中的多个标签页,即使标签页为非激活状态 + +- 可以直接读取浏览器缓存来保持图片,无需用 GUI 点击保存 + +- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持) + +其它更新: + +- 新增与`WebPage`配合的动作链接`ActionChains` + +- ini 文件和`DriverOption`删除`set_window_rect`属性 + +- 浏览器启动配置实现对插件的支持 + +- 浏览器启动配置实现对`experimental_options`的`prefs`属性支持 + +# v2.7.3 - 页面对象和元素对象的`screenshot_as_bytes()`方法合并到`screenshot()`