From ddbe20f7a82e5aa87b734584ce1affa37a93ba2e Mon Sep 17 00:00:00 2001 From: g1879 Date: Sat, 8 Aug 2020 00:08:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E8=B0=83=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/common.py | 26 ++--- DrissionPage/driver_element.py | 168 ++++++++++++++++++++++-------- DrissionPage/driver_page.py | 174 +++++++++++++++++++++++--------- DrissionPage/mix_page.py | 142 ++++++++++++++++++++------ DrissionPage/session_element.py | 104 +++++++++++++++---- DrissionPage/session_page.py | 130 +++++++++++++++++------- 6 files changed, 553 insertions(+), 191 deletions(-) diff --git a/DrissionPage/common.py b/DrissionPage/common.py index b720917..1a4352d 100644 --- a/DrissionPage/common.py +++ b/DrissionPage/common.py @@ -67,19 +67,19 @@ def get_loc_from_str(loc: str) -> tuple: """处理元素查找语句 查找方式:属性、tag name及属性、文本、xpath、css selector =表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 - 例: - @class:ele_class - class含有ele_class的元素 - @class=ele_class - class等于ele_class的元素 - @class - 带class属性的元素 - tag:div - div元素 - tag:div@class:ele_class - class含有ele_class的div元素 - tag:div@class=ele_class - class等于ele_class的div元素 - tag:div@text():search_text - 文本含有search_text的div元素 - tag:div@text()=search_text - 文本等于search_text的div元素 - text:search_text - 文本含有search_text的元素 - text=search_text - 文本等于search_text的元素 - xpath://div[@class="ele_class"] - css:div.ele_class + 示例: + @class:ele_class - class含有ele_class的元素 + @class=ele_class - class等于ele_class的元素 + @class - 带class属性的元素 + tag:div - div元素 + tag:div@class:ele_class - class含有ele_class的div元素 + tag:div@class=ele_class - class等于ele_class的div元素 + tag:div@text():search_text - 文本含有search_text的div元素 + tag:div@text()=search_text - 文本等于search_text的div元素 + text:search_text - 文本含有search_text的元素 + text=search_text - 文本等于search_text的元素 + xpath://div[@class="ele_class"] + css:div.ele_class """ loc_by = 'xpath' if loc.startswith('@'): # 根据属性查找 diff --git a/DrissionPage/driver_element.py b/DrissionPage/driver_element.py index c3cef81..1933fb5 100644 --- a/DrissionPage/driver_element.py +++ b/DrissionPage/driver_element.py @@ -12,7 +12,6 @@ from typing import Union, List, Any from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.select import Select from selenium.webdriver.support.wait import WebDriverWait from .common import DrissionElement, get_loc_from_str, translate_loc_to_xpath, avoid_duplicate_name @@ -32,6 +31,7 @@ class DriverElement(DrissionElement): @property def driver(self) -> WebDriver: + """返回控制元素的WebDriver对象""" return self._driver @property @@ -58,51 +58,63 @@ class DriverElement(DrissionElement): @property def text(self) -> str: - """元素内文本""" + """返回元素内所有文本""" return unescape(self.attr('innerText')).replace('\xa0', ' ') @property def html(self) -> str: - """元素innerHTML""" + """返回元素innerHTML文本""" return unescape(self.attr('innerHTML')).replace('\xa0', ' ') @property def tag(self) -> str: - """元素类型""" + """返回元素类型""" return self._inner_ele.tag_name @property def parent(self): - """父级元素""" + """返回父级元素""" return self.parents() @property def next(self): - """下一个兄弟元素""" + """返回后一个兄弟元素""" return self.nexts() @property def prev(self): - """上一个兄弟元素""" + """返回前一个兄弟元素""" return self.prevs() def parents(self, num: int = 1): - """N层父级元素""" + """返回上面第num级父元素 \n + :param num: 第几级父元素 + :return: DriverElement对象 + """ loc = 'xpath', f'.{"/.." * num}' return self.ele(loc, timeout=1, show_errmsg=False) def nexts(self, num: int = 1): - """下N个兄弟元素""" + """返回后面第num个兄弟元素 \n + :param num: 后面第几个兄弟元素 + :return: DriverElement对象 + """ loc = 'xpath', f'./following-sibling::*[{num}]' return self.ele(loc, timeout=1, show_errmsg=False) def prevs(self, num: int = 1): - """上N个兄弟元素""" + """返回前面第num个兄弟元素 \n + :param num: 前面第几个兄弟元素 + :return: DriverElement对象 + """ loc = 'xpath', f'./preceding-sibling::*[{num}]' return self.ele(loc, timeout=1, show_errmsg=False) def attr(self, attr: str) -> str: - """获取属性值""" + """获取属性值 \n + :param attr: 属性名 + :return: 属性值文本 + """ if attr == 'text': return self.text else: @@ -112,11 +124,33 @@ class DriverElement(DrissionElement): def ele(self, loc_or_str: Union[tuple, str], mode: str = None, - show_errmsg: bool = False, - timeout: float = None): - """根据loc获取元素或列表,可用字符串控制获取方式,可选'@属性名:'、'tag:'、'text:'、'css:'、'xpath:' - 如没有控制关键字,会按字符串文本搜索 - 例:ele.ele('@id:ele_id'),ele.ele('首页') + timeout: float = None, + show_errmsg: bool = False): + """返回当前元素下级符合条件的子元素,默认返回第一个 \n + 示例: \n + - 用loc元组查找: \n + ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + ele.ele('@class:ele_class') - 返回第一个class含有ele_class的子元素 \n + ele.ele('@name=ele_name') - 返回第一个name等于ele_name的子元素 \n + ele.ele('@placeholder') - 返回第一个带placeholder属性的子元素 \n + ele.ele('tag:p') - 返回第一个

子元素 \n + ele.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div子元素 \n + ele.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div子元素 \n + ele.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div子元素 \n + ele.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div子元素 \n + ele.ele('text:some_text') - 返回第一个文本含有some_text的子元素 \n + ele.ele('some_text') - 返回第一个文本含有some_text的子元素(等价于上一行) \n + ele.ele('text=some_text') - 返回第一个文本等于some_text的子元素 \n + ele.ele('xpath://div[@class="ele_class"]') - 返回第一个符合xpath的子元素 \n + ele.ele('css:div.ele_class') - 返回第一个符合css selector的子元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param mode: 'single' 或 'all‘,对应查找一个或全部 + :param timeout: 查找元素超时时间 + :param show_errmsg: 出现异常时是否打印信息 + :return: DriverElement对象 """ if isinstance(loc_or_str, str): loc_or_str = get_loc_from_str(loc_or_str) @@ -135,14 +169,44 @@ class DriverElement(DrissionElement): def eles(self, loc_or_str: Union[tuple, str], - show_errmsg: bool = False, - timeout: float = None): - """根据loc获取子元素列表""" + timeout: float = None, + show_errmsg: bool = False) -> list: + """返回当前元素下级所有符合条件的子元素 \n + 示例: \n + - 用loc元组查找: \n + ele.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + ele.eles('@class:ele_class') - 返回所有class含有ele_class的子元素 \n + ele.eles('@name=ele_name') - 返回所有name等于ele_name的子元素 \n + ele.eles('@placeholder') - 返回所有带placeholder属性的子元素 \n + ele.eles('tag:p') - 返回所有

子元素 \n + ele.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div子元素 \n + ele.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div子元素 \n + ele.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div子元素 \n + ele.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div子元素 \n + ele.eles('text:some_text') - 返回所有文本含有some_text的子元素 \n + ele.eles('some_text') - 返回所有文本含有some_text的子元素(等价于上一行) \n + ele.eles('text=some_text') - 返回所有文本等于some_text的子元素 \n + ele.eles('xpath://div[@class="ele_class"]') - 返回所有符合xpath的子元素 \n + ele.eles('css:div.ele_class') - 返回所有符合css selector的子元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 查找元素超时时间 + :param show_errmsg: 出现异常时是否打印信息 + :return: DriverElement对象组成的列表 + """ + if not isinstance(loc_or_str, tuple) or not isinstance(loc_or_str, str): + raise TypeError('Type of loc_or_str can only be tuple or str.') return self.ele(loc_or_str, mode='all', show_errmsg=show_errmsg, timeout=timeout) # -----------------以下为driver独占------------------- def click(self, by_js=None) -> bool: - """点击""" + """点击元素 \n + 尝试点击10次,若都失败就改用js点击 \n + :param by_js: 是否用js点击,为True时直接用js点击,为False时重试失败也不会改用js + :return: 是否点击成功 + """ if not by_js: for _ in range(10): try: @@ -157,7 +221,11 @@ class DriverElement(DrissionElement): return False def input(self, value, clear: bool = True) -> bool: - """输入文本""" + """输入文本 \n + :param value: 文本值 + :param clear: 输入前是否清空文本框 + :return: 是否输入成功 + """ try: if clear: self.clear() @@ -168,7 +236,10 @@ class DriverElement(DrissionElement): return False def run_script(self, script: str) -> Any: - """运行js""" + """执行js \n + :param script: js文本 + :return: js执行结果 + """ return self.inner_ele.parent.execute_script(script, self.inner_ele) def submit(self) -> None: @@ -176,7 +247,7 @@ class DriverElement(DrissionElement): self.inner_ele.submit() def clear(self) -> None: - """清空元素""" + """清空元素文本""" self.run_script("arguments[0].value=''") # self.ele.clear() @@ -202,19 +273,24 @@ class DriverElement(DrissionElement): @property def size(self) -> dict: - """元素大小""" + """返回元素宽和高""" return self.inner_ele.size @property def location(self) -> dict: - """元素坐标""" + """返回元素左上角坐标""" return self.inner_ele.location def screenshot(self, path: str, filename: str = None) -> str: - """元素截图""" + """对元素进行截图 \n + :param path: 保存路径 + :param filename: 图片文件名,不传入时以元素tag name命名 + :return: 图片完整路径 + """ name = filename or self.tag - name = avoid_duplicate_name(path, f'{name}.png') - Path(path).mkdir(parents=True, exist_ok=True) + path = Path(path).absolute() + path.mkdir(parents=True, exist_ok=True) + name = avoid_duplicate_name(str(path), f'{name}.png') # 等待元素加载完成 if self.tag == 'img': js = 'return arguments[0].complete && typeof arguments[0].naturalWidth != "undefined" ' \ @@ -226,7 +302,11 @@ class DriverElement(DrissionElement): return img_path def select(self, text: str) -> bool: - """在下拉列表中选择""" + """选择下拉列表中子元素 \n + :param text: 要选择的文本 + :return: 是否选择成功 + """ + from selenium.webdriver.support.select import Select ele = Select(self.inner_ele) try: ele.select_by_visible_text(text) @@ -235,7 +315,11 @@ class DriverElement(DrissionElement): return False def set_attr(self, attr: str, value: str) -> bool: - """设置元素属性""" + """设置元素属性 \n + :param attr: 属性名 + :param value: 属性值 + :return: 是否设置成功 + """ try: self.run_script(f"arguments[0].{attr} = '{value}';") return True @@ -244,10 +328,10 @@ class DriverElement(DrissionElement): return False def drag(self, x: int, y: int, speed: int = 40, shake: bool = True) -> bool: - """拖拽当前元素到相对位置 + """拖拽当前元素到相对位置 \n :param x: x变化值 :param y: y变化值 - :param speed: 速度 + :param speed: 拖动的速度,传入0即瞬间到达 :param shake: 是否随机抖动 :return: 是否推拽成功 """ @@ -259,9 +343,9 @@ class DriverElement(DrissionElement): ele_or_loc: Union[tuple, WebElement, DrissionElement], speed: int = 40, shake: bool = True) -> bool: - """拖拽当前元素,目标为另一个元素或坐标元组 + """拖拽当前元素,目标为另一个元素或坐标元组 \n :param ele_or_loc: 另一个元素或坐标元组,坐标为元素中点的坐标 - :param speed: 拖动的速度,默认为None即瞬间到达 + :param speed: 拖动的速度,传入0即瞬间到达 :param shake: 是否随机抖动 :return: 是否拖拽成功 """ @@ -298,7 +382,7 @@ class DriverElement(DrissionElement): return False if self.location == loc1 else True - def hover(self): + def hover(self) -> None: """鼠标悬停""" from selenium.webdriver import ActionChains ActionChains(self._driver).move_to_element(self.inner_ele).perform() @@ -309,14 +393,14 @@ def execute_driver_find(page_or_ele: Union[WebElement, WebDriver], mode: str = 'single', show_errmsg: bool = False, timeout: float = 10) -> Union[DriverElement, List[DriverElement]]: - """执行driver模式元素的查找 - 页面查找元素及元素查找下级元素皆使用此方法 - :param page_or_ele: driver模式页面或元素 - :param loc: 元素定位语句 - :param mode: 'single'或'all' - :param show_errmsg: 是否显示错误信息 + """执行driver模式元素的查找 \n + 页面查找元素及元素查找下级元素皆使用此方法 \n + :param page_or_ele: WebDriver对象或WebElement元素对象 + :param loc: 元素定位元组 + :param mode: 'single' 或 'all',对应获取第一个或全部 + :param show_errmsg: 出现异常时是否显示错误信息 :param timeout: 查找元素超时时间 - :return: 返回DriverElement元素或列表 + :return: 返回DriverElement元素或它们组成的列表 """ mode = mode or 'single' if mode not in ['single', 'all']: diff --git a/DrissionPage/driver_page.py b/DrissionPage/driver_page.py index cb7f4cb..26fc0c5 100644 --- a/DrissionPage/driver_page.py +++ b/DrissionPage/driver_page.py @@ -6,6 +6,7 @@ """ import time from glob import glob +from pathlib import Path from typing import Union, List, Any from urllib.parse import quote @@ -13,7 +14,7 @@ from selenium.common.exceptions import NoAlertPresentException from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement -from .common import get_loc_from_str, avoid_duplicate_name +from .common import get_loc_from_str, avoid_duplicate_name, translate_loc_to_xpath from .driver_element import DriverElement, execute_driver_find @@ -33,7 +34,7 @@ class DriverPage(object): @property def url(self) -> Union[str, None]: - """当前网页url""" + """返回当前网页url""" if not self._driver or not self.driver.current_url.startswith('http'): return None else: @@ -41,7 +42,7 @@ class DriverPage(object): @property def html(self) -> str: - """获取元素innerHTML,如未指定元素则获取页面源代码""" + """返回页面html文本""" return self.driver.find_element_by_xpath("//*").get_attribute("outerHTML") @property @@ -56,11 +57,15 @@ class DriverPage(object): @property def title(self) -> str: - """获取网页title""" + """返回网页title""" return self.driver.title def get(self, url: str, go_anyway: bool = False) -> Union[None, bool]: - """跳转到url""" + """访问url \n + :param url: 目标url + :param go_anyway: 若目标url与当前url一致,是否强制跳转 + :return: 目标url是否可用 + """ to_url = quote(url, safe='/:&?=%;#@') if not url or (not go_anyway and self.url == to_url): return @@ -70,40 +75,91 @@ class DriverPage(object): return self._url_available def ele(self, - loc_or_ele: Union[tuple, str, DriverElement], + loc_or_ele: Union[tuple, str, DriverElement, WebElement], mode: str = None, timeout: float = None, show_errmsg: bool = False) -> Union[DriverElement, List[DriverElement], None]: - """根据loc获取元素或列表,可用字符串控制获取方式,可选'@属性名:'、'tag:'、'text:'、'css:'、'xpath:' - 如没有控制关键字,会按字符串文本搜索 - 例:page.ele('@id:ele_id'),page.ele('首页') - :param loc_or_ele: 页面元素地址 - :param mode: 以某种方式查找元素,可选'single' , 'all', 'visible' - :param timeout: 是否显示错误信息 - :param show_errmsg: 是否显示错误信息 - :return: 页面元素对象或列表 + """返回页面中符合条件的元素,默认返回第一个 \n + 示例: \n + - 接收到元素对象时: \n + 返回DriverElement对象 \n + - 用loc元组查找: \n + ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.ele('@class:ele_class') - 返回第一个class含有ele_class的元素 \n + page.ele('@name=ele_name') - 返回第一个name等于ele_name的元素 \n + page.ele('@placeholder') - 返回第一个带placeholder属性的元素 \n + page.ele('tag:p') - 返回第一个

元素 \n + page.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div元素 \n + page.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div元素 \n + page.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div元素 \n + page.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div元素 \n + page.ele('text:some_text') - 返回第一个文本含有some_text的元素 \n + page.ele('some_text') - 返回第一个文本含有some_text的元素(等价于上一行) \n + page.ele('text=some_text') - 返回第一个文本等于some_text的元素 \n + page.ele('xpath://div[@class="ele_class"]') - 返回第一个符合xpath的元素 \n + page.ele('css:div.ele_class') - 返回第一个符合css selector的元素 \n + :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 + :param mode: 'single' 或 'all‘,对应查找一个或全部 + :param timeout: 查找元素超时时间 + :param show_errmsg: 出现异常时是否打印信息 + :return: DriverElement对象 """ - if isinstance(loc_or_ele, DriverElement): - return loc_or_ele - elif isinstance(loc_or_ele, str): + if isinstance(loc_or_ele, str): loc_or_ele = get_loc_from_str(loc_or_ele) + elif isinstance(loc_or_ele, tuple) and len(loc_or_ele) == 2: + loc_or_ele = translate_loc_to_xpath(loc_or_ele) + elif isinstance(loc_or_ele, DriverElement): + return loc_or_ele + elif isinstance(loc_or_ele, WebElement): + return DriverElement(loc_or_ele, self.timeout) + else: + raise ValueError('Argument loc_or_str can only be tuple, str, DriverElement, DriverElement.') timeout = timeout or self.timeout return execute_driver_find(self.driver, loc_or_ele, mode, show_errmsg, timeout) - def eles(self, loc: Union[tuple, str], timeout: float = None, show_errmsg=False) -> List[DriverElement]: - """查找符合条件的所有元素""" - return self.ele(loc, mode='all', timeout=timeout, show_errmsg=show_errmsg) + def eles(self, loc_or_str: Union[tuple, str], timeout: float = None, show_errmsg=False) -> List[DriverElement]: + """返回页面中所有符合条件的元素 \n + 示例: \n + - 用loc元组查找: \n + page.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.eles('@class:ele_class') - 返回所有class含有ele_class的元素 \n + page.eles('@name=ele_name') - 返回所有name等于ele_name的元素 \n + page.eles('@placeholder') - 返回所有带placeholder属性的元素 \n + page.eles('tag:p') - 返回所有

元素 \n + page.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div元素 \n + page.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div元素 \n + page.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div元素 \n + page.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div元素 \n + page.eles('text:some_text') - 返回所有文本含有some_text的元素 \n + page.eles('some_text') - 返回所有文本含有some_text的元素(等价于上一行) \n + page.eles('text=some_text') - 返回所有文本等于some_text的元素 \n + page.eles('xpath://div[@class="ele_class"]') - 返回所有符合xpath的元素 \n + page.eles('css:div.ele_class') - 返回所有符合css selector的元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 查找元素超时时间 + :param show_errmsg: 出现异常时是否打印信息 + :return: DriverElement对象组成的列表 + """ + if not isinstance(loc_or_str, tuple) or not isinstance(loc_or_str, str): + raise TypeError('Type of loc_or_str can only be tuple or str.') + return self.ele(loc_or_str, mode='all', timeout=timeout, show_errmsg=show_errmsg) # ----------------以下为独有函数----------------------- def wait_ele(self, loc_or_ele: Union[str, tuple, DriverElement, WebElement], mode: str, timeout: float = None) -> bool: - """等待元素从dom删除、显示、隐藏 - :param loc_or_ele: 元素、获取元素的字符串或元素的loc + """等待元素从dom删除、显示、隐藏 \n + :param loc_or_ele: 可以是元素、查询字符串、loc元组 :param mode: 等待方式,可选:'del', 'display', 'hidden' - :param timeout: 超时时间 + :param timeout: 等待超时时间 :return: 等待是否成功 """ if mode.lower() not in ['del', 'display', 'hidden']: @@ -152,20 +208,24 @@ class DriverPage(object): return False def check_page(self) -> Union[bool, None]: - """检查页面是否符合预期 - 由子类自行实现各页面的判定规则""" + """检查页面是否符合预期 \n + 由子类自行实现各页面的判定规则 + """ return None def run_script(self, script: str) -> Any: - """执行js脚本""" + """执行js \n + :param script: js文本 + :return: js执行结果 + """ return self.driver.execute_script(script) def get_tabs_sum(self) -> int: - """获取标签页数量""" + """返回标签页数量""" return len(self.driver.window_handles) def get_tab_num(self) -> int: - """获取当前tab号码""" + """返回当前标签页序号""" handle = self.driver.current_window_handle handle_list = self.driver.window_handles return handle_list.index(handle) @@ -181,7 +241,10 @@ class DriverPage(object): self.driver.close() def close_other_tabs(self, index: int = None) -> None: - """传入序号,关闭序号以外标签页,没有传入序号代表保留当前页""" + """关闭序号以外标签页,没有传入序号代表保留当前页 \n + :param index: 要保留的标签页序号,从0开始算 + :return: None + """ tabs = self.driver.window_handles # 获得所有标签页权柄 page_handle = tabs[index] if index >= 0 else self.driver.current_window_handle for i in tabs: # 遍历所有标签页,关闭非保留的 @@ -191,18 +254,20 @@ class DriverPage(object): self.driver.switch_to.window(page_handle) # 把权柄定位回保留的页面 def to_tab(self, index: int = 0) -> None: - """跳转到第几个标签页,从0开始算""" + """跳转到第几个标签页 \n + :param index: 标签页序号,从0开始算 + :return: None + """ tabs = self.driver.window_handles # 获得所有标签页权柄 self.driver.switch_to.window(tabs[index]) def to_iframe(self, loc_or_ele: Union[int, str, tuple, WebElement, DriverElement] = 'main') -> None: - """跳转到iframe - :param loc_or_ele: 可接收iframe序号(0开始)、id或name、控制字符串、loc tuple、WebElement对象、DriverElement对象, + """跳转到iframe \n + :param loc_or_ele: 可接收iframe序号(0开始)、id或name、控制字符串、loc元组、WebElement对象、DriverElement对象, 传入'main'跳到最高层,传入'parent'跳到上一层 :return: None """ - if isinstance(loc_or_ele, int): - # 根据序号跳转 + if isinstance(loc_or_ele, int): # 根据序号跳转 self.driver.switch_to.frame(loc_or_ele) elif isinstance(loc_or_ele, str): if loc_or_ele == 'main': # 跳转到最上级 @@ -223,22 +288,30 @@ class DriverPage(object): self.driver.switch_to.frame(ele.inner_ele) def screenshot(self, path: str, filename: str = None) -> str: - """获取网页截图""" + """截取页面可见范围截图 \n + :param path: 保存路径 + :param filename: 图片文件名,不传入时以页面title命名 + :return: 图片完整路径 + """ name = filename or self.title - name = avoid_duplicate_name(path, f'{name}.png') + path = Path(path).absolute() + path.mkdir(parents=True, exist_ok=True) + name = avoid_duplicate_name(str(path), f'{name}.png') img_path = f'{path}\\{name}' self.driver.save_screenshot(img_path) - # TODO: 实现全页截图 - return name + return img_path def scroll_to_see(self, loc_or_ele: Union[str, tuple, WebElement, DriverElement]) -> None: - """滚动直到元素可见""" + """滚动页面直到元素可见 \n + :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串(详见ele函数注释) + :return: None + """ ele = self.ele(loc_or_ele) ele.run_script("arguments[0].scrollIntoView();") def scroll_to(self, mode: str = 'bottom', pixel: int = 300) -> None: - """滚动页面,按照参数决定如何滚动 - :param mode: 滚动的方向,top、bottom、rightmost、leftmost、up、down、left、right + """按参数指示方式滚动页面 \n + :param mode: 可选滚动方向:'top', 'bottom', 'rightmost', 'leftmost', 'up', 'down', 'left', 'right' :param pixel: 滚动的像素 :return: None """ @@ -264,15 +337,19 @@ class DriverPage(object): "Argument mode can only be 'top', 'bottom', 'rightmost', 'leftmost', 'up', 'down', 'left', 'right'.") def refresh(self) -> None: - """刷新页面""" + """刷新当前页面""" self.driver.refresh() def back(self) -> None: - """后退""" + """浏览器后退""" self.driver.back() def set_window_size(self, x: int = None, y: int = None) -> None: - """设置窗口大小,默认最大化""" + """设置浏览器窗口大小,默认最大化 \n + :param x: 浏览器窗口高 + :param y: 浏览器窗口宽 + :return: None + """ if not x and not y: self.driver.maximize_window() else: @@ -283,14 +360,17 @@ class DriverPage(object): self.driver.set_window_size(new_x, new_y) def chrome_downloading(self, download_path: str) -> list: - """检查下载情况""" + """返回浏览器下载中的文件列表 \n + :param download_path: 下载文件夹路径 + :return: 文件列表 + """ return glob(f'{download_path}\\*.crdownload') def process_alert(self, mode: str = 'ok', text: str = None) -> Union[str, None]: - """处理提示框 + """处理提示框 \n :param mode: 'ok' 或 'cancel',若输入其它值,不会按按钮但依然返回文本值 :param text: 处理prompt提示框时可输入文本 - :return: + :return: 提示框内容文本 """ try: alert = self.driver.switch_to.alert diff --git a/DrissionPage/mix_page.py b/DrissionPage/mix_page.py index b49a1b1..e11bd7c 100644 --- a/DrissionPage/mix_page.py +++ b/DrissionPage/mix_page.py @@ -7,8 +7,9 @@ from typing import Union, List from requests import Response -from requests_html import HTMLSession +from requests_html import HTMLSession, Element from selenium.webdriver.chrome.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement from .drission import Drission from .driver_element import DriverElement @@ -32,7 +33,7 @@ class MixPage(Null, SessionPage, DriverPage): """ def __init__(self, drission: Union[Drission, str] = None, mode: str = 'd', timeout: float = 10): - """初始化函数 + """初始化函数 \n :param drission: 整合了driver和session的类,传入's'或'd'时快速配置相应模式 :param mode: 默认使用selenium的d模式 """ @@ -57,7 +58,7 @@ class MixPage(Null, SessionPage, DriverPage): @property def url(self) -> Union[str, None]: - """根据模式获取当前活动的url""" + """返回当前url""" if self._mode == 'd': if not self._driver or not self._drission.driver.current_url.startswith('http'): return None @@ -68,19 +69,20 @@ class MixPage(Null, SessionPage, DriverPage): @property def session_url(self) -> str: + """返回session访问的url""" return self._response.url if self._response else None @property def mode(self) -> str: - """返回当前模式 - :return: 's'或'd' + """返回当前模式 \n + :return: 's' 或 'd' """ return self._mode def change_mode(self, mode: str = None, go: bool = True) -> None: - """切换模式,接收字符串s或d,除此以外的字符串会切换为d模式 - 切换时会把当前模式的cookies复制到目标模式 - 切换后,如果go是True,调用相应的get函数使访问的页面同步 + """切换模式,接收字符串s或d,除此以外的字符串会切换为d模式 \n + 切换时会把当前模式的cookies复制到目标模式 \n + 切换后,如果go是True,调用相应的get函数使访问的页面同步 \n :param mode: 模式字符串 :param go: 是否跳转到原模式的url """ @@ -109,17 +111,17 @@ class MixPage(Null, SessionPage, DriverPage): @property def driver(self) -> WebDriver: - """返回driver对象,如没有则创建 + """返回driver对象,如没有则创建 \n 每次访问时切换到d模式,用于独有函数及外部调用 - :return:selenium的WebDriver对象 + :return: WebDriver对象 """ self.change_mode('d') return self._drission.driver @property def session(self) -> HTMLSession: - """返回session对象,如没有则创建 - :return:requests-html的HTMLSession对象 + """返回session对象,如没有则创建 \n + :return: HTMLSession对象 """ return self._drission.session @@ -131,27 +133,38 @@ class MixPage(Null, SessionPage, DriverPage): @property def cookies(self) -> Union[dict, list]: - """返回cookies,根据模式获取""" + """返回cookies""" if self._mode == 's': return super().cookies elif self._mode == 'd': return super(SessionPage, self).cookies def cookies_to_session(self, copy_user_agent: bool = False) -> None: - """从driver复制cookies到session + """从driver复制cookies到session \n :param copy_user_agent : 是否复制user agent信息 """ self._drission.cookies_to_session(copy_user_agent) def cookies_to_driver(self, url=None) -> None: - """从session复制cookies到driver,chrome需要指定域才能接收cookies""" + """从session复制cookies到driver \n + chrome需要指定域才能接收cookies \n + :param url: 目标域 + :return: None + """ u = url or self.session_url self._drission.cookies_to_driver(u) # ----------------重写SessionPage的函数----------------------- def post(self, url: str, data: dict = None, go_anyway: bool = False, **kwargs) -> Union[bool, None]: - """post前先转换模式,但不跳转""" + """用post方式跳转到url \n + post前先转换模式,但不跳转 + :param url: 目标url + :param data: 提交的数据 + :param go_anyway: 若目标url与当前url一致,是否强制跳转 + :param kwargs: 连接参数 + :return: url是否可用 + """ self.change_mode('s', go=False) return super().post(url, data, go_anyway, **kwargs) @@ -162,6 +175,16 @@ class MixPage(Null, SessionPage, DriverPage): file_exists: str = 'rename', show_msg: bool = False, **kwargs) -> tuple: + """下载一个文件 \n + d模式下下载前先同步cookies \n + :param file_url: 文件url + :param goal_path: 存放路径 + :param rename: 重命名文件,可不写扩展名 + :param file_exists: 若存在同名文件,可选择 'rename', 'overwrite', 'skip' 方式处理 + :param show_msg: 是否显示下载信息 + :param kwargs: 连接参数 + :return: 下载是否成功(bool)和状态信息(成功时信息为文件路径)的元组 + """ if self.mode == 'd': self.cookies_to_session() return super().download(file_url, goal_path, rename, file_exists, show_msg, **kwargs) @@ -169,7 +192,7 @@ class MixPage(Null, SessionPage, DriverPage): # ----------------重写DriverPage的函数----------------------- def chrome_downloading(self, download_path: str = None) -> list: - """检查浏览器下载情况,返回正在下载的文件列表 + """返回浏览器下载中的文件列表 \n :param download_path: 下载文件夹路径,默认读取配置信息 :return: 正在下载的文件列表 """ @@ -180,13 +203,18 @@ class MixPage(Null, SessionPage, DriverPage): raise except: raise IOError('Download path not found.') - return super().chrome_downloading(path) # ----------------以下为共用函数----------------------- def get(self, url: str, go_anyway=False, **kwargs) -> Union[bool, None]: - """跳转到一个url,跳转前先同步cookies,跳转后判断目标url是否可用""" + """跳转到一个url \n + 跳转前先同步cookies,跳转后判断目标url是否可用 + :param url: 目标url + :param go_anyway: 若目标url与当前url一致,是否强制跳转 + :param kwargs: 连接参数,s模式专用 + :return: url是否可用 + """ if self._mode == 'd': if super(SessionPage, self).get(url=url, go_anyway=go_anyway) is None: return @@ -198,24 +226,74 @@ class MixPage(Null, SessionPage, DriverPage): elif self._mode == 's': return None if super().get(url=url, go_anyway=go_anyway, **kwargs) is None else self._url_available - def ele(self, loc_or_ele: Union[tuple, str, DriverElement, SessionElement], mode: str = None, timeout: float = None, + def ele(self, + loc_or_ele: Union[tuple, str, DriverElement, SessionElement, Element, WebElement], + mode: str = None, + timeout: float = None, show_errmsg: bool = False) -> Union[DriverElement, SessionElement]: - """查找一个元素,根据模式调用对应的查找函数 - :param loc_or_ele: 页面元素地址 - :param mode: 以某种方式查找元素,可选'single','all','visible'(d模式独有) - :param timeout: 超时时间 - :param show_errmsg: 是否显示错误信息 - :return: 页面元素对象,s模式下返回Element,d模式下返回WebElement + """返回页面中符合条件的元素,默认返回第一个 \n + 示例: \n + - 接收到元素对象时: \n + 返回元素对象对象 \n + - 用loc元组查找: \n + ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.ele('@class:ele_class') - 返回第一个class含有ele_class的元素 \n + page.ele('@name=ele_name') - 返回第一个name等于ele_name的元素 \n + page.ele('@placeholder') - 返回第一个带placeholder属性的元素 \n + page.ele('tag:p') - 返回第一个

元素 \n + page.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div元素 \n + page.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div元素 \n + page.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div元素 \n + page.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div元素 \n + page.ele('text:some_text') - 返回第一个文本含有some_text的元素 \n + page.ele('some_text') - 返回第一个文本含有some_text的元素(等价于上一行) \n + page.ele('text=some_text') - 返回第一个文本等于some_text的元素 \n + page.ele('xpath://div[@class="ele_class"]') - 返回第一个符合xpath的元素 \n + page.ele('css:div.ele_class') - 返回第一个符合css selector的元素 \n + :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 + :param mode: 'single' 或 'all‘,对应查找一个或全部 + :param timeout: 查找元素超时时间,d模式专用 + :param show_errmsg: 出现异常时是否打印信息 + :return: 元素对象,d模式为DriverElement,s模式为SessionElement """ if self._mode == 's': return super().ele(loc_or_ele, mode=mode, show_errmsg=show_errmsg) elif self._mode == 'd': timeout = timeout or self.timeout - return DriverPage.ele(self, loc_or_ele, mode=mode, timeout=timeout, show_errmsg=show_errmsg) + return super(SessionPage, self).ele(loc_or_ele, mode=mode, timeout=timeout, show_errmsg=show_errmsg) - def eles(self, loc_or_str: Union[tuple, str], timeout: float = None, show_errmsg: bool = False) \ - -> List[DriverElement]: - """查找符合条件的所有元素""" + def eles(self, + loc_or_str: Union[tuple, str], + timeout: float = None, + show_errmsg: bool = False) -> Union[List[DriverElement], List[SessionElement]]: + """返回页面中所有符合条件的元素 \n + 示例: \n + - 用loc元组查找: \n + page.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.eles('@class:ele_class') - 返回所有class含有ele_class的元素 \n + page.eles('@name=ele_name') - 返回所有name等于ele_name的元素 \n + page.eles('@placeholder') - 返回所有带placeholder属性的元素 \n + page.eles('tag:p') - 返回所有

元素 \n + page.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div元素 \n + page.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div元素 \n + page.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div元素 \n + page.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div元素 \n + page.eles('text:some_text') - 返回所有文本含有some_text的元素 \n + page.eles('some_text') - 返回所有文本含有some_text的元素(等价于上一行) \n + page.eles('text=some_text') - 返回所有文本等于some_text的元素 \n + page.eles('xpath://div[@class="ele_class"]') - 返回所有符合xpath的元素 \n + page.eles('css:div.ele_class') - 返回所有符合css selector的元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 查找元素超时时间,d模式专用 + :param show_errmsg: 出现异常时是否打印信息 + :return: 元素对象组成的列表,d模式下由DriverElement组成,s模式下由SessionElement组成 + """ if self._mode == 's': return super().eles(loc_or_str, show_errmsg) elif self._mode == 'd': @@ -223,7 +301,7 @@ class MixPage(Null, SessionPage, DriverPage): @property def html(self) -> str: - """获取页面HTML""" + """返回页面html文本""" if self._mode == 's': return super().html elif self._mode == 'd': @@ -231,7 +309,7 @@ class MixPage(Null, SessionPage, DriverPage): @property def title(self) -> str: - """获取页面title""" + """返回网页title""" if self._mode == 's': return super().title elif self._mode == 'd': diff --git a/DrissionPage/session_element.py b/DrissionPage/session_element.py index 5287468..ac3ecb3 100644 --- a/DrissionPage/session_element.py +++ b/DrissionPage/session_element.py @@ -33,38 +33,42 @@ class SessionElement(DrissionElement): @property def text(self) -> str: - """元素内文本""" + """返回元素内所有文本""" return unescape(self._inner_ele.text).replace('\xa0', ' ') @property def html(self) -> str: - """元素innerHTML""" + """返回元素innerHTML文本""" html = unescape(self._inner_ele.html).replace('\xa0', ' ') r = re.match(r'<.*?>(.*)', html, flags=re.DOTALL) return None if not r else r.group(1) @property def tag(self) -> str: - """获取标签名""" + """返回元素类型""" return self._inner_ele.tag @property def parent(self): - """父级元素""" + """返回父级元素""" return self.parents() @property def next(self): - """下一个兄弟元素""" + """返回后一个兄弟元素""" return self.nexts() @property def prev(self): - """上一个兄弟元素""" + """返回前一个兄弟元素""" return self.prevs() def parents(self, num: int = 1): - """requests_html的Element打包了lxml的元素对象,从lxml元素对象读取上下级关系后再重新打包""" + """返回上面第num级父元素 \n + requests_html的Element打包了lxml的元素对象,从lxml元素对象读取上下级关系后再重新打包 \n + :param num: 第几级父元素 + :return: SessionElement对象 + """ try: return SessionElement( Element(element=self.inner_ele.element.xpath(f'..{"/.." * (num - 1)}')[0], url=self.inner_ele.url)) @@ -72,7 +76,10 @@ class SessionElement(DrissionElement): return None def nexts(self, num: int = 1): - """requests_html的Element打包了lxml的元素对象,从lxml元素对象读取上下级关系后再重新打包""" + """返回后面第num个兄弟元素 \n + :param num: 后面第几个兄弟元素 + :return: SessionElement对象 + """ try: return SessionElement( Element(element=self.inner_ele.element.xpath(f'./following-sibling::*[{num}]')[0], @@ -81,7 +88,10 @@ class SessionElement(DrissionElement): return None def prevs(self, num: int = 1): - """requests_html的Element打包了lxml的元素对象,从lxml元素对象读取上下级关系后再重新打包""" + """返回前面第num个兄弟元素 \n + :param num: 前面第几个兄弟元素 + :return: SessionElement对象 + """ try: return SessionElement( Element(element=self.inner_ele.element.xpath(f'./preceding-sibling::*[{num}]')[0], @@ -90,9 +100,30 @@ class SessionElement(DrissionElement): return None def ele(self, loc_or_str: Union[tuple, str], mode: str = None, show_errmsg: bool = False): - """根据loc获取元素或列表,可用字符串控制获取方式,可选'@属性名:'、'tag:'、'text:'、'css:'、'xpath:' - 如没有控制关键字,会按字符串文本搜索 - 例:ele.ele('@id:ele_id'),ele.ele('首页') + """返回当前元素下级符合条件的子元素,默认返回第一个 \n + 示例: \n + - 用loc元组查找: \n + ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + ele.ele('@class:ele_class') - 返回第一个class含有ele_class的子元素 \n + ele.ele('@name=ele_name') - 返回第一个name等于ele_name的子元素 \n + ele.ele('@placeholder') - 返回第一个带placeholder属性的子元素 \n + ele.ele('tag:p') - 返回第一个

子元素 \n + ele.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div子元素 \n + ele.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div子元素 \n + ele.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div子元素 \n + ele.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div子元素 \n + ele.ele('text:some_text') - 返回第一个文本含有some_text的子元素 \n + ele.ele('some_text') - 返回第一个文本含有some_text的子元素(等价于上一行) \n + ele.ele('text=some_text') - 返回第一个文本等于some_text的子元素 \n + ele.ele('xpath://div[@class="ele_class"]') - 返回第一个符合xpath的子元素 \n + ele.ele('css:div.ele_class') - 返回第一个符合css selector的子元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param mode: 'single' 或 'all‘,对应查找一个或全部 + :param show_errmsg: 出现异常时是否打印信息 + :return: SessionElement对象 """ if isinstance(loc_or_str, str): loc_or_str = get_loc_from_str(loc_or_str) @@ -111,11 +142,40 @@ class SessionElement(DrissionElement): return execute_session_find(self.inner_ele, loc_or_str, mode, show_errmsg) - def eles(self, loc_or_str: Union[tuple, str], show_errmsg: bool = False): + def eles(self, loc_or_str: Union[tuple, str], show_errmsg: bool = False) -> list: + """返回当前元素下级所有符合条件的子元素 \n + 示例: \n + - 用loc元组查找: \n + ele.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + ele.eles('@class:ele_class') - 返回所有class含有ele_class的子元素 \n + ele.eles('@name=ele_name') - 返回所有name等于ele_name的子元素 \n + ele.eles('@placeholder') - 返回所有带placeholder属性的子元素 \n + ele.eles('tag:p') - 返回所有

子元素 \n + ele.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div子元素 \n + ele.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div子元素 \n + ele.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div子元素 \n + ele.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div子元素 \n + ele.eles('text:some_text') - 返回所有文本含有some_text的子元素 \n + ele.eles('some_text') - 返回所有文本含有some_text的子元素(等价于上一行) \n + ele.eles('text=some_text') - 返回所有文本等于some_text的子元素 \n + ele.eles('xpath://div[@class="ele_class"]') - 返回所有符合xpath的子元素 \n + ele.eles('css:div.ele_class') - 返回所有符合css selector的子元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param show_errmsg: 出现异常时是否打印信息 + :return: SessionElement对象组成的列表 + """ + if not isinstance(loc_or_str, tuple) or not isinstance(loc_or_str, str): + raise TypeError('Type of loc_or_str can only be tuple or str.') return self.ele(loc_or_str, mode='all', show_errmsg=show_errmsg) - def attr(self, attr: str) -> str: - """获取属性值""" + def attr(self, attr: str) -> Union[str, None]: + """返回属性值 \n + :param attr: 属性名 + :return: 属性值文本,没有该属性返回None + """ try: if attr == 'href': # 如直接获取attr只能获取相对地址 @@ -144,19 +204,19 @@ class SessionElement(DrissionElement): else: return self._inner_ele.attrs[attr] except: - return '' + return None def execute_session_find(page_or_ele: BaseParser, loc: tuple, mode: str = 'single', show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement]]: - """执行session模式元素的查找 - 页面查找元素及元素查找下级元素皆使用此方法 - :param page_or_ele: session模式页面或元素 - :param loc: 元素定位语句 - :param mode: 'single'或'all' - :param show_errmsg: 是否显示错误信息 + """执行session模式元素的查找 \n + 页面查找元素及元素查找下级元素皆使用此方法 \n + :param page_or_ele: request_html的页面或元素对象 + :param loc: 元素定位元组 + :param mode: 'single' 或 'all',对应获取第一个或全部 + :param show_errmsg: 出现异常时是否显示错误信息 :return: 返回SessionElement元素或列表 """ mode = mode or 'single' diff --git a/DrissionPage/session_page.py b/DrissionPage/session_page.py index 82c5598..80e99a9 100644 --- a/DrissionPage/session_page.py +++ b/DrissionPage/session_page.py @@ -12,7 +12,7 @@ from time import time from typing import Union, List from urllib.parse import urlparse, quote -from requests_html import HTMLSession, HTMLResponse +from requests_html import HTMLSession, HTMLResponse, Element from .common import get_loc_from_str, translate_loc_to_xpath, avoid_duplicate_name from .config import OptionsManager @@ -32,64 +32,118 @@ class SessionPage(object): @property def session(self) -> HTMLSession: + """返回session对象""" return self._session @property def response(self) -> HTMLResponse: + """返回访问url得到的response对象""" return self._response @property def url(self) -> str: - """当前访问url""" + """返回当前访问url""" return self._url @property def url_available(self) -> bool: - """url有效性""" + """返回当前访问的url有效性""" return self._url_available @property def cookies(self) -> dict: - """当前session的cookies""" + """返回session的cookies""" return self.session.cookies.get_dict() @property def title(self) -> str: - """获取网页title""" + """返回网页title""" return self.ele(('css selector', 'title')).text @property def html(self) -> str: - """获取元素innerHTML,如未指定元素则获取所有源代码""" + """返回页面html文本""" return self.response.html.html def ele(self, - loc_or_ele: Union[tuple, str, SessionElement], + loc_or_ele: Union[tuple, str, SessionElement, Element], mode: str = None, show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement], None]: - """根据loc获取元素或列表,可用字符串控制获取方式,可选'@属性名:'、'tag:'、'text:'、'css:'、'xpath:' - 如没有控制关键字,会按字符串文本搜索 - 例:page.ele('@id:ele_id'),page.ele('首页') - :param loc_or_ele: 页面元素地址 - :param mode: 以某种方式查找元素,可选'single','all' - :param show_errmsg: 是否显示错误信息 - :return: 页面元素对象或列表 + """返回页面中符合条件的元素,默认返回第一个 \n + 示例: \n + - 接收到元素对象时: \n + 返回SessionElement对象 \n + - 用loc元组查找: \n + ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.ele('@class:ele_class') - 返回第一个class含有ele_class的元素 \n + page.ele('@name=ele_name') - 返回第一个name等于ele_name的元素 \n + page.ele('@placeholder') - 返回第一个带placeholder属性的元素 \n + page.ele('tag:p') - 返回第一个

元素 \n + page.ele('tag:div@class:ele_class') - 返回第一个class含有ele_class的div元素 \n + page.ele('tag:div@class=ele_class') - 返回第一个class等于ele_class的div元素 \n + page.ele('tag:div@text():some_text') - 返回第一个文本含有some_text的div元素 \n + page.ele('tag:div@text()=some_text') - 返回第一个文本等于some_text的div元素 \n + page.ele('text:some_text') - 返回第一个文本含有some_text的元素 \n + page.ele('some_text') - 返回第一个文本含有some_text的元素(等价于上一行) \n + page.ele('text=some_text') - 返回第一个文本等于some_text的元素 \n + page.ele('xpath://div[@class="ele_class"]') - 返回第一个符合xpath的元素 \n + page.ele('css:div.ele_class') - 返回第一个符合css selector的元素 \n + :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 + :param mode: 'single' 或 'all‘,对应查找一个或全部 + :param show_errmsg: 出现异常时是否打印信息 + :return: SessionElement对象 """ - if isinstance(loc_or_ele, SessionElement): - return loc_or_ele - elif isinstance(loc_or_ele, str): + if isinstance(loc_or_ele, str): loc = get_loc_from_str(loc_or_ele) - else: + elif isinstance(loc_or_ele, tuple) and len(loc_or_ele) == 2: loc = translate_loc_to_xpath(loc_or_ele) - + elif isinstance(loc_or_ele, SessionElement): + return loc_or_ele + elif isinstance(loc_or_ele, Element): + return SessionElement(loc_or_ele) + else: + raise ValueError('Argument loc_or_str can only be tuple, str, SessionElement, Element.') return execute_session_find(self.response.html, loc, mode, show_errmsg) - def eles(self, loc: Union[tuple, str], show_errmsg: bool = False) -> List[SessionElement]: - """查找符合条件的所有元素""" - return self.ele(loc, mode='all', show_errmsg=True) + def eles(self, loc_or_str: Union[tuple, str], show_errmsg: bool = False) -> List[SessionElement]: + """返回页面中所有符合条件的元素 \n + 示例: \n + - 用loc元组查找: \n + page.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的元素 \n + - 用查询字符串查找: \n + 查找方式:属性、tag name和属性、文本、xpath、css selector \n + 其中,@表示属性,=表示精确匹配,:表示模糊匹配,无控制字符串时默认搜索该字符串 \n + page.eles('@class:ele_class') - 返回所有class含有ele_class的元素 \n + page.eles('@name=ele_name') - 返回所有name等于ele_name的元素 \n + page.eles('@placeholder') - 返回所有带placeholder属性的元素 \n + page.eles('tag:p') - 返回所有

元素 \n + page.eles('tag:div@class:ele_class') - 返回所有class含有ele_class的div元素 \n + page.eles('tag:div@class=ele_class') - 返回所有class等于ele_class的div元素 \n + page.eles('tag:div@text():some_text') - 返回所有文本含有some_text的div元素 \n + page.eles('tag:div@text()=some_text') - 返回所有文本等于some_text的div元素 \n + page.eles('text:some_text') - 返回所有文本含有some_text的元素 \n + page.eles('some_text') - 返回所有文本含有some_text的元素(等价于上一行) \n + page.eles('text=some_text') - 返回所有文本等于some_text的元素 \n + page.eles('xpath://div[@class="ele_class"]') - 返回所有符合xpath的元素 \n + page.eles('css:div.ele_class') - 返回所有符合css selector的元素 \n + :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param show_errmsg: 出现异常时是否打印信息 + :return: SessionElement对象组成的列表 + """ + if not isinstance(loc_or_str, tuple) or not isinstance(loc_or_str, str): + raise TypeError('Type of loc_or_str can only be tuple or str.') + return self.ele(loc_or_str, mode='all', show_errmsg=True) def get(self, url: str, go_anyway: bool = False, **kwargs) -> Union[bool, None]: - """用get方式跳转到url,调用_make_response()函数生成response对象""" + """用get方式跳转到url \n + :param url: 目标url + :param go_anyway: 若目标url与当前url一致,是否强制跳转 + :param kwargs: 连接参数 + :return: url是否可用 + """ to_url = quote(url, safe='/:&?=%;#@') if not url or (not go_anyway and self.url == to_url): return @@ -100,8 +154,14 @@ class SessionPage(object): self._url_available = True if self._response and self._response.ok else False return self._url_available - def post(self, url: str, data: dict = None, go_anyway: bool = False, **kwargs) -> Union[bool, None]: - """用post方式跳转到url,调用_make_response()函数生成response对象""" + def post(self, url: str, data: dict = None, go_anyway: bool = True, **kwargs) -> Union[bool, None]: + """用post方式跳转到url \n + :param url: 目标url + :param data: 提交的数据 + :param go_anyway: 若目标url与当前url一致,是否强制跳转 + :param kwargs: 连接参数 + :return: url是否可用 + """ to_url = quote(url, safe='/:&?=%;#@') if not url or (not go_anyway and self._url == to_url): return @@ -122,16 +182,16 @@ class SessionPage(object): file_exists: str = 'rename', show_msg: bool = False, **kwargs) -> tuple: - """下载一个文件 - 生成的response不写入self._response,是临时的 + """下载一个文件 \n :param file_url: 文件url - :param goal_path: 存放路径url - :param rename: 重命名文件,不改变扩展名 - :param kwargs: 连接参数 - :param file_exists: 若存在同名文件,可选择'rename', 'overwrite', 'skip'方式处理 + :param goal_path: 存放路径 + :param rename: 重命名文件,可不写扩展名 + :param file_exists: 若存在同名文件,可选择 'rename', 'overwrite', 'skip' 方式处理 :param show_msg: 是否显示下载信息 - :return: 元组,bool和状态信息(成功时信息为文件路径) + :param kwargs: 连接参数 + :return: 下载是否成功(bool)和状态信息(成功时信息为文件路径)的元组 """ + # 生成的response不写入self._response,是临时的 goal_path = goal_path or OptionsManager().get_value('paths', 'global_tmp_path') if not goal_path: raise IOError('No path specified.') @@ -220,10 +280,10 @@ class SessionPage(object): return download_status, info def _make_response(self, url: str, mode: str = 'get', data: dict = None, **kwargs) -> tuple: - """生成response对象。接收mode参数,以决定用什么方式。 - :param url: 要访问的网址 + """生成response对象 \n + :param url: 目标url :param mode: 'get', 'post' 中选择 - :param data: 提交的数据 + :param data: post方式要提交的数据 :param kwargs: 其它参数 :return: Response对象 """