mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
4.0.1
This commit is contained in:
parent
a1ec25dc44
commit
cf4ba9cda9
@ -1,324 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from abc import abstractmethod
|
|
||||||
from re import sub
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
from ..commons.locator import get_loc
|
|
||||||
from ..commons.web import format_html
|
|
||||||
|
|
||||||
|
|
||||||
class BaseParser(object):
|
|
||||||
"""所有页面、元素类的基类"""
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str):
|
|
||||||
return self.ele(loc_or_str)
|
|
||||||
|
|
||||||
def ele(self, loc_or_ele, timeout=None):
|
|
||||||
return self._ele(loc_or_ele, timeout, True)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
return self._ele(loc_or_str, timeout, False)
|
|
||||||
|
|
||||||
# ----------------以下属性或方法待后代实现----------------
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _ele(self, loc_or_ele, timeout=None, single=True):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BaseElement(BaseParser):
|
|
||||||
"""各元素类的基类"""
|
|
||||||
|
|
||||||
def __init__(self, page=None):
|
|
||||||
self.page = page
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def tag(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _ele(self, loc_or_str, timeout=None, single=True, relative=False):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def parent(self, level_or_loc=1):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def prev(self, index=1):
|
|
||||||
return None # ShadowRootElement直接继承
|
|
||||||
|
|
||||||
def prevs(self) -> None:
|
|
||||||
return None # ShadowRootElement直接继承
|
|
||||||
|
|
||||||
def next(self, index=1):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def nexts(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DrissionElement(BaseElement):
|
|
||||||
"""DriverElement、ChromiumElement 和 SessionElement的基类
|
|
||||||
但不是ShadowRootElement的基类"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def link(self):
|
|
||||||
"""返回href或src绝对url"""
|
|
||||||
return self.attr('href') or self.attr('src')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def css_path(self):
|
|
||||||
"""返回css path路径"""
|
|
||||||
return self._get_ele_path('css')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def xpath(self):
|
|
||||||
"""返回xpath路径"""
|
|
||||||
return self._get_ele_path('xpath')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def comments(self):
|
|
||||||
"""返回元素注释文本组成的列表"""
|
|
||||||
return self.eles('xpath:.//comment()')
|
|
||||||
|
|
||||||
def texts(self, text_node_only=False):
|
|
||||||
"""返回元素内所有直接子节点的文本,包括元素和文本节点
|
|
||||||
:param text_node_only: 是否只返回文本节点
|
|
||||||
:return: 文本列表
|
|
||||||
"""
|
|
||||||
if text_node_only:
|
|
||||||
texts = self.eles('xpath:/text()')
|
|
||||||
else:
|
|
||||||
texts = [x if isinstance(x, str) else x.text for x in self.eles('xpath:./text() | *')]
|
|
||||||
|
|
||||||
return [format_html(x.strip(' ').rstrip('\n')) for x in texts if x and sub('[\r\n\t ]', '', x) != '']
|
|
||||||
|
|
||||||
def parent(self, level_or_loc=1):
|
|
||||||
"""返回上面某一级父元素,可指定层数或用查询语法定位
|
|
||||||
:param level_or_loc: 第几级父元素,或定位符
|
|
||||||
:return: 上级元素对象
|
|
||||||
"""
|
|
||||||
if isinstance(level_or_loc, int):
|
|
||||||
loc = f'xpath:./ancestor::*[{level_or_loc}]'
|
|
||||||
|
|
||||||
elif isinstance(level_or_loc, (tuple, str)):
|
|
||||||
loc = get_loc(level_or_loc, True)
|
|
||||||
|
|
||||||
if loc[0] == 'css selector':
|
|
||||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
|
||||||
|
|
||||||
loc = f'xpath:./ancestor::{loc[1].lstrip(". / ")}'
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError('level_or_loc参数只能是tuple、int或str。')
|
|
||||||
|
|
||||||
return self._ele(loc, timeout=0, relative=True)
|
|
||||||
|
|
||||||
def prev(self, index=1, filter_loc='', timeout=0):
|
|
||||||
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 前面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素
|
|
||||||
"""
|
|
||||||
nodes = self._get_brothers(index, filter_loc, 'preceding', timeout=timeout)
|
|
||||||
return nodes[-1] if nodes else None
|
|
||||||
|
|
||||||
def next(self, index=1, filter_loc='', timeout=0):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 后面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素
|
|
||||||
"""
|
|
||||||
nodes = self._get_brothers(index, filter_loc, 'following', timeout=timeout)
|
|
||||||
return nodes[0] if nodes else None
|
|
||||||
|
|
||||||
def before(self, index=1, filter_loc='', timeout=None):
|
|
||||||
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 前面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素前面的某个元素或节点
|
|
||||||
"""
|
|
||||||
nodes = self._get_brothers(index, filter_loc, 'preceding', False, timeout=timeout)
|
|
||||||
return nodes[-1] if nodes else None
|
|
||||||
|
|
||||||
def after(self, index=1, filter_loc='', timeout=None):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 后面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素后面的某个元素或节点
|
|
||||||
"""
|
|
||||||
nodes = self._get_brothers(index, filter_loc, 'following', False, timeout)
|
|
||||||
return nodes[0] if nodes else None
|
|
||||||
|
|
||||||
def prevs(self, filter_loc='', timeout=0):
|
|
||||||
"""返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素或节点文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._get_brothers(filter_loc=filter_loc, direction='preceding', timeout=timeout)
|
|
||||||
|
|
||||||
def nexts(self, filter_loc='', timeout=0):
|
|
||||||
"""返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素或节点文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._get_brothers(filter_loc=filter_loc, direction='following', timeout=timeout)
|
|
||||||
|
|
||||||
def befores(self, filter_loc='', timeout=None):
|
|
||||||
"""返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素前面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
return self._get_brothers(filter_loc=filter_loc, direction='preceding', brother=False, timeout=timeout)
|
|
||||||
|
|
||||||
def afters(self, filter_loc='', timeout=None):
|
|
||||||
"""返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素后面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
return self._get_brothers(filter_loc=filter_loc, direction='following', brother=False, timeout=timeout)
|
|
||||||
|
|
||||||
def _get_brothers(self, index=None, filter_loc='', direction='following', brother=True, timeout=.5):
|
|
||||||
"""按要求返回兄弟元素或节点组成的列表
|
|
||||||
:param index: 获取第几个,该参数不为None时只获取该编号的元素
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param direction: 'following' 或 'preceding',查找的方向
|
|
||||||
:param brother: 查找范围,在同级查找还是整个dom前后查找
|
|
||||||
:param timeout: 查找等待时间
|
|
||||||
:return: DriverElement对象或字符串
|
|
||||||
"""
|
|
||||||
if index is not None and index < 1:
|
|
||||||
raise ValueError('index必须大于等于1。')
|
|
||||||
|
|
||||||
brother = '-sibling' if brother else ''
|
|
||||||
|
|
||||||
if not filter_loc:
|
|
||||||
loc = '*'
|
|
||||||
|
|
||||||
else:
|
|
||||||
loc = get_loc(filter_loc, True) # 把定位符转换为xpath
|
|
||||||
if loc[0] == 'css selector':
|
|
||||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
|
||||||
loc = loc[1].lstrip('./')
|
|
||||||
|
|
||||||
loc = f'xpath:./{direction}{brother}::{loc}'
|
|
||||||
|
|
||||||
nodes = self._ele(loc, timeout=timeout, single=False, relative=True)
|
|
||||||
nodes = [e for e in nodes if not (isinstance(e, str) and sub('[ \n\t\r]', '', e) == '')]
|
|
||||||
|
|
||||||
if nodes and index is not None:
|
|
||||||
index = index - 1 if direction == 'following' else -index
|
|
||||||
try:
|
|
||||||
return [nodes[index]]
|
|
||||||
except IndexError:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def attrs(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_text(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def attr(self, attr: str):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def _get_ele_path(self, mode):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class BasePage(BaseParser):
|
|
||||||
"""页面类的基类"""
|
|
||||||
|
|
||||||
def __init__(self, timeout=None):
|
|
||||||
"""初始化函数"""
|
|
||||||
self._url = None
|
|
||||||
self.timeout = timeout if timeout is not None else 10
|
|
||||||
self.retry_times = 3
|
|
||||||
self.retry_interval = 2
|
|
||||||
self._url_available = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title(self):
|
|
||||||
"""返回网页title"""
|
|
||||||
ele = self.ele('xpath://title')
|
|
||||||
return ele.text if ele else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeout(self):
|
|
||||||
"""返回查找元素时等待的秒数"""
|
|
||||||
return self._timeout
|
|
||||||
|
|
||||||
@timeout.setter
|
|
||||||
def timeout(self, second):
|
|
||||||
"""设置查找元素时等待的秒数"""
|
|
||||||
self._timeout = second
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cookies(self):
|
|
||||||
"""返回cookies"""
|
|
||||||
return self.get_cookies(True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url_available(self):
|
|
||||||
"""返回当前访问的url有效性"""
|
|
||||||
return self._url_available
|
|
||||||
|
|
||||||
def _before_connect(self, url, retry, interval):
|
|
||||||
"""连接前的准备
|
|
||||||
:param url: 要访问的url
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔
|
|
||||||
:return: 重试次数和间隔组成的tuple
|
|
||||||
"""
|
|
||||||
self._url = quote(url, safe='/:&?=%;#@+!')
|
|
||||||
retry = retry if retry is not None else self.retry_times
|
|
||||||
interval = interval if interval is not None else self.retry_interval
|
|
||||||
return retry, interval
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_cookies(self, as_dict=False):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None):
|
|
||||||
pass
|
|
@ -1,175 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from abc import abstractmethod
|
|
||||||
from typing import Union, Tuple, List
|
|
||||||
|
|
||||||
|
|
||||||
class BaseParser(object):
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str: Union[Tuple[str, str], str]): ...
|
|
||||||
|
|
||||||
def ele(self, loc_or_ele: Union[Tuple[str, str], str, BaseElement], timeout: float = None): ...
|
|
||||||
|
|
||||||
def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None): ...
|
|
||||||
|
|
||||||
# ----------------以下属性或方法待后代实现----------------
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, BaseElement]): ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]): ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _ele(self, loc_or_ele, timeout: float = None, single: bool = True): ...
|
|
||||||
|
|
||||||
|
|
||||||
class BaseElement(BaseParser):
|
|
||||||
|
|
||||||
def __init__(self, page: BasePage = None):
|
|
||||||
self.page: BasePage = ...
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def tag(self) -> str: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _ele(self, loc_or_str: Union[Tuple[str, str], str], timeout: float = None, single: bool = True,
|
|
||||||
relative: bool = False): ...
|
|
||||||
|
|
||||||
def parent(self, level_or_loc: Union[tuple, str, int] = 1): ...
|
|
||||||
|
|
||||||
def prev(self, index: int = 1) -> None: ...
|
|
||||||
|
|
||||||
def prevs(self) -> None: ...
|
|
||||||
|
|
||||||
def next(self, index: int = 1): ...
|
|
||||||
|
|
||||||
def nexts(self): ...
|
|
||||||
|
|
||||||
|
|
||||||
class DrissionElement(BaseElement):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
page: BasePage = ...):
|
|
||||||
self.page: BasePage = ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def link(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def css_path(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def xpath(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def comments(self) -> list: ...
|
|
||||||
|
|
||||||
def texts(self, text_node_only: bool = False) -> list: ...
|
|
||||||
|
|
||||||
def parent(self, level_or_loc: Union[tuple, str, int] = 1) -> Union[DrissionElement, None]: ...
|
|
||||||
|
|
||||||
def prev(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> Union[DrissionElement, str, None]: ...
|
|
||||||
|
|
||||||
def next(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> Union[DrissionElement, str, None]: ...
|
|
||||||
|
|
||||||
def before(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> Union[DrissionElement, str, None]: ...
|
|
||||||
|
|
||||||
def after(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> Union[DrissionElement, str, None]: ...
|
|
||||||
|
|
||||||
def prevs(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> List[Union[DrissionElement, str]]: ...
|
|
||||||
|
|
||||||
def nexts(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> List[Union[DrissionElement, str]]: ...
|
|
||||||
|
|
||||||
def befores(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union[DrissionElement, str]]: ...
|
|
||||||
|
|
||||||
def afters(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union[DrissionElement, str]]: ...
|
|
||||||
|
|
||||||
def _get_brothers(self,
|
|
||||||
index: int = None,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
direction: str = 'following',
|
|
||||||
brother: bool = True,
|
|
||||||
timeout: float = 0.5) -> List[Union[DrissionElement, str]]: ...
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def attrs(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_text(self) -> str: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def attr(self, attr: str) -> str: ...
|
|
||||||
|
|
||||||
def _get_ele_path(self, mode) -> str: ...
|
|
||||||
|
|
||||||
|
|
||||||
class BasePage(BaseParser):
|
|
||||||
|
|
||||||
def __init__(self, timeout: float = None):
|
|
||||||
self._url_available: bool = ...
|
|
||||||
self.retry_times: int = ...
|
|
||||||
self.retry_interval: float = ...
|
|
||||||
self._timeout = float = ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title(self) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeout(self) -> float: ...
|
|
||||||
|
|
||||||
@timeout.setter
|
|
||||||
def timeout(self, second: float) -> None: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cookies(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url_available(self) -> bool: ...
|
|
||||||
|
|
||||||
def _before_connect(self, url: str, retry: int, interval: float) -> tuple: ...
|
|
||||||
|
|
||||||
# ----------------以下属性或方法由后代实现----------------
|
|
||||||
@property
|
|
||||||
def url(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self) -> dict: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_cookies(self, as_dict: bool = False) -> Union[list, dict]: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get(self,
|
|
||||||
url: str,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
retry: int = None,
|
|
||||||
interval: float = None): ...
|
|
@ -1,458 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from platform import system
|
|
||||||
from sys import exit
|
|
||||||
|
|
||||||
from requests import Session
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
|
||||||
from selenium import webdriver
|
|
||||||
from selenium.common.exceptions import SessionNotCreatedException, WebDriverException
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
|
||||||
from tldextract import extract
|
|
||||||
|
|
||||||
from ..commons.browser import connect_browser
|
|
||||||
from ..commons.tools import get_pid_from_port, get_exe_from_port
|
|
||||||
from ..commons.web import cookies_to_tuple
|
|
||||||
from ..configs.driver_options import DriverOptions
|
|
||||||
from ..configs.session_options import SessionOptions, session_options_to_dict
|
|
||||||
|
|
||||||
|
|
||||||
class Drission(object):
|
|
||||||
"""Drission类用于管理WebDriver对象和Session对象,是驱动器的角色"""
|
|
||||||
|
|
||||||
def __init__(self, driver_or_options=None, session_or_options=None, ini_path=None, proxy=None):
|
|
||||||
"""初始化,可接收现成的WebDriver和Session对象,或接收它们的配置信息生成对象
|
|
||||||
:param driver_or_options: driver对象或DriverOptions、Options类,传入False则创建空配置对象
|
|
||||||
:param session_or_options: Session对象或设置字典,传入False则创建空配置对象
|
|
||||||
:param ini_path: ini文件路径
|
|
||||||
:param proxy: 代理设置
|
|
||||||
"""
|
|
||||||
self._session = None
|
|
||||||
self._driver = None
|
|
||||||
self._session_options = None
|
|
||||||
self._driver_options = None
|
|
||||||
self._debugger = None
|
|
||||||
self._proxy = proxy
|
|
||||||
|
|
||||||
# ------------------处理session options----------------------
|
|
||||||
if session_or_options is None:
|
|
||||||
self._session_options = SessionOptions(ini_path=ini_path).as_dict()
|
|
||||||
|
|
||||||
elif session_or_options is False:
|
|
||||||
self._session_options = SessionOptions(read_file=False).as_dict()
|
|
||||||
|
|
||||||
elif isinstance(session_or_options, Session):
|
|
||||||
self._session = session_or_options
|
|
||||||
|
|
||||||
elif isinstance(session_or_options, SessionOptions):
|
|
||||||
self._session_options = session_or_options.as_dict()
|
|
||||||
|
|
||||||
elif isinstance(session_or_options, dict):
|
|
||||||
self._session_options = session_or_options
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError('session_or_options参数只能接收Session, dict, SessionOptions或False。')
|
|
||||||
|
|
||||||
# ------------------处理driver options----------------------
|
|
||||||
if driver_or_options is None:
|
|
||||||
self._driver_options = DriverOptions(ini_path=ini_path)
|
|
||||||
|
|
||||||
elif driver_or_options is False:
|
|
||||||
self._driver_options = DriverOptions(read_file=False)
|
|
||||||
|
|
||||||
elif isinstance(driver_or_options, RemoteWebDriver):
|
|
||||||
self._driver = driver_or_options
|
|
||||||
|
|
||||||
elif isinstance(driver_or_options, (Options, DriverOptions)):
|
|
||||||
self._driver_options = driver_or_options
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError('driver_or_options参数只能接收WebDriver, Options, DriverOptions或False。')
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""关闭对象时关闭浏览器和Session"""
|
|
||||||
try:
|
|
||||||
self.close()
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self):
|
|
||||||
"""返回Session对象,如未初始化则按配置信息创建"""
|
|
||||||
if self._session is None:
|
|
||||||
self._set_session(self._session_options)
|
|
||||||
|
|
||||||
if self._proxy:
|
|
||||||
self._session.proxies = self._proxy
|
|
||||||
|
|
||||||
return self._session
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver(self):
|
|
||||||
"""返回WebDriver对象,如未初始化则按配置信息创建。
|
|
||||||
如设置了本地调试浏览器,可自动接入或打开浏览器进程。
|
|
||||||
"""
|
|
||||||
if self._driver is None:
|
|
||||||
if not self.driver_options.debugger_address and self._proxy:
|
|
||||||
self.driver_options.add_argument(f'--proxy-server={self._proxy["http"]}')
|
|
||||||
|
|
||||||
driver_path = self.driver_options.driver_path or 'chromedriver'
|
|
||||||
chrome_path = self.driver_options.chrome_path
|
|
||||||
|
|
||||||
# -----------若指定debug端口且该端口未在使用中,则先启动浏览器进程-----------
|
|
||||||
if self.driver_options.debugger_address:
|
|
||||||
# 启动浏览器进程,同时返回该进程使用的 chrome.exe 路径
|
|
||||||
cp, self._debugger = connect_browser(self.driver_options)
|
|
||||||
|
|
||||||
if cp in (None, 'chrome'):
|
|
||||||
system_type = system().lower()
|
|
||||||
ip, port = self.driver_options.debugger_address.split(':')
|
|
||||||
if ip not in ('127.0.0.1', 'localhost'):
|
|
||||||
chrome_path = None
|
|
||||||
elif chrome_path == 'chrome' and system_type == 'windows':
|
|
||||||
chrome_path = get_exe_from_port(port)
|
|
||||||
|
|
||||||
# -----------创建WebDriver对象-----------
|
|
||||||
self._driver = create_driver(chrome_path, driver_path, self.driver_options)
|
|
||||||
|
|
||||||
# -----------解决接管新版浏览器不能定位到正确的标签页的问题-----------
|
|
||||||
active_tab = self._driver.window_handles[0]
|
|
||||||
if active_tab != self._driver.current_window_handle:
|
|
||||||
self._driver.switch_to.window(active_tab)
|
|
||||||
|
|
||||||
return self._driver
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver_options(self):
|
|
||||||
"""返回driver配置信息"""
|
|
||||||
return self._driver_options
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session_options(self):
|
|
||||||
"""返回session配置信息"""
|
|
||||||
return self._session_options
|
|
||||||
|
|
||||||
@session_options.setter
|
|
||||||
def session_options(self, options):
|
|
||||||
"""设置session配置
|
|
||||||
:param options: session配置字典
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._session_options = session_options_to_dict(options)
|
|
||||||
self._set_session(self._session_options)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def proxy(self):
|
|
||||||
"""返回代理信息"""
|
|
||||||
return self._proxy
|
|
||||||
|
|
||||||
@proxy.setter
|
|
||||||
def proxy(self, proxies=None):
|
|
||||||
"""设置代理信息
|
|
||||||
:param proxies: 代理信息字典
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._proxy = proxies
|
|
||||||
|
|
||||||
if self._session:
|
|
||||||
self._session.proxies = proxies
|
|
||||||
|
|
||||||
if self._driver:
|
|
||||||
cookies = self._driver.get_cookies()
|
|
||||||
url = self._driver.current_url
|
|
||||||
self._driver.quit()
|
|
||||||
self._driver = None
|
|
||||||
self._driver = self.driver
|
|
||||||
self._driver.get(url)
|
|
||||||
|
|
||||||
for cookie in cookies:
|
|
||||||
self.set_cookies(cookie, set_driver=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def debugger_progress(self):
|
|
||||||
"""调试浏览器进程"""
|
|
||||||
return self._debugger
|
|
||||||
|
|
||||||
def kill_browser(self):
|
|
||||||
"""关闭浏览器进程(如果可以)"""
|
|
||||||
pid = self.get_browser_progress_id()
|
|
||||||
if not kill_progress(pid):
|
|
||||||
self._driver.quit()
|
|
||||||
|
|
||||||
def get_browser_progress_id(self):
|
|
||||||
"""获取浏览器进程id"""
|
|
||||||
if self.debugger_progress:
|
|
||||||
return self.debugger_progress.pid
|
|
||||||
|
|
||||||
address = str(self.driver_options.debugger_address).split(':')
|
|
||||||
if len(address) == 2:
|
|
||||||
ip, port = address
|
|
||||||
if ip not in ('127.0.0.1', 'localhost') or not port.isdigit():
|
|
||||||
return None
|
|
||||||
|
|
||||||
from os import popen
|
|
||||||
txt = ''
|
|
||||||
progresses = popen(f'netstat -nao | findstr :{port}').read().split('\n')
|
|
||||||
for progress in progresses:
|
|
||||||
if 'LISTENING' in progress:
|
|
||||||
txt = progress
|
|
||||||
break
|
|
||||||
if not txt:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return txt.split(' ')[-1]
|
|
||||||
|
|
||||||
def hide_browser(self):
|
|
||||||
"""隐藏浏览器界面"""
|
|
||||||
self._show_or_hide_browser()
|
|
||||||
|
|
||||||
def show_browser(self):
|
|
||||||
"""显示浏览器界面"""
|
|
||||||
self._show_or_hide_browser(False)
|
|
||||||
|
|
||||||
def _show_or_hide_browser(self, hide=True):
|
|
||||||
if system().lower() != 'windows':
|
|
||||||
raise OSError('该方法只能在Windows系统使用。')
|
|
||||||
|
|
||||||
try:
|
|
||||||
from win32gui import ShowWindow
|
|
||||||
from win32con import SW_HIDE, SW_SHOW
|
|
||||||
except ImportError:
|
|
||||||
raise ImportError('请先安装:pip install pypiwin32')
|
|
||||||
|
|
||||||
pid = self.get_browser_progress_id()
|
|
||||||
if not pid:
|
|
||||||
print('只有设置了debugger_address参数才能使用 show_browser() 和 hide_browser()')
|
|
||||||
return
|
|
||||||
hds = get_chrome_hwnds_from_pid(pid)
|
|
||||||
sw = SW_HIDE if hide else SW_SHOW
|
|
||||||
for hd in hds:
|
|
||||||
ShowWindow(hd, sw)
|
|
||||||
|
|
||||||
def set_cookies(self, cookies, set_session=False, set_driver=False):
|
|
||||||
"""设置cookies
|
|
||||||
:param cookies: cookies信息,可为CookieJar, list, tuple, str, dict
|
|
||||||
:param set_session: 是否设置session的cookies
|
|
||||||
:param set_driver: 是否设置driver的cookies
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
cookies = cookies_to_tuple(cookies)
|
|
||||||
|
|
||||||
for cookie in cookies:
|
|
||||||
if cookie['value'] is None:
|
|
||||||
cookie['value'] = ''
|
|
||||||
|
|
||||||
# 添加cookie到session
|
|
||||||
if set_session:
|
|
||||||
kwargs = {x: cookie[x] for x in cookie
|
|
||||||
if x.lower() not in ('name', 'value', 'httponly', 'expiry', 'samesite')}
|
|
||||||
|
|
||||||
if 'expiry' in cookie:
|
|
||||||
kwargs['expires'] = cookie['expiry']
|
|
||||||
|
|
||||||
self.session.cookies.set(cookie['name'], cookie['value'], **kwargs)
|
|
||||||
|
|
||||||
# 添加cookie到driver
|
|
||||||
if set_driver:
|
|
||||||
if 'expiry' in cookie:
|
|
||||||
cookie['expiry'] = int(cookie['expiry'])
|
|
||||||
|
|
||||||
try:
|
|
||||||
browser_domain = extract(self.driver.current_url).fqdn
|
|
||||||
except AttributeError:
|
|
||||||
browser_domain = ''
|
|
||||||
|
|
||||||
if not cookie.get('domain', None):
|
|
||||||
if browser_domain:
|
|
||||||
url = extract(browser_domain)
|
|
||||||
cookie_domain = f'{url.domain}.{url.suffix}'
|
|
||||||
else:
|
|
||||||
raise ValueError('cookie中没有域名或浏览器未访问过URL。')
|
|
||||||
|
|
||||||
cookie['domain'] = cookie_domain
|
|
||||||
|
|
||||||
else:
|
|
||||||
cookie_domain = cookie['domain'] if cookie['domain'][0] != '.' else cookie['domain'][1:]
|
|
||||||
|
|
||||||
if cookie_domain not in browser_domain:
|
|
||||||
self.driver.get(cookie_domain if cookie_domain.startswith('http://')
|
|
||||||
else f'http://{cookie_domain}')
|
|
||||||
|
|
||||||
# 避免selenium自动添加.后无法正确覆盖已有cookie
|
|
||||||
if cookie['domain'][0] != '.':
|
|
||||||
c = self.driver.get_cookie(cookie['name'])
|
|
||||||
if c and c['domain'] == cookie['domain']:
|
|
||||||
self.driver.delete_cookie(cookie['name'])
|
|
||||||
|
|
||||||
self.driver.add_cookie(cookie)
|
|
||||||
|
|
||||||
def _set_session(self, data):
|
|
||||||
"""根据传入字典对session进行设置
|
|
||||||
:param data: session配置字典
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self._session is None:
|
|
||||||
self._session = Session()
|
|
||||||
|
|
||||||
if 'headers' in data:
|
|
||||||
self._session.headers = CaseInsensitiveDict(data['headers'])
|
|
||||||
if 'cookies' in data:
|
|
||||||
self.set_cookies(data['cookies'], set_session=True)
|
|
||||||
|
|
||||||
attrs = ['auth', 'proxies', 'hooks', 'params', 'verify',
|
|
||||||
'cert', 'stream', 'trust_env', 'max_redirects'] # , 'adapters'
|
|
||||||
for i in attrs:
|
|
||||||
if i in data:
|
|
||||||
self._session.__setattr__(i, data[i])
|
|
||||||
|
|
||||||
def cookies_to_session(self, copy_user_agent=False):
|
|
||||||
"""把driver对象的cookies复制到session对象
|
|
||||||
:param copy_user_agent: 是否复制ua信息
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if copy_user_agent:
|
|
||||||
user_agent_to_session(self.driver, self.session)
|
|
||||||
|
|
||||||
self.set_cookies(self.driver.get_cookies(), set_session=True)
|
|
||||||
|
|
||||||
def cookies_to_driver(self, url):
|
|
||||||
"""把session对象的cookies复制到driver对象
|
|
||||||
:param url: 作用域
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
browser_domain = extract(self.driver.current_url).fqdn
|
|
||||||
ex_url = extract(url)
|
|
||||||
|
|
||||||
if ex_url.fqdn not in browser_domain:
|
|
||||||
self.driver.get(url)
|
|
||||||
|
|
||||||
domain = f'{ex_url.domain}.{ex_url.suffix}'
|
|
||||||
|
|
||||||
cookies = []
|
|
||||||
for cookie in self.session.cookies:
|
|
||||||
if cookie.domain == '':
|
|
||||||
cookie.domain = domain
|
|
||||||
|
|
||||||
if domain in cookie.domain:
|
|
||||||
cookies.append(cookie)
|
|
||||||
|
|
||||||
self.set_cookies(cookies, set_driver=True)
|
|
||||||
|
|
||||||
def close_driver(self, kill=False):
|
|
||||||
"""关闭driver和浏览器"""
|
|
||||||
if self._driver:
|
|
||||||
kill_progress(port=self._driver.service.port) # 关闭chromedriver.exe进程
|
|
||||||
|
|
||||||
if kill:
|
|
||||||
self.kill_browser()
|
|
||||||
else:
|
|
||||||
self._driver.quit()
|
|
||||||
|
|
||||||
self._driver = None
|
|
||||||
|
|
||||||
def close_session(self):
|
|
||||||
"""关闭session"""
|
|
||||||
if self._session:
|
|
||||||
self._session.close()
|
|
||||||
self._session = None
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""关闭session、driver和浏览器"""
|
|
||||||
if self._driver:
|
|
||||||
self.close_driver()
|
|
||||||
|
|
||||||
if self._session:
|
|
||||||
self.close_session()
|
|
||||||
|
|
||||||
|
|
||||||
def user_agent_to_session(driver, session):
|
|
||||||
"""把driver的user-agent复制到session
|
|
||||||
:param driver: 来源driver对象
|
|
||||||
:param session: 目标session对象
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
driver = driver
|
|
||||||
session = session
|
|
||||||
selenium_user_agent = driver.execute_script("return navigator.userAgent;")
|
|
||||||
session.headers.update({"User-Agent": selenium_user_agent})
|
|
||||||
|
|
||||||
|
|
||||||
def create_driver(chrome_path, driver_path, options):
|
|
||||||
"""创建 WebDriver 对象
|
|
||||||
:param chrome_path: chrome.exe 路径
|
|
||||||
:param driver_path: chromedriver.exe 路径
|
|
||||||
:param options: Options 对象
|
|
||||||
:return: WebDriver 对象
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
debugger_address = options.debugger_address
|
|
||||||
if options.debugger_address:
|
|
||||||
options = Options()
|
|
||||||
options.debugger_address = debugger_address
|
|
||||||
|
|
||||||
return webdriver.Chrome(driver_path, options=options)
|
|
||||||
|
|
||||||
# 若版本不对,获取对应 chromedriver 再试
|
|
||||||
except (WebDriverException, SessionNotCreatedException):
|
|
||||||
print('打开失败,尝试获取driver。\n')
|
|
||||||
from ..easy_set import get_match_driver, get_chrome_path
|
|
||||||
|
|
||||||
if chrome_path == 'chrome':
|
|
||||||
chrome_path = get_chrome_path(show_msg=False, from_ini=False)
|
|
||||||
|
|
||||||
if chrome_path:
|
|
||||||
driver_path = get_match_driver(chrome_path=chrome_path, check_version=False, show_msg=True)
|
|
||||||
if driver_path:
|
|
||||||
try:
|
|
||||||
options.binary_location = chrome_path
|
|
||||||
return webdriver.Chrome(driver_path, options=options)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
print('无法启动,请检查浏览器路径,或手动设置chromedriver。\n下载地址:http://npm.taobao.org/mirrors/chromedriver/')
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def get_chrome_hwnds_from_pid(pid):
|
|
||||||
"""通过PID查询句柄ID"""
|
|
||||||
try:
|
|
||||||
from win32gui import IsWindow, GetWindowText, EnumWindows
|
|
||||||
from win32process import GetWindowThreadProcessId
|
|
||||||
except ImportError:
|
|
||||||
raise ImportError('请先安装win32gui,pip install pypiwin32')
|
|
||||||
|
|
||||||
def callback(hwnd, hds):
|
|
||||||
if IsWindow(hwnd) and '- Google Chrome' in GetWindowText(hwnd):
|
|
||||||
_, found_pid = GetWindowThreadProcessId(hwnd)
|
|
||||||
if str(found_pid) == str(pid):
|
|
||||||
hds.append(hwnd)
|
|
||||||
return True
|
|
||||||
|
|
||||||
hwnds = []
|
|
||||||
EnumWindows(callback, hwnds)
|
|
||||||
return hwnds
|
|
||||||
|
|
||||||
|
|
||||||
def kill_progress(pid=None, port=None):
|
|
||||||
"""关闭浏览器进程
|
|
||||||
:param pid: 进程id
|
|
||||||
:param port: 端口号,如没有进程id,从端口号获取
|
|
||||||
:return: 是否成功
|
|
||||||
"""
|
|
||||||
from os import popen
|
|
||||||
if system().lower() != 'windows':
|
|
||||||
return False
|
|
||||||
|
|
||||||
pid = pid or get_pid_from_port(port)
|
|
||||||
if not pid:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if popen(f'tasklist | findstr {pid}').read().lower().startswith('chrome.exe'):
|
|
||||||
popen(f'taskkill /pid {pid} /F')
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
@ -1,96 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from subprocess import Popen
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from requests import Session
|
|
||||||
from requests.cookies import RequestsCookieJar
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
|
||||||
|
|
||||||
from DrissionPage.configs.driver_options import DriverOptions
|
|
||||||
from DrissionPage.configs.session_options import SessionOptions
|
|
||||||
|
|
||||||
|
|
||||||
class Drission(object):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
driver_or_options: Union[RemoteWebDriver, Options, DriverOptions, bool] = None,
|
|
||||||
session_or_options: Union[Session, dict, SessionOptions, bool] = None,
|
|
||||||
ini_path: str = None,
|
|
||||||
proxy: dict = None):
|
|
||||||
self._session: Session = ...
|
|
||||||
self._session_options: dict = ...
|
|
||||||
self._proxy: dict = ...
|
|
||||||
self._driver: WebDriver = ...
|
|
||||||
self._debugger: Popen = ...
|
|
||||||
self._driver_options: DriverOptions = ...
|
|
||||||
|
|
||||||
def __del__(self): ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self) -> Session: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver(self) -> WebDriver: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver_options(self) -> Union[DriverOptions, Options]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session_options(self) -> dict: ...
|
|
||||||
|
|
||||||
@session_options.setter
|
|
||||||
def session_options(self, options: Union[dict, SessionOptions]) -> None: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def proxy(self) -> Union[None, dict]: ...
|
|
||||||
|
|
||||||
@proxy.setter
|
|
||||||
def proxy(self, proxies: dict = None) -> None: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def debugger_progress(self): ...
|
|
||||||
|
|
||||||
def kill_browser(self) -> None: ...
|
|
||||||
|
|
||||||
def get_browser_progress_id(self) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
def hide_browser(self) -> None: ...
|
|
||||||
|
|
||||||
def show_browser(self) -> None: ...
|
|
||||||
|
|
||||||
def _show_or_hide_browser(self, hide: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def set_cookies(self,
|
|
||||||
cookies: Union[RequestsCookieJar, list, tuple, str, dict],
|
|
||||||
set_session: bool = False,
|
|
||||||
set_driver: bool = False) -> None: ...
|
|
||||||
|
|
||||||
def _set_session(self, data: dict) -> None: ...
|
|
||||||
|
|
||||||
def cookies_to_session(self, copy_user_agent: bool = False) -> None: ...
|
|
||||||
|
|
||||||
def cookies_to_driver(self, url: str) -> None: ...
|
|
||||||
|
|
||||||
def close_driver(self, kill: bool = False) -> None: ...
|
|
||||||
|
|
||||||
def close_session(self) -> None: ...
|
|
||||||
|
|
||||||
def close(self) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
def user_agent_to_session(driver: RemoteWebDriver, session: Session) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
def create_driver(chrome_path: str, driver_path: str, options: Options) -> WebDriver: ...
|
|
||||||
|
|
||||||
|
|
||||||
def get_chrome_hwnds_from_pid(pid: str) -> list: ...
|
|
||||||
|
|
||||||
|
|
||||||
def kill_progress(pid: str = None, port: int = None) -> bool: ...
|
|
File diff suppressed because it is too large
Load Diff
@ -1,326 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from typing import Union, List, Any, Tuple
|
|
||||||
|
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
from selenium.webdriver.support.select import Select as SeleniumSelect
|
|
||||||
|
|
||||||
from .driver_page import DriverPage
|
|
||||||
from .mix_page import MixPage
|
|
||||||
from .shadow_root_element import ShadowRootElement
|
|
||||||
from .base import DrissionElement
|
|
||||||
from .session_element import SessionElement
|
|
||||||
|
|
||||||
|
|
||||||
class DriverElement(DrissionElement):
|
|
||||||
|
|
||||||
def __init__(self, ele: WebElement, page: Union[DriverPage, MixPage] = None):
|
|
||||||
self._inner_ele: WebElement = ...
|
|
||||||
self._select: Select = ...
|
|
||||||
self._scroll: Scroll = ...
|
|
||||||
self.page: Union[DriverPage, MixPage] = ...
|
|
||||||
|
|
||||||
def __repr__(self) -> str: ...
|
|
||||||
|
|
||||||
def __call__(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def inner_ele(self) -> WebElement: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tag(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attrs(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_text(self) -> str: ...
|
|
||||||
|
|
||||||
def attr(self, attr: str) -> str: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union['DriverElement', str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_str: Union[Tuple[str, str], str] = None) -> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None,
|
|
||||||
single: bool = True,
|
|
||||||
relative: bool = False) -> Union['DriverElement', str, None, List[Union['DriverElement', str]]]: ...
|
|
||||||
|
|
||||||
def _get_ele_path(self, mode) -> str: ...
|
|
||||||
|
|
||||||
# -----------------driver独有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def size(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def location(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def shadow_root(self) -> ShadowRootElement: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sr(self) -> ShadowRootElement: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pseudo_before(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pseudo_after(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def select(self) -> Select: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scroll(self) -> Scroll: ...
|
|
||||||
|
|
||||||
def parent(self, level_or_loc: Union[tuple, str, int] = 1) -> Union['DriverElement', None]: ...
|
|
||||||
|
|
||||||
def prev(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
def next(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
def before(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
def after(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> Union['DriverElement', str, None]: ...
|
|
||||||
|
|
||||||
def prevs(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> List[Union['DriverElement', str]]: ...
|
|
||||||
|
|
||||||
def nexts(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = 0) -> List[Union['DriverElement', str]]: ...
|
|
||||||
|
|
||||||
def befores(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['DriverElement', str]]: ...
|
|
||||||
|
|
||||||
def afters(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['DriverElement', str]]: ...
|
|
||||||
|
|
||||||
def left(self, index: int = 1, filter_loc: Union[tuple, str] = '') -> DriverElement: ...
|
|
||||||
|
|
||||||
def right(self, index: int = 1, filter_loc: Union[tuple, str] = '') -> 'DriverElement': ...
|
|
||||||
|
|
||||||
def above(self, index: int = 1, filter_loc: Union[tuple, str] = '') -> 'DriverElement': ...
|
|
||||||
|
|
||||||
def below(self, index: int = 1, filter_loc: Union[tuple, str] = '') -> 'DriverElement': ...
|
|
||||||
|
|
||||||
def near(self, index: int = 1, filter_loc: Union[tuple, str] = '') -> 'DriverElement': ...
|
|
||||||
|
|
||||||
def lefts(self, filter_loc: Union[tuple, str] = '') -> List['DriverElement']: ...
|
|
||||||
|
|
||||||
def rights(self, filter_loc: Union[tuple, str] = '') -> List['DriverElement']: ...
|
|
||||||
|
|
||||||
def aboves(self, filter_loc: Union[tuple, str] = '') -> List['DriverElement']: ...
|
|
||||||
|
|
||||||
def belows(self, filter_loc: Union[tuple, str] = '') -> List['DriverElement']: ...
|
|
||||||
|
|
||||||
def nears(self, filter_loc: Union[tuple, str] = '') -> List['DriverElement']: ...
|
|
||||||
|
|
||||||
def wait_ele(self,
|
|
||||||
loc_or_ele: Union[str, tuple, DrissionElement, WebElement],
|
|
||||||
timeout: float = None) -> 'ElementWaiter': ...
|
|
||||||
|
|
||||||
def style(self, style: str, pseudo_ele: str = '') -> str: ...
|
|
||||||
|
|
||||||
def click(self, by_js: bool = None, timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def click_at(self,
|
|
||||||
x: Union[int, str] = None,
|
|
||||||
y: Union[int, str] = None,
|
|
||||||
by_js: bool = False) -> None: ...
|
|
||||||
|
|
||||||
def r_click(self) -> None: ...
|
|
||||||
|
|
||||||
def r_click_at(self, x: Union[int, str] = None, y: Union[int, str] = None) -> None: ...
|
|
||||||
|
|
||||||
def input(self,
|
|
||||||
vals: Union[str, tuple],
|
|
||||||
clear: bool = True,
|
|
||||||
insure: bool = True,
|
|
||||||
timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def run_script(self, script: str, *args) -> Any: ...
|
|
||||||
|
|
||||||
def submit(self) -> Union[bool, None]: ...
|
|
||||||
|
|
||||||
def clear(self, insure: bool = True) -> Union[None, bool]: ...
|
|
||||||
|
|
||||||
def is_selected(self) -> bool: ...
|
|
||||||
|
|
||||||
def is_enabled(self) -> bool: ...
|
|
||||||
|
|
||||||
def is_displayed(self) -> bool: ...
|
|
||||||
|
|
||||||
def is_valid(self) -> bool: ...
|
|
||||||
|
|
||||||
def screenshot(self, path: str = None, filename: str = None, as_bytes: bool = False) -> Union[str, bytes]: ...
|
|
||||||
|
|
||||||
def prop(self, prop: str) -> str: ...
|
|
||||||
|
|
||||||
def set_prop(self, prop: str, value: str) -> bool: ...
|
|
||||||
|
|
||||||
def set_attr(self, attr: str, value: str) -> bool: ...
|
|
||||||
|
|
||||||
def remove_attr(self, attr: str) -> bool: ...
|
|
||||||
|
|
||||||
def drag(self, x: int, y: int, speed: int = 40, shake: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def drag_to(self,
|
|
||||||
ele_or_loc: Union[tuple, WebElement, DrissionElement],
|
|
||||||
speed: int = 40,
|
|
||||||
shake: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def hover(self, x: int = None, y: int = None) -> None: ...
|
|
||||||
|
|
||||||
def _get_relative_eles(self,
|
|
||||||
mode: str,
|
|
||||||
loc: Union[tuple, str] = '') -> Union[List['DriverElement'], 'DriverElement']: ...
|
|
||||||
|
|
||||||
|
|
||||||
def make_driver_ele(page_or_ele: Union[DriverPage, MixPage, DriverElement, ShadowRootElement],
|
|
||||||
loc: Union[str, Tuple[str, str]],
|
|
||||||
single: bool = True,
|
|
||||||
timeout: float = None) -> Union[DriverElement, str, None, List[Union[DriverElement, str]]]: ...
|
|
||||||
|
|
||||||
|
|
||||||
class ElementsByXpath(object):
|
|
||||||
|
|
||||||
def __init__(self, page, xpath: str = None, single: bool = False, timeout: float = 10):
|
|
||||||
self.single: bool = ...
|
|
||||||
self.xpath: str = ...
|
|
||||||
self.page: Union[MixPage, DriverPage] = ...
|
|
||||||
|
|
||||||
def __call__(self, ele_or_driver: Union[RemoteWebDriver, WebElement]) \
|
|
||||||
-> Union[str, DriverElement, None, List[str or DriverElement]]: ...
|
|
||||||
|
|
||||||
|
|
||||||
class Select(object):
|
|
||||||
|
|
||||||
def __init__(self, ele: DriverElement):
|
|
||||||
self.select_ele: SeleniumSelect = ...
|
|
||||||
self.inner_ele: DriverElement = ...
|
|
||||||
|
|
||||||
def __call__(self, text_or_index: Union[str, int, list, tuple], timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_multi(self) -> bool: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def options(self) -> List[DriverElement]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def selected_option(self) -> Union[DriverElement, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def selected_options(self) -> List[DriverElement]: ...
|
|
||||||
|
|
||||||
def clear(self) -> None: ...
|
|
||||||
|
|
||||||
def select(self, text_or_index: Union[str, int, list, tuple], timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def select_by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def deselect(self, text_or_index: Union[str, int, list, tuple], timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def deselect_by_value(self, value: Union[str, list, tuple], timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def invert(self) -> None: ...
|
|
||||||
|
|
||||||
def _select(self,
|
|
||||||
text_value_index: Union[str, int, list, tuple] = ...,
|
|
||||||
para_type: str = 'text',
|
|
||||||
deselect: bool = False,
|
|
||||||
timeout: float = None) -> bool: ...
|
|
||||||
|
|
||||||
def _select_multi(self,
|
|
||||||
text_value_index: Union[list, tuple] = None,
|
|
||||||
para_type: str = 'text',
|
|
||||||
deselect: bool = False) -> bool: ...
|
|
||||||
|
|
||||||
|
|
||||||
class ElementWaiter(object):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
page_or_ele,
|
|
||||||
loc_or_ele: Union[str, tuple, DriverElement, WebElement],
|
|
||||||
timeout: float = None):
|
|
||||||
self.target: Union[DriverElement, WebElement, tuple] = ...
|
|
||||||
self.timeout: float = ...
|
|
||||||
self.driver: Union[WebElement, RemoteWebDriver] = ...
|
|
||||||
|
|
||||||
def delete(self) -> bool: ...
|
|
||||||
|
|
||||||
def display(self) -> bool: ...
|
|
||||||
|
|
||||||
def hidden(self) -> bool: ...
|
|
||||||
|
|
||||||
def _wait_ele(self, mode: str) -> bool: ...
|
|
||||||
|
|
||||||
|
|
||||||
class Scroll(object):
|
|
||||||
|
|
||||||
def __init__(self, page_or_ele):
|
|
||||||
self.driver: Union[DriverElement, DriverPage] = ...
|
|
||||||
self.t1: str = ...
|
|
||||||
self.t2: str = ...
|
|
||||||
|
|
||||||
def to_top(self) -> None: ...
|
|
||||||
|
|
||||||
def to_bottom(self) -> None: ...
|
|
||||||
|
|
||||||
def to_half(self) -> None: ...
|
|
||||||
|
|
||||||
def to_rightmost(self) -> None: ...
|
|
||||||
|
|
||||||
def to_leftmost(self) -> None: ...
|
|
||||||
|
|
||||||
def to_location(self, x: int, y: int) -> None: ...
|
|
||||||
|
|
||||||
def up(self, pixel: int = 300) -> None: ...
|
|
||||||
|
|
||||||
def down(self, pixel: int = 300) -> None: ...
|
|
||||||
|
|
||||||
def left(self, pixel: int = 300) -> None: ...
|
|
||||||
|
|
||||||
def right(self, pixel: int = 300) -> None: ...
|
|
@ -1,611 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from glob import glob
|
|
||||||
from os import sep
|
|
||||||
from pathlib import Path
|
|
||||||
from time import sleep, perf_counter
|
|
||||||
|
|
||||||
from selenium.common.exceptions import NoAlertPresentException
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
|
||||||
|
|
||||||
from .base import BasePage
|
|
||||||
from .driver_element import DriverElement, make_driver_ele, Scroll, ElementWaiter
|
|
||||||
from .session_element import make_session_ele
|
|
||||||
from ..commons.tools import get_usable_path
|
|
||||||
|
|
||||||
|
|
||||||
class DriverPage(BasePage):
|
|
||||||
"""DriverPage封装了页面操作的常用功能,使用selenium来获取、解析、操作网页"""
|
|
||||||
|
|
||||||
def __init__(self, driver, timeout=10):
|
|
||||||
"""初始化函数,接收一个WebDriver对象,用来操作网页"""
|
|
||||||
super().__init__(timeout)
|
|
||||||
self._driver = driver
|
|
||||||
self._wait_object = None
|
|
||||||
self._scroll = None
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str, timeout=None):
|
|
||||||
"""在内部查找元素
|
|
||||||
例:ele = page('@id=ele_id')
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 超时时间
|
|
||||||
:return: DriverElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self.ele(loc_or_str, timeout)
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
"""返回当前网页url"""
|
|
||||||
if not self._driver or not self.driver.current_url.startswith('http'):
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return self.driver.current_url
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
"""返回页面的html文本"""
|
|
||||||
return self.driver.find_element('xpath', "//*").get_attribute("outerHTML")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self):
|
|
||||||
"""当返回内容是json格式时,返回对应的字典"""
|
|
||||||
from json import loads
|
|
||||||
return loads(self('t:pre').text)
|
|
||||||
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None):
|
|
||||||
"""访问url
|
|
||||||
:param url: 目标url
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:return: 目标url是否可用,返回None表示不确定
|
|
||||||
"""
|
|
||||||
retry, interval = self._before_connect(url, retry, interval)
|
|
||||||
self._url_available = self._d_connect(self._url, times=retry, interval=interval, show_errmsg=show_errmsg)
|
|
||||||
return self._url_available
|
|
||||||
|
|
||||||
def ele(self, loc_or_ele, timeout=None):
|
|
||||||
"""返回页面中符合条件的第一个元素
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与页面等待时间一致
|
|
||||||
:return: DriverElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_ele, timeout)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
"""返回页面中所有符合条件的元素
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与页面等待时间一致
|
|
||||||
:return: DriverElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, timeout, single=False)
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele=None):
|
|
||||||
"""查找第一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
if isinstance(loc_or_ele, DriverElement):
|
|
||||||
return make_session_ele(loc_or_ele)
|
|
||||||
else:
|
|
||||||
return make_session_ele(self, loc_or_ele)
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
"""查找所有符合条件的元素以SessionElement列表形式返回
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象组成的列表
|
|
||||||
"""
|
|
||||||
return make_session_ele(self, loc_or_str, single=False)
|
|
||||||
|
|
||||||
def _ele(self, loc_or_ele, timeout=None, single=True):
|
|
||||||
"""返回页面中符合条件的元素,默认返回第一个
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:return: DriverElement对象
|
|
||||||
"""
|
|
||||||
# 接收到字符串或元组,获取定位loc元组
|
|
||||||
if isinstance(loc_or_ele, (str, tuple)):
|
|
||||||
return make_driver_ele(self, loc_or_ele, single, timeout)
|
|
||||||
|
|
||||||
# 接收到DriverElement对象直接返回
|
|
||||||
elif isinstance(loc_or_ele, DriverElement):
|
|
||||||
return loc_or_ele
|
|
||||||
|
|
||||||
# 接收到WebElement对象打包成DriverElement对象返回
|
|
||||||
elif isinstance(loc_or_ele, WebElement):
|
|
||||||
return DriverElement(loc_or_ele, self)
|
|
||||||
|
|
||||||
# 接收到的类型不正确,抛出异常
|
|
||||||
else:
|
|
||||||
raise ValueError('loc_or_str参数只能是tuple、str、DriverElement 或 WebElement类型。')
|
|
||||||
|
|
||||||
def get_cookies(self, as_dict=False):
|
|
||||||
"""返回当前网站cookies"""
|
|
||||||
if as_dict:
|
|
||||||
return {cookie['name']: cookie['value'] for cookie in self.driver.get_cookies()}
|
|
||||||
else:
|
|
||||||
return self.driver.get_cookies()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeout(self):
|
|
||||||
"""返回查找元素时等待的秒数"""
|
|
||||||
return self._timeout
|
|
||||||
|
|
||||||
@timeout.setter
|
|
||||||
def timeout(self, second):
|
|
||||||
"""设置查找元素时等待的秒数"""
|
|
||||||
self._timeout = second
|
|
||||||
self._wait_object = None
|
|
||||||
|
|
||||||
def _d_connect(self, to_url, times=0, interval=1, show_errmsg=False):
|
|
||||||
"""尝试连接,重试若干次
|
|
||||||
:param to_url: 要访问的url
|
|
||||||
:param times: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param show_errmsg: 是否抛出异常
|
|
||||||
:return: 是否成功,返回None表示不确定
|
|
||||||
"""
|
|
||||||
err = None
|
|
||||||
is_ok = False
|
|
||||||
|
|
||||||
for _ in range(times + 1):
|
|
||||||
try:
|
|
||||||
self.driver.get(to_url)
|
|
||||||
go_ok = True
|
|
||||||
except Exception as e:
|
|
||||||
err = e
|
|
||||||
go_ok = False
|
|
||||||
|
|
||||||
is_ok = self.check_page() if go_ok else False
|
|
||||||
|
|
||||||
if is_ok is not False:
|
|
||||||
break
|
|
||||||
|
|
||||||
if _ < times:
|
|
||||||
sleep(interval)
|
|
||||||
if show_errmsg:
|
|
||||||
print(f'重试 {to_url}')
|
|
||||||
|
|
||||||
if is_ok is False and show_errmsg:
|
|
||||||
raise err if err is not None else ConnectionError('连接异常。')
|
|
||||||
|
|
||||||
return is_ok
|
|
||||||
|
|
||||||
# ----------------driver独有属性和方法-----------------------
|
|
||||||
@property
|
|
||||||
def driver(self):
|
|
||||||
return self._driver
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wait_object(self):
|
|
||||||
"""返回WebDriverWait对象,重用避免每次新建对象"""
|
|
||||||
if self._wait_object is None:
|
|
||||||
self._wait_object = WebDriverWait(self.driver, timeout=self.timeout)
|
|
||||||
|
|
||||||
return self._wait_object
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeouts(self):
|
|
||||||
"""返回三种超时时间,selenium4以上版本可用"""
|
|
||||||
return {'implicit': self.timeout,
|
|
||||||
'pageLoad': self.driver.timeouts.page_load,
|
|
||||||
'script': self.driver.timeouts.script}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tabs_count(self):
|
|
||||||
"""返回标签页数量"""
|
|
||||||
try:
|
|
||||||
return len(self.driver.window_handles)
|
|
||||||
except Exception:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tab_handles(self):
|
|
||||||
"""返回所有标签页handle列表"""
|
|
||||||
return self.driver.window_handles
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_tab_index(self):
|
|
||||||
"""返回当前标签页序号"""
|
|
||||||
return self.driver.window_handles.index(self.driver.current_window_handle)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_tab_handle(self):
|
|
||||||
"""返回当前标签页handle"""
|
|
||||||
return self.driver.current_window_handle
|
|
||||||
|
|
||||||
@property
|
|
||||||
def active_ele(self):
|
|
||||||
"""返回当前焦点所在元素"""
|
|
||||||
return DriverElement(self.driver.switch_to.active_element, self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scroll(self):
|
|
||||||
"""用于滚动滚动条的对象"""
|
|
||||||
if self._scroll is None:
|
|
||||||
self._scroll = Scroll(self)
|
|
||||||
return self._scroll
|
|
||||||
|
|
||||||
@property
|
|
||||||
def to_frame(self):
|
|
||||||
"""用于跳转到frame的对象,调用其方法实现跳转
|
|
||||||
示例:
|
|
||||||
page.to_frame.by_loc('tag:iframe') - 通过传入frame的查询字符串定位
|
|
||||||
page.to_frame.by_loc((By.TAG_NAME, 'iframe')) - 通过传入定位符定位
|
|
||||||
page.to_frame.by_id('iframe_id') - 通过frame的id属性定位
|
|
||||||
page.to_frame('iframe_name') - 通过frame的name属性定位
|
|
||||||
page.to_frame(iframe_element) - 通过传入元素对象定位
|
|
||||||
page.to_frame(0) - 通过frame的序号定位
|
|
||||||
page.to_frame.main() - 跳到最顶层
|
|
||||||
page.to_frame.parent() - 跳到上一层
|
|
||||||
"""
|
|
||||||
return ToFrame(self)
|
|
||||||
|
|
||||||
def set_timeouts(self, implicit=None, pageLoad=None, script=None):
|
|
||||||
"""设置超时时间,单位为秒,selenium4以上版本有效
|
|
||||||
:param implicit: 查找元素超时时间
|
|
||||||
:param pageLoad: 页面加载超时时间
|
|
||||||
:param script: 脚本运行超时时间
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if implicit is not None:
|
|
||||||
self.timeout = implicit
|
|
||||||
|
|
||||||
if pageLoad is not None:
|
|
||||||
self.driver.set_page_load_timeout(pageLoad)
|
|
||||||
|
|
||||||
if script is not None:
|
|
||||||
self.driver.set_script_timeout(script)
|
|
||||||
|
|
||||||
def wait_ele(self, loc_or_ele, timeout=None):
|
|
||||||
"""等待元素从dom删除、显示、隐藏
|
|
||||||
:param loc_or_ele: 可以是元素、查询字符串、loc元组
|
|
||||||
:param timeout: 等待超时时间
|
|
||||||
:return: 用于等待的ElementWaiter对象
|
|
||||||
"""
|
|
||||||
return ElementWaiter(self, loc_or_ele, timeout)
|
|
||||||
|
|
||||||
def check_page(self):
|
|
||||||
"""检查页面是否符合预期
|
|
||||||
由子类自行实现各页面的判定规则
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def run_script(self, script, *args):
|
|
||||||
"""执行js代码
|
|
||||||
:param script: js文本
|
|
||||||
:param args: 传入的参数
|
|
||||||
:return: js执行结果
|
|
||||||
"""
|
|
||||||
return self.driver.execute_script(script, *args)
|
|
||||||
|
|
||||||
def run_async_script(self, script, *args):
|
|
||||||
"""以异步方式执行js代码
|
|
||||||
:param script: js文本
|
|
||||||
:param args: 传入的参数
|
|
||||||
:return: js执行结果
|
|
||||||
"""
|
|
||||||
return self.driver.execute_async_script(script, *args)
|
|
||||||
|
|
||||||
def run_cdp(self, cmd, **cmd_args):
|
|
||||||
"""执行Chrome DevTools Protocol语句
|
|
||||||
:param cmd: 协议项目
|
|
||||||
:param cmd_args: 参数
|
|
||||||
:return: 执行的结果
|
|
||||||
"""
|
|
||||||
return self.driver.execute_cdp_cmd(cmd, cmd_args)
|
|
||||||
|
|
||||||
def create_tab(self, url=''):
|
|
||||||
"""新建并定位到一个标签页,该标签页在最后面
|
|
||||||
:param url: 新标签页跳转到的网址
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.driver.switch_to.new_window('tab')
|
|
||||||
if url:
|
|
||||||
self.get(url)
|
|
||||||
|
|
||||||
def close_tabs(self, num_or_handles=None):
|
|
||||||
"""关闭传入的标签页,默认关闭当前页。可传入多个
|
|
||||||
注意:当程序使用的是接管的浏览器,获取到的 handle 顺序和视觉效果不一致,不能按序号关闭。
|
|
||||||
:param num_or_handles:要关闭的标签页序号或handle,可传入handle和序号组成的列表或元组,为None时关闭当前页
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
tabs = (self.current_tab_handle,) if num_or_handles is None else get_handles(self.tab_handles, num_or_handles)
|
|
||||||
for i in tabs:
|
|
||||||
self.driver.switch_to.window(i)
|
|
||||||
self.driver.close()
|
|
||||||
|
|
||||||
self.to_tab(0)
|
|
||||||
|
|
||||||
def close_other_tabs(self, num_or_handles=None):
|
|
||||||
"""关闭传入的标签页以外标签页,默认保留当前页。可传入多个
|
|
||||||
注意:当程序使用的是接管的浏览器,获取到的 handle 顺序和视觉效果不一致,不能按序号关闭。
|
|
||||||
:param num_or_handles: 要保留的标签页序号或handle,可传入handle和序号组成的列表或元组,为None时保存当前页
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
all_tabs = self.driver.window_handles
|
|
||||||
reserve_tabs = {self.current_tab_handle} if num_or_handles is None else get_handles(all_tabs, num_or_handles)
|
|
||||||
|
|
||||||
for i in set(all_tabs) - reserve_tabs:
|
|
||||||
self.driver.switch_to.window(i)
|
|
||||||
self.driver.close()
|
|
||||||
|
|
||||||
self.to_tab(0)
|
|
||||||
|
|
||||||
def to_tab(self, num_or_handle=0):
|
|
||||||
"""跳转到标签页
|
|
||||||
注意:当程序使用的是接管的浏览器,获取到的 handle 顺序和视觉效果不一致
|
|
||||||
:param num_or_handle: 标签页序号或handle字符串,序号第一个为0,最后为-1
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
tab = int(num_or_handle)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
tab = num_or_handle
|
|
||||||
|
|
||||||
tab = self.driver.window_handles[tab] if isinstance(tab, int) else tab
|
|
||||||
self.driver.switch_to.window(tab)
|
|
||||||
|
|
||||||
def set_ua_to_tab(self, ua):
|
|
||||||
"""为当前tab设置user agent,只在当前tab有效
|
|
||||||
:param ua: user agent字符串
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.driver.execute_cdp_cmd("Network.setUserAgentOverride", {"userAgent": ua})
|
|
||||||
|
|
||||||
def get_session_storage(self, item=None):
|
|
||||||
"""获取sessionStorage信息,不设置item则获取全部
|
|
||||||
:param item: 要获取的项,不设置则返回全部
|
|
||||||
:return: sessionStorage一个或所有项内容
|
|
||||||
"""
|
|
||||||
js = f'return sessionStorage.getItem("{item}");' if item else 'return sessionStorage;'
|
|
||||||
return self.run_script(js)
|
|
||||||
|
|
||||||
def get_local_storage(self, item=None):
|
|
||||||
"""获取localStorage信息,不设置item则获取全部
|
|
||||||
:param item: 要获取的项目,不设置则返回全部
|
|
||||||
:return: localStorage一个或所有项内容
|
|
||||||
"""
|
|
||||||
js = f'return localStorage.getItem("{item}");' if item else 'return localStorage;'
|
|
||||||
return self.run_script(js)
|
|
||||||
|
|
||||||
def set_session_storage(self, item, value):
|
|
||||||
"""设置或删除某项sessionStorage信息
|
|
||||||
:param item: 要设置的项
|
|
||||||
:param value: 项的值,设置为False时,删除该项
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
s = f'sessionStorage.removeItem("{item}");' if item is False else f'sessionStorage.setItem("{item}","{value}");'
|
|
||||||
self.run_script(s)
|
|
||||||
|
|
||||||
def set_local_storage(self, item, value):
|
|
||||||
"""设置或删除某项localStorage信息
|
|
||||||
:param item: 要设置的项
|
|
||||||
:param value: 项的值,设置为False时,删除该项
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
s = f'localStorage.removeItem("{item}");' if item is False else f'localStorage.setItem("{item}","{value}");'
|
|
||||||
self.run_script(s)
|
|
||||||
|
|
||||||
def clean_cache(self, session_storage=True, local_storage=True, cache=True, cookies=True):
|
|
||||||
"""清除缓存,可选要清除的项
|
|
||||||
:param session_storage: 是否清除sessionStorage
|
|
||||||
:param local_storage: 是否清除localStorage
|
|
||||||
:param cache: 是否清除cache
|
|
||||||
:param cookies: 是否清除cookies
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if session_storage:
|
|
||||||
self.run_script('sessionStorage.clear();')
|
|
||||||
if local_storage:
|
|
||||||
self.run_script('localStorage.clear();')
|
|
||||||
if cache:
|
|
||||||
self.run_cdp('Network.clearBrowserCache')
|
|
||||||
if cookies:
|
|
||||||
self.run_cdp('Network.clearBrowserCookies')
|
|
||||||
|
|
||||||
def screenshot(self, path=None, filename=None, as_bytes=False):
|
|
||||||
"""截取页面可见范围截图
|
|
||||||
:param path: 保存路径
|
|
||||||
:param filename: 图片文件名,不传入时以页面title命名
|
|
||||||
:param as_bytes: 是否已字节形式返回图片,为True时上面两个参数失效
|
|
||||||
:return: 图片完整路径或字节文本
|
|
||||||
"""
|
|
||||||
if as_bytes:
|
|
||||||
return self.driver.get_screenshot_as_png()
|
|
||||||
|
|
||||||
name = filename or self.title
|
|
||||||
if not name.lower().endswith('.png'):
|
|
||||||
name = f'{name}.png'
|
|
||||||
path = Path(path or '.').absolute()
|
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
|
||||||
img_path = str(get_usable_path(f'{path}{sep}{name}'))
|
|
||||||
self.driver.save_screenshot(img_path)
|
|
||||||
return img_path
|
|
||||||
|
|
||||||
def scroll_to_see(self, loc_or_ele):
|
|
||||||
"""滚动页面直到元素可见
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串(详见ele函数注释)
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
ele = self.ele(loc_or_ele)
|
|
||||||
ele.run_script("arguments[0].scrollIntoView();")
|
|
||||||
|
|
||||||
def refresh(self):
|
|
||||||
"""刷新当前页面"""
|
|
||||||
self.driver.refresh()
|
|
||||||
|
|
||||||
def stop_loading(self):
|
|
||||||
"""强制停止页面加载"""
|
|
||||||
self.run_cdp('Page.stopLoading')
|
|
||||||
|
|
||||||
def back(self):
|
|
||||||
"""在浏览历史中后退一步"""
|
|
||||||
self.driver.back()
|
|
||||||
|
|
||||||
def forward(self):
|
|
||||||
"""在浏览历史中前进一步"""
|
|
||||||
self.driver.forward()
|
|
||||||
|
|
||||||
def set_window_size(self, width=None, height=None):
|
|
||||||
"""设置浏览器窗口大小,默认最大化,任一参数为0最小化
|
|
||||||
:param width: 浏览器窗口高
|
|
||||||
:param height: 浏览器窗口宽
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if width is None and height is None:
|
|
||||||
self.driver.maximize_window()
|
|
||||||
|
|
||||||
elif width == 0 or height == 0:
|
|
||||||
self.driver.minimize_window()
|
|
||||||
|
|
||||||
else:
|
|
||||||
if width < 0 or height < 0:
|
|
||||||
raise ValueError('x 和 y参数必须大于0。')
|
|
||||||
|
|
||||||
new_x = width or self.driver.get_window_size()['width']
|
|
||||||
new_y = height or self.driver.get_window_size()['height']
|
|
||||||
self.driver.set_window_size(new_x, new_y)
|
|
||||||
|
|
||||||
def chrome_downloading(self, download_path):
|
|
||||||
"""返回浏览器下载中的文件列表
|
|
||||||
:param download_path: 下载文件夹路径
|
|
||||||
:return: 文件列表
|
|
||||||
"""
|
|
||||||
return glob(f'{download_path}{sep}*.crdownload')
|
|
||||||
|
|
||||||
def process_alert(self, ok=True, send=None, timeout=None):
|
|
||||||
"""处理提示框
|
|
||||||
:param ok: True表示确认,False表示取消,其它值不会按按钮但依然返回文本值
|
|
||||||
:param send: 处理prompt提示框时可输入文本
|
|
||||||
:param timeout: 等待提示框出现的超时时间
|
|
||||||
:return: 提示框内容文本,未等到提示框则返回None
|
|
||||||
"""
|
|
||||||
|
|
||||||
def do_it():
|
|
||||||
try:
|
|
||||||
return self.driver.switch_to.alert
|
|
||||||
except NoAlertPresentException:
|
|
||||||
return False
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
t1 = perf_counter()
|
|
||||||
alert = do_it()
|
|
||||||
while alert is False and perf_counter() - t1 <= timeout:
|
|
||||||
alert = do_it()
|
|
||||||
|
|
||||||
if alert is False:
|
|
||||||
return None
|
|
||||||
|
|
||||||
res_text = alert.text
|
|
||||||
|
|
||||||
if send is not None:
|
|
||||||
alert.send_keys(send)
|
|
||||||
|
|
||||||
if ok is True:
|
|
||||||
alert.accept()
|
|
||||||
elif ok is False:
|
|
||||||
alert.dismiss()
|
|
||||||
|
|
||||||
return res_text
|
|
||||||
|
|
||||||
|
|
||||||
class ToFrame(object):
|
|
||||||
"""用于处理焦点跳转到页面框架的类"""
|
|
||||||
|
|
||||||
def __init__(self, page):
|
|
||||||
self.page = page
|
|
||||||
|
|
||||||
def __call__(self, condition='main'):
|
|
||||||
"""跳转到(i)frame,可传入id、name、序号、元素对象、定位符
|
|
||||||
:param condition: (i)frame,可传入id、name、序号、元素对象、定位符
|
|
||||||
:return: 当前页面对象
|
|
||||||
"""
|
|
||||||
if isinstance(condition, (DriverElement, WebElement)):
|
|
||||||
self.by_ele(condition)
|
|
||||||
elif isinstance(condition, int):
|
|
||||||
self.by_index(condition)
|
|
||||||
elif ':' not in condition and '=' not in condition and not condition.startswith(('#', '.', '@')):
|
|
||||||
self.by_id(condition)
|
|
||||||
else:
|
|
||||||
self.by_loc(condition)
|
|
||||||
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def main(self):
|
|
||||||
"""焦点跳转到最高层级框架"""
|
|
||||||
self.page.driver.switch_to.default_content()
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def parent(self, level=1):
|
|
||||||
"""焦点跳转到上级框架,可指定上级层数
|
|
||||||
:param level: 上面第几层框架
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
if level < 1:
|
|
||||||
raise ValueError('level参数须是大于0的整数。')
|
|
||||||
for _ in range(level):
|
|
||||||
self.page.driver.switch_to.parent_frame()
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def by_id(self, id_):
|
|
||||||
"""焦点跳转到id为该值的(i)frame
|
|
||||||
:param id_: (i)frame的id属性值
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
self.page.driver.switch_to.frame(id_)
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def by_name(self, name):
|
|
||||||
"""焦点跳转到name为该值的(i)frame
|
|
||||||
:param name: (i)frame的name属性值
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
self.page.driver.switch_to.frame(name)
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def by_index(self, index):
|
|
||||||
"""焦点跳转到页面中第几个(i)frame
|
|
||||||
:param index: 页面中第几个(i)frame
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
self.page.driver.switch_to.frame(index)
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def by_loc(self, loc):
|
|
||||||
"""焦点跳转到根据定位符获取到的(i)frame
|
|
||||||
:param loc: 定位符,支持selenium原生和DriverPage定位符
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
self.page.driver.switch_to.frame(self.page(loc).inner_ele)
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
def by_ele(self, ele):
|
|
||||||
"""焦点跳转到传入的(i)frame元素对象
|
|
||||||
:param ele: (i)frame元素对象
|
|
||||||
:return: 框架所在页面对象
|
|
||||||
"""
|
|
||||||
if isinstance(ele, DriverElement):
|
|
||||||
ele = ele.inner_ele
|
|
||||||
self.page.driver.switch_to.frame(ele)
|
|
||||||
return self.page
|
|
||||||
|
|
||||||
|
|
||||||
def get_handles(handles, num_or_handles):
|
|
||||||
"""返回指定标签页组成的set
|
|
||||||
:param handles: handles列表
|
|
||||||
:param num_or_handles: 指定的标签页,可以是多个
|
|
||||||
:return: 指定标签页组成的set
|
|
||||||
"""
|
|
||||||
if isinstance(num_or_handles, (int, str)):
|
|
||||||
num_or_handles = (num_or_handles,)
|
|
||||||
elif not isinstance(num_or_handles, (list, tuple)):
|
|
||||||
raise TypeError('num_or_handle参数只能是int、str、list 或 tuple类型。')
|
|
||||||
|
|
||||||
return set(i if isinstance(i, str) else handles[i] for i in num_or_handles)
|
|
@ -1,189 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from typing import Union, List, Any, Tuple
|
|
||||||
|
|
||||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
|
||||||
|
|
||||||
from .base import BasePage
|
|
||||||
from .driver_element import DriverElement, Scroll, ElementWaiter
|
|
||||||
from .mix_page import MixPage
|
|
||||||
from .session_element import SessionElement
|
|
||||||
|
|
||||||
|
|
||||||
class DriverPage(BasePage):
|
|
||||||
|
|
||||||
def __init__(self, driver: RemoteWebDriver, timeout: float = 10) -> None:
|
|
||||||
self._driver: RemoteWebDriver = ...
|
|
||||||
self._url: str = ...
|
|
||||||
self._wait_object: WebDriverWait = ...
|
|
||||||
self._scroll: Scroll = ...
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str: Union[Tuple[str, str], str, DriverElement, WebElement],
|
|
||||||
timeout: float = None) -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def url(self) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self) -> dict: ...
|
|
||||||
|
|
||||||
def get(self,
|
|
||||||
url: str,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
retry: int = None,
|
|
||||||
interval: float = None) -> Union[None, bool]: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, DriverElement, WebElement],
|
|
||||||
timeout: float = None) -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union[DriverElement, str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, DriverElement] = None) \
|
|
||||||
-> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, DriverElement, WebElement],
|
|
||||||
timeout: float = None,
|
|
||||||
single: bool = True) -> Union[DriverElement, str, None, List[Union[DriverElement, str]]]: ...
|
|
||||||
|
|
||||||
def get_cookies(self, as_dict: bool = False) -> Union[list, dict]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeout(self) -> float: ...
|
|
||||||
|
|
||||||
@timeout.setter
|
|
||||||
def timeout(self, second: float) -> None: ...
|
|
||||||
|
|
||||||
def _d_connect(self,
|
|
||||||
to_url: str,
|
|
||||||
times: int = 0,
|
|
||||||
interval: float = 1,
|
|
||||||
show_errmsg: bool = False) -> Union[bool, None]: ...
|
|
||||||
|
|
||||||
# ----------------driver独有属性和方法-----------------------
|
|
||||||
@property
|
|
||||||
def driver(self) -> WebDriver: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wait_object(self) -> WebDriverWait: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timeouts(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tabs_count(self) -> int: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tab_handles(self) -> list: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_tab_index(self) -> int: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_tab_handle(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def active_ele(self) -> DriverElement: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scroll(self) -> Scroll: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def to_frame(self) -> ToFrame: ...
|
|
||||||
|
|
||||||
def set_timeouts(self, implicit: float = None, pageLoad: float = None, script: float = None) -> None: ...
|
|
||||||
|
|
||||||
def wait_ele(self,
|
|
||||||
loc_or_ele: Union[str, tuple, DriverElement, WebElement],
|
|
||||||
timeout: float = None) -> ElementWaiter: ...
|
|
||||||
|
|
||||||
def check_page(self) -> Union[bool, None]: ...
|
|
||||||
|
|
||||||
def run_script(self, script: str, *args) -> Any: ...
|
|
||||||
|
|
||||||
def run_async_script(self, script: str, *args) -> Any: ...
|
|
||||||
|
|
||||||
def run_cdp(self, cmd: str, **cmd_args) -> Any: ...
|
|
||||||
|
|
||||||
def create_tab(self, url: str = '') -> None: ...
|
|
||||||
|
|
||||||
def close_tabs(self, num_or_handles: Union[int, str, list, tuple] = None) -> None: ...
|
|
||||||
|
|
||||||
def close_other_tabs(self, num_or_handles: Union[int, str, list, tuple] = None) -> None: ...
|
|
||||||
|
|
||||||
def to_tab(self, num_or_handle: Union[int, str] = 0) -> None: ...
|
|
||||||
|
|
||||||
def set_ua_to_tab(self, ua: str) -> None: ...
|
|
||||||
|
|
||||||
def get_session_storage(self, item: str = None) -> Union[str, dict, None]: ...
|
|
||||||
|
|
||||||
def get_local_storage(self, item: str = None) -> Union[str, dict, None]: ...
|
|
||||||
|
|
||||||
def set_session_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
|
||||||
|
|
||||||
def set_local_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
|
||||||
|
|
||||||
def clean_cache(self,
|
|
||||||
session_storage: bool = True,
|
|
||||||
local_storage: bool = True,
|
|
||||||
cache: bool = True,
|
|
||||||
cookies: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def screenshot(self, path: str = None, filename: str = None, as_bytes: bool = False) -> Union[str, bytes]: ...
|
|
||||||
|
|
||||||
def scroll_to_see(self, loc_or_ele: Union[str, tuple, WebElement, DriverElement]) -> None: ...
|
|
||||||
|
|
||||||
def refresh(self) -> None: ...
|
|
||||||
|
|
||||||
def stop_loading(self) -> None: ...
|
|
||||||
|
|
||||||
def back(self) -> None: ...
|
|
||||||
|
|
||||||
def forward(self) -> None: ...
|
|
||||||
|
|
||||||
def set_window_size(self, width: int = None, height: int = None) -> None: ...
|
|
||||||
|
|
||||||
def chrome_downloading(self, download_path: str) -> list: ...
|
|
||||||
|
|
||||||
def process_alert(self, ok: bool = True, send: str = None, timeout: float = None) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
|
|
||||||
class ToFrame(object):
|
|
||||||
|
|
||||||
def __init__(self, page: DriverPage):
|
|
||||||
self.page: DriverPage = ...
|
|
||||||
|
|
||||||
def __call__(self, condition: Union[int, str, tuple, WebElement, DriverElement] = 'main') -> Union[
|
|
||||||
DriverPage, MixPage]: ...
|
|
||||||
|
|
||||||
def main(self) -> DriverPage: ...
|
|
||||||
|
|
||||||
def parent(self, level: int = 1) -> DriverPage: ...
|
|
||||||
|
|
||||||
def by_id(self, id_: str) -> DriverPage: ...
|
|
||||||
|
|
||||||
def by_name(self, name: str) -> DriverPage: ...
|
|
||||||
|
|
||||||
def by_index(self, index: int) -> DriverPage: ...
|
|
||||||
|
|
||||||
def by_loc(self, loc: Union[str, tuple]) -> DriverPage: ...
|
|
||||||
|
|
||||||
def by_ele(self, ele: Union[DriverElement, WebElement]) -> DriverPage: ...
|
|
||||||
|
|
||||||
|
|
||||||
def get_handles(handles: list, num_or_handles: Union[int, str, list, tuple]) -> set: ...
|
|
@ -1,344 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from .base import BasePage
|
|
||||||
from .drission import Drission
|
|
||||||
from .driver_page import DriverPage
|
|
||||||
from .session_page import SessionPage
|
|
||||||
|
|
||||||
|
|
||||||
class MixPage(SessionPage, DriverPage, BasePage):
|
|
||||||
"""MixPage整合了DriverPage和SessionPage,封装了对页面的操作,
|
|
||||||
可在selenium(d模式)和requests(s模式)间无缝切换。
|
|
||||||
切换的时候会自动同步cookies。
|
|
||||||
获取信息功能为两种模式共有,操作页面元素功能只有d模式有。
|
|
||||||
调用某种模式独有的功能,会自动切换到该模式。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, mode='d', drission=None, timeout=None, driver_options=None, session_options=None):
|
|
||||||
"""初始化函数
|
|
||||||
:param mode: 'd' 或 's',即driver模式和session模式
|
|
||||||
:param drission: Drission对象,不传入时会自动创建,有传入时driver_options和session_options参数无效
|
|
||||||
:param timeout: 超时时间,d模式时为寻找元素时间,s模式时为连接时间,默认10秒
|
|
||||||
:param driver_options: 浏览器设置,没传入drission参数时会用这个设置新建Drission对象中的WebDriver对象,传入False则不创建
|
|
||||||
:param session_options: requests设置,没传入drission参数时会用这个设置新建Drission对象中的Session对象,传入False则不创建
|
|
||||||
"""
|
|
||||||
self._mode = mode.lower()
|
|
||||||
if self._mode not in ('s', 'd'):
|
|
||||||
raise ValueError('mode参数只能是s或d。')
|
|
||||||
|
|
||||||
super(DriverPage, self).__init__(timeout)
|
|
||||||
self._driver, self._session = (None, True) if self._mode == 's' else (True, None)
|
|
||||||
self._drission = drission or Drission(driver_options, session_options)
|
|
||||||
self._wait_object = None
|
|
||||||
self._response = None
|
|
||||||
self._scroll = None
|
|
||||||
self._download_set = None
|
|
||||||
self._download_path = None
|
|
||||||
|
|
||||||
if self._mode == 'd':
|
|
||||||
try:
|
|
||||||
timeouts = self.drission.driver_options.timeouts
|
|
||||||
t = timeout if isinstance(timeout, (int, float)) else timeouts['implicit']
|
|
||||||
self.set_timeouts(t, timeouts['pageLoad'], timeouts['script'])
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
self.timeout = timeout if timeout is not None else 10
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str, timeout=None):
|
|
||||||
"""在内部查找元素
|
|
||||||
例:ele = page('@id=ele_id')
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 超时时间
|
|
||||||
:return: 子元素对象或属性文本
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().__call__(loc_or_str)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).__call__(loc_or_str, timeout)
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
"""返回当前url"""
|
|
||||||
if self._mode == 'd':
|
|
||||||
return self._drission.driver.current_url if self._driver else None
|
|
||||||
elif self._mode == 's':
|
|
||||||
return self._session_url
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title(self):
|
|
||||||
"""返回网页title"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().title
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).title
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
"""返回页面html文本"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().html
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).html
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self):
|
|
||||||
"""当返回内容是json格式时,返回对应的字典"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().json
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).json
|
|
||||||
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None, **kwargs):
|
|
||||||
"""跳转到一个url
|
|
||||||
:param url: 目标url
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param kwargs: 连接参数,s模式专用
|
|
||||||
:return: url是否可用,d模式返回None时表示不确定
|
|
||||||
"""
|
|
||||||
if self._mode == 'd':
|
|
||||||
return super(SessionPage, self).get(url, show_errmsg, retry, interval)
|
|
||||||
elif self._mode == 's':
|
|
||||||
return super().get(url, show_errmsg, retry, interval, **kwargs)
|
|
||||||
|
|
||||||
def ele(self, loc_or_ele, timeout=None):
|
|
||||||
"""返回第一个符合条件的元素、属性或节点文本
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与页面等待时间一致
|
|
||||||
:return: 元素对象或属性、文本节点文本
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().ele(loc_or_ele)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).ele(loc_or_ele, timeout=timeout)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
"""返回页面中所有符合条件的元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与页面等待时间一致
|
|
||||||
:return: 元素对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().eles(loc_or_str)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).eles(loc_or_str, timeout=timeout)
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele=None):
|
|
||||||
"""查找第一个符合条件的元素以SessionElement形式返回,d模式处理复杂页面时效率很高
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().s_ele(loc_or_ele)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).s_ele(loc_or_ele)
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
"""查找所有符合条件的元素以SessionElement形式返回,d模式处理复杂页面时效率很高
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().s_eles(loc_or_str)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).s_eles(loc_or_str)
|
|
||||||
|
|
||||||
def _ele(self, loc_or_ele, timeout=None, single=True):
|
|
||||||
"""返回页面中符合条件的元素、属性或节点文本,默认返回第一个
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,d模式专用
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:return: 元素对象或属性、文本节点文本
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super()._ele(loc_or_ele, single=single)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self)._ele(loc_or_ele, timeout=timeout, single=single)
|
|
||||||
|
|
||||||
def get_cookies(self, as_dict=False, all_domains=False):
|
|
||||||
"""返回cookies
|
|
||||||
:param as_dict: 是否以字典方式返回
|
|
||||||
:param all_domains: 是否返回所有域的cookies
|
|
||||||
:return: cookies信息
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
return super().get_cookies(as_dict, all_domains)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
return super(SessionPage, self).get_cookies(as_dict)
|
|
||||||
|
|
||||||
# ----------------MixPage独有属性和方法-----------------------
|
|
||||||
@property
|
|
||||||
def drission(self):
|
|
||||||
"""返回当前使用的 Dirssion 对象"""
|
|
||||||
return self._drission
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver(self):
|
|
||||||
"""返回 driver 对象,如没有则创建
|
|
||||||
每次访问时切换到 d 模式,用于独有函数及外部调用
|
|
||||||
:return: WebDriver对象
|
|
||||||
"""
|
|
||||||
self.change_mode('d')
|
|
||||||
return self._drission.driver
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self):
|
|
||||||
"""返回 Session 对象,如没有则创建"""
|
|
||||||
return self._drission.session
|
|
||||||
|
|
||||||
@property
|
|
||||||
def response(self):
|
|
||||||
"""返回 s 模式获取到的 Response 对象,切换到 s 模式"""
|
|
||||||
self.change_mode('s')
|
|
||||||
return self._response
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mode(self):
|
|
||||||
"""返回当前模式,'s'或'd' """
|
|
||||||
return self._mode
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _session_url(self):
|
|
||||||
"""返回 session 保存的url"""
|
|
||||||
return self._response.url if self._response else None
|
|
||||||
|
|
||||||
def change_mode(self, mode=None, go=True, copy_cookies=True):
|
|
||||||
"""切换模式,接收's'或'd',除此以外的字符串会切换为 d 模式
|
|
||||||
切换时会把当前模式的cookies复制到目标模式
|
|
||||||
切换后,如果go是True,调用相应的get函数使访问的页面同步
|
|
||||||
注意:s转d时,若浏览器当前网址域名和s模式不一样,必须会跳转
|
|
||||||
:param mode: 模式字符串
|
|
||||||
:param go: 是否跳转到原模式的url
|
|
||||||
:param copy_cookies: 是否复制cookies到目标模式
|
|
||||||
"""
|
|
||||||
if mode is not None and mode.lower() == self._mode:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._mode = 's' if self._mode == 'd' else 'd'
|
|
||||||
|
|
||||||
# s模式转d模式
|
|
||||||
if self._mode == 'd':
|
|
||||||
self._driver = True
|
|
||||||
self._url = None if not self._driver else self._drission.driver.current_url
|
|
||||||
|
|
||||||
if self._session_url:
|
|
||||||
if copy_cookies:
|
|
||||||
self.cookies_to_driver(self._session_url)
|
|
||||||
|
|
||||||
if go:
|
|
||||||
self.get(self._session_url)
|
|
||||||
|
|
||||||
# d模式转s模式
|
|
||||||
elif self._mode == 's':
|
|
||||||
self._session = True
|
|
||||||
self._url = self._session_url
|
|
||||||
|
|
||||||
if self._driver:
|
|
||||||
if copy_cookies:
|
|
||||||
self.cookies_to_session()
|
|
||||||
|
|
||||||
if go and self._drission.driver.current_url.startswith('http'):
|
|
||||||
self.get(self._drission.driver.current_url)
|
|
||||||
|
|
||||||
def set_cookies(self, cookies, refresh=True):
|
|
||||||
"""设置cookies
|
|
||||||
:param cookies: cookies信息,可为CookieJar, list, tuple, str, dict
|
|
||||||
:param refresh: 设置cookies后是否刷新页面
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self._mode == 's':
|
|
||||||
self.drission.set_cookies(cookies, set_session=True)
|
|
||||||
elif self._mode == 'd':
|
|
||||||
self.drission.set_cookies(cookies, set_driver=True)
|
|
||||||
if refresh:
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def cookies_to_session(self, copy_user_agent=False):
|
|
||||||
"""从driver复制cookies到session
|
|
||||||
:param copy_user_agent : 是否复制user agent信息
|
|
||||||
"""
|
|
||||||
self._drission.cookies_to_session(copy_user_agent)
|
|
||||||
|
|
||||||
def cookies_to_driver(self, url=None):
|
|
||||||
"""从session复制cookies到driver
|
|
||||||
chrome需要指定域才能接收cookies
|
|
||||||
:param url: 目标域
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
url = url or self._session_url
|
|
||||||
self._drission.cookies_to_driver(url)
|
|
||||||
|
|
||||||
def check_page(self, by_requests=False):
|
|
||||||
"""d模式时检查网页是否符合预期
|
|
||||||
默认由response状态检查,可重载实现针对性检查
|
|
||||||
:param by_requests: 是否用内置response检查
|
|
||||||
:return: bool或None,None代表不知道结果
|
|
||||||
"""
|
|
||||||
if self._session_url and self._session_url == self.url:
|
|
||||||
return self._response.ok
|
|
||||||
|
|
||||||
# 使用requests访问url并判断可用性
|
|
||||||
if by_requests:
|
|
||||||
self.cookies_to_session()
|
|
||||||
r = self._make_response(self.url, retry=0)[0]
|
|
||||||
return r.ok if r else False
|
|
||||||
|
|
||||||
def close_driver(self):
|
|
||||||
"""关闭driver及浏览器"""
|
|
||||||
self._driver = None
|
|
||||||
self.drission.close_driver(True)
|
|
||||||
|
|
||||||
def close_session(self):
|
|
||||||
"""关闭session"""
|
|
||||||
self._session = None
|
|
||||||
self._response = None
|
|
||||||
self.drission.close_session()
|
|
||||||
|
|
||||||
# ----------------重写SessionPage的函数-----------------------
|
|
||||||
def post(self, url, data=None, show_errmsg=False, retry=None, interval=None, **kwargs):
|
|
||||||
"""用post方式跳转到url,会切换到s模式
|
|
||||||
:param url: 目标url
|
|
||||||
:param data: post方式时提交的数据
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param kwargs: 连接参数
|
|
||||||
:return: url是否可用
|
|
||||||
"""
|
|
||||||
self.change_mode('s', go=False)
|
|
||||||
return super().post(url, data, show_errmsg, retry, interval, **kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download(self):
|
|
||||||
"""返回下载器对象"""
|
|
||||||
if self.mode == 'd':
|
|
||||||
self.cookies_to_session()
|
|
||||||
return super().download
|
|
||||||
|
|
||||||
def chrome_downloading(self, path=None):
|
|
||||||
"""返回浏览器下载中的文件列表
|
|
||||||
:param path: 下载文件夹路径,默认读取配置信息
|
|
||||||
:return: 正在下载的文件列表
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
path = path or self._drission.driver_options.experimental_options['prefs']['download.default_directory']
|
|
||||||
if not path:
|
|
||||||
raise ValueError('未指定下载路径。')
|
|
||||||
except Exception:
|
|
||||||
raise IOError('无法找到下载路径。')
|
|
||||||
|
|
||||||
return super().chrome_downloading(path)
|
|
||||||
|
|
||||||
# ----------------MixPage独有函数-----------------------
|
|
||||||
def hide_browser(self):
|
|
||||||
"""隐藏浏览器窗口"""
|
|
||||||
self.drission.hide_browser()
|
|
||||||
|
|
||||||
def show_browser(self):
|
|
||||||
"""显示浏览器窗口"""
|
|
||||||
self.drission.show_browser()
|
|
@ -1,156 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from typing import Union, List, Tuple, Any
|
|
||||||
|
|
||||||
from DownloadKit import DownloadKit
|
|
||||||
from requests import Response, Session
|
|
||||||
from requests.cookies import RequestsCookieJar
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.webdriver.chrome.webdriver import WebDriver
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
|
|
||||||
from .base import BasePage
|
|
||||||
from DrissionPage.configs.session_options import SessionOptions
|
|
||||||
from DrissionPage.configs.driver_options import DriverOptions
|
|
||||||
from .drission import Drission
|
|
||||||
from .driver_element import DriverElement
|
|
||||||
from .driver_page import DriverPage
|
|
||||||
from .session_element import SessionElement
|
|
||||||
from .session_page import SessionPage
|
|
||||||
|
|
||||||
|
|
||||||
class MixPage(SessionPage, DriverPage, BasePage):
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
mode: str = 'd',
|
|
||||||
drission: Union[Drission, str] = None,
|
|
||||||
timeout: float = None,
|
|
||||||
driver_options: Union[Options, DriverOptions, bool] = None,
|
|
||||||
session_options: Union[dict, SessionOptions, bool] = None) -> None:
|
|
||||||
self._mode: str = ...
|
|
||||||
self._drission: Drission = ...
|
|
||||||
|
|
||||||
def __call__(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str, DriverElement, SessionElement, WebElement],
|
|
||||||
timeout: float = None) -> Union[DriverElement, SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def url(self) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self) -> dict: ...
|
|
||||||
|
|
||||||
def get(self,
|
|
||||||
url: str,
|
|
||||||
show_errmsg: bool | None = False,
|
|
||||||
retry: int | None = None,
|
|
||||||
interval: float | None = None,
|
|
||||||
timeout: float | None = ...,
|
|
||||||
params: dict | None = ...,
|
|
||||||
data: Union[dict, str, None] = ...,
|
|
||||||
json: Union[dict, str, None] = ...,
|
|
||||||
headers: dict | None = ...,
|
|
||||||
cookies: Any | None = ...,
|
|
||||||
files: Any | None = ...,
|
|
||||||
auth: Any | None = ...,
|
|
||||||
allow_redirects: bool = ...,
|
|
||||||
proxies: dict | None = ...,
|
|
||||||
hooks: Any | None = ...,
|
|
||||||
stream: Any | None = ...,
|
|
||||||
verify: Any | None = ...,
|
|
||||||
cert: Any | None = ...) -> Union[bool, None]: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, DriverElement, SessionElement, WebElement],
|
|
||||||
timeout: float = None) -> Union[DriverElement, SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union[DriverElement, SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele: Union[Tuple[str, str], str, DriverElement, SessionElement] = None) \
|
|
||||||
-> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, DriverElement, SessionElement, WebElement],
|
|
||||||
timeout: float = None, single: bool = False) \
|
|
||||||
-> Union[DriverElement, SessionElement, str, None, List[Union[SessionElement, str]], List[
|
|
||||||
Union[DriverElement, str]]]: ...
|
|
||||||
|
|
||||||
def get_cookies(self, as_dict: bool = False, all_domains: bool = False) -> Union[dict, list]: ...
|
|
||||||
|
|
||||||
# ----------------MixPage独有属性和方法-----------------------
|
|
||||||
@property
|
|
||||||
def drission(self) -> Drission: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def driver(self) -> WebDriver: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self) -> Session: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def response(self) -> Response: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def mode(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _session_url(self) -> str: ...
|
|
||||||
|
|
||||||
def change_mode(self, mode: str = None, go: bool = True, copy_cookies: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def set_cookies(self, cookies: Union[RequestsCookieJar, list, tuple, str, dict], refresh: bool = True) -> None: ...
|
|
||||||
|
|
||||||
def cookies_to_session(self, copy_user_agent: bool = False) -> None: ...
|
|
||||||
|
|
||||||
def cookies_to_driver(self, url: str = None) -> None: ...
|
|
||||||
|
|
||||||
def check_page(self, by_requests: bool = False) -> Union[bool, None]: ...
|
|
||||||
|
|
||||||
def close_driver(self) -> None: ...
|
|
||||||
|
|
||||||
def close_session(self) -> None: ...
|
|
||||||
|
|
||||||
# ----------------重写SessionPage的函数-----------------------
|
|
||||||
def post(self,
|
|
||||||
url: str,
|
|
||||||
data: Union[dict, str, None] = None,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
retry: int | None = None,
|
|
||||||
interval: float | None = None,
|
|
||||||
timeout: float | None = ...,
|
|
||||||
params: dict | None = ...,
|
|
||||||
json: Union[dict, str, None] = ...,
|
|
||||||
headers: dict | None = ...,
|
|
||||||
cookies: Any | None = ...,
|
|
||||||
files: Any | None = ...,
|
|
||||||
auth: Any | None = ...,
|
|
||||||
allow_redirects: bool = ...,
|
|
||||||
proxies: dict | None = ...,
|
|
||||||
hooks: Any | None = ...,
|
|
||||||
stream: Any | None = ...,
|
|
||||||
verify: Any | None = ...,
|
|
||||||
cert: Any | None = ...) -> bool: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download(self) -> DownloadKit: ...
|
|
||||||
|
|
||||||
def chrome_downloading(self, path: str = None) -> list: ...
|
|
||||||
|
|
||||||
# ----------------MixPage独有函数-----------------------
|
|
||||||
def hide_browser(self) -> None: ...
|
|
||||||
|
|
||||||
def show_browser(self) -> None: ...
|
|
@ -1,357 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from html import unescape
|
|
||||||
from re import match, DOTALL
|
|
||||||
|
|
||||||
from lxml.etree import tostring
|
|
||||||
from lxml.html import HtmlElement, fromstring
|
|
||||||
|
|
||||||
from .base import DrissionElement, BasePage, BaseElement
|
|
||||||
from ..commons.locator import get_loc
|
|
||||||
from ..commons.web import get_ele_txt, make_absolute_link
|
|
||||||
|
|
||||||
|
|
||||||
class SessionElement(DrissionElement):
|
|
||||||
"""session模式的元素对象,包装了一个lxml的Element对象,并封装了常用功能"""
|
|
||||||
|
|
||||||
def __init__(self, ele, page=None):
|
|
||||||
"""初始化对象
|
|
||||||
:param ele: 被包装的HtmlElement元素
|
|
||||||
:param page: 元素所在页面对象,如果是从 html 文本生成的元素,则为 None
|
|
||||||
"""
|
|
||||||
super().__init__(page)
|
|
||||||
self._inner_ele = ele
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_ele(self):
|
|
||||||
return self._inner_ele
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
attrs = [f"{attr}='{self.attrs[attr]}'" for attr in self.attrs]
|
|
||||||
return f'<SessionElement {self.tag} {" ".join(attrs)}>'
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str, timeout=None):
|
|
||||||
"""在内部查找元素
|
|
||||||
例:ele2 = ele1('@id=ele_id')
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self.ele(loc_or_str)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tag(self):
|
|
||||||
"""返回元素类型"""
|
|
||||||
return self._inner_ele.tag
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
"""返回outerHTML文本"""
|
|
||||||
html = tostring(self._inner_ele, method="html").decode()
|
|
||||||
return unescape(html[:html.rfind('>') + 1]) # tostring()会把跟紧元素的文本节点也带上,因此要去掉
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_html(self):
|
|
||||||
"""返回元素innerHTML文本"""
|
|
||||||
r = match(r'<.*?>(.*)</.*?>', self.html, flags=DOTALL)
|
|
||||||
return '' if not r else r.group(1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attrs(self):
|
|
||||||
"""返回元素所有属性及值"""
|
|
||||||
return {attr: self.attr(attr) for attr, val in self.inner_ele.items()}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self):
|
|
||||||
"""返回元素内所有文本"""
|
|
||||||
return get_ele_txt(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_text(self):
|
|
||||||
"""返回未格式化处理的元素内文本"""
|
|
||||||
return str(self._inner_ele.text_content())
|
|
||||||
|
|
||||||
def parent(self, level_or_loc=1):
|
|
||||||
"""返回上面某一级父元素,可指定层数或用查询语法定位
|
|
||||||
:param level_or_loc: 第几级父元素,或定位符
|
|
||||||
:return: 上级元素对象
|
|
||||||
"""
|
|
||||||
return super().parent(level_or_loc)
|
|
||||||
|
|
||||||
def prev(self, filter_loc='', index=1, timeout=None):
|
|
||||||
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param index: 前面第几个查询结果
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素
|
|
||||||
"""
|
|
||||||
return super().prev(index, filter_loc, timeout)
|
|
||||||
|
|
||||||
def next(self, filter_loc='', index=1, timeout=None):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param index: 后面第几个查询结果
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素
|
|
||||||
"""
|
|
||||||
return super().next(index, filter_loc, timeout)
|
|
||||||
|
|
||||||
def before(self, filter_loc='', index=1, timeout=None):
|
|
||||||
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param index: 前面第几个查询结果
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素前面的某个元素或节点
|
|
||||||
"""
|
|
||||||
return super().before(index, filter_loc, timeout)
|
|
||||||
|
|
||||||
def after(self, filter_loc='', index=1, timeout=None):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param index: 后面第几个查询结果
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素后面的某个元素或节点
|
|
||||||
"""
|
|
||||||
return super().after(index, filter_loc, timeout)
|
|
||||||
|
|
||||||
def prevs(self, filter_loc='', timeout=None):
|
|
||||||
"""返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素或节点文本组成的列表
|
|
||||||
"""
|
|
||||||
return super().prevs(filter_loc, timeout)
|
|
||||||
|
|
||||||
def nexts(self, filter_loc='', timeout=None):
|
|
||||||
"""返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 兄弟元素或节点文本组成的列表
|
|
||||||
"""
|
|
||||||
return super().nexts(filter_loc, timeout)
|
|
||||||
|
|
||||||
def befores(self, filter_loc='', timeout=None):
|
|
||||||
"""返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素前面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
return super().befores(filter_loc, timeout)
|
|
||||||
|
|
||||||
def afters(self, filter_loc='', timeout=None):
|
|
||||||
"""返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:param timeout: 查找节点的超时时间
|
|
||||||
:return: 本元素后面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
return super().afters(filter_loc, timeout)
|
|
||||||
|
|
||||||
def attr(self, attr):
|
|
||||||
"""返回attribute属性值
|
|
||||||
:param attr: 属性名
|
|
||||||
:return: 属性值文本,没有该属性返回None
|
|
||||||
"""
|
|
||||||
# 获取href属性时返回绝对url
|
|
||||||
if attr == 'href':
|
|
||||||
link = self.inner_ele.get('href')
|
|
||||||
# 若为链接为None、js或邮件,直接返回
|
|
||||||
if not link or link.lower().startswith(('javascript:', 'mailto:')):
|
|
||||||
return link
|
|
||||||
|
|
||||||
else: # 其它情况直接返回绝对url
|
|
||||||
return make_absolute_link(link, self.page)
|
|
||||||
|
|
||||||
elif attr == 'src':
|
|
||||||
return make_absolute_link(self.inner_ele.get('src'), self.page)
|
|
||||||
|
|
||||||
elif attr == 'text':
|
|
||||||
return self.text
|
|
||||||
|
|
||||||
elif attr == 'innerText':
|
|
||||||
return self.raw_text
|
|
||||||
|
|
||||||
elif attr in ('html', 'outerHTML'):
|
|
||||||
return self.html
|
|
||||||
|
|
||||||
elif attr == 'innerHTML':
|
|
||||||
return self.inner_html
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self.inner_ele.get(attr)
|
|
||||||
|
|
||||||
def ele(self, loc_or_str, timeout=None):
|
|
||||||
"""返回当前元素下级符合条件的第一个元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
"""返回当前元素下级所有符合条件的子元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, single=False)
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_str=None):
|
|
||||||
"""返回当前元素下级符合条件的第一个元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str)
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
"""返回当前元素下级所有符合条件的子元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, single=False)
|
|
||||||
|
|
||||||
def _ele(self, loc_or_str, timeout=None, single=True, relative=False):
|
|
||||||
"""返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和父类对应
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:param relative: WebPage用的表示是否相对定位的参数
|
|
||||||
:return: SessionElement对象
|
|
||||||
"""
|
|
||||||
return make_session_ele(self, loc_or_str, single)
|
|
||||||
|
|
||||||
def _get_ele_path(self, mode):
|
|
||||||
"""获取css路径或xpath路径
|
|
||||||
:param mode: 'css' 或 'xpath'
|
|
||||||
:return: css路径或xpath路径
|
|
||||||
"""
|
|
||||||
path_str = ''
|
|
||||||
ele = self
|
|
||||||
|
|
||||||
while ele:
|
|
||||||
if mode == 'css':
|
|
||||||
brothers = len(ele.eles(f'xpath:./preceding-sibling::*'))
|
|
||||||
path_str = f'>:nth-child({brothers + 1}){path_str}'
|
|
||||||
else:
|
|
||||||
brothers = len(ele.eles(f'xpath:./preceding-sibling::{ele.tag}'))
|
|
||||||
path_str = f'/{ele.tag}[{brothers + 1}]{path_str}' if brothers > 0 else f'/{ele.tag}{path_str}'
|
|
||||||
|
|
||||||
ele = ele.parent()
|
|
||||||
|
|
||||||
return f':root{path_str[1:]}' if mode == 'css' else path_str
|
|
||||||
|
|
||||||
|
|
||||||
def make_session_ele(html_or_ele, loc=None, single=True):
|
|
||||||
"""从接收到的对象或html文本中查找元素,返回SessionElement对象
|
|
||||||
如要直接从html生成SessionElement而不在下级查找,loc输入None即可
|
|
||||||
:param html_or_ele: html文本、BaseParser对象
|
|
||||||
:param loc: 定位元组或字符串,为None时不在下级查找,返回根元素
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:return: 返回SessionElement元素或列表,或属性文本
|
|
||||||
"""
|
|
||||||
# ---------------处理定位符---------------
|
|
||||||
if not loc:
|
|
||||||
if isinstance(html_or_ele, SessionElement):
|
|
||||||
return html_or_ele if single else [html_or_ele]
|
|
||||||
|
|
||||||
loc = ('xpath', '.')
|
|
||||||
|
|
||||||
elif isinstance(loc, (str, tuple)):
|
|
||||||
loc = get_loc(loc)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise ValueError("定位符必须为str或长度为2的tuple。")
|
|
||||||
|
|
||||||
# ---------------根据传入对象类型获取页面对象和lxml元素对象---------------
|
|
||||||
the_type = str(type(html_or_ele))
|
|
||||||
# SessionElement
|
|
||||||
if the_type.endswith(".SessionElement'>"):
|
|
||||||
page = html_or_ele.page
|
|
||||||
|
|
||||||
loc_str = loc[1]
|
|
||||||
if loc[0] == 'xpath' and loc[1].lstrip().startswith('/'):
|
|
||||||
loc_str = f'.{loc[1]}'
|
|
||||||
html_or_ele = html_or_ele.inner_ele
|
|
||||||
|
|
||||||
# 若css以>开头,表示找元素的直接子元素,要用page以绝对路径才能找到
|
|
||||||
elif loc[0] == 'css selector' and loc[1].lstrip().startswith('>'):
|
|
||||||
loc_str = f'{html_or_ele.css_path}{loc[1]}'
|
|
||||||
if html_or_ele.page:
|
|
||||||
html_or_ele = fromstring(html_or_ele.page.html)
|
|
||||||
else: # 接收html文本,无page的情况
|
|
||||||
html_or_ele = fromstring(html_or_ele('xpath:/ancestor::*').html)
|
|
||||||
|
|
||||||
else:
|
|
||||||
html_or_ele = html_or_ele.inner_ele
|
|
||||||
|
|
||||||
loc = loc[0], loc_str
|
|
||||||
|
|
||||||
# ChromiumElement, DriverElement
|
|
||||||
elif the_type.endswith((".ChromiumElement'>", ".DriverElement'>")):
|
|
||||||
loc_str = loc[1]
|
|
||||||
if loc[0] == 'xpath' and loc[1].lstrip().startswith('/'):
|
|
||||||
loc_str = f'.{loc[1]}'
|
|
||||||
elif loc[0] == 'css selector' and loc[1].lstrip().startswith('>'):
|
|
||||||
loc_str = f'{html_or_ele.css_path}{loc[1]}'
|
|
||||||
loc = loc[0], loc_str
|
|
||||||
|
|
||||||
# 获取整个页面html再定位到当前元素,以实现查找上级元素
|
|
||||||
page = html_or_ele.page
|
|
||||||
xpath = html_or_ele.xpath
|
|
||||||
if hasattr(html_or_ele, 'doc_id'): # ChromiumElement,兼容传入的元素在iframe内的情况
|
|
||||||
html = html_or_ele.page.run_cdp('DOM.getOuterHTML', objectId=html_or_ele.doc_id)['outerHTML']
|
|
||||||
else:
|
|
||||||
html = html_or_ele.page.html
|
|
||||||
html_or_ele = fromstring(html)
|
|
||||||
html_or_ele = html_or_ele.xpath(xpath)[0]
|
|
||||||
|
|
||||||
# 各种页面对象
|
|
||||||
elif isinstance(html_or_ele, BasePage):
|
|
||||||
page = html_or_ele
|
|
||||||
html_or_ele = fromstring(html_or_ele.html)
|
|
||||||
|
|
||||||
# 直接传入html文本
|
|
||||||
elif isinstance(html_or_ele, str):
|
|
||||||
page = None
|
|
||||||
html_or_ele = fromstring(html_or_ele)
|
|
||||||
|
|
||||||
# ShadowRootElement, ChromiumShadowRoot, ChromiumFrame
|
|
||||||
elif isinstance(html_or_ele, BaseElement) or the_type.endswith(".ChromiumFrame'>"):
|
|
||||||
page = html_or_ele.page
|
|
||||||
html_or_ele = fromstring(html_or_ele.html)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError('html_or_ele参数只能是元素、页面对象或html文本。')
|
|
||||||
|
|
||||||
# ---------------执行查找-----------------
|
|
||||||
try:
|
|
||||||
if loc[0] == 'xpath': # 用lxml内置方法获取lxml的元素对象列表
|
|
||||||
ele = html_or_ele.xpath(loc[1])
|
|
||||||
else: # 用css selector获取元素对象列表
|
|
||||||
ele = html_or_ele.cssselect(loc[1])
|
|
||||||
|
|
||||||
if not isinstance(ele, list): # 结果不是列表,如数字
|
|
||||||
return ele
|
|
||||||
|
|
||||||
# 把lxml元素对象包装成SessionElement对象并按需要返回第一个或全部
|
|
||||||
if single:
|
|
||||||
ele = ele[0] if ele else None
|
|
||||||
if isinstance(ele, HtmlElement):
|
|
||||||
return SessionElement(ele, page)
|
|
||||||
elif isinstance(ele, str):
|
|
||||||
return ele
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
else: # 返回全部
|
|
||||||
return [SessionElement(e, page) if isinstance(e, HtmlElement) else e for e in ele if e != '\n']
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if 'Invalid expression' in str(e):
|
|
||||||
raise SyntaxError(f'无效的xpath语句:{loc}')
|
|
||||||
elif 'Expected selector' in str(e):
|
|
||||||
raise SyntaxError(f'无效的css select语句:{loc}')
|
|
||||||
|
|
||||||
raise e
|
|
@ -1,114 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from typing import Union, List, Tuple
|
|
||||||
|
|
||||||
from lxml.html import HtmlElement
|
|
||||||
|
|
||||||
from .base import DrissionElement, BaseElement
|
|
||||||
from .driver_element import DriverElement
|
|
||||||
from .driver_page import DriverPage
|
|
||||||
from .session_page import SessionPage
|
|
||||||
|
|
||||||
|
|
||||||
class SessionElement(DrissionElement):
|
|
||||||
|
|
||||||
def __init__(self, ele: HtmlElement, page: Union[SessionPage, None] = None):
|
|
||||||
self._inner_ele: HtmlElement = ...
|
|
||||||
self.page: SessionPage = ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_ele(self) -> HtmlElement: ...
|
|
||||||
|
|
||||||
def __repr__(self) -> str: ...
|
|
||||||
|
|
||||||
def __call__(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tag(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attrs(self) -> dict: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def raw_text(self) -> str: ...
|
|
||||||
|
|
||||||
def parent(self, level_or_loc: Union[tuple, str, int] = 1) -> Union['SessionElement', None]: ...
|
|
||||||
|
|
||||||
def prev(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
index: int = 1,
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def next(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
index: int = 1,
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def before(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
index: int = 1,
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def after(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
index: int = 1,
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def prevs(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def nexts(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def befores(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def afters(self,
|
|
||||||
filter_loc: Union[tuple, str] = '',
|
|
||||||
timeout: float = None) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def attr(self, attr: str) -> Union[str, None]: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str] = None) -> Union['SessionElement', str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str]) -> List[Union['SessionElement', str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None,
|
|
||||||
single: bool = True,
|
|
||||||
relative: bool = False) -> Union['SessionElement', str, None, List[Union['SessionElement', str]]]: ...
|
|
||||||
|
|
||||||
def _get_ele_path(self, mode: str) -> str: ...
|
|
||||||
|
|
||||||
|
|
||||||
def make_session_ele(html_or_ele: Union[str, SessionElement, SessionPage, DriverElement, BaseElement, DriverPage],
|
|
||||||
loc: Union[str, Tuple[str, str]] = None,
|
|
||||||
single: bool = True) -> Union[SessionElement, str, None, List[Union[SessionElement, str]]]: ...
|
|
@ -1,533 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from re import search
|
|
||||||
from time import sleep
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from DownloadKit import DownloadKit
|
|
||||||
from requests import Session, Response
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
|
||||||
from tldextract import extract
|
|
||||||
|
|
||||||
from .base import BasePage
|
|
||||||
from .session_element import SessionElement, make_session_ele
|
|
||||||
from ..commons.web import cookie_to_dict, set_session_cookies
|
|
||||||
from ..configs.session_options import SessionOptions
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPage(BasePage):
|
|
||||||
"""SessionPage封装了页面操作的常用功能,使用requests来获取、解析网页"""
|
|
||||||
|
|
||||||
def __init__(self, session_or_options=None, timeout=None):
|
|
||||||
"""
|
|
||||||
:param session_or_options: Session对象或SessionOptions对象
|
|
||||||
:param timeout: 连接超时时间,为None时从ini文件读取
|
|
||||||
"""
|
|
||||||
self._response = None
|
|
||||||
self._download_set = None
|
|
||||||
self._session = None
|
|
||||||
self._set = None
|
|
||||||
self._set_start_options(session_or_options, None)
|
|
||||||
self._set_runtime_settings()
|
|
||||||
self._create_session()
|
|
||||||
timeout = timeout if timeout is not None else self.timeout
|
|
||||||
super().__init__(timeout)
|
|
||||||
|
|
||||||
def _set_start_options(self, session_or_options, none):
|
|
||||||
"""启动配置
|
|
||||||
:param session_or_options: Session、SessionOptions
|
|
||||||
:param none: 用于后代继承
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if not session_or_options or isinstance(session_or_options, SessionOptions):
|
|
||||||
self._session_options = session_or_options or SessionOptions(session_or_options)
|
|
||||||
|
|
||||||
elif isinstance(session_or_options, Session):
|
|
||||||
self._session_options = SessionOptions()
|
|
||||||
self._session = session_or_options
|
|
||||||
|
|
||||||
def _set_runtime_settings(self):
|
|
||||||
"""设置运行时用到的属性"""
|
|
||||||
self._timeout = self._session_options.timeout
|
|
||||||
self._download_path = self._session_options.download_path
|
|
||||||
|
|
||||||
def _create_session(self):
|
|
||||||
"""创建内建Session对象"""
|
|
||||||
if not self._session:
|
|
||||||
self._set_session(self._session_options)
|
|
||||||
|
|
||||||
def _set_session(self, opt):
|
|
||||||
"""根据传入字典对session进行设置
|
|
||||||
:param opt: session配置字典
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._session = Session()
|
|
||||||
|
|
||||||
if opt.headers:
|
|
||||||
self._session.headers = CaseInsensitiveDict(opt.headers)
|
|
||||||
if opt.cookies:
|
|
||||||
self.set.cookies(opt.cookies)
|
|
||||||
if opt.adapters:
|
|
||||||
for url, adapter in opt.adapters:
|
|
||||||
self._session.mount(url, adapter)
|
|
||||||
|
|
||||||
attrs = ['auth', 'proxies', 'hooks', 'params', 'verify',
|
|
||||||
'cert', 'stream', 'trust_env', 'max_redirects']
|
|
||||||
for i in attrs:
|
|
||||||
attr = opt.__getattribute__(i)
|
|
||||||
if attr:
|
|
||||||
self._session.__setattr__(i, attr)
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str, timeout=None):
|
|
||||||
"""在内部查找元素
|
|
||||||
例:ele2 = ele1('@id=ele_id')
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和ChromiumElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性文本
|
|
||||||
"""
|
|
||||||
return self.ele(loc_or_str)
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def title(self):
|
|
||||||
"""返回网页title"""
|
|
||||||
ele = self.ele('xpath://title')
|
|
||||||
return ele.text if ele else None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
"""返回当前访问url"""
|
|
||||||
return self._url
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
"""返回页面的html文本"""
|
|
||||||
return self.response.text if self.response else ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self):
|
|
||||||
"""当返回内容是json格式时,返回对应的字典,非json格式时返回None"""
|
|
||||||
try:
|
|
||||||
return self.response.json()
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_path(self):
|
|
||||||
"""返回下载路径"""
|
|
||||||
return self._download_path
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_set(self):
|
|
||||||
"""返回用于设置下载参数的对象"""
|
|
||||||
if self._download_set is None:
|
|
||||||
self._download_set = DownloadSetter(self)
|
|
||||||
return self._download_set
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download(self):
|
|
||||||
"""返回下载器对象"""
|
|
||||||
return self.download_set.DownloadKit
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self):
|
|
||||||
"""返回session对象"""
|
|
||||||
return self._session
|
|
||||||
|
|
||||||
@property
|
|
||||||
def response(self):
|
|
||||||
"""返回访问url得到的response对象"""
|
|
||||||
return self._response
|
|
||||||
|
|
||||||
@property
|
|
||||||
def set(self):
|
|
||||||
"""返回用于等待的对象"""
|
|
||||||
if self._set is None:
|
|
||||||
self._set = SessionPageSetter(self)
|
|
||||||
return self._set
|
|
||||||
|
|
||||||
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
|
|
||||||
"""用get方式跳转到url
|
|
||||||
:param url: 目标url
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param timeout: 连接超时时间(秒)
|
|
||||||
:param kwargs: 连接参数
|
|
||||||
:return: url是否可用
|
|
||||||
"""
|
|
||||||
return self._s_connect(url, 'get', None, show_errmsg, retry, interval, **kwargs)
|
|
||||||
|
|
||||||
def ele(self, loc_or_ele, timeout=None):
|
|
||||||
"""返回页面中符合条件的第一个元素、属性或节点文本
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和ChromiumElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_ele)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
"""返回页面中所有符合条件的元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和ChromiumElement对应,便于无差别调用
|
|
||||||
:return: SessionElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, single=False)
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_ele=None):
|
|
||||||
"""返回页面中符合条件的第一个元素、属性或节点文本
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return make_session_ele(self.html) if loc_or_ele is None else self._ele(loc_or_ele)
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
"""返回页面中符合条件的所有元素、属性或节点文本
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, single=False)
|
|
||||||
|
|
||||||
def _ele(self, loc_or_ele, timeout=None, single=True):
|
|
||||||
"""返回页面中符合条件的元素、属性或节点文本,默认返回第一个
|
|
||||||
:param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串
|
|
||||||
:param timeout: 不起实际作用,用于和父类对应
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:return: SessionElement对象
|
|
||||||
"""
|
|
||||||
return loc_or_ele if isinstance(loc_or_ele, SessionElement) else make_session_ele(self, loc_or_ele, single)
|
|
||||||
|
|
||||||
def get_cookies(self, as_dict=False, all_domains=False):
|
|
||||||
"""返回cookies
|
|
||||||
:param as_dict: 是否以字典方式返回
|
|
||||||
:param all_domains: 是否返回所有域的cookies
|
|
||||||
:return: cookies信息
|
|
||||||
"""
|
|
||||||
if all_domains:
|
|
||||||
cookies = self.session.cookies
|
|
||||||
else:
|
|
||||||
if self.url:
|
|
||||||
url = extract(self.url)
|
|
||||||
domain = f'{url.domain}.{url.suffix}'
|
|
||||||
cookies = tuple(x for x in self.session.cookies if domain in x.domain or x.domain == '')
|
|
||||||
else:
|
|
||||||
cookies = tuple(x for x in self.session.cookies)
|
|
||||||
|
|
||||||
if as_dict:
|
|
||||||
return {x.name: x.value for x in cookies}
|
|
||||||
else:
|
|
||||||
return [cookie_to_dict(cookie) for cookie in cookies]
|
|
||||||
|
|
||||||
def post(self, url, data=None, show_errmsg=False, retry=None, interval=None, **kwargs):
|
|
||||||
"""用post方式跳转到url
|
|
||||||
:param url: 目标url
|
|
||||||
:param data: 提交的数据
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param kwargs: 连接参数
|
|
||||||
:return: url是否可用
|
|
||||||
"""
|
|
||||||
return self._s_connect(url, 'post', data, show_errmsg, retry, interval, **kwargs)
|
|
||||||
|
|
||||||
def _s_connect(self, url, mode, data=None, show_errmsg=False, retry=None, interval=None, **kwargs):
|
|
||||||
"""执行get或post连接
|
|
||||||
:param url: 目标url
|
|
||||||
:param mode: 'get' 或 'post'
|
|
||||||
:param data: 提交的数据
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param retry: 重试次数
|
|
||||||
:param interval: 重试间隔(秒)
|
|
||||||
:param kwargs: 连接参数
|
|
||||||
:return: url是否可用
|
|
||||||
"""
|
|
||||||
retry, interval = self._before_connect(url, retry, interval)
|
|
||||||
self._response, info = self._make_response(self._url, mode, data, retry, interval, show_errmsg, **kwargs)
|
|
||||||
|
|
||||||
if self._response is None:
|
|
||||||
self._url_available = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
if self._response.ok:
|
|
||||||
self._url_available = True
|
|
||||||
|
|
||||||
else:
|
|
||||||
if show_errmsg:
|
|
||||||
raise ConnectionError(f'状态码:{self._response.status_code}.')
|
|
||||||
self._url_available = False
|
|
||||||
|
|
||||||
return self._url_available
|
|
||||||
|
|
||||||
def _make_response(self, url, mode='get', data=None, retry=None, interval=None, show_errmsg=False, **kwargs):
|
|
||||||
"""生成Response对象
|
|
||||||
:param url: 目标url
|
|
||||||
:param mode: 'get' 或 'post'
|
|
||||||
:param data: post方式要提交的数据
|
|
||||||
:param show_errmsg: 是否显示和抛出异常
|
|
||||||
:param kwargs: 其它参数
|
|
||||||
:return: tuple,第一位为Response或None,第二位为出错信息或'Success'
|
|
||||||
"""
|
|
||||||
kwargs = CaseInsensitiveDict(kwargs)
|
|
||||||
if 'headers' not in kwargs:
|
|
||||||
kwargs['headers'] = {}
|
|
||||||
else:
|
|
||||||
kwargs['headers'] = CaseInsensitiveDict(kwargs['headers'])
|
|
||||||
|
|
||||||
# 设置referer和host值
|
|
||||||
parsed_url = urlparse(url)
|
|
||||||
hostname = parsed_url.hostname
|
|
||||||
scheme = parsed_url.scheme
|
|
||||||
if not check_headers(kwargs, self.session.headers, 'Referer'):
|
|
||||||
kwargs['headers']['Referer'] = self.url if self.url else f'{scheme}://{hostname}'
|
|
||||||
if 'Host' not in kwargs['headers']:
|
|
||||||
kwargs['headers']['Host'] = hostname
|
|
||||||
|
|
||||||
if not check_headers(kwargs, self.session.headers, 'timeout'):
|
|
||||||
kwargs['timeout'] = self.timeout
|
|
||||||
|
|
||||||
if 'allow_redirects' not in kwargs:
|
|
||||||
kwargs['allow_redirects'] = False
|
|
||||||
|
|
||||||
r = err = None
|
|
||||||
retry = retry if retry is not None else self.retry_times
|
|
||||||
interval = interval if interval is not None else self.retry_interval
|
|
||||||
for i in range(retry + 1):
|
|
||||||
try:
|
|
||||||
if mode == 'get':
|
|
||||||
r = self.session.get(url, **kwargs)
|
|
||||||
elif mode == 'post':
|
|
||||||
r = self.session.post(url, data=data, **kwargs)
|
|
||||||
|
|
||||||
if r:
|
|
||||||
return set_charset(r), 'Success'
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
err = e
|
|
||||||
|
|
||||||
# if r and r.status_code in (403, 404):
|
|
||||||
# break
|
|
||||||
|
|
||||||
if i < retry:
|
|
||||||
sleep(interval)
|
|
||||||
if show_errmsg:
|
|
||||||
print(f'重试 {url}')
|
|
||||||
|
|
||||||
if r is None:
|
|
||||||
if show_errmsg:
|
|
||||||
if err:
|
|
||||||
raise err
|
|
||||||
else:
|
|
||||||
raise ConnectionError('连接失败')
|
|
||||||
return None, '连接失败' if err is None else err
|
|
||||||
|
|
||||||
if not r.ok:
|
|
||||||
if show_errmsg:
|
|
||||||
raise ConnectionError(f'状态码:{r.status_code}')
|
|
||||||
return r, f'状态码:{r.status_code}'
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPageSetter(object):
|
|
||||||
def __init__(self, page):
|
|
||||||
self._page = page
|
|
||||||
|
|
||||||
def timeout(self, second):
|
|
||||||
"""设置连接超时时间
|
|
||||||
:param second: 秒数
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.timeout = second
|
|
||||||
|
|
||||||
def cookies(self, cookies):
|
|
||||||
"""为Session对象设置cookies
|
|
||||||
:param cookies: cookies信息
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
set_session_cookies(self._page.session, cookies)
|
|
||||||
|
|
||||||
def headers(self, headers):
|
|
||||||
"""设置通用的headers
|
|
||||||
:param headers: dict形式的headers
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.headers = CaseInsensitiveDict(headers)
|
|
||||||
|
|
||||||
def header(self, attr, value):
|
|
||||||
"""设置headers中一个项
|
|
||||||
:param attr: 设置名称
|
|
||||||
:param value: 设置值
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.headers[attr.lower()] = value
|
|
||||||
|
|
||||||
def user_agent(self, ua):
|
|
||||||
"""设置user agent
|
|
||||||
:param ua: user agent
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.headers['user-agent'] = ua
|
|
||||||
|
|
||||||
def proxies(self, http, https=None):
|
|
||||||
"""设置proxies参数
|
|
||||||
:param http: http代理地址
|
|
||||||
:param https: https代理地址
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
proxies = None if http == https is None else {'http': http, 'https': https or http}
|
|
||||||
self._page.session.proxies = proxies
|
|
||||||
|
|
||||||
def auth(self, auth):
|
|
||||||
"""设置认证元组或对象
|
|
||||||
:param auth: 认证元组或对象
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.auth = auth
|
|
||||||
|
|
||||||
def hooks(self, hooks):
|
|
||||||
"""设置回调方法
|
|
||||||
:param hooks: 回调方法
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.hooks = hooks
|
|
||||||
|
|
||||||
def params(self, params):
|
|
||||||
"""设置查询参数字典
|
|
||||||
:param params: 查询参数字典
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.params = params
|
|
||||||
|
|
||||||
def verify(self, on_off):
|
|
||||||
"""设置是否验证SSL证书
|
|
||||||
:param on_off: 是否验证 SSL 证书
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.verify = on_off
|
|
||||||
|
|
||||||
def cert(self, cert):
|
|
||||||
"""SSL客户端证书文件的路径(.pem格式),或(‘cert’, ‘key’)元组
|
|
||||||
:param cert: 证书路径或元组
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.cert = cert
|
|
||||||
|
|
||||||
def stream(self, on_off):
|
|
||||||
"""设置是否使用流式响应内容
|
|
||||||
:param on_off: 是否使用流式响应内容
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.stream = on_off
|
|
||||||
|
|
||||||
def trust_env(self, on_off):
|
|
||||||
"""设置是否信任环境
|
|
||||||
:param on_off: 是否信任环境
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.trust_env = on_off
|
|
||||||
|
|
||||||
def max_redirects(self, times):
|
|
||||||
"""设置最大重定向次数
|
|
||||||
:param times: 最大重定向次数
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.max_redirects = times
|
|
||||||
|
|
||||||
def add_adapter(self, url, adapter):
|
|
||||||
"""添加适配器
|
|
||||||
:param url: 适配器对应url
|
|
||||||
:param adapter: 适配器对象
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self._page.session.mount(url, adapter)
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadSetter(object):
|
|
||||||
"""用于设置下载参数的类"""
|
|
||||||
|
|
||||||
def __init__(self, page):
|
|
||||||
self._page = page
|
|
||||||
self._DownloadKit = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def DownloadKit(self):
|
|
||||||
if self._DownloadKit is None:
|
|
||||||
self._DownloadKit = DownloadKit(session=self._page.session, goal_path=self._page.download_path)
|
|
||||||
return self._DownloadKit
|
|
||||||
|
|
||||||
@property
|
|
||||||
def if_file_exists(self):
|
|
||||||
"""返回用于设置存在同名文件时处理方法的对象"""
|
|
||||||
return FileExists(self)
|
|
||||||
|
|
||||||
def split(self, on_off):
|
|
||||||
"""设置是否允许拆分大文件用多线程下载
|
|
||||||
:param on_off: 是否启用多线程下载大文件
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
self.DownloadKit.split = on_off
|
|
||||||
|
|
||||||
def save_path(self, path):
|
|
||||||
"""设置下载保存路径
|
|
||||||
:param path: 下载保存路径
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
path = path if path is None else str(path)
|
|
||||||
self._page._download_path = path
|
|
||||||
self.DownloadKit.goal_path = path
|
|
||||||
|
|
||||||
|
|
||||||
class FileExists(object):
|
|
||||||
"""用于设置存在同名文件时处理方法"""
|
|
||||||
|
|
||||||
def __init__(self, setter):
|
|
||||||
"""
|
|
||||||
:param setter: DownloadSetter对象
|
|
||||||
"""
|
|
||||||
self._setter = setter
|
|
||||||
|
|
||||||
def __call__(self, mode):
|
|
||||||
if mode not in ('skip', 'rename', 'overwrite'):
|
|
||||||
raise ValueError("mode参数只能是'skip', 'rename', 'overwrite'")
|
|
||||||
self._setter.DownloadKit.file_exists = mode
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
"""设为跳过"""
|
|
||||||
self._setter.DownloadKit.file_exists = 'skip'
|
|
||||||
|
|
||||||
def rename(self):
|
|
||||||
"""设为重命名,文件名后加序号"""
|
|
||||||
self._setter.DownloadKit._file_exists = 'rename'
|
|
||||||
|
|
||||||
def overwrite(self):
|
|
||||||
"""设为覆盖"""
|
|
||||||
self._setter.DownloadKit._file_exists = 'overwrite'
|
|
||||||
|
|
||||||
|
|
||||||
def check_headers(kwargs, headers, arg) -> bool:
|
|
||||||
"""检查kwargs或headers中是否有arg所示属性"""
|
|
||||||
return arg in kwargs['headers'] or arg in headers
|
|
||||||
|
|
||||||
|
|
||||||
def set_charset(response) -> Response:
|
|
||||||
"""设置Response对象的编码"""
|
|
||||||
# 在headers中获取编码
|
|
||||||
content_type = response.headers.get('content-type', '').lower()
|
|
||||||
charset = search(r'charset[=: ]*(.*)?;', content_type)
|
|
||||||
|
|
||||||
if charset:
|
|
||||||
response.encoding = charset.group(1)
|
|
||||||
|
|
||||||
# 在headers中获取不到编码,且如果是网页
|
|
||||||
elif content_type.replace(' ', '').startswith('text/html'):
|
|
||||||
re_result = search(b'<meta.*?charset=[ \\\'"]*([^"\\\' />]+).*?>', response.content)
|
|
||||||
|
|
||||||
if re_result:
|
|
||||||
charset = re_result.group(1).decode()
|
|
||||||
else:
|
|
||||||
charset = response.apparent_encoding
|
|
||||||
|
|
||||||
response.encoding = charset
|
|
||||||
|
|
||||||
return response
|
|
@ -1,237 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Union, Tuple, List
|
|
||||||
|
|
||||||
from DownloadKit import DownloadKit
|
|
||||||
from requests import Session, Response
|
|
||||||
from requests.adapters import HTTPAdapter
|
|
||||||
from requests.auth import HTTPBasicAuth
|
|
||||||
from requests.cookies import RequestsCookieJar
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
|
||||||
|
|
||||||
from .base import BasePage
|
|
||||||
from DrissionPage.configs.session_options import SessionOptions
|
|
||||||
from .session_element import SessionElement
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPage(BasePage):
|
|
||||||
def __init__(self,
|
|
||||||
session_or_options: Union[Session, SessionOptions] = None,
|
|
||||||
timeout: float = None):
|
|
||||||
self._session: Session = ...
|
|
||||||
self._session_options: SessionOptions = ...
|
|
||||||
self._url: str = ...
|
|
||||||
self._response: Response = ...
|
|
||||||
self._download_path: str = ...
|
|
||||||
self._download_set: DownloadSetter = ...
|
|
||||||
self._url_available: bool = ...
|
|
||||||
self.timeout: float = ...
|
|
||||||
self.retry_times: int = ...
|
|
||||||
self.retry_interval: float = ...
|
|
||||||
self._set: SessionPageSetter = ...
|
|
||||||
|
|
||||||
def _set_start_options(self, session_or_options, none) -> None: ...
|
|
||||||
|
|
||||||
def _create_session(self) -> None: ...
|
|
||||||
|
|
||||||
def _set_session(self, opt: SessionOptions) -> None: ...
|
|
||||||
|
|
||||||
def _set_runtime_settings(self) -> None: ...
|
|
||||||
|
|
||||||
def set_cookies(self, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
|
||||||
|
|
||||||
def set_headers(self, headers: dict) -> None: ...
|
|
||||||
|
|
||||||
def set_user_agent(self, ua: str) -> None: ...
|
|
||||||
|
|
||||||
def __call__(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str, SessionElement],
|
|
||||||
timeout: float = None) -> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
# -----------------共有属性和方法-------------------
|
|
||||||
@property
|
|
||||||
def title(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self) -> Union[dict, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_path(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_set(self) -> DownloadSetter: ...
|
|
||||||
|
|
||||||
def get(self,
|
|
||||||
url: str,
|
|
||||||
show_errmsg: bool | None = False,
|
|
||||||
retry: int | None = None,
|
|
||||||
interval: float | None = None,
|
|
||||||
timeout: float | None = None,
|
|
||||||
params: dict | None = ...,
|
|
||||||
data: Union[dict, str, None] = ...,
|
|
||||||
json: Union[dict, str, None] = ...,
|
|
||||||
headers: dict | None = ...,
|
|
||||||
cookies: Any | None = ...,
|
|
||||||
files: Any | None = ...,
|
|
||||||
auth: Any | None = ...,
|
|
||||||
allow_redirects: bool = ...,
|
|
||||||
proxies: dict | None = ...,
|
|
||||||
hooks: Any | None = ...,
|
|
||||||
stream: Any | None = ...,
|
|
||||||
verify: Any | None = ...,
|
|
||||||
cert: Any | None = ...) -> bool: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, SessionElement],
|
|
||||||
timeout: float = None) -> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, SessionElement] = None) \
|
|
||||||
-> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_ele: Union[Tuple[str, str], str, SessionElement],
|
|
||||||
timeout: float = None,
|
|
||||||
single: bool = True) -> Union[SessionElement, str, None, List[Union[SessionElement, str]]]: ...
|
|
||||||
|
|
||||||
def get_cookies(self,
|
|
||||||
as_dict: bool = False,
|
|
||||||
all_domains: bool = False) -> Union[dict, list]: ...
|
|
||||||
|
|
||||||
# ----------------session独有属性和方法-----------------------
|
|
||||||
@property
|
|
||||||
def session(self) -> Session: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def response(self) -> Response: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def set(self) -> SessionPageSetter: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download(self) -> DownloadKit: ...
|
|
||||||
|
|
||||||
def post(self,
|
|
||||||
url: str,
|
|
||||||
data: Union[dict, str, None] = ...,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
retry: int | None = None,
|
|
||||||
interval: float | None = None,
|
|
||||||
timeout: float | None = ...,
|
|
||||||
params: dict | None = ...,
|
|
||||||
json: Union[dict, str, None] = ...,
|
|
||||||
headers: dict | None = ...,
|
|
||||||
cookies: Any | None = ...,
|
|
||||||
files: Any | None = ...,
|
|
||||||
auth: Any | None = ...,
|
|
||||||
allow_redirects: bool = ...,
|
|
||||||
proxies: dict | None = ...,
|
|
||||||
hooks: Any | None = ...,
|
|
||||||
stream: Any | None = ...,
|
|
||||||
verify: Any | None = ...,
|
|
||||||
cert: Any | None = ...) -> bool: ...
|
|
||||||
|
|
||||||
def _s_connect(self,
|
|
||||||
url: str,
|
|
||||||
mode: str,
|
|
||||||
data: Union[dict, str, None] = None,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
retry: int = None,
|
|
||||||
interval: float = None,
|
|
||||||
**kwargs) -> bool: ...
|
|
||||||
|
|
||||||
def _make_response(self,
|
|
||||||
url: str,
|
|
||||||
mode: str = 'get',
|
|
||||||
data: Union[dict, str] = None,
|
|
||||||
retry: int = None,
|
|
||||||
interval: float = None,
|
|
||||||
show_errmsg: bool = False,
|
|
||||||
**kwargs) -> tuple: ...
|
|
||||||
|
|
||||||
|
|
||||||
class SessionPageSetter(object):
|
|
||||||
def __init__(self, page: SessionPage):
|
|
||||||
self._page: SessionPage = ...
|
|
||||||
|
|
||||||
def timeout(self, second: float) -> None: ...
|
|
||||||
|
|
||||||
def cookies(self, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
|
||||||
|
|
||||||
def headers(self, headers: dict) -> None: ...
|
|
||||||
|
|
||||||
def header(self, attr: str, value: str) -> None: ...
|
|
||||||
|
|
||||||
def user_agent(self, ua: str) -> None: ...
|
|
||||||
|
|
||||||
def proxies(self, http, https=None) -> None: ...
|
|
||||||
|
|
||||||
def auth(self, auth: Union[Tuple[str, str], HTTPBasicAuth, None]) -> None: ...
|
|
||||||
|
|
||||||
def hooks(self, hooks: Union[dict, None]) -> None: ...
|
|
||||||
|
|
||||||
def params(self, params: Union[dict, None]) -> None: ...
|
|
||||||
|
|
||||||
def verify(self, on_off: Union[bool, None]) -> None: ...
|
|
||||||
|
|
||||||
def cert(self, cert: Union[str, Tuple[str, str], None]) -> None: ...
|
|
||||||
|
|
||||||
def stream(self, on_off: Union[bool, None]) -> None: ...
|
|
||||||
|
|
||||||
def trust_env(self, on_off: Union[bool, None]) -> None: ...
|
|
||||||
|
|
||||||
def max_redirects(self, times: Union[int, None]) -> None: ...
|
|
||||||
|
|
||||||
def add_adapter(self, url: str, adapter: HTTPAdapter) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadSetter(object):
|
|
||||||
def __init__(self, page: SessionPage):
|
|
||||||
self._page: SessionPage = ...
|
|
||||||
self._DownloadKit: DownloadKit = ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def DownloadKit(self) -> DownloadKit: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def if_file_exists(self) -> FileExists: ...
|
|
||||||
|
|
||||||
def split(self, on_off: bool) -> None: ...
|
|
||||||
|
|
||||||
def save_path(self, path: Union[str, Path]): ...
|
|
||||||
|
|
||||||
|
|
||||||
class FileExists(object):
|
|
||||||
def __init__(self, setter: DownloadSetter):
|
|
||||||
self._setter: DownloadSetter = ...
|
|
||||||
|
|
||||||
def __call__(self, mode: str) -> None: ...
|
|
||||||
|
|
||||||
def skip(self) -> None: ...
|
|
||||||
|
|
||||||
def rename(self) -> None: ...
|
|
||||||
|
|
||||||
def overwrite(self) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
def check_headers(kwargs: Union[dict, CaseInsensitiveDict], headers: Union[dict, CaseInsensitiveDict],
|
|
||||||
arg: str) -> bool: ...
|
|
||||||
|
|
||||||
|
|
||||||
def set_charset(response: Response) -> Response: ...
|
|
@ -1,219 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from time import perf_counter
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
|
|
||||||
from .base import BaseElement
|
|
||||||
from .driver_element import make_driver_ele
|
|
||||||
from .session_element import make_session_ele, SessionElement
|
|
||||||
from ..commons.locator import get_loc
|
|
||||||
|
|
||||||
|
|
||||||
class ShadowRootElement(BaseElement):
|
|
||||||
"""ShadowRootElement是用于处理ShadowRoot的类,使用方法和DriverElement基本一致"""
|
|
||||||
|
|
||||||
def __init__(self, inner_ele, parent_ele):
|
|
||||||
super().__init__(parent_ele.page)
|
|
||||||
self.parent_ele = parent_ele
|
|
||||||
self._inner_ele = inner_ele
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_ele(self):
|
|
||||||
return self._inner_ele
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'<ShadowRootElement in {self.parent_ele} >'
|
|
||||||
|
|
||||||
def __call__(self, loc_or_str, timeout=None):
|
|
||||||
"""在内部查找元素
|
|
||||||
例:ele2 = ele1('@id=ele_id')
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 超时时间
|
|
||||||
:return: DriverElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self.ele(loc_or_str, timeout)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tag(self):
|
|
||||||
"""元素标签名"""
|
|
||||||
return 'shadow-root'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self):
|
|
||||||
return f'<shadow_root>{self.inner_html}</shadow_root>'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_html(self):
|
|
||||||
"""返回内部的html文本"""
|
|
||||||
shadow_root = WebElement(self.page.driver, self.inner_ele._id)
|
|
||||||
return shadow_root.get_attribute('innerHTML')
|
|
||||||
|
|
||||||
def parent(self, level_or_loc=1):
|
|
||||||
"""返回上面某一级父元素,可指定层数或用查询语法定位
|
|
||||||
:param level_or_loc: 第几级父元素,或定位符
|
|
||||||
:return: DriverElement对象
|
|
||||||
"""
|
|
||||||
if isinstance(level_or_loc, int):
|
|
||||||
loc = f'xpath:./ancestor-or-self::*[{level_or_loc}]'
|
|
||||||
|
|
||||||
elif isinstance(level_or_loc, (tuple, str)):
|
|
||||||
loc = get_loc(level_or_loc, True)
|
|
||||||
|
|
||||||
if loc[0] == 'css selector':
|
|
||||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
|
||||||
|
|
||||||
loc = f'xpath:./ancestor-or-self::{loc[1].lstrip(". / ")}'
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise TypeError('level_or_loc参数只能是tuple、int或str。')
|
|
||||||
|
|
||||||
return self.parent_ele.ele(loc, timeout=0)
|
|
||||||
|
|
||||||
def next(self, index=1, filter_loc=''):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: DriverElement对象
|
|
||||||
"""
|
|
||||||
nodes = self.nexts(filter_loc=filter_loc)
|
|
||||||
return nodes[index - 1] if nodes else None
|
|
||||||
|
|
||||||
def before(self, index=1, filter_loc=''):
|
|
||||||
"""返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 前面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: 本元素前面的某个元素或节点
|
|
||||||
"""
|
|
||||||
nodes = self.befores(filter_loc=filter_loc)
|
|
||||||
return nodes[index - 1] if nodes else None
|
|
||||||
|
|
||||||
def after(self, index=1, filter_loc=''):
|
|
||||||
"""返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个
|
|
||||||
:param index: 后面第几个查询结果
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: 本元素后面的某个元素或节点
|
|
||||||
"""
|
|
||||||
nodes = self.afters(filter_loc=filter_loc)
|
|
||||||
return nodes[index - 1] if nodes else None
|
|
||||||
|
|
||||||
def nexts(self, filter_loc=''):
|
|
||||||
"""返回后面所有兄弟元素或节点组成的列表
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: DriverElement对象组成的列表
|
|
||||||
"""
|
|
||||||
loc = get_loc(filter_loc, True)
|
|
||||||
if loc[0] == 'css selector':
|
|
||||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
|
||||||
|
|
||||||
loc = loc[1].lstrip('./')
|
|
||||||
xpath = f'xpath:./{loc}'
|
|
||||||
return self.parent_ele.eles(xpath, timeout=0.1)
|
|
||||||
|
|
||||||
def befores(self, filter_loc=''):
|
|
||||||
"""返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: 本元素前面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
loc = get_loc(filter_loc, True)
|
|
||||||
if loc[0] == 'css selector':
|
|
||||||
raise ValueError('此css selector语法不受支持,请换成xpath。')
|
|
||||||
|
|
||||||
loc = loc[1].lstrip('./')
|
|
||||||
xpath = f'xpath:./preceding::{loc}'
|
|
||||||
return self.parent_ele.eles(xpath, timeout=0.1)
|
|
||||||
|
|
||||||
def afters(self, filter_loc=''):
|
|
||||||
"""返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选
|
|
||||||
:param filter_loc: 用于筛选的查询语法
|
|
||||||
:return: 本元素后面的元素或节点组成的列表
|
|
||||||
"""
|
|
||||||
eles1 = self.nexts(filter_loc)
|
|
||||||
loc = get_loc(filter_loc, True)[1].lstrip('./')
|
|
||||||
xpath = f'xpath:./following::{loc}'
|
|
||||||
return eles1 + self.parent_ele.eles(xpath, timeout=0.1)
|
|
||||||
|
|
||||||
def ele(self, loc_or_str, timeout=None):
|
|
||||||
"""返回当前元素下级符合条件的第一个元素,默认返回
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致
|
|
||||||
:return: DriverElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, timeout)
|
|
||||||
|
|
||||||
def eles(self, loc_or_str, timeout=None):
|
|
||||||
"""返回当前元素下级所有符合条件的子元素
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间,默认与元素所在页面等待时间一致
|
|
||||||
:return: DriverElement对象或属性、文本组成的列表
|
|
||||||
"""
|
|
||||||
return self._ele(loc_or_str, timeout=timeout, single=False)
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_str=None) -> Union[SessionElement, str, None]:
|
|
||||||
"""查找第一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return make_session_ele(self, loc_or_str)
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str):
|
|
||||||
"""查找所有符合条件的元素以SessionElement列表形式返回,处理复杂页面时效率很高
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:return: SessionElement对象或属性、文本
|
|
||||||
"""
|
|
||||||
return make_session_ele(self, loc_or_str, single=False)
|
|
||||||
|
|
||||||
def _ele(self, loc_or_str, timeout=None, single=True, relative=False):
|
|
||||||
"""返回当前元素下级符合条件的子元素,默认返回第一个
|
|
||||||
:param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串
|
|
||||||
:param timeout: 查找元素超时时间
|
|
||||||
:param single: True则返回第一个,False则返回全部
|
|
||||||
:param relative: WebPage用的表示是否相对定位的参数
|
|
||||||
:return: DriverElement对象
|
|
||||||
"""
|
|
||||||
# 先转换为sessionElement,再获取所有元素,获取它们的css selector路径,再用路径在页面上执行查找
|
|
||||||
loc = get_loc(loc_or_str)
|
|
||||||
if loc[0] == 'css selector' and str(loc[1]).startswith(':root'):
|
|
||||||
loc = loc[0], loc[1][5:]
|
|
||||||
|
|
||||||
timeout = timeout if timeout is not None else self.page.timeout
|
|
||||||
t1 = perf_counter()
|
|
||||||
eles = make_session_ele(self.html).eles(loc)
|
|
||||||
while not eles and perf_counter() - t1 <= timeout:
|
|
||||||
eles = make_session_ele(self.html).eles(loc)
|
|
||||||
|
|
||||||
if not eles:
|
|
||||||
return None if single else eles
|
|
||||||
|
|
||||||
css_paths = [i.css_path[47:] for i in eles]
|
|
||||||
|
|
||||||
if single:
|
|
||||||
return make_driver_ele(self, f'css:{css_paths[0]}', single, timeout)
|
|
||||||
else:
|
|
||||||
return [make_driver_ele(self, f'css:{css}', True, timeout) for css in css_paths]
|
|
||||||
|
|
||||||
def run_script(self, script, *args):
|
|
||||||
"""执行js代码,传入自己为第一个参数
|
|
||||||
:param script: js文本
|
|
||||||
:param args: 传入的参数
|
|
||||||
:return: js执行结果
|
|
||||||
"""
|
|
||||||
shadow_root = WebElement(self.page.driver, self.inner_ele._id)
|
|
||||||
return shadow_root.parent.execute_script(script, shadow_root, *args)
|
|
||||||
|
|
||||||
def is_enabled(self):
|
|
||||||
"""是否可用"""
|
|
||||||
return self.inner_ele.is_enabled()
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
"""用于判断元素是否还能用,应对页面跳转元素不能用的情况"""
|
|
||||||
try:
|
|
||||||
self.is_enabled()
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
@ -1,84 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
"""
|
|
||||||
@Author : g1879
|
|
||||||
@Contact : g1879@qq.com
|
|
||||||
"""
|
|
||||||
from typing import Union, Any, Tuple, List
|
|
||||||
|
|
||||||
from selenium.webdriver.remote.webelement import WebElement
|
|
||||||
|
|
||||||
from .driver_page import DriverPage
|
|
||||||
from .mix_page import MixPage
|
|
||||||
from .base import BaseElement
|
|
||||||
from .driver_element import DriverElement
|
|
||||||
from .session_element import SessionElement
|
|
||||||
|
|
||||||
|
|
||||||
class ShadowRootElement(BaseElement):
|
|
||||||
|
|
||||||
def __init__(self, inner_ele: WebElement, parent_ele: DriverElement):
|
|
||||||
self._inner_ele: WebElement = ...
|
|
||||||
self.parent_ele: DriverElement = ...
|
|
||||||
self.page: Union[MixPage, DriverPage] = ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_ele(self) -> WebElement: ...
|
|
||||||
|
|
||||||
def __repr__(self) -> str: ...
|
|
||||||
|
|
||||||
def __call__(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tag(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def html(self) -> str: ...
|
|
||||||
|
|
||||||
@property
|
|
||||||
def inner_html(self) -> str: ...
|
|
||||||
|
|
||||||
def parent(self, level_or_loc: Union[str, int] = 1) -> DriverElement: ...
|
|
||||||
|
|
||||||
def next(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '') -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
def before(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '') -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
def after(self,
|
|
||||||
index: int = 1,
|
|
||||||
filter_loc: Union[tuple, str] = '') -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
def nexts(self, filter_loc: Union[tuple, str] = '') -> List[Union[DriverElement, str]]: ...
|
|
||||||
|
|
||||||
def befores(self, filter_loc: Union[tuple, str] = '') -> List[Union[DriverElement, str]]: ...
|
|
||||||
|
|
||||||
def afters(self, filter_loc: Union[tuple, str] = '') -> List[Union[DriverElement, str]]: ...
|
|
||||||
|
|
||||||
def ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> Union[DriverElement, str, None]: ...
|
|
||||||
|
|
||||||
def eles(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = None) -> List[Union[DriverElement, str]]: ...
|
|
||||||
|
|
||||||
def s_ele(self, loc_or_str: Union[Tuple[str, str], str] = None) -> Union[SessionElement, str, None]: ...
|
|
||||||
|
|
||||||
def s_eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[Union[SessionElement, str]]: ...
|
|
||||||
|
|
||||||
def _ele(self,
|
|
||||||
loc_or_str: Union[Tuple[str, str], str],
|
|
||||||
timeout: float = ...,
|
|
||||||
single: bool = ...,
|
|
||||||
relative: bool = ...) -> Union[DriverElement, str, None, List[Union[DriverElement, str]]]: ...
|
|
||||||
|
|
||||||
def run_script(self, script: str, *args) -> Any: ...
|
|
||||||
|
|
||||||
def is_enabled(self) -> bool: ...
|
|
||||||
|
|
||||||
def is_valid(self) -> bool: ...
|
|
Loading…
x
Reference in New Issue
Block a user