diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 3b92ff7..f76ac4a 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -13,3 +13,4 @@ from .chromium_page import ChromiumPage from .session_page import SessionPage from .drission import Drission from .config import DriverOptions, SessionOptions +from .action_chains import ActionChains diff --git a/DrissionPage/chromium_element.py b/DrissionPage/chromium_element.py index 339da01..ff8a247 100644 --- a/DrissionPage/chromium_element.py +++ b/DrissionPage/chromium_element.py @@ -156,7 +156,7 @@ class ChromiumElement(DrissionElement): def _client_click_point(self): """返回元素左上角可接受点击的点视口坐标""" m = self._get_client_rect('padding') - return (m[0], m[1]) if m else (0, 0) + return (self.client_midpoint[0], m[1]) if m else (0, 0) @property def _click_point(self): @@ -664,7 +664,7 @@ class ChromiumElement(DrissionElement): """实施点击 \n :param client_x: 视口中的x坐标 :param client_y: 视口中的y坐标 - :param button: 'left'或'right' + :param button: 'left' 或 'right' :return: None """ self.page.driver.Input.dispatchMouseEvent(type='mousePressed', x=client_x, y=client_y, button=button, @@ -1385,17 +1385,17 @@ def _offset_scroll(ele, offset_x, offset_y): :param offset_y: 偏移量y :return: 视口中的坐标 """ - location = ele.location - midpoint = ele._click_point - lx = location['x'] + offset_x if offset_x else midpoint['x'] - ly = location['y'] + offset_y if offset_y else midpoint['y'] + loc_x, loc_y = ele.location + cp_x, cp_y = ele._click_point + lx = loc_x + offset_x if offset_x else cp_x + ly = loc_y + offset_y if offset_y else cp_y if not _location_in_viewport(ele.page, lx, ly): ele.page.scroll.to_location(lx, ly) - cl = ele.client_location - cm = ele._client_click_point - cx = cl['x'] + offset_x if offset_x else cm['x'] - cy = cl['y'] + offset_y if offset_y else cm['y'] + cl_x, cl_y = ele.client_location + ccp_x, ccp_y = ele._client_click_point + cx = cl_x + offset_x if offset_x else ccp_x + cy = cl_y + offset_y if offset_y else ccp_y return cx, cy diff --git a/DrissionPage/chromium_frame.py b/DrissionPage/chromium_frame.py index b53ae36..430f281 100644 --- a/DrissionPage/chromium_frame.py +++ b/DrissionPage/chromium_frame.py @@ -13,7 +13,7 @@ from .chromium_element import ChromiumElement class ChromiumFrame(ChromiumBase): def __init__(self, page, ele): self.page = page - self.address = self.page.address + self.address = page.address node = page.run_cdp('DOM.describeNode', nodeId=ele.node_id, not_change=True)['node'] self.frame_id = node['frameId'] self._backend_id = ele.backend_id @@ -48,18 +48,25 @@ class ChromiumFrame(ChromiumBase): node = self.page.run_cdp('DOM.describeNode', nodeId=self._frame_ele.node_id, not_change=True)['node'] if self._is_inner_frame(): - print('-111') self._is_diff_domain = False self.doc_ele = ChromiumElement(self.page, backend_id=node['contentDocument']['backendNodeId']) super().__init__(self.address, self.page.tab_id, self.page.timeout) - print('+111') else: - print('-222') self._is_diff_domain = True + self._tab_obj.stop() super().__init__(self.address, self.frame_id, self.page.timeout) obj_id = super().run_script('document;', as_expr=True)['objectId'] self.doc_ele = ChromiumElement(self, obj_id=obj_id) - print('+222') + + def _check_ok(self): + if self._tab_obj._stopped.is_set(): + self._reload() + + try: + self._tab_obj.DOM.describeNode(nodeId=self.node_id) + except: + self._reload() + sleep(2) def _get_new_document(self): """刷新cdp使用的document数据""" @@ -94,7 +101,6 @@ class ChromiumFrame(ChromiumBase): def _onFrameStartedLoading(self, **kwargs): """页面开始加载时触发""" - # print('开始', kwargs['frameId']) if kwargs['frameId'] == self.frame_id: self._is_loading = True if self._debug: @@ -102,7 +108,6 @@ class ChromiumFrame(ChromiumBase): def _onFrameStoppedLoading(self, **kwargs): """页面加载完成后触发""" - # print('停止', kwargs['frameId']) if kwargs['frameId'] == self.frame_id and self._first_run is False and self._is_loading: if self._debug: print('页面停止加载 FrameStoppedLoading') @@ -136,16 +141,19 @@ class ChromiumFrame(ChromiumBase): @property def tag(self): """返回元素tag""" + self._check_ok() return self.frame_ele.tag @property def url(self): """返回frame当前访问的url""" + self._check_ok() return self.doc_ele.run_script('return this.location.href;') @property def html(self): """返回元素outerHTML文本""" + self._check_ok() tag = self.tag out_html = self.page.run_cdp('DOM.getOuterHTML', nodeId=self.frame_ele.node_id, not_change=True)['outerHTML'] @@ -155,30 +163,31 @@ class ChromiumFrame(ChromiumBase): @property def inner_html(self): """返回元素innerHTML文本""" + self._check_ok() return self.doc_ele.run_script('return this.documentElement.outerHTML;') @property def title(self): """返回页面title""" - while True: - try: - return self.ele('t:title').text - except: - self._reload() + self._check_ok() + return self.ele('t:title').text @property def cookies(self): """以dict格式返回cookies""" + self._check_ok() return super().cookies if self._is_diff_domain else self.doc_ele.run_script('return this.cookie;') @property def attrs(self): """返回frame元素所有attribute属性""" + self._check_ok() return self.frame_ele.attrs @property def frame_size(self): """返回frame内页面尺寸,格式:(长, 高)""" + self._check_ok() w = self.doc_ele.run_script('return this.body.scrollWidth') h = self.doc_ele.run_script('return this.body.scrollHeight') return w, h @@ -186,31 +195,37 @@ class ChromiumFrame(ChromiumBase): @property def size(self): """返回frame元素大小""" + self._check_ok() return self.frame_ele.size @property def active_ele(self): """返回当前焦点所在元素""" + self._check_ok() return self.doc_ele.run_script('return this.activeElement;') @property def location(self): """返回frame元素左上角的绝对坐标""" + self._check_ok() return self.frame_ele.location @property def is_displayed(self): """返回frame元素是否显示""" + self._check_ok() return self.frame_ele.is_displayed @property def xpath(self): """返回frame的xpath绝对路径""" + self._check_ok() return self.frame_ele.xpath @property def css_path(self): """返回frame的css selector绝对路径""" + self._check_ok() return self.frame_ele.css_path @property @@ -231,6 +246,7 @@ class ChromiumFrame(ChromiumBase): def refresh(self): """刷新frame页面""" + self._check_ok() self.doc_ele.run_script('this.location.reload();') def attr(self, attr): @@ -238,6 +254,7 @@ class ChromiumFrame(ChromiumBase): :param attr: 属性名 :return: 属性值文本,没有该属性返回None """ + self._check_ok() return self.frame_ele.attr(attr) def set_attr(self, attr, value): @@ -246,6 +263,7 @@ class ChromiumFrame(ChromiumBase): :param value: 属性值 :return: None """ + self._check_ok() self.frame_ele.set_attr(attr, value) def remove_attr(self, attr): @@ -253,6 +271,7 @@ class ChromiumFrame(ChromiumBase): :param attr: 属性名 :return: None """ + self._check_ok() self.frame_ele.remove_attr(attr) def run_script(self, script, as_expr=False, *args): @@ -262,6 +281,7 @@ class ChromiumFrame(ChromiumBase): :param args: 参数,按顺序在js文本中对应argument[0]、argument[1]... :return: 运行的结果 """ + self._check_ok() return self.doc_ele.run_script(script, as_expr=as_expr, *args) def parent(self, level_or_loc=1): @@ -269,6 +289,7 @@ class ChromiumFrame(ChromiumBase): :param level_or_loc: 第几级父元素,或定位符 :return: 上级元素对象 """ + self._check_ok() return self.frame_ele.parent(level_or_loc) def prev(self, filter_loc='', index=1, timeout=0): @@ -278,6 +299,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 兄弟元素 """ + self._check_ok() return self.frame_ele.prev(filter_loc, index, timeout) def next(self, filter_loc='', index=1, timeout=0): @@ -287,6 +309,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 兄弟元素 """ + self._check_ok() return self.frame_ele.next(filter_loc, index, timeout) def before(self, filter_loc='', index=1, timeout=None): @@ -296,6 +319,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 本元素前面的某个元素或节点 """ + self._check_ok() return self.frame_ele.before(filter_loc, index, timeout) def after(self, filter_loc='', index=1, timeout=None): @@ -305,6 +329,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 本元素后面的某个元素或节点 """ + self._check_ok() return self.frame_ele.after(filter_loc, index, timeout) def prevs(self, filter_loc='', timeout=0): @@ -313,6 +338,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 兄弟元素或节点文本组成的列表 """ + self._check_ok() return self.frame_ele.prevs(filter_loc, timeout) def nexts(self, filter_loc='', timeout=0): @@ -321,6 +347,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 兄弟元素或节点文本组成的列表 """ + self._check_ok() return self.frame_ele.nexts(filter_loc, timeout) def befores(self, filter_loc='', timeout=None): @@ -329,6 +356,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 查找元素的超时时间 :return: 本元素前面的元素或节点组成的列表 """ + self._check_ok() return self.frame_ele.befores(filter_loc, timeout) def _ele(self, loc_or_ele, timeout=None, single=True, relative=False): @@ -354,6 +382,7 @@ class ChromiumFrame(ChromiumBase): :param timeout: 连接超时时间 :return: 是否成功,返回None表示不确定 """ + self._check_ok() err = None timeout = timeout if timeout is not None else self.timeouts.page_load diff --git a/DrissionPage/chromium_frame.pyi b/DrissionPage/chromium_frame.pyi index fb668a8..12a8cdc 100644 --- a/DrissionPage/chromium_frame.pyi +++ b/DrissionPage/chromium_frame.pyi @@ -30,6 +30,8 @@ class ChromiumFrame(ChromiumBase): def _reload(self) -> None: ... + def _check_ok(self) -> None: ... + def _get_new_document(self) -> None: ... def _onFrameAttached(self, **kwargs): ... diff --git a/DrissionPage/easy_set.py b/DrissionPage/easy_set.py index 3a03e05..cdafd2d 100644 --- a/DrissionPage/easy_set.py +++ b/DrissionPage/easy_set.py @@ -294,7 +294,7 @@ def _get_chrome_path(ini_path: str = None, # -----------从系统变量中获取-------------- if from_system_path: - paths = popen('set path').read().lower() + paths = popen('set path').read().encode('gbk').decode('utf-8').lower() r = search(r'[^;]*chrome[^;]*', paths) if r: diff --git a/docs/WebPage使用方法/3.10动作链.md b/docs/WebPage使用方法/3.10动作链.md new file mode 100644 index 0000000..8d801c2 --- /dev/null +++ b/docs/WebPage使用方法/3.10动作链.md @@ -0,0 +1,128 @@ +参考 selenium 的`ActionChains`,本库也提供了自己的`ActionChains`。语法更简洁易用,每个动作执行即生效,无须`perform()`。 + +# ✔ 创建对象 + +## 📍 `ActionChains` + +创建动作链对象非常简单,只要把`WebPage`对象或`ChromiumPage`对象传入即可。动作链只在这个页面上生效。 + +**参数:** + +- `page`:`WebPage`对象或`ChromiumPage`对象 + +```python +# 创建前先导入 +from DrissionPage import WebPage, ActionChains + +page = WebPage() +ac = ActionChains(page) +``` + +# ✔ 移动鼠标 + +## 📍 `move_to()` + +此方法用于移动鼠标到元素中点,或页面上的某个绝对坐标。可设置偏移量,当带偏移量时,偏移量相对于元素左上角坐标。 + +**参数:** + +- `ele_or_loc`:元素对象或绝对坐标,坐标为`tuple`(int, int) 形式 + +- `offset_x`:偏移量 x + +- `offset_y`:偏移量 y + +**返回:**`ActionChains`对象自己 + +```python +ele = page('tag:a') +ac.move_to(ele_or_loc=ele) # 使鼠标移动过到 ele 元素上 +``` + +## 📍 `move()` + +此方法用于使鼠标相对当前位置移动若干距离。 + +**参数:** + +- `offset_x`:偏移量 x + +- `offset_y`:偏移量 y + +**返回:**`ActionChains`对象自己 + +```python +ac.move(300, 0) # 鼠标向右移动 300 像素 +``` + +## 📍 `up()` + +此方法用于使鼠标相对当前位置向上移动若干距离。 + +**参数:** + +- `pixel`:移动距离 + +**返回:**`ActionChains`对象自己 + +```python +ac.up(50) # 鼠标向上移动 50 像素 +``` + +## 📍 `down()` + +此方法用于使鼠标相对当前位置向下移动若干距离。 + +**参数:** + +- `pixel`:移动距离 + +**返回:**`ActionChains`对象自己 + +## 📍 `left()` + +此方法用于使鼠标相对当前位置向左移动若干距离。 + +**参数:** + +- `pixel`:移动距离 + +**返回:**`ActionChains`对象自己 + +## 📍 `right()` + +此方法用于使鼠标相对当前位置向右移动若干距离。 + +**参数:** + +- `pixel`:移动距离 + +**返回:**`ActionChains`对象自己 + +# ✔ 点击鼠标 + +## 📍 `click()` + +## 📍 `r_click()` + +## 📍 `hold()` + +## 📍 `release()` + +# ✔ 滚动滚轮 + +## 📍 `scroll()` + +# ✔ 键盘按键 + +## 📍 `key_down()` + +## 📍 `key_up()` + +# ✔ 等待 + +## 📍 `wait()` + +# ✔ 链式操作 + + diff --git a/docs/WebPage使用方法/3.6获取网页信息.md b/docs/WebPage使用方法/3.6获取网页信息.md index 3ca45e4..df3de94 100644 --- a/docs/WebPage使用方法/3.6获取网页信息.md +++ b/docs/WebPage使用方法/3.6获取网页信息.md @@ -131,6 +131,15 @@ print(r.status_code) 此属性返回当前标签页的 id。 +## 📍 `address` + +此属性返回当前对象控制的页面地址和端口。 + +```python +print(page.address) +# 输出:127.0.0.1:9222 +``` + ## 📍 `driver` 此属性返回当前页面对象使用的`ChromiumDriver`对象。 diff --git a/docs/_sidebar.md b/docs/_sidebar.md index be67cab..aafb44e 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -22,6 +22,7 @@ * [🔨 3.7 页面操作](WebPage使用方法\3.7页面操作.md) * [🔨 3.8 标签页操作](WebPage使用方法\3.8标签页操作.md) * [🔨 3.9 iframe操作](WebPage使用方法\3.9iframe操作.md) + * [🔨 3.10 动作链](WebPage使用方法\3.10动作链.md) * [🛠 4 MixPage 使用方法](#)