From 1a6418918c95f0311ba8dad470362ce6fd234400 Mon Sep 17 00:00:00 2001 From: g1879 Date: Sun, 28 Jan 2024 21:33:54 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0upload()=E6=96=B9=E6=B3=95=EF=BC=9B=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AF=B9=E8=B1=A1=E5=A2=9E=E5=8A=A0clear=5Fa?= =?UTF-8?q?rguments()=E3=80=81clear=5Fprefs()=E3=80=81clear=5Fflags()?= =?UTF-8?q?=E3=80=81clear=5Fheaders()=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/__init__.py | 2 +- DrissionPage/_configs/chromium_options.py | 15 +++++++++++++++ DrissionPage/_configs/chromium_options.pyi | 10 ++++++++-- DrissionPage/_configs/session_options.py | 5 +++++ DrissionPage/_configs/session_options.pyi | 2 ++ DrissionPage/_pages/chromium_base.py | 14 ++++++++++++-- DrissionPage/_pages/chromium_base.pyi | 8 +++++++- 7 files changed, 50 insertions(+), 6 deletions(-) diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 7d17ec5..029e38e 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -14,4 +14,4 @@ from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions __all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__'] -__version__ = '4.0.4.1' +__version__ = '4.0.4.2' diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 8dbdf1e..d483967 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -267,6 +267,21 @@ class ChromiumOptions(object): self.clear_file_flags = True return self + def clear_flags(self): + """清空本对象已设置的flag参数""" + self._flags = {} + return self + + def clear_arguments(self): + """清空本对象已设置的argument参数""" + self._arguments = [] + return self + + def clear_prefs(self): + """清空本对象已设置的pref参数""" + self._prefs = {} + return self + def set_timeouts(self, base=None, page_load=None, script=None, implicit=None): """设置超时时间,单位为秒 :param base: 默认超时时间 diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index 8dd05b2..c682a27 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -109,8 +109,14 @@ class ChromiumOptions(object): def clear_flags_in_file(self) -> ChromiumOptions: ... - def set_timeouts(self, - base: float = None, + def clear_flags(self) -> ChromiumOptions: ... + + def clear_arguments(self) -> ChromiumOptions: ... + + def clear_prefs(self) -> ChromiumOptions: ... + + def set_timeouts(self, + base: float = None, page_load: float = None, script: float = None) -> ChromiumOptions: ... diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index 1983801..6373441 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -198,6 +198,11 @@ class SessionOptions(object): return self + def clear_headers(self): + """清空已设置的header参数""" + self._headers = None + self._del_set.add('headers') + @property def cookies(self): """以list形式返回cookies""" diff --git a/DrissionPage/_configs/session_options.pyi b/DrissionPage/_configs/session_options.pyi index 532dd06..6af3375 100644 --- a/DrissionPage/_configs/session_options.pyi +++ b/DrissionPage/_configs/session_options.pyi @@ -55,6 +55,8 @@ class SessionOptions(object): def remove_a_header(self, name: str) -> SessionOptions: ... + def clear_headers(self) -> SessionOptions: ... + @property def cookies(self) -> list: ... diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index eb0aa96..ebf93f3 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -264,7 +264,6 @@ class ChromiumBase(BasePage): self.stop_loading() # ----------挂件---------- - @property def wait(self): """返回用于等待的对象""" @@ -329,7 +328,7 @@ class ChromiumBase(BasePage): """返回timeouts设置""" return self._timeouts - # ----------挂件---------- + # ----------挂件结束---------- @property def browser(self): @@ -722,6 +721,17 @@ class ChromiumBase(BasePage): frames = self._ele(locator, timeout=timeout, index=None, raise_err=False) return [i for i in frames if i._type == 'ChromiumFrame'] + def upload(self, loc_or_ele, file_paths, by_js=False): + """触发上传文件选择框并自动填入指定路径 + :param loc_or_ele: 被点击后会触发文件选择框的元素或它的定位符 + :param file_paths: 文件路径,如果上传框支持多文件,可传入列表或字符串,字符串时多个文件用回车分隔 + :param by_js: 是否用js方式点击 + :return: None + """ + self.set.upload_files(file_paths) + self.ele(loc_or_ele).click(by_js=by_js) + self.wait.upload_paths_inputted() + def session_storage(self, item=None): """返回sessionStorage信息,不设置item则获取全部 :param item: 要获取的项,不设置则返回全部 diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 0b51500..4e09e61 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -176,7 +176,8 @@ class ChromiumBase(BasePage): def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None, timeout: float = None) -> Union[None, bool]: ... - def cookies(self, as_dict: bool = False, all_domains: bool = False, all_info: bool = False) -> Union[list, dict]: ... + def cookies(self, as_dict: bool = False, all_domains: bool = False, all_info: bool = False) -> Union[ + list, dict]: ... def ele(self, locator: Union[Tuple[str, str], str, ChromiumElement, ChromiumFrame], @@ -217,6 +218,11 @@ class ChromiumBase(BasePage): def get_frames(self, locator: Union[str, tuple] = None, timeout: float = None) -> List[ChromiumFrame]: ... + def upload(self, + loc_or_ele: Union[str, Tuple[str, str], ChromiumElement], + file_paths: Union[str, list, tuple], + by_js: bool = False) -> None: ... + def run_cdp(self, cmd: str, **cmd_args) -> dict: ... def run_cdp_loaded(self, cmd: str, **cmd_args) -> dict: ... From 7e08dea72e5ca86fc693557838914201953b7a2c Mon Sep 17 00:00:00 2001 From: g1879 Date: Mon, 29 Jan 2024 23:06:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?4.0.4.2(+)=20=E5=A2=9E=E5=8A=A0add=5Fele()?= =?UTF-8?q?=20run=5Fjs()=E5=8F=AF=E8=AF=BB=E5=8F=96=E6=96=87=E4=BB=B6=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A7=E7=89=88python=E4=B8=ADget()?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E9=97=AE=E9=A2=98=20click.multiple()?= =?UTF-8?q?=E6=94=B9=E4=B8=BAclick.multi()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/_base/base.py | 20 +++++++++ DrissionPage/_base/base.pyi | 2 + DrissionPage/_elements/chromium_element.py | 7 +++ DrissionPage/_pages/chromium_base.py | 52 +++++++++++++--------- DrissionPage/_pages/chromium_base.pyi | 13 +++--- DrissionPage/_pages/session_page.py | 39 +++++----------- DrissionPage/_pages/session_page.pyi | 2 - DrissionPage/_pages/web_page.py | 8 +--- DrissionPage/_units/clicker.py | 9 +++- DrissionPage/_units/clicker.pyi | 2 +- 10 files changed, 91 insertions(+), 63 deletions(-) diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 0339ef1..4c5e344 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -6,7 +6,9 @@ @License : BSD 3-Clause. """ from abc import abstractmethod +from pathlib import Path from re import sub +from urllib.parse import quote from DownloadKit import DownloadKit @@ -395,6 +397,24 @@ class BasePage(BaseParser): self._DownloadKit = DownloadKit(driver=self, goal_path=self.download_path) return self._DownloadKit + def _before_connect(self, url, retry, interval): + """连接前的准备 + :param url: 要访问的url + :param retry: 重试次数 + :param interval: 重试间隔 + :return: 重试次数、间隔、是否文件组成的tuple + """ + is_file = False + if isinstance(url, Path) or '://' not in url or ':\\\\' not in url: + p = Path(url) + if p.exists(): + url = str(p.absolute()) + is_file = True + self._url = quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%') + retry = retry if retry is not None else self.retry_times + interval = interval if interval is not None else self.retry_interval + return retry, interval, is_file + # ----------------以下属性或方法由后代实现---------------- @property def url(self): diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 6d7a54b..2fabbe6 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -218,6 +218,8 @@ class BasePage(BaseParser): @property def download(self) -> DownloadKit: ... + def _before_connect(self, url: str, retry: int, interval: float) -> tuple: ... + # ----------------以下属性或方法由后代实现---------------- @property def url(self) -> str: ... diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index a50566b..7442ace 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -1409,6 +1409,13 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None): if page.states.has_alert: raise AlertExistsError + try: + if Path(script).exists(): + with open(script, 'r', encoding='utf-8') as f: + script = f.read() + except OSError: + pass + end_time = perf_counter() + timeout try: if as_expr: diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index ebf93f3..d738b63 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -11,7 +11,6 @@ from pathlib import Path from re import findall from threading import Thread from time import perf_counter, sleep -from urllib.parse import quote from DataRecorder.tools import make_valid_name @@ -431,7 +430,7 @@ class ChromiumBase(BasePage): def run_js(self, script, *args, as_expr=False, timeout=None): """运行javascript代码 - :param script: js文本 + :param script: js文本或js文件路径 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... :param as_expr: 是否作为表达式运行,为True时args无效 :param timeout: js超时时间(秒),为None则使用页面timeouts.script设置 @@ -441,7 +440,7 @@ class ChromiumBase(BasePage): def run_js_loaded(self, script, *args, as_expr=False, timeout=None): """运行javascript代码,执行前等待页面加载完毕 - :param script: js文本 + :param script: js文本或js文件路径 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... :param as_expr: 是否作为表达式运行,为True时args无效 :param timeout: js超时时间(秒),为None则使用页面timeouts.script属性值 @@ -451,7 +450,7 @@ class ChromiumBase(BasePage): return run_js(self, script, as_expr, self.timeouts.script if timeout is None else timeout, args) def run_async_js(self, script, *args, as_expr=False): - """以异步方式执行js代码 + """以异步方式执行js代码或js文件路径 :param script: js文本 :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... :param as_expr: 是否作为表达式运行,为True时args无效 @@ -468,7 +467,7 @@ class ChromiumBase(BasePage): :param timeout: 连接超时时间(秒),为None时使用页面对象timeouts.page_load属性值 :return: 目标url是否可用 """ - retry, interval = self._before_connect(url, retry, interval) + retry, interval, is_file = self._before_connect(url, retry, interval) self._url_available = self._d_connect(self._url, times=retry, interval=interval, show_errmsg=show_errmsg, timeout=timeout) return self._url_available @@ -669,6 +668,33 @@ class ChromiumBase(BasePage): if ele: self.run_cdp('DOM.removeNode', nodeId=ele._node_id) + def add_ele(self, outerHTML, insert_to, before=None): + """新建一个元素 + :param outerHTML: 新元素的html文本 + :param insert_to: 插入到哪个元素中,可接收元素对象和定位符,为None添加到body + :param before: 在哪个子节点前面插入,可接收对象和定位符,为None插入到父元素末尾 + :return: 元素对象 + """ + insert_to = self.ele(insert_to) if insert_to else self.ele('t:body') + args = [outerHTML, insert_to] + if before: + args.append(self.ele(before)) + js = ''' + ele = document.createElement(null); + arguments[1].insertBefore(ele, arguments[2]); + ele.outerHTML = arguments[0]; + return arguments[2].previousElementSibling; + ''' + else: + js = ''' + ele = document.createElement(null); + arguments[1].appendChild(ele); + ele.outerHTML = arguments[0]; + return arguments[1].lastElementChild; + ''' + ele = self.run_js(js, *args) + return ele + def get_frame(self, loc_ind_ele, timeout=None): """获取页面中一个frame对象 :param loc_ind_ele: 定位符、iframe序号、ChromiumFrame对象,序号从1开始,可传入负数获取倒数第几个 @@ -933,22 +959,6 @@ class ChromiumBase(BasePage): pass return False - def _before_connect(self, url, retry, interval): - """连接前的准备 - :param url: 要访问的url - :param retry: 重试次数 - :param interval: 重试间隔 - :return: 重试次数和间隔组成的tuple - """ - p = Path(url) - if p.exists(): - self._url = str(p.absolute()) - else: - self._url = quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%') or 'chrome://newtab/' - retry = retry if retry is not None else self.retry_times - interval = interval if interval is not None else self.retry_interval - return retry, interval - def _d_connect(self, to_url, times=0, interval=1, show_errmsg=False, timeout=None): """尝试连接,重试若干次 :param to_url: 要访问的url diff --git a/DrissionPage/_pages/chromium_base.pyi b/DrissionPage/_pages/chromium_base.pyi index 4e09e61..fd7dab4 100644 --- a/DrissionPage/_pages/chromium_base.pyi +++ b/DrissionPage/_pages/chromium_base.pyi @@ -167,11 +167,11 @@ class ChromiumBase(BasePage): @property def states(self) -> PageStates: ... - def run_js(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ... + def run_js(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ... - def run_js_loaded(self, script: str, *args, as_expr: bool = False, timeout: float = None) -> Any: ... + def run_js_loaded(self, script: Union[str, Path], *args, as_expr: bool = False, timeout: float = None) -> Any: ... - def run_async_js(self, script: str, *args, as_expr: bool = False) -> None: ... + def run_async_js(self, script: Union[str, Path], *args, as_expr: bool = False) -> None: ... def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None, timeout: float = None) -> Union[None, bool]: ... @@ -214,6 +214,11 @@ class ChromiumBase(BasePage): def remove_ele(self, loc_or_ele: Union[ChromiumElement, ChromiumFrame, str, Tuple[str, str]]) -> None: ... + def add_ele(self, + outerHTML: str, + insert_to: Optional[ChromiumElement, str, Tuple[str, str]], + before: Optional[ChromiumElement, str, Tuple[str, str]] = None) -> ChromiumElement: ... + def get_frame(self, loc_ind_ele: Union[str, int, tuple, ChromiumFrame], timeout: float = None) -> ChromiumFrame: ... def get_frames(self, locator: Union[str, tuple] = None, timeout: float = None) -> List[ChromiumFrame]: ... @@ -260,8 +265,6 @@ class ChromiumBase(BasePage): def _on_alert_open(self, **kwargs): ... - def _before_connect(self, url: str, retry: int, interval: float) -> tuple: ... - def _d_connect(self, to_url: str, times: int = 0, interval: float = 1, show_errmsg: bool = False, timeout: float = None) -> Union[bool, None]: ... diff --git a/DrissionPage/_pages/session_page.py b/DrissionPage/_pages/session_page.py index 7c13ad2..0079aed 100644 --- a/DrissionPage/_pages/session_page.py +++ b/DrissionPage/_pages/session_page.py @@ -8,7 +8,7 @@ from pathlib import Path from re import search, DOTALL from time import sleep -from urllib.parse import urlparse, quote +from urllib.parse import urlparse from requests import Session, Response from requests.structures import CaseInsensitiveDict @@ -154,19 +154,16 @@ class SessionPage(BasePage): :param kwargs: 连接参数 :return: url是否可用 """ - if isinstance(url, Path): - url = str(url.absolute()) - if not url.lower().startswith('http'): - if url.startswith('file:///'): - url = url[8:] - if Path(url).exists(): - with open(url, 'rb') as f: - r = Response() - r._content = f.read() - r.status_code = 200 - self._response = r - return - return self._s_connect(url, 'get', show_errmsg, retry, interval, **kwargs) + retry, interval, is_file = self._before_connect(url.lstrip('file:///'), retry, interval) + if is_file: + with open(self._url, 'rb') as f: + r = Response() + r._content = f.read() + r.status_code = 200 + r.url = self._url + self._response = r + return True + return self._s_connect(self._url, 'get', show_errmsg, retry, interval, **kwargs) def post(self, url, show_errmsg=False, retry=None, interval=None, **kwargs): """用post方式跳转到url @@ -256,18 +253,6 @@ class SessionPage(BasePage): if self._response is not None: self._response.close() - def _before_connect(self, url, retry, interval): - """连接前的准备 - :param url: 要访问的url - :param retry: 重试次数 - :param interval: 重试间隔 - :return: 重试次数和间隔组成的tuple - """ - self._url = quote(url, safe='-_.~!*\'"();:@&=+$,/\\?#[]%') - retry = retry if retry is not None else self.retry_times - interval = interval if interval is not None else self.retry_interval - return retry, interval - def _s_connect(self, url, mode, show_errmsg=False, retry=None, interval=None, **kwargs): """执行get或post连接 :param url: 目标url @@ -278,7 +263,7 @@ class SessionPage(BasePage): :param kwargs: 连接参数 :return: url是否可用 """ - retry, interval = self._before_connect(url, retry, interval) + retry, interval, is_file = self._before_connect(url, retry, interval) self._response, info = self._make_response(self._url, mode, retry, interval, show_errmsg, **kwargs) if self._response is None: diff --git a/DrissionPage/_pages/session_page.pyi b/DrissionPage/_pages/session_page.pyi index 90673e2..ca8b6b2 100644 --- a/DrissionPage/_pages/session_page.pyi +++ b/DrissionPage/_pages/session_page.pyi @@ -152,8 +152,6 @@ class SessionPage(BasePage): def close(self) -> None: ... - def _before_connect(self, url: str, retry: int, interval: float) -> tuple: ... - def _s_connect(self, url: str, mode: str, diff --git a/DrissionPage/_pages/web_page.py b/DrissionPage/_pages/web_page.py index dd0b843..39fa3a0 100644 --- a/DrissionPage/_pages/web_page.py +++ b/DrissionPage/_pages/web_page.py @@ -273,12 +273,8 @@ class WebPage(SessionPage, ChromiumPage, BasePage): if copy_cookies: self.cookies_to_session() - if go: - url = super(SessionPage, self).url - if url.startswith('http'): - r = self.get(url) - if not r: - raise ConnectionError('s模式访问失败,请设置go=False,自行构造连接参数进行访问。') + if go and not self.get(super(SessionPage, self).url): + raise ConnectionError('s模式访问失败,请设置go=False,自行构造连接参数进行访问。') def cookies_to_session(self, copy_user_agent=True): """把driver对象的cookies复制到session对象 diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index 1c397ac..93be96e 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -133,7 +133,7 @@ class Clicker(object): x, y = offset_scroll(self._ele, offset_x, offset_y) self._click(x, y, button, count) - def multiple(self, times=2): + def multi(self, times=2): """多次点击 :param times: 默认双击 :return: None @@ -159,3 +159,10 @@ class Clicker(object): def twice(self): """双击元素""" self.at(count=2) + + def multiple(self, times=2): + """多次点击 + :param times: 默认双击 + :return: None + """ + self.at(count=times) diff --git a/DrissionPage/_units/clicker.pyi b/DrissionPage/_units/clicker.pyi index 5ac0a16..5eae4a1 100644 --- a/DrissionPage/_units/clicker.pyi +++ b/DrissionPage/_units/clicker.pyi @@ -24,6 +24,6 @@ class Clicker(object): def at(self, offset_x: float = None, offset_y: float = None, button: str = 'left', count: int = 1) -> None: ... - def multiple(self, times: int = 2) -> None: ... + def multi(self, times: int = 2) -> None: ... def _click(self, client_x: float, client_y: float, button: str = 'left', count: int = 1) -> None: ...