微调功能,完善注释

This commit is contained in:
g1879 2020-08-08 00:08:52 +08:00
parent 29dfda713d
commit ddbe20f7a8
6 changed files with 553 additions and 191 deletions

View File

@ -67,19 +67,19 @@ def get_loc_from_str(loc: str) -> tuple:
"""处理元素查找语句 """处理元素查找语句
查找方式属性tag name及属性文本xpathcss selector 查找方式属性tag name及属性文本xpathcss selector
=表示精确匹配:表示模糊匹配无控制字符串时默认搜索该字符串 =表示精确匹配:表示模糊匹配无控制字符串时默认搜索该字符串
@class:ele_class - class含有ele_class的元素 @class:ele_class - class含有ele_class的元素
@class=ele_class - class等于ele_class的元素 @class=ele_class - class等于ele_class的元素
@class - 带class属性的元素 @class - 带class属性的元素
tag:div - div元素 tag:div - div元素
tag:div@class:ele_class - class含有ele_class的div元素 tag:div@class:ele_class - class含有ele_class的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元素
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的元素
text=search_text - 文本等于search_text的元素 text=search_text - 文本等于search_text的元素
xpath://div[@class="ele_class"] xpath://div[@class="ele_class"]
css:div.ele_class css:div.ele_class
""" """
loc_by = 'xpath' loc_by = 'xpath'
if loc.startswith('@'): # 根据属性查找 if loc.startswith('@'): # 根据属性查找

View File

@ -12,7 +12,6 @@ from typing import Union, List, Any
from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
from .common import DrissionElement, get_loc_from_str, translate_loc_to_xpath, avoid_duplicate_name from .common import DrissionElement, get_loc_from_str, translate_loc_to_xpath, avoid_duplicate_name
@ -32,6 +31,7 @@ class DriverElement(DrissionElement):
@property @property
def driver(self) -> WebDriver: def driver(self) -> WebDriver:
"""返回控制元素的WebDriver对象"""
return self._driver return self._driver
@property @property
@ -58,51 +58,63 @@ class DriverElement(DrissionElement):
@property @property
def text(self) -> str: def text(self) -> str:
"""元素内文本""" """返回元素内所有文本"""
return unescape(self.attr('innerText')).replace('\xa0', ' ') return unescape(self.attr('innerText')).replace('\xa0', ' ')
@property @property
def html(self) -> str: def html(self) -> str:
"""元素innerHTML""" """返回元素innerHTML文本"""
return unescape(self.attr('innerHTML')).replace('\xa0', ' ') return unescape(self.attr('innerHTML')).replace('\xa0', ' ')
@property @property
def tag(self) -> str: def tag(self) -> str:
"""元素类型""" """返回元素类型"""
return self._inner_ele.tag_name return self._inner_ele.tag_name
@property @property
def parent(self): def parent(self):
"""父级元素""" """返回父级元素"""
return self.parents() return self.parents()
@property @property
def next(self): def next(self):
"""一个兄弟元素""" """返回后一个兄弟元素"""
return self.nexts() return self.nexts()
@property @property
def prev(self): def prev(self):
"""一个兄弟元素""" """返回前一个兄弟元素"""
return self.prevs() return self.prevs()
def parents(self, num: int = 1): def parents(self, num: int = 1):
"""N层父级元素""" """返回上面第num级父元素 \n
:param num: 第几级父元素
:return: DriverElement对象
"""
loc = 'xpath', f'.{"/.." * num}' loc = 'xpath', f'.{"/.." * num}'
return self.ele(loc, timeout=1, show_errmsg=False) return self.ele(loc, timeout=1, show_errmsg=False)
def nexts(self, num: int = 1): def nexts(self, num: int = 1):
"""下N个兄弟元素""" """返回后面第num个兄弟元素 \n
:param num: 后面第几个兄弟元素
:return: DriverElement对象
"""
loc = 'xpath', f'./following-sibling::*[{num}]' loc = 'xpath', f'./following-sibling::*[{num}]'
return self.ele(loc, timeout=1, show_errmsg=False) return self.ele(loc, timeout=1, show_errmsg=False)
def prevs(self, num: int = 1): def prevs(self, num: int = 1):
"""上N个兄弟元素""" """返回前面第num个兄弟元素 \n
:param num: 前面第几个兄弟元素
:return: DriverElement对象
"""
loc = 'xpath', f'./preceding-sibling::*[{num}]' loc = 'xpath', f'./preceding-sibling::*[{num}]'
return self.ele(loc, timeout=1, show_errmsg=False) return self.ele(loc, timeout=1, show_errmsg=False)
def attr(self, attr: str) -> str: def attr(self, attr: str) -> str:
"""获取属性值""" """获取属性值 \n
:param attr: 属性名
:return: 属性值文本
"""
if attr == 'text': if attr == 'text':
return self.text return self.text
else: else:
@ -112,11 +124,33 @@ class DriverElement(DrissionElement):
def ele(self, def ele(self,
loc_or_str: Union[tuple, str], loc_or_str: Union[tuple, str],
mode: str = None, mode: str = None,
show_errmsg: bool = False, timeout: float = None,
timeout: float = None): show_errmsg: bool = False):
"""根据loc获取元素或列表可用字符串控制获取方式可选'@属性名:''tag:''text:''css:''xpath:' """返回当前元素下级符合条件的子元素,默认返回第一个 \n
如没有控制关键字会按字符串文本搜索 示例 \n
ele.ele('@id:ele_id')ele.ele('首页') - 用loc元组查找 \n
ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回第一个<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): if isinstance(loc_or_str, str):
loc_or_str = get_loc_from_str(loc_or_str) loc_or_str = get_loc_from_str(loc_or_str)
@ -135,14 +169,44 @@ class DriverElement(DrissionElement):
def eles(self, def eles(self,
loc_or_str: Union[tuple, str], loc_or_str: Union[tuple, str],
show_errmsg: bool = False, timeout: float = None,
timeout: float = None): show_errmsg: bool = False) -> list:
"""根据loc获取子元素列表""" """返回当前元素下级所有符合条件的子元素 \n
示例 \n
- 用loc元组查找 \n
ele.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回所有<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) return self.ele(loc_or_str, mode='all', show_errmsg=show_errmsg, timeout=timeout)
# -----------------以下为driver独占------------------- # -----------------以下为driver独占-------------------
def click(self, by_js=None) -> bool: def click(self, by_js=None) -> bool:
"""点击""" """点击元素 \n
尝试点击10次若都失败就改用js点击 \n
:param by_js: 是否用js点击为True时直接用js点击为False时重试失败也不会改用js
:return: 是否点击成功
"""
if not by_js: if not by_js:
for _ in range(10): for _ in range(10):
try: try:
@ -157,7 +221,11 @@ class DriverElement(DrissionElement):
return False return False
def input(self, value, clear: bool = True) -> bool: def input(self, value, clear: bool = True) -> bool:
"""输入文本""" """输入文本 \n
:param value: 文本值
:param clear: 输入前是否清空文本框
:return: 是否输入成功
"""
try: try:
if clear: if clear:
self.clear() self.clear()
@ -168,7 +236,10 @@ class DriverElement(DrissionElement):
return False return False
def run_script(self, script: str) -> Any: 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) return self.inner_ele.parent.execute_script(script, self.inner_ele)
def submit(self) -> None: def submit(self) -> None:
@ -176,7 +247,7 @@ class DriverElement(DrissionElement):
self.inner_ele.submit() self.inner_ele.submit()
def clear(self) -> None: def clear(self) -> None:
"""清空元素""" """清空元素文本"""
self.run_script("arguments[0].value=''") self.run_script("arguments[0].value=''")
# self.ele.clear() # self.ele.clear()
@ -202,19 +273,24 @@ class DriverElement(DrissionElement):
@property @property
def size(self) -> dict: def size(self) -> dict:
"""元素大小""" """返回元素宽和高"""
return self.inner_ele.size return self.inner_ele.size
@property @property
def location(self) -> dict: def location(self) -> dict:
"""元素坐标""" """返回元素左上角坐标"""
return self.inner_ele.location return self.inner_ele.location
def screenshot(self, path: str, filename: str = None) -> str: def screenshot(self, path: str, filename: str = None) -> str:
"""元素截图""" """对元素进行截图 \n
:param path: 保存路径
:param filename: 图片文件名不传入时以元素tag name命名
:return: 图片完整路径
"""
name = filename or self.tag name = filename or self.tag
name = avoid_duplicate_name(path, f'{name}.png') path = Path(path).absolute()
Path(path).mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
name = avoid_duplicate_name(str(path), f'{name}.png')
# 等待元素加载完成 # 等待元素加载完成
if self.tag == 'img': if self.tag == 'img':
js = 'return arguments[0].complete && typeof arguments[0].naturalWidth != "undefined" ' \ js = 'return arguments[0].complete && typeof arguments[0].naturalWidth != "undefined" ' \
@ -226,7 +302,11 @@ class DriverElement(DrissionElement):
return img_path return img_path
def select(self, text: str) -> bool: def select(self, text: str) -> bool:
"""在下拉列表中选择""" """选择下拉列表中子元素 \n
:param text: 要选择的文本
:return: 是否选择成功
"""
from selenium.webdriver.support.select import Select
ele = Select(self.inner_ele) ele = Select(self.inner_ele)
try: try:
ele.select_by_visible_text(text) ele.select_by_visible_text(text)
@ -235,7 +315,11 @@ class DriverElement(DrissionElement):
return False return False
def set_attr(self, attr: str, value: str) -> bool: def set_attr(self, attr: str, value: str) -> bool:
"""设置元素属性""" """设置元素属性 \n
:param attr: 属性名
:param value: 属性值
:return: 是否设置成功
"""
try: try:
self.run_script(f"arguments[0].{attr} = '{value}';") self.run_script(f"arguments[0].{attr} = '{value}';")
return True return True
@ -244,10 +328,10 @@ class DriverElement(DrissionElement):
return False return False
def drag(self, x: int, y: int, speed: int = 40, shake: bool = True) -> bool: def drag(self, x: int, y: int, speed: int = 40, shake: bool = True) -> bool:
"""拖拽当前元素到相对位置 """拖拽当前元素到相对位置 \n
:param x: x变化值 :param x: x变化值
:param y: y变化值 :param y: y变化值
:param speed: 速度 :param speed: 拖动的速度传入0即瞬间到达
:param shake: 是否随机抖动 :param shake: 是否随机抖动
:return: 是否推拽成功 :return: 是否推拽成功
""" """
@ -259,9 +343,9 @@ class DriverElement(DrissionElement):
ele_or_loc: Union[tuple, WebElement, DrissionElement], ele_or_loc: Union[tuple, WebElement, DrissionElement],
speed: int = 40, speed: int = 40,
shake: bool = True) -> bool: shake: bool = True) -> bool:
"""拖拽当前元素,目标为另一个元素或坐标元组 """拖拽当前元素,目标为另一个元素或坐标元组 \n
:param ele_or_loc: 另一个元素或坐标元组坐标为元素中点的坐标 :param ele_or_loc: 另一个元素或坐标元组坐标为元素中点的坐标
:param speed: 拖动的速度默认为None即瞬间到达 :param speed: 拖动的速度传入0即瞬间到达
:param shake: 是否随机抖动 :param shake: 是否随机抖动
:return: 是否拖拽成功 :return: 是否拖拽成功
""" """
@ -298,7 +382,7 @@ class DriverElement(DrissionElement):
return False if self.location == loc1 else True return False if self.location == loc1 else True
def hover(self): def hover(self) -> None:
"""鼠标悬停""" """鼠标悬停"""
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
ActionChains(self._driver).move_to_element(self.inner_ele).perform() 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', mode: str = 'single',
show_errmsg: bool = False, show_errmsg: bool = False,
timeout: float = 10) -> Union[DriverElement, List[DriverElement]]: timeout: float = 10) -> Union[DriverElement, List[DriverElement]]:
"""执行driver模式元素的查找 """执行driver模式元素的查找 \n
页面查找元素及元素查找下级元素皆使用此方法 页面查找元素及元素查找下级元素皆使用此方法 \n
:param page_or_ele: driver模式页面或元素 :param page_or_ele: WebDriver对象或WebElement元素对象
:param loc: 元素定位语句 :param loc: 元素定位元组
:param mode: 'single''all' :param mode: 'single' 'all'对应获取第一个或全部
:param show_errmsg: 是否显示错误信息 :param show_errmsg: 出现异常时是否显示错误信息
:param timeout: 查找元素超时时间 :param timeout: 查找元素超时时间
:return: 返回DriverElement元素或列表 :return: 返回DriverElement元素或它们组成的列表
""" """
mode = mode or 'single' mode = mode or 'single'
if mode not in ['single', 'all']: if mode not in ['single', 'all']:

View File

@ -6,6 +6,7 @@
""" """
import time import time
from glob import glob from glob import glob
from pathlib import Path
from typing import Union, List, Any from typing import Union, List, Any
from urllib.parse import quote 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.chrome.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement 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 from .driver_element import DriverElement, execute_driver_find
@ -33,7 +34,7 @@ class DriverPage(object):
@property @property
def url(self) -> Union[str, None]: def url(self) -> Union[str, None]:
"""当前网页url""" """返回当前网页url"""
if not self._driver or not self.driver.current_url.startswith('http'): if not self._driver or not self.driver.current_url.startswith('http'):
return None return None
else: else:
@ -41,7 +42,7 @@ class DriverPage(object):
@property @property
def html(self) -> str: def html(self) -> str:
"""获取元素innerHTML如未指定元素则获取页面源代码""" """返回页面html文本"""
return self.driver.find_element_by_xpath("//*").get_attribute("outerHTML") return self.driver.find_element_by_xpath("//*").get_attribute("outerHTML")
@property @property
@ -56,11 +57,15 @@ class DriverPage(object):
@property @property
def title(self) -> str: def title(self) -> str:
"""获取网页title""" """返回网页title"""
return self.driver.title return self.driver.title
def get(self, url: str, go_anyway: bool = False) -> Union[None, bool]: 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='/:&?=%;#@') to_url = quote(url, safe='/:&?=%;#@')
if not url or (not go_anyway and self.url == to_url): if not url or (not go_anyway and self.url == to_url):
return return
@ -70,40 +75,91 @@ class DriverPage(object):
return self._url_available return self._url_available
def ele(self, def ele(self,
loc_or_ele: Union[tuple, str, DriverElement], loc_or_ele: Union[tuple, str, DriverElement, WebElement],
mode: str = None, mode: str = None,
timeout: float = None, timeout: float = None,
show_errmsg: bool = False) -> Union[DriverElement, List[DriverElement], None]: show_errmsg: bool = False) -> Union[DriverElement, List[DriverElement], None]:
"""根据loc获取元素或列表可用字符串控制获取方式可选'@属性名:''tag:''text:''css:''xpath:' """返回页面中符合条件的元素,默认返回第一个 \n
如没有控制关键字会按字符串文本搜索 示例 \n
page.ele('@id:ele_id')page.ele('首页') - 接收到元素对象时 \n
:param loc_or_ele: 页面元素地址 返回DriverElement对象 \n
:param mode: 以某种方式查找元素可选'single' , 'all', 'visible' - 用loc元组查找 \n
:param timeout: 是否显示错误信息 ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
:param show_errmsg: 是否显示错误信息 - 用查询字符串查找 \n
:return: 页面元素对象或列表 查找方式属性tag name和属性文本xpathcss 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') - 返回第一个<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): if isinstance(loc_or_ele, str):
return loc_or_ele
elif isinstance(loc_or_ele, str):
loc_or_ele = get_loc_from_str(loc_or_ele) 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 timeout = timeout or self.timeout
return execute_driver_find(self.driver, loc_or_ele, mode, show_errmsg, 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]: def eles(self, loc_or_str: Union[tuple, str], timeout: float = None, show_errmsg=False) -> List[DriverElement]:
"""查找符合条件的所有元素""" """返回页面中所有符合条件的元素 \n
return self.ele(loc, mode='all', timeout=timeout, show_errmsg=show_errmsg) 示例 \n
- 用loc元组查找 \n
page.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回所有<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, def wait_ele(self,
loc_or_ele: Union[str, tuple, DriverElement, WebElement], loc_or_ele: Union[str, tuple, DriverElement, WebElement],
mode: str, mode: str,
timeout: float = None) -> bool: timeout: float = None) -> bool:
"""等待元素从dom删除、显示、隐藏 """等待元素从dom删除、显示、隐藏 \n
:param loc_or_ele: 元素获取元素的字符串或元素的loc :param loc_or_ele: 可以是元素查询字符串loc元组
:param mode: 等待方式可选'del', 'display', 'hidden' :param mode: 等待方式可选'del', 'display', 'hidden'
:param timeout: 超时时间 :param timeout: 等待超时时间
:return: 等待是否成功 :return: 等待是否成功
""" """
if mode.lower() not in ['del', 'display', 'hidden']: if mode.lower() not in ['del', 'display', 'hidden']:
@ -152,20 +208,24 @@ class DriverPage(object):
return False return False
def check_page(self) -> Union[bool, None]: def check_page(self) -> Union[bool, None]:
"""检查页面是否符合预期 """检查页面是否符合预期 \n
由子类自行实现各页面的判定规则""" 由子类自行实现各页面的判定规则
"""
return None return None
def run_script(self, script: str) -> Any: def run_script(self, script: str) -> Any:
"""执行js脚本""" """执行js \n
:param script: js文本
:return: js执行结果
"""
return self.driver.execute_script(script) return self.driver.execute_script(script)
def get_tabs_sum(self) -> int: def get_tabs_sum(self) -> int:
"""获取标签页数量""" """返回标签页数量"""
return len(self.driver.window_handles) return len(self.driver.window_handles)
def get_tab_num(self) -> int: def get_tab_num(self) -> int:
"""获取当前tab号码""" """返回当前标签页序号"""
handle = self.driver.current_window_handle handle = self.driver.current_window_handle
handle_list = self.driver.window_handles handle_list = self.driver.window_handles
return handle_list.index(handle) return handle_list.index(handle)
@ -181,7 +241,10 @@ class DriverPage(object):
self.driver.close() self.driver.close()
def close_other_tabs(self, index: int = None) -> None: def close_other_tabs(self, index: int = None) -> None:
"""传入序号,关闭序号以外标签页,没有传入序号代表保留当前页""" """关闭序号以外标签页,没有传入序号代表保留当前页 \n
:param index: 要保留的标签页序号从0开始算
:return: None
"""
tabs = self.driver.window_handles # 获得所有标签页权柄 tabs = self.driver.window_handles # 获得所有标签页权柄
page_handle = tabs[index] if index >= 0 else self.driver.current_window_handle page_handle = tabs[index] if index >= 0 else self.driver.current_window_handle
for i in tabs: # 遍历所有标签页,关闭非保留的 for i in tabs: # 遍历所有标签页,关闭非保留的
@ -191,18 +254,20 @@ class DriverPage(object):
self.driver.switch_to.window(page_handle) # 把权柄定位回保留的页面 self.driver.switch_to.window(page_handle) # 把权柄定位回保留的页面
def to_tab(self, index: int = 0) -> None: def to_tab(self, index: int = 0) -> None:
"""跳转到第几个标签页从0开始算""" """跳转到第几个标签页 \n
:param index: 标签页序号从0开始算
:return: None
"""
tabs = self.driver.window_handles # 获得所有标签页权柄 tabs = self.driver.window_handles # 获得所有标签页权柄
self.driver.switch_to.window(tabs[index]) self.driver.switch_to.window(tabs[index])
def to_iframe(self, loc_or_ele: Union[int, str, tuple, WebElement, DriverElement] = 'main') -> None: def to_iframe(self, loc_or_ele: Union[int, str, tuple, WebElement, DriverElement] = 'main') -> None:
"""跳转到iframe """跳转到iframe \n
:param loc_or_ele: 可接收iframe序号(0开始)id或name控制字符串loc tupleWebElement对象DriverElement对象 :param loc_or_ele: 可接收iframe序号(0开始)id或name控制字符串loc元组WebElement对象DriverElement对象
传入'main'跳到最高层传入'parent'跳到上一层 传入'main'跳到最高层传入'parent'跳到上一层
:return: None :return: None
""" """
if isinstance(loc_or_ele, int): if isinstance(loc_or_ele, int): # 根据序号跳转
# 根据序号跳转
self.driver.switch_to.frame(loc_or_ele) self.driver.switch_to.frame(loc_or_ele)
elif isinstance(loc_or_ele, str): elif isinstance(loc_or_ele, str):
if loc_or_ele == 'main': # 跳转到最上级 if loc_or_ele == 'main': # 跳转到最上级
@ -223,22 +288,30 @@ class DriverPage(object):
self.driver.switch_to.frame(ele.inner_ele) self.driver.switch_to.frame(ele.inner_ele)
def screenshot(self, path: str, filename: str = None) -> str: def screenshot(self, path: str, filename: str = None) -> str:
"""获取网页截图""" """截取页面可见范围截图 \n
:param path: 保存路径
:param filename: 图片文件名不传入时以页面title命名
:return: 图片完整路径
"""
name = filename or self.title 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}' img_path = f'{path}\\{name}'
self.driver.save_screenshot(img_path) self.driver.save_screenshot(img_path)
# TODO: 实现全页截图 return img_path
return name
def scroll_to_see(self, loc_or_ele: Union[str, tuple, WebElement, DriverElement]) -> None: 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 = self.ele(loc_or_ele)
ele.run_script("arguments[0].scrollIntoView();") ele.run_script("arguments[0].scrollIntoView();")
def scroll_to(self, mode: str = 'bottom', pixel: int = 300) -> None: def scroll_to(self, mode: str = 'bottom', pixel: int = 300) -> None:
"""滚动页面,按照参数决定如何滚动 """按参数指示方式滚动页面 \n
:param mode: 滚动的方向topbottomrightmostleftmostupdownleftright :param mode: 可选滚动方向'top', 'bottom', 'rightmost', 'leftmost', 'up', 'down', 'left', 'right'
:param pixel: 滚动的像素 :param pixel: 滚动的像素
:return: None :return: None
""" """
@ -264,15 +337,19 @@ class DriverPage(object):
"Argument mode can only be 'top', 'bottom', 'rightmost', 'leftmost', 'up', 'down', 'left', 'right'.") "Argument mode can only be 'top', 'bottom', 'rightmost', 'leftmost', 'up', 'down', 'left', 'right'.")
def refresh(self) -> None: def refresh(self) -> None:
"""刷新页面""" """刷新当前页面"""
self.driver.refresh() self.driver.refresh()
def back(self) -> None: def back(self) -> None:
"""后退""" """浏览器后退"""
self.driver.back() self.driver.back()
def set_window_size(self, x: int = None, y: int = None) -> None: 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: if not x and not y:
self.driver.maximize_window() self.driver.maximize_window()
else: else:
@ -283,14 +360,17 @@ class DriverPage(object):
self.driver.set_window_size(new_x, new_y) self.driver.set_window_size(new_x, new_y)
def chrome_downloading(self, download_path: str) -> list: def chrome_downloading(self, download_path: str) -> list:
"""检查下载情况""" """返回浏览器下载中的文件列表 \n
:param download_path: 下载文件夹路径
:return: 文件列表
"""
return glob(f'{download_path}\\*.crdownload') return glob(f'{download_path}\\*.crdownload')
def process_alert(self, mode: str = 'ok', text: str = None) -> Union[str, None]: def process_alert(self, mode: str = 'ok', text: str = None) -> Union[str, None]:
"""处理提示框 """处理提示框 \n
:param mode: 'ok' 'cancel'若输入其它值不会按按钮但依然返回文本值 :param mode: 'ok' 'cancel'若输入其它值不会按按钮但依然返回文本值
:param text: 处理prompt提示框时可输入文本 :param text: 处理prompt提示框时可输入文本
:return: :return: 提示框内容文本
""" """
try: try:
alert = self.driver.switch_to.alert alert = self.driver.switch_to.alert

View File

@ -7,8 +7,9 @@
from typing import Union, List from typing import Union, List
from requests import Response 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.chrome.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from .drission import Drission from .drission import Drission
from .driver_element import DriverElement 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): def __init__(self, drission: Union[Drission, str] = None, mode: str = 'd', timeout: float = 10):
"""初始化函数 """初始化函数 \n
:param drission: 整合了driver和session的类传入's''d'时快速配置相应模式 :param drission: 整合了driver和session的类传入's''d'时快速配置相应模式
:param mode: 默认使用selenium的d模式 :param mode: 默认使用selenium的d模式
""" """
@ -57,7 +58,7 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def url(self) -> Union[str, None]: def url(self) -> Union[str, None]:
"""根据模式获取当前活动的url""" """返回当前url"""
if self._mode == 'd': if self._mode == 'd':
if not self._driver or not self._drission.driver.current_url.startswith('http'): if not self._driver or not self._drission.driver.current_url.startswith('http'):
return None return None
@ -68,19 +69,20 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def session_url(self) -> str: def session_url(self) -> str:
"""返回session访问的url"""
return self._response.url if self._response else None return self._response.url if self._response else None
@property @property
def mode(self) -> str: def mode(self) -> str:
"""返回当前模式 """返回当前模式 \n
:return: 's''d' :return: 's' 'd'
""" """
return self._mode return self._mode
def change_mode(self, mode: str = None, go: bool = True) -> None: def change_mode(self, mode: str = None, go: bool = True) -> None:
"""切换模式接收字符串s或d除此以外的字符串会切换为d模式 """切换模式接收字符串s或d除此以外的字符串会切换为d模式 \n
切换时会把当前模式的cookies复制到目标模式 切换时会把当前模式的cookies复制到目标模式 \n
切换后如果go是True调用相应的get函数使访问的页面同步 切换后如果go是True调用相应的get函数使访问的页面同步 \n
:param mode: 模式字符串 :param mode: 模式字符串
:param go: 是否跳转到原模式的url :param go: 是否跳转到原模式的url
""" """
@ -109,17 +111,17 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def driver(self) -> WebDriver: def driver(self) -> WebDriver:
"""返回driver对象如没有则创建 """返回driver对象如没有则创建 \n
每次访问时切换到d模式用于独有函数及外部调用 每次访问时切换到d模式用于独有函数及外部调用
:return:selenium的WebDriver对象 :return: WebDriver对象
""" """
self.change_mode('d') self.change_mode('d')
return self._drission.driver return self._drission.driver
@property @property
def session(self) -> HTMLSession: def session(self) -> HTMLSession:
"""返回session对象如没有则创建 """返回session对象如没有则创建 \n
:return:requests-html的HTMLSession对象 :return: HTMLSession对象
""" """
return self._drission.session return self._drission.session
@ -131,27 +133,38 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def cookies(self) -> Union[dict, list]: def cookies(self) -> Union[dict, list]:
"""返回cookies,根据模式获取""" """返回cookies"""
if self._mode == 's': if self._mode == 's':
return super().cookies return super().cookies
elif self._mode == 'd': elif self._mode == 'd':
return super(SessionPage, self).cookies return super(SessionPage, self).cookies
def cookies_to_session(self, copy_user_agent: bool = False) -> None: def cookies_to_session(self, copy_user_agent: bool = False) -> None:
"""从driver复制cookies到session """从driver复制cookies到session \n
:param copy_user_agent : 是否复制user agent信息 :param copy_user_agent : 是否复制user agent信息
""" """
self._drission.cookies_to_session(copy_user_agent) self._drission.cookies_to_session(copy_user_agent)
def cookies_to_driver(self, url=None) -> None: def cookies_to_driver(self, url=None) -> None:
"""从session复制cookies到driverchrome需要指定域才能接收cookies""" """从session复制cookies到driver \n
chrome需要指定域才能接收cookies \n
:param url: 目标域
:return: None
"""
u = url or self.session_url u = url or self.session_url
self._drission.cookies_to_driver(u) self._drission.cookies_to_driver(u)
# ----------------重写SessionPage的函数----------------------- # ----------------重写SessionPage的函数-----------------------
def post(self, url: str, data: dict = None, go_anyway: bool = False, **kwargs) -> Union[bool, None]: 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) self.change_mode('s', go=False)
return super().post(url, data, go_anyway, **kwargs) return super().post(url, data, go_anyway, **kwargs)
@ -162,6 +175,16 @@ class MixPage(Null, SessionPage, DriverPage):
file_exists: str = 'rename', file_exists: str = 'rename',
show_msg: bool = False, show_msg: bool = False,
**kwargs) -> tuple: **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': if self.mode == 'd':
self.cookies_to_session() self.cookies_to_session()
return super().download(file_url, goal_path, rename, file_exists, show_msg, **kwargs) return super().download(file_url, goal_path, rename, file_exists, show_msg, **kwargs)
@ -169,7 +192,7 @@ class MixPage(Null, SessionPage, DriverPage):
# ----------------重写DriverPage的函数----------------------- # ----------------重写DriverPage的函数-----------------------
def chrome_downloading(self, download_path: str = None) -> list: def chrome_downloading(self, download_path: str = None) -> list:
"""检查浏览器下载情况,返回正在下载的文件列表 """返回浏览器下载中的文件列表 \n
:param download_path: 下载文件夹路径默认读取配置信息 :param download_path: 下载文件夹路径默认读取配置信息
:return: 正在下载的文件列表 :return: 正在下载的文件列表
""" """
@ -180,13 +203,18 @@ class MixPage(Null, SessionPage, DriverPage):
raise raise
except: except:
raise IOError('Download path not found.') raise IOError('Download path not found.')
return super().chrome_downloading(path) return super().chrome_downloading(path)
# ----------------以下为共用函数----------------------- # ----------------以下为共用函数-----------------------
def get(self, url: str, go_anyway=False, **kwargs) -> Union[bool, None]: 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 self._mode == 'd':
if super(SessionPage, self).get(url=url, go_anyway=go_anyway) is None: if super(SessionPage, self).get(url=url, go_anyway=go_anyway) is None:
return return
@ -198,24 +226,74 @@ class MixPage(Null, SessionPage, DriverPage):
elif self._mode == 's': elif self._mode == 's':
return None if super().get(url=url, go_anyway=go_anyway, **kwargs) is None else self._url_available 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]: show_errmsg: bool = False) -> Union[DriverElement, SessionElement]:
"""查找一个元素,根据模式调用对应的查找函数 """返回页面中符合条件的元素,默认返回第一个 \n
:param loc_or_ele: 页面元素地址 示例 \n
:param mode: 以某种方式查找元素可选'single','all','visible'(d模式独有) - 接收到元素对象时 \n
:param timeout: 超时时间 返回元素对象对象 \n
:param show_errmsg: 是否显示错误信息 - 用loc元组查找 \n
:return: 页面元素对象s模式下返回Elementd模式下返回WebElement ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回第一个<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模式为DriverElements模式为SessionElement
""" """
if self._mode == 's': if self._mode == 's':
return super().ele(loc_or_ele, mode=mode, show_errmsg=show_errmsg) return super().ele(loc_or_ele, mode=mode, show_errmsg=show_errmsg)
elif self._mode == 'd': elif self._mode == 'd':
timeout = timeout or self.timeout 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) \ def eles(self,
-> List[DriverElement]: 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和属性文本xpathcss 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') - 返回所有<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': if self._mode == 's':
return super().eles(loc_or_str, show_errmsg) return super().eles(loc_or_str, show_errmsg)
elif self._mode == 'd': elif self._mode == 'd':
@ -223,7 +301,7 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def html(self) -> str: def html(self) -> str:
"""获取页面HTML""" """返回页面html文本"""
if self._mode == 's': if self._mode == 's':
return super().html return super().html
elif self._mode == 'd': elif self._mode == 'd':
@ -231,7 +309,7 @@ class MixPage(Null, SessionPage, DriverPage):
@property @property
def title(self) -> str: def title(self) -> str:
"""获取页面title""" """返回网页title"""
if self._mode == 's': if self._mode == 's':
return super().title return super().title
elif self._mode == 'd': elif self._mode == 'd':

View File

@ -33,38 +33,42 @@ class SessionElement(DrissionElement):
@property @property
def text(self) -> str: def text(self) -> str:
"""元素内文本""" """返回元素内所有文本"""
return unescape(self._inner_ele.text).replace('\xa0', ' ') return unescape(self._inner_ele.text).replace('\xa0', ' ')
@property @property
def html(self) -> str: def html(self) -> str:
"""元素innerHTML""" """返回元素innerHTML文本"""
html = unescape(self._inner_ele.html).replace('\xa0', ' ') html = unescape(self._inner_ele.html).replace('\xa0', ' ')
r = re.match(r'<.*?>(.*)</.*?>', html, flags=re.DOTALL) r = re.match(r'<.*?>(.*)</.*?>', html, flags=re.DOTALL)
return None if not r else r.group(1) return None if not r else r.group(1)
@property @property
def tag(self) -> str: def tag(self) -> str:
"""获取标签名""" """返回元素类型"""
return self._inner_ele.tag return self._inner_ele.tag
@property @property
def parent(self): def parent(self):
"""父级元素""" """返回父级元素"""
return self.parents() return self.parents()
@property @property
def next(self): def next(self):
"""一个兄弟元素""" """返回后一个兄弟元素"""
return self.nexts() return self.nexts()
@property @property
def prev(self): def prev(self):
"""一个兄弟元素""" """返回前一个兄弟元素"""
return self.prevs() return self.prevs()
def parents(self, num: int = 1): def parents(self, num: int = 1):
"""requests_html的Element打包了lxml的元素对象从lxml元素对象读取上下级关系后再重新打包""" """返回上面第num级父元素 \n
requests_html的Element打包了lxml的元素对象从lxml元素对象读取上下级关系后再重新打包 \n
:param num: 第几级父元素
:return: SessionElement对象
"""
try: try:
return SessionElement( return SessionElement(
Element(element=self.inner_ele.element.xpath(f'..{"/.." * (num - 1)}')[0], url=self.inner_ele.url)) 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 return None
def nexts(self, num: int = 1): def nexts(self, num: int = 1):
"""requests_html的Element打包了lxml的元素对象从lxml元素对象读取上下级关系后再重新打包""" """返回后面第num个兄弟元素 \n
:param num: 后面第几个兄弟元素
:return: SessionElement对象
"""
try: try:
return SessionElement( return SessionElement(
Element(element=self.inner_ele.element.xpath(f'./following-sibling::*[{num}]')[0], Element(element=self.inner_ele.element.xpath(f'./following-sibling::*[{num}]')[0],
@ -81,7 +88,10 @@ class SessionElement(DrissionElement):
return None return None
def prevs(self, num: int = 1): def prevs(self, num: int = 1):
"""requests_html的Element打包了lxml的元素对象从lxml元素对象读取上下级关系后再重新打包""" """返回前面第num个兄弟元素 \n
:param num: 前面第几个兄弟元素
:return: SessionElement对象
"""
try: try:
return SessionElement( return SessionElement(
Element(element=self.inner_ele.element.xpath(f'./preceding-sibling::*[{num}]')[0], Element(element=self.inner_ele.element.xpath(f'./preceding-sibling::*[{num}]')[0],
@ -90,9 +100,30 @@ class SessionElement(DrissionElement):
return None return None
def ele(self, loc_or_str: Union[tuple, str], mode: str = None, show_errmsg: bool = False): def ele(self, loc_or_str: Union[tuple, str], mode: str = None, show_errmsg: bool = False):
"""根据loc获取元素或列表可用字符串控制获取方式可选'@属性名:''tag:''text:''css:''xpath:' """返回当前元素下级符合条件的子元素,默认返回第一个 \n
如没有控制关键字会按字符串文本搜索 示例 \n
ele.ele('@id:ele_id')ele.ele('首页') - 用loc元组查找 \n
ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回第一个<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): if isinstance(loc_or_str, str):
loc_or_str = get_loc_from_str(loc_or_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) 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和属性文本xpathcss 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') - 返回所有<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) 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: try:
if attr == 'href': if attr == 'href':
# 如直接获取attr只能获取相对地址 # 如直接获取attr只能获取相对地址
@ -144,19 +204,19 @@ class SessionElement(DrissionElement):
else: else:
return self._inner_ele.attrs[attr] return self._inner_ele.attrs[attr]
except: except:
return '' return None
def execute_session_find(page_or_ele: BaseParser, def execute_session_find(page_or_ele: BaseParser,
loc: tuple, loc: tuple,
mode: str = 'single', mode: str = 'single',
show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement]]: show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement]]:
"""执行session模式元素的查找 """执行session模式元素的查找 \n
页面查找元素及元素查找下级元素皆使用此方法 页面查找元素及元素查找下级元素皆使用此方法 \n
:param page_or_ele: session模式页面或元素 :param page_or_ele: request_html的页面或元素对象
:param loc: 元素定位语句 :param loc: 元素定位元组
:param mode: 'single''all' :param mode: 'single' 'all'对应获取第一个或全部
:param show_errmsg: 是否显示错误信息 :param show_errmsg: 出现异常时是否显示错误信息
:return: 返回SessionElement元素或列表 :return: 返回SessionElement元素或列表
""" """
mode = mode or 'single' mode = mode or 'single'

View File

@ -12,7 +12,7 @@ from time import time
from typing import Union, List from typing import Union, List
from urllib.parse import urlparse, quote 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 .common import get_loc_from_str, translate_loc_to_xpath, avoid_duplicate_name
from .config import OptionsManager from .config import OptionsManager
@ -32,64 +32,118 @@ class SessionPage(object):
@property @property
def session(self) -> HTMLSession: def session(self) -> HTMLSession:
"""返回session对象"""
return self._session return self._session
@property @property
def response(self) -> HTMLResponse: def response(self) -> HTMLResponse:
"""返回访问url得到的response对象"""
return self._response return self._response
@property @property
def url(self) -> str: def url(self) -> str:
"""当前访问url""" """返回当前访问url"""
return self._url return self._url
@property @property
def url_available(self) -> bool: def url_available(self) -> bool:
"""url有效性""" """返回当前访问的url有效性"""
return self._url_available return self._url_available
@property @property
def cookies(self) -> dict: def cookies(self) -> dict:
"""当前session的cookies""" """返回session的cookies"""
return self.session.cookies.get_dict() return self.session.cookies.get_dict()
@property @property
def title(self) -> str: def title(self) -> str:
"""获取网页title""" """返回网页title"""
return self.ele(('css selector', 'title')).text return self.ele(('css selector', 'title')).text
@property @property
def html(self) -> str: def html(self) -> str:
"""获取元素innerHTML如未指定元素则获取所有源代码""" """返回页面html文本"""
return self.response.html.html return self.response.html.html
def ele(self, def ele(self,
loc_or_ele: Union[tuple, str, SessionElement], loc_or_ele: Union[tuple, str, SessionElement, Element],
mode: str = None, mode: str = None,
show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement], None]: show_errmsg: bool = False) -> Union[SessionElement, List[SessionElement], None]:
"""根据loc获取元素或列表可用字符串控制获取方式可选'@属性名:''tag:''text:''css:''xpath:' """返回页面中符合条件的元素,默认返回第一个 \n
如没有控制关键字会按字符串文本搜索 示例 \n
page.ele('@id:ele_id')page.ele('首页') - 接收到元素对象时 \n
:param loc_or_ele: 页面元素地址 返回SessionElement对象 \n
:param mode: 以某种方式查找元素可选'single','all' - 用loc元组查找 \n
:param show_errmsg: 是否显示错误信息 ele.ele((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的子元素 \n
:return: 页面元素对象或列表 - 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回第一个<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): if isinstance(loc_or_ele, str):
return loc_or_ele
elif isinstance(loc_or_ele, str):
loc = get_loc_from_str(loc_or_ele) 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) 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) return execute_session_find(self.response.html, loc, mode, show_errmsg)
def eles(self, loc: Union[tuple, str], show_errmsg: bool = False) -> List[SessionElement]: def eles(self, loc_or_str: Union[tuple, str], show_errmsg: bool = False) -> List[SessionElement]:
"""查找符合条件的所有元素""" """返回页面中所有符合条件的元素 \n
return self.ele(loc, mode='all', show_errmsg=True) 示例 \n
- 用loc元组查找 \n
page.eles((By.CLASS_NAME, 'ele_class')) - 返回所有class为ele_class的元素 \n
- 用查询字符串查找 \n
查找方式属性tag name和属性文本xpathcss 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') - 返回所有<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]: 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='/:&?=%;#@') to_url = quote(url, safe='/:&?=%;#@')
if not url or (not go_anyway and self.url == to_url): if not url or (not go_anyway and self.url == to_url):
return return
@ -100,8 +154,14 @@ class SessionPage(object):
self._url_available = True if self._response and self._response.ok else False self._url_available = True if self._response and self._response.ok else False
return self._url_available return self._url_available
def post(self, url: str, data: dict = None, go_anyway: bool = False, **kwargs) -> Union[bool, None]: def post(self, url: str, data: dict = None, go_anyway: bool = True, **kwargs) -> Union[bool, None]:
"""用post方式跳转到url调用_make_response()函数生成response对象""" """用post方式跳转到url \n
:param url: 目标url
:param data: 提交的数据
:param go_anyway: 若目标url与当前url一致是否强制跳转
:param kwargs: 连接参数
:return: url是否可用
"""
to_url = quote(url, safe='/:&?=%;#@') to_url = quote(url, safe='/:&?=%;#@')
if not url or (not go_anyway and self._url == to_url): if not url or (not go_anyway and self._url == to_url):
return return
@ -122,16 +182,16 @@ class SessionPage(object):
file_exists: str = 'rename', file_exists: str = 'rename',
show_msg: bool = False, show_msg: bool = False,
**kwargs) -> tuple: **kwargs) -> tuple:
"""下载一个文件 """下载一个文件 \n
生成的response不写入self._response是临时的
:param file_url: 文件url :param file_url: 文件url
:param goal_path: 存放路径url :param goal_path: 存放路径
:param rename: 重命名文件不改变扩展名 :param rename: 重命名文件可不写扩展名
:param kwargs: 连接参数 :param file_exists: 若存在同名文件可选择 'rename', 'overwrite', 'skip' 方式处理
:param file_exists: 若存在同名文件可选择'rename', 'overwrite', 'skip'方式处理
:param show_msg: 是否显示下载信息 :param show_msg: 是否显示下载信息
:return: 元组bool和状态信息成功时信息为文件路径 :param kwargs: 连接参数
:return: 下载是否成功bool和状态信息成功时信息为文件路径的元组
""" """
# 生成的response不写入self._response是临时的
goal_path = goal_path or OptionsManager().get_value('paths', 'global_tmp_path') goal_path = goal_path or OptionsManager().get_value('paths', 'global_tmp_path')
if not goal_path: if not goal_path:
raise IOError('No path specified.') raise IOError('No path specified.')
@ -220,10 +280,10 @@ class SessionPage(object):
return download_status, info return download_status, info
def _make_response(self, url: str, mode: str = 'get', data: dict = None, **kwargs) -> tuple: def _make_response(self, url: str, mode: str = 'get', data: dict = None, **kwargs) -> tuple:
"""生成response对象。接收mode参数以决定用什么方式。 """生成response对象 \n
:param url: 要访问的网址 :param url: 目标url
:param mode: 'get', 'post' 中选择 :param mode: 'get', 'post' 中选择
:param data: 提交的数据 :param data: post方式要提交的数据
:param kwargs: 其它参数 :param kwargs: 其它参数
:return: Response对象 :return: Response对象
""" """