mirror of
https://gitee.com/g1879/DrissionPage.git
synced 2024-12-10 04:00:23 +08:00
Pre Merge pull request !45 from g1879/dev
This commit is contained in:
commit
02156d15a1
@ -1,3 +1,11 @@
|
||||
1. 使用上的问题请先查看文档[使用文档](http://g1879.gitee.io/drissionpagedocs)
|
||||
2. 遇到bug请详细描述如何重现,并附上代码
|
||||
3. 提问前先给本库打个星,谢谢
|
||||
在提交issue前,请确认已经给本库点了星星,这对我来说很重要。
|
||||
|
||||
使用方法请查看[使用文档](http://drissionpage.cn),文档里都有。
|
||||
也可在QQ群里提问(636361957)。
|
||||
|
||||
请围绕以下内容陈述您的问题:
|
||||
|
||||
1. 遇到了什么问题?什么场景下出现的?如何重现?
|
||||
2. 请附上代码和报错信息(如有)
|
||||
3. DrissionPage、浏览器、python版本号是多少?
|
||||
4. 有什么意见建议?
|
||||
|
@ -14,4 +14,4 @@ from ._configs.chromium_options import ChromiumOptions
|
||||
from ._configs.session_options import SessionOptions
|
||||
|
||||
__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__']
|
||||
__version__ = '4.0.4.7'
|
||||
__version__ = '4.0.4.21'
|
||||
|
@ -5,6 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from os import waitpid
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from time import perf_counter, sleep
|
||||
@ -72,7 +73,9 @@ class Browser(object):
|
||||
:param owner: 使用该驱动的对象
|
||||
:return: Driver对象
|
||||
"""
|
||||
d = self._drivers.pop(tab_id, Driver(tab_id, 'page', self.address))
|
||||
d = self._drivers.pop(tab_id, None)
|
||||
if not d:
|
||||
d = Driver(tab_id, 'page', self.address)
|
||||
d.owner = owner
|
||||
self._all_drivers.setdefault(tab_id, set()).add(d)
|
||||
return d
|
||||
@ -129,7 +132,7 @@ class Browser(object):
|
||||
return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')])
|
||||
|
||||
@property
|
||||
def tabs(self):
|
||||
def tab_ids(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp,因为顺序不对
|
||||
return [i['id'] for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]
|
||||
@ -139,13 +142,12 @@ class Browser(object):
|
||||
"""返回浏览器进程id"""
|
||||
return self._process_id
|
||||
|
||||
def find_tabs(self, title=None, url=None, tab_type=None, single=True):
|
||||
"""查找符合条件的tab,返回它们的id组成的列表
|
||||
def find_tabs(self, title=None, url=None, tab_type=None):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param single: 是否返回首个结果的id,为False返回所有信息
|
||||
:return: tab id或tab列表
|
||||
:return: dict格式的tab信息列表列表
|
||||
"""
|
||||
tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp
|
||||
|
||||
@ -156,9 +158,8 @@ class Browser(object):
|
||||
elif tab_type is not None:
|
||||
raise TypeError('tab_type只能是set、list、tuple、str、None。')
|
||||
|
||||
r = [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
|
||||
and (tab_type is None or i['type'] in tab_type))]
|
||||
return r[0]['id'] if r and single else r
|
||||
return [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url'])
|
||||
and (tab_type is None or i['type'] in tab_type))]
|
||||
|
||||
def close_tab(self, tab_id):
|
||||
"""关闭标签页
|
||||
@ -190,6 +191,30 @@ class Browser(object):
|
||||
"""
|
||||
return self.run_cdp('Browser.getWindowForTarget', targetId=tab_id or self.id)['bounds']
|
||||
|
||||
def new_tab(self, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页id
|
||||
"""
|
||||
bid = None
|
||||
if new_context:
|
||||
bid = self.run_cdp('Target.createBrowserContext')['browserContextId']
|
||||
|
||||
kwargs = {'url': ''}
|
||||
if new_window:
|
||||
kwargs['newWindow'] = True
|
||||
if background:
|
||||
kwargs['background'] = True
|
||||
if bid:
|
||||
kwargs['browserContextId'] = bid
|
||||
|
||||
tid = self.run_cdp('Target.createTarget', **kwargs)['targetId']
|
||||
while tid not in self._drivers:
|
||||
sleep(.1)
|
||||
return tid
|
||||
|
||||
def reconnect(self):
|
||||
"""断开重连"""
|
||||
self._driver.stop()
|
||||
@ -205,21 +230,31 @@ class Browser(object):
|
||||
:param force: 是否立刻强制终止进程
|
||||
:return: None
|
||||
"""
|
||||
pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']]
|
||||
for tab in self._all_drivers.values():
|
||||
try:
|
||||
self.run_cdp('Browser.close')
|
||||
except PageDisconnectedError:
|
||||
pass
|
||||
self.driver.stop()
|
||||
|
||||
drivers = list(self._all_drivers.values())
|
||||
for tab in drivers:
|
||||
for driver in tab:
|
||||
driver.stop()
|
||||
|
||||
if force:
|
||||
from psutil import Process
|
||||
for pid in pids:
|
||||
Process(pid).kill()
|
||||
else:
|
||||
if not force:
|
||||
return
|
||||
|
||||
try:
|
||||
pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']]
|
||||
except:
|
||||
return
|
||||
|
||||
from psutil import Process
|
||||
for pid in pids:
|
||||
try:
|
||||
self.run_cdp('Browser.close')
|
||||
self.driver.stop()
|
||||
except PageDisconnectedError:
|
||||
self.driver.stop()
|
||||
Process(pid).kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
from os import popen
|
||||
from platform import system
|
||||
@ -239,6 +274,10 @@ class Browser(object):
|
||||
|
||||
if ok:
|
||||
break
|
||||
sleep(.05)
|
||||
|
||||
if self.process_id:
|
||||
waitpid(self.process_id, 0)
|
||||
|
||||
def _on_disconnect(self):
|
||||
self.page._on_disconnect()
|
||||
@ -254,3 +293,4 @@ class Browser(object):
|
||||
break
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
pass
|
||||
sleep(.05)
|
||||
|
@ -40,13 +40,13 @@ class Browser(object):
|
||||
def tabs_count(self) -> int: ...
|
||||
|
||||
@property
|
||||
def tabs(self) -> List[str]: ...
|
||||
def tab_ids(self) -> List[str]: ...
|
||||
|
||||
@property
|
||||
def process_id(self) -> Optional[int]: ...
|
||||
|
||||
def find_tabs(self, title: str = None, url: str = None,
|
||||
tab_type: Union[str, list, tuple] = None, single: bool = True) -> Union[str, List[str]]: ...
|
||||
tab_type: Union[str, list, tuple] = None) -> List[dict]: ...
|
||||
|
||||
def close_tab(self, tab_id: str) -> None: ...
|
||||
|
||||
@ -56,6 +56,8 @@ class Browser(object):
|
||||
|
||||
def get_window_bounds(self, tab_id: str = None) -> dict: ...
|
||||
|
||||
def new_tab(self, new_window: bool = False, background: bool = False, new_context: bool = False) -> str: ...
|
||||
|
||||
def reconnect(self) -> None: ...
|
||||
|
||||
def connect_to_page(self) -> None: ...
|
||||
|
@ -30,7 +30,7 @@ class Driver(object):
|
||||
self.address = address
|
||||
self.type = tab_type
|
||||
self.owner = owner
|
||||
self._debug = False
|
||||
# self._debug = False
|
||||
self.alert_flag = False # 标记alert出现,跳过一条请求后复原
|
||||
|
||||
self._websocket_url = f'ws://{address}/devtools/{tab_type}/{tab_id}'
|
||||
@ -180,7 +180,6 @@ class Driver(object):
|
||||
def run(self, _method, **kwargs):
|
||||
"""执行cdp方法
|
||||
:param _method: cdp方法名
|
||||
:param args: cdp参数
|
||||
:param kwargs: cdp参数
|
||||
:return: 执行结果
|
||||
"""
|
||||
@ -202,7 +201,13 @@ class Driver(object):
|
||||
try:
|
||||
self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True)
|
||||
except WebSocketBadStatusException as e:
|
||||
raise TargetNotFoundError(f'找不到页面:{self.id}。') if 'No such target id' in str(e) else e
|
||||
txt = str(e)
|
||||
if 'No such target id' in txt:
|
||||
raise TargetNotFoundError(f'找不到页面:{self.id}。')
|
||||
elif 'Handshake status 403 Forbidden' in txt:
|
||||
raise RuntimeError('请升级websocket-client库。')
|
||||
else:
|
||||
raise e
|
||||
self._recv_th.start()
|
||||
self._handle_event_th.start()
|
||||
return True
|
||||
@ -224,14 +229,15 @@ class Driver(object):
|
||||
self._ws.close()
|
||||
self._ws = None
|
||||
|
||||
try:
|
||||
while not self.event_queue.empty():
|
||||
event = self.event_queue.get_nowait()
|
||||
function = self.event_handlers.get(event['method'])
|
||||
if function:
|
||||
function(**event['params'])
|
||||
except:
|
||||
pass
|
||||
# try:
|
||||
# while not self.event_queue.empty():
|
||||
# event = self.event_queue.get_nowait()
|
||||
# function = self.event_handlers.get(event['method'])
|
||||
# if function:
|
||||
# function(**event['params'])
|
||||
# sleep(.1)
|
||||
# except:
|
||||
# pass
|
||||
|
||||
self.event_handlers.clear()
|
||||
self.method_results.clear()
|
||||
|
@ -5,13 +5,14 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from copy import copy
|
||||
from pathlib import Path
|
||||
|
||||
from requests import Session
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .options_manage import OptionsManager
|
||||
from .._functions.web import cookies_to_tuple, set_session_cookies
|
||||
from .._functions.web import cookies_to_tuple, set_session_cookies, format_headers
|
||||
|
||||
|
||||
class SessionOptions(object):
|
||||
@ -171,6 +172,7 @@ class SessionOptions(object):
|
||||
self._headers = None
|
||||
self._del_set.add('headers')
|
||||
else:
|
||||
headers = format_headers(headers)
|
||||
self._headers = {key.lower(): headers[key] for key in headers}
|
||||
return self
|
||||
|
||||
@ -211,8 +213,8 @@ class SessionOptions(object):
|
||||
return self._cookies
|
||||
|
||||
def set_cookies(self, cookies):
|
||||
"""设置cookies信息
|
||||
:param cookies: cookies,可为CookieJar, list, tuple, str, dict,传入None可在ini文件标记删除
|
||||
"""设置一个或多个cookies信息
|
||||
:param cookies: cookies,可为Cookie, CookieJar, list, tuple, str, dict,传入None可在ini文件标记删除
|
||||
:return: 返回当前对象
|
||||
"""
|
||||
cookies = cookies if cookies is None else list(cookies_to_tuple(cookies))
|
||||
@ -440,7 +442,7 @@ class SessionOptions(object):
|
||||
:param headers: headers
|
||||
:return: 当前对象
|
||||
"""
|
||||
self._headers = CaseInsensitiveDict(**session.headers, **headers) if headers else session.headers
|
||||
self._headers = CaseInsensitiveDict(copy(session.headers).update(headers)) if headers else session.headers
|
||||
self._cookies = session.cookies
|
||||
self._auth = session.auth
|
||||
self._proxies = session.proxies
|
||||
|
@ -5,13 +5,13 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import CookieJar, Cookie
|
||||
from pathlib import Path
|
||||
from typing import Any, Union, Tuple, Optional
|
||||
|
||||
from requests import Session
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from requests.cookies import RequestsCookieJar
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ class SessionOptions(object):
|
||||
@property
|
||||
def headers(self) -> dict: ...
|
||||
|
||||
def set_headers(self, headers: Union[dict, None]) -> SessionOptions: ...
|
||||
def set_headers(self, headers: Union[dict, str, None]) -> SessionOptions: ...
|
||||
|
||||
def set_a_header(self, name: str, value: str) -> SessionOptions: ...
|
||||
|
||||
@ -60,7 +60,7 @@ class SessionOptions(object):
|
||||
@property
|
||||
def cookies(self) -> list: ...
|
||||
|
||||
def set_cookies(self, cookies: Union[RequestsCookieJar, list, tuple, str, dict, None]) -> SessionOptions: ...
|
||||
def set_cookies(self, cookies: Union[Cookie, CookieJar, list, tuple, str, dict, None]) -> SessionOptions: ...
|
||||
|
||||
@property
|
||||
def auth(self) -> Union[Tuple[str, str], HTTPBasicAuth]: ...
|
||||
|
@ -6,12 +6,12 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from json import loads
|
||||
from os.path import basename, sep
|
||||
from os.path import basename
|
||||
from pathlib import Path
|
||||
from re import search
|
||||
from time import perf_counter, sleep
|
||||
|
||||
from DataRecorder.tools import get_usable_path
|
||||
from DataRecorder.tools import get_usable_path, make_valid_name
|
||||
|
||||
from .none_element import NoneElement
|
||||
from .session_element import make_session_ele
|
||||
@ -93,6 +93,14 @@ class ChromiumElement(DrissionElement):
|
||||
def __eq__(self, other):
|
||||
return self._backend_id == getattr(other, '_backend_id', None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
"""获取元素属性
|
||||
:param item: 属性名
|
||||
:return: 属性值
|
||||
"""
|
||||
a = self.attr(item)
|
||||
return a if a is not None else self.property(item)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
"""返回元素tag"""
|
||||
@ -117,6 +125,10 @@ class ChromiumElement(DrissionElement):
|
||||
try:
|
||||
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
|
||||
except ElementLostError:
|
||||
self._refresh_id()
|
||||
attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes']
|
||||
return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)}
|
||||
except CDPError: # 文档根元素不能调用此方法
|
||||
return {}
|
||||
|
||||
@ -447,10 +459,7 @@ class ChromiumElement(DrissionElement):
|
||||
:param index: 获取第几个,从1开始,可传入负数获取倒数第几个
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
if self.tag in __FRAME_ELEMENT__:
|
||||
r = make_session_ele(self.inner_html, locator, index=index)
|
||||
else:
|
||||
r = make_session_ele(self, locator, index=index)
|
||||
r = make_session_ele(self, locator, index=index)
|
||||
if isinstance(r, NoneElement):
|
||||
if Settings.raise_when_ele_not_found:
|
||||
raise ElementNotFoundError(None, 's_ele()', {'locator': locator})
|
||||
@ -464,8 +473,6 @@ class ChromiumElement(DrissionElement):
|
||||
:param locator: 定位符
|
||||
:return: SessionElement或属性、文本组成的列表
|
||||
"""
|
||||
if self.tag in __FRAME_ELEMENT__:
|
||||
return make_session_ele(self.inner_html, locator, index=None)
|
||||
return make_session_ele(self, locator, index=None)
|
||||
|
||||
def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None):
|
||||
@ -505,24 +512,27 @@ class ChromiumElement(DrissionElement):
|
||||
sleep(.1)
|
||||
|
||||
src = self.attr('src')
|
||||
if not src:
|
||||
raise RuntimeError('元素没有src值或该值为空。')
|
||||
if src.lower().startswith('data:image'):
|
||||
if base64_to_bytes:
|
||||
from base64 import b64decode
|
||||
return b64decode(src.split(',', 1)[-1])
|
||||
|
||||
else:
|
||||
return src.split(',', 1)[-1]
|
||||
|
||||
is_blob = src.startswith('blob')
|
||||
result = None
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if is_blob:
|
||||
if is_blob:
|
||||
while perf_counter() < end_time:
|
||||
result = get_blob(self.owner, src, base64_to_bytes)
|
||||
if result:
|
||||
break
|
||||
sleep(.05)
|
||||
|
||||
else:
|
||||
else:
|
||||
while perf_counter() < end_time:
|
||||
src = self.property('currentSrc')
|
||||
if not src:
|
||||
continue
|
||||
@ -534,7 +544,8 @@ class ChromiumElement(DrissionElement):
|
||||
result = self.owner.run_cdp('Page.getResourceContent', frameId=frame, url=src)
|
||||
break
|
||||
except CDPError:
|
||||
sleep(.1)
|
||||
pass
|
||||
sleep(.1)
|
||||
|
||||
if not result:
|
||||
return None
|
||||
@ -548,11 +559,12 @@ class ChromiumElement(DrissionElement):
|
||||
else:
|
||||
return result['content']
|
||||
|
||||
def save(self, path=None, name=None, timeout=None):
|
||||
def save(self, path=None, name=None, timeout=None, rename=True):
|
||||
"""保存图片或其它有src属性的元素的资源
|
||||
:param path: 文件保存路径,为None时保存到当前文件夹
|
||||
:param name: 文件名称,为None时从资源url获取
|
||||
:param timeout: 等待资源加载的超时时间(秒)
|
||||
:param rename: 遇到重名文件时是否自动重命名
|
||||
:return: 返回保存路径
|
||||
"""
|
||||
data = self.src(timeout=timeout)
|
||||
@ -565,8 +577,13 @@ class ChromiumElement(DrissionElement):
|
||||
if src.lower().startswith('data:image'):
|
||||
r = search(r'data:image/(.*?);base64,', src)
|
||||
name = f'img.{r.group(1)}' if r else None
|
||||
name = name or basename(self.property('currentSrc'))
|
||||
path = get_usable_path(f'{path}{sep}{name}').absolute()
|
||||
path = Path(path) / make_valid_name(name or basename(self.property('currentSrc')))
|
||||
if not path.suffix:
|
||||
path = path.with_suffix('.jpg')
|
||||
if rename:
|
||||
path = get_usable_path(path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path = path.absolute()
|
||||
write_type = 'wb' if isinstance(data, bytes) else 'w'
|
||||
|
||||
with open(path, write_type) as f:
|
||||
@ -602,7 +619,7 @@ class ChromiumElement(DrissionElement):
|
||||
return self.owner._get_screenshot(path, name, as_bytes=as_bytes, as_base64=as_base64, full_page=False,
|
||||
left_top=left_top, right_bottom=right_bottom, ele=self)
|
||||
|
||||
def input(self, vals, clear=True, by_js=False):
|
||||
def input(self, vals, clear=False, by_js=False):
|
||||
"""输入文本或组合键,也可用于输入文件路径到input元素(路径间用\n间隔)
|
||||
:param vals: 文本值或按键组合
|
||||
:param clear: 输入前是否清空文本框
|
||||
@ -723,10 +740,15 @@ class ChromiumElement(DrissionElement):
|
||||
self._tag = n['localName']
|
||||
return n['backendNodeId']
|
||||
|
||||
def _refresh_id(self):
|
||||
"""根据backend id刷新其它id"""
|
||||
self._obj_id = self._get_obj_id(backend_id=self._backend_id)
|
||||
self._node_id = self._get_node_id(obj_id=self._obj_id)
|
||||
|
||||
def _get_ele_path(self, mode):
|
||||
"""返获取绝对的css路径或xpath路径"""
|
||||
if mode == 'xpath':
|
||||
txt1 = 'var tag = el.nodeName.toLowerCase();'
|
||||
txt1 = 'let tag = el.nodeName.toLowerCase();'
|
||||
txt3 = ''' && sib.nodeName.toLowerCase()==tag'''
|
||||
txt4 = '''
|
||||
if(nth>1){path = '/' + tag + '[' + nth + ']' + path;}
|
||||
@ -745,10 +767,10 @@ class ChromiumElement(DrissionElement):
|
||||
js = '''function(){
|
||||
function e(el) {
|
||||
if (!(el instanceof Element)) return;
|
||||
var path = '';
|
||||
let path = '';
|
||||
while (el.nodeType === Node.ELEMENT_NODE) {
|
||||
''' + txt1 + '''
|
||||
var sib = el, nth = 0;
|
||||
let sib = el, nth = 0;
|
||||
while (sib) {
|
||||
if(sib.nodeType === Node.ELEMENT_NODE''' + txt3 + '''){nth += 1;}
|
||||
sib = sib.previousSibling;
|
||||
@ -1079,7 +1101,7 @@ class ShadowRoot(BaseElement):
|
||||
return None if r is False else r
|
||||
|
||||
else:
|
||||
eles = make_session_ele(self.html).eles(loc)
|
||||
eles = make_session_ele(self, loc, index=None)
|
||||
if not eles:
|
||||
return None
|
||||
|
||||
@ -1093,8 +1115,8 @@ class ShadowRoot(BaseElement):
|
||||
r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False)
|
||||
return None if r is False else r
|
||||
else:
|
||||
node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId']
|
||||
for i in css]
|
||||
node_ids = [self.owner.run_cdp('DOM.querySelector',
|
||||
nodeId=self._node_id, selector=i)['nodeId'] for i in css]
|
||||
if 0 in node_ids:
|
||||
return None
|
||||
r = make_chromium_eles(self.owner, _ids=node_ids, index=index, is_obj_id=False)
|
||||
@ -1376,8 +1398,8 @@ else{return e.singleNodeValue;}'''
|
||||
# 按顺序获取所有元素、节点或属性
|
||||
elif type_txt == '7':
|
||||
for_txt = """
|
||||
var a=new Array();
|
||||
for(var i = 0; i <e.snapshotLength ; i++){
|
||||
let a=new Array();
|
||||
for(let i = 0; i <e.snapshotLength ; i++){
|
||||
if(e.snapshotItem(i).constructor.name=="Text"){a.push(e.snapshotItem(i).data);}
|
||||
else if(e.snapshotItem(i).constructor.name=="Attr"){a.push(e.snapshotItem(i).nodeValue);}
|
||||
else if(e.snapshotItem(i).constructor.name=="Comment"){a.push(e.snapshotItem(i).nodeValue);}
|
||||
@ -1392,7 +1414,7 @@ else{a.push(e.snapshotItem(i));}}"""
|
||||
return_txt = 'return e.singleNodeValue;'
|
||||
|
||||
xpath = xpath.replace(r"'", r"\'")
|
||||
js = f'function(){{var e=document.evaluate(\'{xpath}\',{node_txt},null,{type_txt},null);\n{for_txt}\n{return_txt}}}'
|
||||
js = f'function(){{let e=document.evaluate(\'{xpath}\',{node_txt},null,{type_txt},null);\n{for_txt}\n{return_txt}}}'
|
||||
|
||||
return js
|
||||
|
||||
@ -1418,6 +1440,7 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None):
|
||||
obj_id = page_or_ele._root_id
|
||||
if obj_id is not None:
|
||||
break
|
||||
sleep(.01)
|
||||
else:
|
||||
raise RuntimeError('js运行环境出错。')
|
||||
|
||||
@ -1518,7 +1541,7 @@ def convert_argument(arg):
|
||||
if isinstance(arg, ChromiumElement):
|
||||
return {'objectId': arg._obj_id}
|
||||
|
||||
elif isinstance(arg, (int, float, str, bool)):
|
||||
elif isinstance(arg, (int, float, str, bool, dict)):
|
||||
return {'value': arg}
|
||||
|
||||
from math import inf
|
||||
|
@ -56,6 +56,8 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def __eq__(self, other: ChromiumElement) -> bool: ...
|
||||
|
||||
def __getattr__(self, item: str) -> str: ...
|
||||
|
||||
@property
|
||||
def tag(self) -> str: ...
|
||||
|
||||
@ -206,7 +208,11 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def src(self, timeout: float = None, base64_to_bytes: bool = True) -> Union[bytes, str, None]: ...
|
||||
|
||||
def save(self, path: [str, bool] = None, name: str = None, timeout: float = None) -> str: ...
|
||||
def save(self,
|
||||
path: [str, bool] = None,
|
||||
name: str = None,
|
||||
timeout: float = None,
|
||||
rename: bool = True) -> str: ...
|
||||
|
||||
def get_screenshot(self,
|
||||
path: [str, Path] = None,
|
||||
@ -215,7 +221,7 @@ class ChromiumElement(DrissionElement):
|
||||
as_base64: PIC_TYPE = None,
|
||||
scroll_to_center: bool = True) -> Union[str, bytes]: ...
|
||||
|
||||
def input(self, vals: Any, clear: bool = True, by_js: bool = False) -> None: ...
|
||||
def input(self, vals: Any, clear: bool = False, by_js: bool = False) -> None: ...
|
||||
|
||||
def _set_file_input(self, files: Union[str, list, tuple]) -> None: ...
|
||||
|
||||
@ -237,6 +243,8 @@ class ChromiumElement(DrissionElement):
|
||||
|
||||
def _get_backend_id(self, node_id: int) -> int: ...
|
||||
|
||||
def _refresh_id(self) -> None: ...
|
||||
|
||||
def _get_ele_path(self, mode: str) -> str: ...
|
||||
|
||||
|
||||
|
@ -37,18 +37,26 @@ class SessionElement(DrissionElement):
|
||||
attrs = [f"{k}='{v}'" for k, v in self.attrs.items()]
|
||||
return f'<SessionElement {self.tag} {" ".join(attrs)}>'
|
||||
|
||||
def __call__(self, locator, timeout=None):
|
||||
def __call__(self, locator, index=1, timeout=None):
|
||||
"""在内部查找元素
|
||||
例:ele2 = ele1('@id=ele_id')
|
||||
:param locator: 元素的定位信息,可以是loc元组,或查询字符串
|
||||
:param index: 第几个元素,从1开始,可传入负数获取倒数第几个
|
||||
:param timeout: 不起实际作用
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
return self.ele(locator)
|
||||
return self.ele(locator, index=index)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.xpath == getattr(other, 'xpath', None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
"""获取元素属性
|
||||
:param item: 属性名
|
||||
:return: 属性值
|
||||
"""
|
||||
return self.attr(item)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
"""返回元素类型"""
|
||||
@ -199,12 +207,12 @@ class SessionElement(DrissionElement):
|
||||
# 若为链接为None、js或邮件,直接返回
|
||||
if not link or link.lower().startswith(('javascript:', 'mailto:')):
|
||||
return link
|
||||
|
||||
else: # 其它情况直接返回绝对url
|
||||
return make_absolute_link(link, self.owner.url)
|
||||
return make_absolute_link(link, self.owner.url) if self.owner else link
|
||||
|
||||
elif name == 'src':
|
||||
return make_absolute_link(self.inner_ele.get('src'), self.owner.url)
|
||||
return make_absolute_link(self.inner_ele.get('src'),
|
||||
self.owner.url) if self.owner else self.inner_ele.get('src')
|
||||
|
||||
elif name == 'text':
|
||||
return self.text
|
||||
@ -350,6 +358,10 @@ def make_session_ele(html_or_ele, loc=None, index=1):
|
||||
html_or_ele = fromstring(html)
|
||||
html_or_ele = html_or_ele.xpath(xpath)[0]
|
||||
|
||||
elif html_or_ele._type == 'ChromiumFrame':
|
||||
page = html_or_ele
|
||||
html_or_ele = fromstring(html_or_ele.inner_html)
|
||||
|
||||
# 各种页面对象
|
||||
elif isinstance(html_or_ele, BasePage):
|
||||
page = html_or_ele
|
||||
|
@ -35,6 +35,8 @@ class SessionElement(DrissionElement):
|
||||
|
||||
def __eq__(self, other: SessionElement) -> bool: ...
|
||||
|
||||
def __getattr__(self, item: str) -> str: ...
|
||||
|
||||
@property
|
||||
def tag(self) -> str: ...
|
||||
|
||||
|
@ -94,12 +94,12 @@ def get_launch_args(opt):
|
||||
opt.set_user_data_path(path)
|
||||
result.add(f'--user-data-dir={path}')
|
||||
|
||||
if headless is None and system().lower() == 'linux':
|
||||
from os import popen
|
||||
r = popen('systemctl list-units | grep graphical.target')
|
||||
if 'graphical.target' not in r.read():
|
||||
headless = True
|
||||
result.add('--headless=new')
|
||||
# if headless is None and system().lower() == 'linux': # 无界面Linux自动加入无头
|
||||
# from os import popen
|
||||
# r = popen('systemctl list-units | grep graphical.target')
|
||||
# if 'graphical.target' not in r.read():
|
||||
# headless = True
|
||||
# result.add('--headless=new')
|
||||
|
||||
result = list(result)
|
||||
opt._headless = headless
|
||||
@ -176,8 +176,8 @@ def set_flags(opt):
|
||||
states_dict = load(f)
|
||||
except JSONDecodeError:
|
||||
states_dict = {}
|
||||
flags_list = [] if opt.clear_file_flags else states_dict.setdefault(
|
||||
'browser', {}).setdefault('enabled_labs_experiments', [])
|
||||
states_dict.setdefault('browser', {}).setdefault('enabled_labs_experiments', [])
|
||||
flags_list = [] if opt.clear_file_flags else states_dict['browser']['enabled_labs_experiments']
|
||||
flags_dict = {}
|
||||
for i in flags_list:
|
||||
f = str(i).split('@', 1)
|
||||
|
@ -10,6 +10,12 @@ from ..errors import AlertExistsError
|
||||
|
||||
class Keys:
|
||||
"""特殊按键"""
|
||||
CTRL_A = ('\ue009', 'a')
|
||||
CTRL_C = ('\ue009', 'c')
|
||||
CTRL_X = ('\ue009', 'x')
|
||||
CTRL_V = ('\ue009', 'v')
|
||||
CTRL_Z = ('\ue009', 'z')
|
||||
CTRL_Y = ('\ue009', 'y')
|
||||
|
||||
NULL = '\ue000'
|
||||
CANCEL = '\ue001' # ^break
|
||||
@ -94,35 +100,108 @@ keyDefinitions = {
|
||||
'7': {'keyCode': 55, 'key': '7', 'code': 'Digit7'},
|
||||
'8': {'keyCode': 56, 'key': '8', 'code': 'Digit8'},
|
||||
'9': {'keyCode': 57, 'key': '9', 'code': 'Digit9'},
|
||||
'Power': {'key': 'Power', 'code': 'Power'},
|
||||
'Eject': {'key': 'Eject', 'code': 'Eject'},
|
||||
'\ue001': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'},
|
||||
'\ue002': {'keyCode': 6, 'code': 'Help', 'key': 'Help'},
|
||||
'\ue003': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'},
|
||||
'\ue004': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'},
|
||||
'\ue005': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},
|
||||
'\ue006': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3},
|
||||
'\ue007': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
|
||||
'\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
|
||||
'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'},
|
||||
'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'},
|
||||
'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'},
|
||||
'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'},
|
||||
'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'},
|
||||
'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'},
|
||||
'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'},
|
||||
'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'},
|
||||
'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'},
|
||||
'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'},
|
||||
'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'},
|
||||
'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'},
|
||||
'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'},
|
||||
'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'},
|
||||
'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'},
|
||||
'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'},
|
||||
'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'},
|
||||
'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'},
|
||||
's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'},
|
||||
't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'},
|
||||
'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'},
|
||||
'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'},
|
||||
'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'},
|
||||
'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'},
|
||||
'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'},
|
||||
'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'},
|
||||
' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'},
|
||||
'*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3},
|
||||
'+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3},
|
||||
'-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3},
|
||||
'/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3},
|
||||
';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'},
|
||||
'=': {'keyCode': 187, 'key': '=', 'code': 'Equal'},
|
||||
',': {'keyCode': 188, 'key': ',', 'code': 'Comma'},
|
||||
'.': {'keyCode': 190, 'key': '.', 'code': 'Period'},
|
||||
'`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'},
|
||||
'[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'},
|
||||
'\\': {'keyCode': 220, 'key': '\\', 'code': 'Backslash'},
|
||||
']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'},
|
||||
'\'': {'keyCode': 222, 'key': '\'', 'code': 'Quote'},
|
||||
')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'},
|
||||
'!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'},
|
||||
'@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'},
|
||||
'#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'},
|
||||
'$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'},
|
||||
'%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'},
|
||||
'^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'},
|
||||
'&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'},
|
||||
'(': {'keyCode': 57, 'key': '(', 'code': 'Digit9'},
|
||||
'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'},
|
||||
'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'},
|
||||
'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'},
|
||||
'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'},
|
||||
'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'},
|
||||
'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'},
|
||||
'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'},
|
||||
'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'},
|
||||
'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'},
|
||||
'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'},
|
||||
'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'},
|
||||
'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'},
|
||||
'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'},
|
||||
'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'},
|
||||
'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'},
|
||||
'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'},
|
||||
'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'},
|
||||
'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'},
|
||||
'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'},
|
||||
'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'},
|
||||
'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'},
|
||||
'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'},
|
||||
'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'},
|
||||
'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'},
|
||||
'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'},
|
||||
'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'},
|
||||
':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'},
|
||||
'<': {'keyCode': 188, 'key': '<', 'code': 'Comma'},
|
||||
'_': {'keyCode': 189, 'key': '_', 'code': 'Minus'},
|
||||
'>': {'keyCode': 190, 'key': '>', 'code': 'Period'},
|
||||
'?': {'keyCode': 191, 'key': '?', 'code': 'Slash'},
|
||||
'~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'},
|
||||
'{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'},
|
||||
'|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},
|
||||
# '\ue026': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},
|
||||
'}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},
|
||||
'"': {'keyCode': 222, 'key': '"', 'code': 'Quote'},
|
||||
'\n': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
|
||||
'\ue007': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
|
||||
'\ue003': {'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace'},
|
||||
'\ue00d': {'keyCode': 32, 'code': 'Space', 'key': ' '},
|
||||
# 'PageUp': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3},
|
||||
'\ue00e': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'},
|
||||
# 'PageDown': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3},
|
||||
'\ue00f': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'},
|
||||
'\ue008': {'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1},
|
||||
# 'ShiftRight': {'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2},
|
||||
'\ue009': {'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1},
|
||||
# 'ControlRight': {'keyCode': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2},
|
||||
'\ue00a': {'keyCode': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1},
|
||||
# 'AltRight': {'keyCode': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2},
|
||||
'\ue00b': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},
|
||||
'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},
|
||||
'\ue00c': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},
|
||||
'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},
|
||||
'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},
|
||||
'\ue00d': {'keyCode': 32, 'code': 'Space', 'key': ' '},
|
||||
# 'PageUp': {'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3},
|
||||
'\ue00e': {'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp'},
|
||||
# 'PageDown': {'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3},
|
||||
'\ue00f': {'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown'},
|
||||
'\ue010': {'keyCode': 35, 'code': 'End', 'key': 'End'},
|
||||
# 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},
|
||||
'\ue03d': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft'},
|
||||
'\r': {'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r'},
|
||||
'\ue011': {'keyCode': 36, 'code': 'Home', 'key': 'Home'},
|
||||
# 'Numpad7': {'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3},
|
||||
'\ue012': {'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft'},
|
||||
@ -133,6 +212,19 @@ keyDefinitions = {
|
||||
# 'Numpad6': {'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3},
|
||||
# 'Numpad2': {'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3},
|
||||
'\ue015': {'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown'},
|
||||
|
||||
'\ue001': {'keyCode': 3, 'code': 'Abort', 'key': 'Cancel'},
|
||||
'\ue002': {'keyCode': 6, 'code': 'Help', 'key': 'Help'},
|
||||
'\ue004': {'keyCode': 9, 'code': 'Tab', 'key': 'Tab'},
|
||||
'\ue005': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3},
|
||||
'\ue006': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3},
|
||||
'\ue00b': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'},
|
||||
'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'},
|
||||
'\ue00c': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'},
|
||||
'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'},
|
||||
'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'},
|
||||
'\ue010': {'keyCode': 35, 'code': 'End', 'key': 'End'},
|
||||
# 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3},
|
||||
'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'},
|
||||
'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'},
|
||||
'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'},
|
||||
@ -238,102 +330,17 @@ keyDefinitions = {
|
||||
'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'},
|
||||
'Accept': {'keyCode': 30, 'key': 'Accept'},
|
||||
'ModeChange': {'keyCode': 31, 'key': 'ModeChange'},
|
||||
' ': {'keyCode': 32, 'key': ' ', 'code': 'Space'},
|
||||
'Print': {'keyCode': 42, 'key': 'Print'},
|
||||
'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'},
|
||||
'\u0000': {'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3},
|
||||
'a': {'keyCode': 65, 'key': 'a', 'code': 'KeyA'},
|
||||
'b': {'keyCode': 66, 'key': 'b', 'code': 'KeyB'},
|
||||
'c': {'keyCode': 67, 'key': 'c', 'code': 'KeyC'},
|
||||
'd': {'keyCode': 68, 'key': 'd', 'code': 'KeyD'},
|
||||
'e': {'keyCode': 69, 'key': 'e', 'code': 'KeyE'},
|
||||
'f': {'keyCode': 70, 'key': 'f', 'code': 'KeyF'},
|
||||
'g': {'keyCode': 71, 'key': 'g', 'code': 'KeyG'},
|
||||
'h': {'keyCode': 72, 'key': 'h', 'code': 'KeyH'},
|
||||
'i': {'keyCode': 73, 'key': 'i', 'code': 'KeyI'},
|
||||
'j': {'keyCode': 74, 'key': 'j', 'code': 'KeyJ'},
|
||||
'k': {'keyCode': 75, 'key': 'k', 'code': 'KeyK'},
|
||||
'l': {'keyCode': 76, 'key': 'l', 'code': 'KeyL'},
|
||||
'm': {'keyCode': 77, 'key': 'm', 'code': 'KeyM'},
|
||||
'n': {'keyCode': 78, 'key': 'n', 'code': 'KeyN'},
|
||||
'o': {'keyCode': 79, 'key': 'o', 'code': 'KeyO'},
|
||||
'p': {'keyCode': 80, 'key': 'p', 'code': 'KeyP'},
|
||||
'q': {'keyCode': 81, 'key': 'q', 'code': 'KeyQ'},
|
||||
'r': {'keyCode': 82, 'key': 'r', 'code': 'KeyR'},
|
||||
's': {'keyCode': 83, 'key': 's', 'code': 'KeyS'},
|
||||
't': {'keyCode': 84, 'key': 't', 'code': 'KeyT'},
|
||||
'u': {'keyCode': 85, 'key': 'u', 'code': 'KeyU'},
|
||||
'v': {'keyCode': 86, 'key': 'v', 'code': 'KeyV'},
|
||||
'w': {'keyCode': 87, 'key': 'w', 'code': 'KeyW'},
|
||||
'x': {'keyCode': 88, 'key': 'x', 'code': 'KeyX'},
|
||||
'y': {'keyCode': 89, 'key': 'y', 'code': 'KeyY'},
|
||||
'z': {'keyCode': 90, 'key': 'z', 'code': 'KeyZ'},
|
||||
'\ue03d': {'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft'},
|
||||
'*': {'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3},
|
||||
'+': {'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3},
|
||||
'-': {'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3},
|
||||
'/': {'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3},
|
||||
';': {'keyCode': 186, 'key': ';', 'code': 'Semicolon'},
|
||||
'=': {'keyCode': 187, 'key': '=', 'code': 'Equal'},
|
||||
',': {'keyCode': 188, 'key': ',', 'code': 'Comma'},
|
||||
'.': {'keyCode': 190, 'key': '.', 'code': 'Period'},
|
||||
'`': {'keyCode': 192, 'key': '`', 'code': 'Backquote'},
|
||||
'[': {'keyCode': 219, 'key': '[', 'code': 'BracketLeft'},
|
||||
'\\': {'keyCode': 220, 'key': '\\', 'code': 'Backslash'},
|
||||
']': {'keyCode': 221, 'key': ']', 'code': 'BracketRight'},
|
||||
'\'': {'keyCode': 222, 'key': '\'', 'code': 'Quote'},
|
||||
'Attn': {'keyCode': 246, 'key': 'Attn'},
|
||||
'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'},
|
||||
'ExSel': {'keyCode': 248, 'key': 'ExSel'},
|
||||
'EraseEof': {'keyCode': 249, 'key': 'EraseEof'},
|
||||
'Play': {'keyCode': 250, 'key': 'Play'},
|
||||
'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'},
|
||||
')': {'keyCode': 48, 'key': ')', 'code': 'Digit0'},
|
||||
'!': {'keyCode': 49, 'key': '!', 'code': 'Digit1'},
|
||||
'@': {'keyCode': 50, 'key': '@', 'code': 'Digit2'},
|
||||
'#': {'keyCode': 51, 'key': '#', 'code': 'Digit3'},
|
||||
'$': {'keyCode': 52, 'key': '$', 'code': 'Digit4'},
|
||||
'%': {'keyCode': 53, 'key': '%', 'code': 'Digit5'},
|
||||
'^': {'keyCode': 54, 'key': '^', 'code': 'Digit6'},
|
||||
'&': {'keyCode': 55, 'key': '&', 'code': 'Digit7'},
|
||||
'(': {'keyCode': 57, 'key': '(', 'code': 'Digit9'},
|
||||
'A': {'keyCode': 65, 'key': 'A', 'code': 'KeyA'},
|
||||
'B': {'keyCode': 66, 'key': 'B', 'code': 'KeyB'},
|
||||
'C': {'keyCode': 67, 'key': 'C', 'code': 'KeyC'},
|
||||
'D': {'keyCode': 68, 'key': 'D', 'code': 'KeyD'},
|
||||
'E': {'keyCode': 69, 'key': 'E', 'code': 'KeyE'},
|
||||
'F': {'keyCode': 70, 'key': 'F', 'code': 'KeyF'},
|
||||
'G': {'keyCode': 71, 'key': 'G', 'code': 'KeyG'},
|
||||
'H': {'keyCode': 72, 'key': 'H', 'code': 'KeyH'},
|
||||
'I': {'keyCode': 73, 'key': 'I', 'code': 'KeyI'},
|
||||
'J': {'keyCode': 74, 'key': 'J', 'code': 'KeyJ'},
|
||||
'K': {'keyCode': 75, 'key': 'K', 'code': 'KeyK'},
|
||||
'L': {'keyCode': 76, 'key': 'L', 'code': 'KeyL'},
|
||||
'M': {'keyCode': 77, 'key': 'M', 'code': 'KeyM'},
|
||||
'N': {'keyCode': 78, 'key': 'N', 'code': 'KeyN'},
|
||||
'O': {'keyCode': 79, 'key': 'O', 'code': 'KeyO'},
|
||||
'P': {'keyCode': 80, 'key': 'P', 'code': 'KeyP'},
|
||||
'Q': {'keyCode': 81, 'key': 'Q', 'code': 'KeyQ'},
|
||||
'R': {'keyCode': 82, 'key': 'R', 'code': 'KeyR'},
|
||||
'S': {'keyCode': 83, 'key': 'S', 'code': 'KeyS'},
|
||||
'T': {'keyCode': 84, 'key': 'T', 'code': 'KeyT'},
|
||||
'U': {'keyCode': 85, 'key': 'U', 'code': 'KeyU'},
|
||||
'V': {'keyCode': 86, 'key': 'V', 'code': 'KeyV'},
|
||||
'W': {'keyCode': 87, 'key': 'W', 'code': 'KeyW'},
|
||||
'X': {'keyCode': 88, 'key': 'X', 'code': 'KeyX'},
|
||||
'Y': {'keyCode': 89, 'key': 'Y', 'code': 'KeyY'},
|
||||
'Z': {'keyCode': 90, 'key': 'Z', 'code': 'KeyZ'},
|
||||
':': {'keyCode': 186, 'key': ':', 'code': 'Semicolon'},
|
||||
'<': {'keyCode': 188, 'key': '<', 'code': 'Comma'},
|
||||
'_': {'keyCode': 189, 'key': '_', 'code': 'Minus'},
|
||||
'>': {'keyCode': 190, 'key': '>', 'code': 'Period'},
|
||||
'?': {'keyCode': 191, 'key': '?', 'code': 'Slash'},
|
||||
'~': {'keyCode': 192, 'key': '~', 'code': 'Backquote'},
|
||||
'{': {'keyCode': 219, 'key': '{', 'code': 'BracketLeft'},
|
||||
'|': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},
|
||||
# '\ue026': {'keyCode': 220, 'key': '|', 'code': 'Backslash'},
|
||||
'}': {'keyCode': 221, 'key': '}', 'code': 'BracketRight'},
|
||||
'"': {'keyCode': 222, 'key': '"', 'code': 'Quote'},
|
||||
'Power': {'key': 'Power', 'code': 'Power'},
|
||||
'Eject': {'key': 'Eject', 'code': 'Eject'},
|
||||
}
|
||||
modifierBit = {'\ue00a': 1,
|
||||
'\ue009': 2,
|
||||
@ -405,10 +412,7 @@ def keyDescriptionForString(_modifiers, keyString): # noqa: C901
|
||||
|
||||
def send_key(page, modifier, key):
|
||||
"""发送一个字,在键盘中的字符触发按键,其它直接发送文本"""
|
||||
if key not in keyDefinitions:
|
||||
page.run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)
|
||||
|
||||
else:
|
||||
if key in keyDefinitions:
|
||||
description = keyDescriptionForString(modifier, key)
|
||||
text = description['text']
|
||||
data = {'type': 'keyDown' if text else 'rawKeyDown',
|
||||
@ -427,6 +431,9 @@ def send_key(page, modifier, key):
|
||||
data['type'] = 'keyUp'
|
||||
page.run_cdp('Input.dispatchKeyEvent', **data)
|
||||
|
||||
else:
|
||||
page.run_cdp('Input.insertText', text=key, _ignore=AlertExistsError)
|
||||
|
||||
|
||||
def input_text_or_keys(page, text_or_keys):
|
||||
"""输入文本,也可输入组合键,组合键用tuple形式输入
|
||||
|
@ -12,6 +12,12 @@ from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
class Keys:
|
||||
"""特殊按键"""
|
||||
CTRL_A: tuple
|
||||
CTRL_C: tuple
|
||||
CTRL_X: tuple
|
||||
CTRL_V: tuple
|
||||
CTRL_Z: tuple
|
||||
CTRL_Y: tuple
|
||||
|
||||
NULL: str
|
||||
CANCEL: str
|
||||
|
@ -13,3 +13,4 @@ class Settings(object):
|
||||
raise_when_wait_failed = False
|
||||
singleton_tab_obj = True
|
||||
cdp_timeout = 30
|
||||
auto_handle_alert = None
|
||||
|
@ -10,7 +10,7 @@ from platform import system
|
||||
from shutil import rmtree
|
||||
from tempfile import gettempdir, TemporaryDirectory
|
||||
from threading import Lock
|
||||
from time import perf_counter
|
||||
from time import perf_counter, sleep
|
||||
|
||||
from .._configs.options_manage import OptionsManager
|
||||
from ..errors import (ContextLostError, ElementLostError, CDPError, PageDisconnectedError, NoRectError,
|
||||
@ -107,7 +107,7 @@ def show_or_hide_browser(page, hide=True):
|
||||
except ImportError:
|
||||
raise ImportError('请先安装:pip install pypiwin32')
|
||||
|
||||
pid = page.process_id
|
||||
pid = page._page.process_id
|
||||
if not pid:
|
||||
return None
|
||||
hds = get_hwnds_from_pid(pid, page.title)
|
||||
@ -177,6 +177,7 @@ def wait_until(function, kwargs=None, timeout=10):
|
||||
value = function(**kwargs)
|
||||
if value:
|
||||
return value
|
||||
sleep(.01)
|
||||
raise TimeoutError
|
||||
|
||||
|
||||
@ -197,7 +198,8 @@ def raise_error(result, ignore=None):
|
||||
:return: None
|
||||
"""
|
||||
error = result['error']
|
||||
if error in ('Cannot find context with specified id', 'Inspected target navigated or closed'):
|
||||
if error in ('Cannot find context with specified id', 'Inspected target navigated or closed',
|
||||
'No frame with given id found'):
|
||||
r = ContextLostError()
|
||||
elif error in ('Could not find node with given id', 'Could not find object with given id',
|
||||
'No node with given id found', 'Node with given id does not belong to the document',
|
||||
@ -221,9 +223,8 @@ def raise_error(result, ignore=None):
|
||||
r = RuntimeError(f'你的浏览器可能太旧。\n方法:{result["method"]}\n参数:{result["args"]}')
|
||||
elif result['type'] in ('call_method_error', 'timeout'):
|
||||
from DrissionPage import __version__
|
||||
from time import process_time
|
||||
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \
|
||||
f'版本:{__version__}\n运行时间:{process_time()}\n出现这个错误可能意味着程序有bug,请把错误信息和重现方法' \
|
||||
f'版本:{__version__}\n出现这个错误可能意味着程序有bug,请把错误信息和重现方法' \
|
||||
'告知作者,谢谢。\n报告网站:https://gitee.com/g1879/DrissionPage/issues'
|
||||
r = TimeoutError(txt) if result['type'] == 'timeout' else CDPError(txt)
|
||||
else:
|
||||
|
@ -10,7 +10,8 @@ from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from ..errors import BaseError
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
|
||||
class PortFinder(object):
|
||||
@ -30,7 +31,7 @@ def port_is_using(ip: str, port: Union[str, int]) -> bool: ...
|
||||
def clean_folder(folder_path: Union[str, Path], ignore: Union[tuple, list] = None) -> None: ...
|
||||
|
||||
|
||||
def show_or_hide_browser(page: ChromiumPage, hide: bool = True) -> None: ...
|
||||
def show_or_hide_browser(page: ChromiumBase, hide: bool = True) -> None: ...
|
||||
|
||||
|
||||
def get_browser_progress_id(progress: Union[popen, None], address: str) -> Union[str, None]: ...
|
||||
@ -45,4 +46,4 @@ def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): ..
|
||||
def configs_to_here(file_name: Union[Path, str] = None) -> None: ...
|
||||
|
||||
|
||||
def raise_error(result: dict, ignore=None) -> None: ...
|
||||
def raise_error(result: dict, ignore: BaseError = None) -> None: ...
|
||||
|
@ -7,11 +7,10 @@
|
||||
"""
|
||||
from datetime import datetime
|
||||
from html import unescape
|
||||
from http.cookiejar import Cookie
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from re import sub
|
||||
from urllib.parse import urlparse, urljoin, urlunparse
|
||||
|
||||
from requests.cookies import RequestsCookieJar
|
||||
from tldextract import extract
|
||||
|
||||
|
||||
@ -97,7 +96,7 @@ def location_in_viewport(page, loc_x, loc_y):
|
||||
:param loc_y: 页面绝对坐标y
|
||||
:return: bool
|
||||
"""
|
||||
js = f'''function(){{var x = {loc_x}; var y = {loc_y};
|
||||
js = f'''function(){{let x = {loc_x}; let y = {loc_y};
|
||||
const scrollLeft = document.documentElement.scrollLeft;
|
||||
const scrollTop = document.documentElement.scrollTop;
|
||||
const vWidth = document.documentElement.clientWidth;
|
||||
@ -181,17 +180,14 @@ def cookie_to_dict(cookie):
|
||||
cookie_dict = cookie
|
||||
|
||||
elif isinstance(cookie, str):
|
||||
cookie = cookie.rstrip(';,').split(',' if ',' in cookie else ';')
|
||||
cookie_dict = {}
|
||||
|
||||
for key, attr in enumerate(cookie):
|
||||
attr_val = attr.lstrip().split('=', 1)
|
||||
|
||||
if key == 0:
|
||||
for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'):
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'):
|
||||
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
else:
|
||||
cookie_dict['name'] = attr_val[0]
|
||||
cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
else:
|
||||
cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else ''
|
||||
|
||||
return cookie_dict
|
||||
|
||||
@ -206,17 +202,24 @@ def cookies_to_tuple(cookies):
|
||||
:param cookies: cookies信息,可为CookieJar, list, tuple, str, dict
|
||||
:return: 返回tuple形式的cookies
|
||||
"""
|
||||
if isinstance(cookies, (list, tuple, RequestsCookieJar)):
|
||||
if isinstance(cookies, (list, tuple, CookieJar)):
|
||||
cookies = tuple(cookie_to_dict(cookie) for cookie in cookies)
|
||||
|
||||
elif isinstance(cookies, str):
|
||||
cookies = tuple(cookie_to_dict(c.lstrip()) for c in cookies.rstrip(';,').split(',' if ',' in cookies else ';'))
|
||||
c_dict = {}
|
||||
for attr in cookies.strip().rstrip(';, ').split(',' if ',' in cookies else ';'):
|
||||
attr_val = attr.strip().split('=', 1)
|
||||
c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True
|
||||
cookies = _dict_cookies_to_tuple(c_dict)
|
||||
|
||||
elif isinstance(cookies, dict):
|
||||
cookies = tuple({'name': cookie, 'value': cookies[cookie]} for cookie in cookies)
|
||||
cookies = _dict_cookies_to_tuple(cookies)
|
||||
|
||||
elif isinstance(cookies, Cookie):
|
||||
cookies = (cookie_to_dict(cookies),)
|
||||
|
||||
else:
|
||||
raise TypeError('cookies参数必须为RequestsCookieJar、list、tuple、str或dict类型。')
|
||||
raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。')
|
||||
|
||||
return cookies
|
||||
|
||||
@ -227,8 +230,7 @@ def set_session_cookies(session, cookies):
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
cookies = cookies_to_tuple(cookies)
|
||||
for cookie in cookies:
|
||||
for cookie in cookies_to_tuple(cookies):
|
||||
if cookie['value'] is None:
|
||||
cookie['value'] = ''
|
||||
|
||||
@ -276,16 +278,20 @@ def set_browser_cookies(page, cookies):
|
||||
cookie['value'] = ''
|
||||
elif not isinstance(cookie['value'], str):
|
||||
cookie['value'] = str(cookie['value'])
|
||||
if cookie['name'].startswith('__Secure-'):
|
||||
cookie['secure'] = True
|
||||
|
||||
if cookie['name'].startswith('__Host-'):
|
||||
cookie['path'] = '/'
|
||||
cookie['secure'] = True
|
||||
cookie['url'] = page.url
|
||||
if not page.url.startswith('http'):
|
||||
cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1)
|
||||
else:
|
||||
cookie['url'] = page.url
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
continue # 不用设置域名,可退出
|
||||
|
||||
if cookie['name'].startswith('__Secure-'):
|
||||
cookie['secure'] = True
|
||||
|
||||
if cookie.get('domain', None):
|
||||
try:
|
||||
page.run_cdp_loaded('Network.setCookie', **cookie)
|
||||
@ -294,7 +300,10 @@ def set_browser_cookies(page, cookies):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ex_url = extract(page._browser_url)
|
||||
url = page._browser_url
|
||||
if not url.startswith('http'):
|
||||
raise RuntimeError(f'未设置域名,请设置cookie的domain参数或先访问一个网站。{cookie}')
|
||||
ex_url = extract(url)
|
||||
d_list = ex_url.subdomain.split('.')
|
||||
d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain)
|
||||
|
||||
@ -342,10 +351,10 @@ def get_blob(page, url, as_bytes=True):
|
||||
js = """
|
||||
function fetchData(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest();
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'blob';
|
||||
xhr.onload = function() {
|
||||
var reader = new FileReader();
|
||||
let reader = new FileReader();
|
||||
reader.onloadend = function(){resolve(reader.result);}
|
||||
reader.readAsDataURL(xhr.response);
|
||||
};
|
||||
@ -370,6 +379,7 @@ def tree(ele_or_page):
|
||||
:param ele_or_page: 页面或元素对象
|
||||
:return: None
|
||||
"""
|
||||
|
||||
def _tree(obj, last_one=True, body=''):
|
||||
list_ele = obj.children()
|
||||
length = len(list_ele)
|
||||
@ -394,3 +404,30 @@ def tree(ele_or_page):
|
||||
attrs = ' '.join([f"{k}='{v}'" for k, v in ele.attrs.items()])
|
||||
print(f'<{ele.tag} {attrs}>'.replace('\n', ' '))
|
||||
_tree(ele)
|
||||
|
||||
|
||||
def format_headers(txt):
|
||||
"""从浏览器复制的文本生成dict格式headers,文本用换行分隔
|
||||
:param txt: 从浏览器复制的原始文本格式headers
|
||||
:return: dict格式headers
|
||||
"""
|
||||
if not isinstance(txt, str):
|
||||
return txt
|
||||
headers = {}
|
||||
for header in txt.split('\n'):
|
||||
if header:
|
||||
name, value = header.split(': ', maxsplit=1)
|
||||
headers[name] = value
|
||||
return headers
|
||||
|
||||
|
||||
def _dict_cookies_to_tuple(cookies: dict):
|
||||
"""把dict形式的cookies转换为tuple形式
|
||||
:param cookies: 单个或多个cookies,单个时包含'name'和'value'
|
||||
:return: 多个dict格式cookies组成的列表
|
||||
"""
|
||||
if 'name' in cookies and 'value' in cookies: # 单个cookie
|
||||
return (cookies,)
|
||||
keys = ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry')
|
||||
template = {k: v for k, v in cookies.items() if k in keys}
|
||||
return tuple(dict(**{'name': k, 'value': v}, **template) for k, v in cookies.items() if k not in keys)
|
||||
|
@ -37,7 +37,7 @@ def is_js_func(func: str) -> bool: ...
|
||||
def cookie_to_dict(cookie: Union[Cookie, str, dict]) -> dict: ...
|
||||
|
||||
|
||||
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> tuple: ...
|
||||
def cookies_to_tuple(cookies: Union[RequestsCookieJar, list, tuple, str, dict, Cookie]) -> tuple: ...
|
||||
|
||||
|
||||
def set_session_cookies(session: Session, cookies: Union[RequestsCookieJar, list, tuple, str, dict]) -> None: ...
|
||||
@ -52,4 +52,7 @@ def is_cookie_in_driver(page: ChromiumBase, cookie: dict) -> bool: ...
|
||||
def get_blob(page: ChromiumBase, url: str, as_bytes: bool = True) -> bytes: ...
|
||||
|
||||
|
||||
def tree(ele_or_page:BaseParser) -> None: ...
|
||||
def tree(ele_or_page: BaseParser) -> None: ...
|
||||
|
||||
|
||||
def format_headers(txt: str) -> dict: ...
|
||||
|
@ -30,7 +30,7 @@ from .._units.scroller import PageScroller
|
||||
from .._units.setter import ChromiumBaseSetter
|
||||
from .._units.states import PageStates
|
||||
from .._units.waiter import BaseWaiter
|
||||
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementNotFoundError
|
||||
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementNotFoundError, ElementLostError
|
||||
|
||||
__ERROR__ = 'error'
|
||||
|
||||
@ -156,21 +156,25 @@ class ChromiumBase(BasePage):
|
||||
if self._is_reading:
|
||||
return
|
||||
self._is_reading = True
|
||||
timeout = timeout if timeout >= .5 else .5
|
||||
timeout = max(timeout, 2)
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
try:
|
||||
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
timeout = end_time - perf_counter()
|
||||
timeout = .5 if timeout <= 0 else timeout
|
||||
timeout = 1 if timeout <= 1 else timeout
|
||||
self._root_id = self.run_cdp('DOM.resolveNode', backendNodeId=b_id,
|
||||
_timeout=timeout)['object']['objectId']
|
||||
result = True
|
||||
break
|
||||
|
||||
except PageDisconnectedError:
|
||||
result = False
|
||||
break
|
||||
except:
|
||||
timeout = end_time - perf_counter()
|
||||
timeout = .5 if timeout <= 0 else timeout
|
||||
sleep(.1)
|
||||
|
||||
else:
|
||||
result = False
|
||||
@ -474,7 +478,7 @@ class ChromiumBase(BasePage):
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
"""返回cookies信息
|
||||
:param as_dict: 为True时返回由{name: value}键值对组成的dict,为True时返回list且all_info无效
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,为False时只返回name、value、domain
|
||||
:return: cookies信息
|
||||
@ -667,32 +671,62 @@ class ChromiumBase(BasePage):
|
||||
return
|
||||
ele = self._ele(loc_or_ele, raise_err=False)
|
||||
if ele:
|
||||
self.run_cdp('DOM.removeNode', nodeId=ele._node_id)
|
||||
self.run_cdp('DOM.removeNode', nodeId=ele._node_id, _ignore=ElementLostError)
|
||||
|
||||
def add_ele(self, outerHTML, insert_to=None, before=None):
|
||||
def add_ele(self, html_or_info, insert_to=None, before=None):
|
||||
"""新建一个元素
|
||||
:param outerHTML: 新元素的html文本
|
||||
:param insert_to: 插入到哪个元素中,可接收元素对象和定位符,为None添加到body
|
||||
:param html_or_info: 新元素的html文本或信息。信息格式为:(tag, {attr1: value, ...})
|
||||
:param insert_to: 插入到哪个元素中,可接收元素对象和定位符,为None且为html添加到body,不为html不插入
|
||||
:param before: 在哪个子节点前面插入,可接收对象和定位符,为None插入到父元素末尾
|
||||
:return: 元素对象
|
||||
"""
|
||||
insert_to = self.ele(insert_to) if insert_to else self.ele('t:body')
|
||||
args = [outerHTML, insert_to]
|
||||
if before:
|
||||
args.append(self.ele(before))
|
||||
js = '''
|
||||
ele = document.createElement(null);
|
||||
arguments[1].insertBefore(ele, arguments[2]);
|
||||
ele.outerHTML = arguments[0];
|
||||
return arguments[2].previousElementSibling;
|
||||
'''
|
||||
if isinstance(html_or_info, str):
|
||||
insert_to = self.ele(insert_to) if insert_to else self.ele('t:body')
|
||||
args = [html_or_info, insert_to]
|
||||
if before:
|
||||
args.append(self.ele(before))
|
||||
js = '''
|
||||
ele = document.createElement(null);
|
||||
arguments[1].insertBefore(ele, arguments[2]);
|
||||
ele.outerHTML = arguments[0];
|
||||
return arguments[2].previousElementSibling;
|
||||
'''
|
||||
else:
|
||||
js = '''
|
||||
ele = document.createElement(null);
|
||||
arguments[1].appendChild(ele);
|
||||
ele.outerHTML = arguments[0];
|
||||
return arguments[1].lastElementChild;
|
||||
'''
|
||||
|
||||
elif isinstance(html_or_info, tuple):
|
||||
args = [html_or_info[0], html_or_info[1]]
|
||||
txt = ''
|
||||
if insert_to:
|
||||
args.append(self.ele(insert_to))
|
||||
if before:
|
||||
args.append(self.ele(before))
|
||||
txt = '''
|
||||
arguments[2].insertBefore(ele, arguments[3]);
|
||||
'''
|
||||
else:
|
||||
txt = '''
|
||||
arguments[2].appendChild(ele);
|
||||
'''
|
||||
js = f'''
|
||||
ele = document.createElement(arguments[0]);
|
||||
for(let k in arguments[1]){{
|
||||
if(k=="innerHTML"){{ele.innerHTML=arguments[1][k]}}
|
||||
else if(k=="innerText"){{ele.innerText=arguments[1][k]}}
|
||||
else{{ele.setAttribute(k, arguments[1][k]);}}
|
||||
}}
|
||||
{txt}
|
||||
return ele;
|
||||
'''
|
||||
|
||||
else:
|
||||
js = '''
|
||||
ele = document.createElement(null);
|
||||
arguments[1].appendChild(ele);
|
||||
ele.outerHTML = arguments[0];
|
||||
return arguments[1].lastElementChild;
|
||||
'''
|
||||
raise TypeError('html_or_info参数必须是html文本或tuple,tuple格式为(tag, {name: value})。')
|
||||
|
||||
ele = self.run_js(js, *args)
|
||||
return ele
|
||||
|
||||
@ -753,42 +787,16 @@ class ChromiumBase(BasePage):
|
||||
:param item: 要获取的项,不设置则返回全部
|
||||
:return: sessionStorage一个或所有项内容
|
||||
"""
|
||||
if item:
|
||||
js = f'sessionStorage.getItem("{item}");'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
else:
|
||||
js = '''
|
||||
var dp_ls_len = sessionStorage.length;
|
||||
var dp_ls_arr = new Array();
|
||||
for(var i = 0; i < dp_ls_len; i++) {
|
||||
var getKey = sessionStorage.key(i);
|
||||
var getVal = sessionStorage.getItem(getKey);
|
||||
dp_ls_arr[i] = {'key': getKey, 'val': getVal}
|
||||
}
|
||||
return dp_ls_arr;
|
||||
'''
|
||||
return {i['key']: i['val'] for i in self.run_js_loaded(js)}
|
||||
js = f'sessionStorage.getItem("{item}")' if item else 'sessionStorage'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
|
||||
def local_storage(self, item=None):
|
||||
"""返回localStorage信息,不设置item则获取全部
|
||||
:param item: 要获取的项目,不设置则返回全部
|
||||
:return: localStorage一个或所有项内容
|
||||
"""
|
||||
if item:
|
||||
js = f'localStorage.getItem("{item}");'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
else:
|
||||
js = '''
|
||||
var dp_ls_len = localStorage.length;
|
||||
var dp_ls_arr = new Array();
|
||||
for(var i = 0; i < dp_ls_len; i++) {
|
||||
var getKey = localStorage.key(i);
|
||||
var getVal = localStorage.getItem(getKey);
|
||||
dp_ls_arr[i] = {'key': getKey, 'val': getVal}
|
||||
}
|
||||
return dp_ls_arr;
|
||||
'''
|
||||
return {i['key']: i['val'] for i in self.run_js_loaded(js)}
|
||||
js = f'localStorage.getItem("{item}")' if item else 'localStorage'
|
||||
return self.run_js_loaded(js, as_expr=True)
|
||||
|
||||
def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None,
|
||||
full_page=False, left_top=None, right_bottom=None):
|
||||
@ -867,6 +875,8 @@ class ChromiumBase(BasePage):
|
||||
sleep(wait)
|
||||
self.browser.reconnect()
|
||||
self._driver = self.browser._get_driver(t_id, self)
|
||||
self._driver_init(t_id)
|
||||
self._get_document()
|
||||
|
||||
def handle_alert(self, accept=True, send=None, timeout=None, next_one=False):
|
||||
"""处理提示框,可以自动等待提示框出现
|
||||
@ -917,7 +927,7 @@ class ChromiumBase(BasePage):
|
||||
"""alert出现时触发的方法"""
|
||||
self._alert.activated = True
|
||||
self._alert.text = kwargs['message']
|
||||
self._alert.type = kwargs['message']
|
||||
self._alert.type = kwargs['type']
|
||||
self._alert.defaultPrompt = kwargs.get('defaultPrompt', None)
|
||||
self._alert.response_accept = None
|
||||
self._alert.response_text = None
|
||||
@ -925,6 +935,8 @@ class ChromiumBase(BasePage):
|
||||
|
||||
if self._alert.auto is not None:
|
||||
self._handle_alert(self._alert.auto)
|
||||
elif Settings.auto_handle_alert is not None:
|
||||
self._handle_alert(Settings.auto_handle_alert)
|
||||
elif self._alert.handle_next is not None:
|
||||
self._handle_alert(self._alert.handle_next, self._alert.next_text)
|
||||
self._alert.handle_next = None
|
||||
@ -1206,6 +1218,7 @@ def close_privacy_dialog(page, tid):
|
||||
break
|
||||
except KeyError:
|
||||
pass
|
||||
sleep(.05)
|
||||
driver.run('DOM.discardSearchResults', searchId=sid)
|
||||
r = driver.run('DOM.resolveNode', backendNodeId=r)['object']['objectId']
|
||||
r = driver.run('Runtime.callFunctionOn', objectId=r,
|
||||
@ -1231,7 +1244,7 @@ def get_mhtml(page, path=None, name=None):
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
name = make_valid_name(name or page.title)
|
||||
with open(f'{path}{sep}{name}.mhtml', 'w', encoding='utf-8') as f:
|
||||
f.write(r)
|
||||
f.write(r.replace('\r\n', '\n'))
|
||||
return r
|
||||
|
||||
|
||||
|
@ -8,8 +8,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple, List, Any, Optional, Literal
|
||||
|
||||
from .chromium_tab import ChromiumTab, WebPageTab
|
||||
from .web_page import WebPage
|
||||
from .chromium_tab import ChromiumTab
|
||||
from .._base.base import BasePage
|
||||
from .._base.browser import Browser
|
||||
from .._base.driver import Driver
|
||||
@ -217,7 +216,7 @@ class ChromiumBase(BasePage):
|
||||
def remove_ele(self, loc_or_ele: Union[ChromiumElement, ChromiumFrame, str, Tuple[str, str]]) -> None: ...
|
||||
|
||||
def add_ele(self,
|
||||
outerHTML: str,
|
||||
html_or_info: Union[str, Tuple[str, dict]],
|
||||
insert_to: Union[ChromiumElement, str, Tuple[str, str], None] = None,
|
||||
before: Union[ChromiumElement, str, Tuple[str, str], None] = None) -> ChromiumElement: ...
|
||||
|
||||
|
@ -59,7 +59,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
self._rect = None
|
||||
self._type = 'ChromiumFrame'
|
||||
end_time = perf_counter() + 2
|
||||
while perf_counter() < end_time:
|
||||
while perf_counter() < end_time: # todo: 优化
|
||||
if self.url not in (None, 'about:blank'):
|
||||
break
|
||||
sleep(.1)
|
||||
@ -119,6 +119,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
node = self._target_page.run_cdp('DOM.describeNode', backendNodeId=self._frame_ele._backend_id)['node']
|
||||
if 'frameId' in node:
|
||||
break
|
||||
sleep(.05)
|
||||
|
||||
else:
|
||||
return
|
||||
@ -164,7 +165,7 @@ class ChromiumFrame(ChromiumBase):
|
||||
self.doc_ele = ChromiumElement(self._target_page, backend_id=node['contentDocument']['backendNodeId'])
|
||||
|
||||
else:
|
||||
timeout = timeout if timeout >= .5 else .5
|
||||
timeout = max(timeout, 2)
|
||||
b_id = self.run_cdp('DOM.getDocument', _timeout=timeout)['root']['backendNodeId']
|
||||
self.doc_ele = ChromiumElement(self, backend_id=b_id)
|
||||
|
||||
@ -176,7 +177,6 @@ class ChromiumFrame(ChromiumBase):
|
||||
return True
|
||||
|
||||
except:
|
||||
raise
|
||||
return False
|
||||
|
||||
finally:
|
||||
|
@ -14,6 +14,7 @@ from requests import get
|
||||
from .._base.browser import Browser
|
||||
from .._configs.chromium_options import ChromiumOptions
|
||||
from .._functions.browser import connect_browser
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import PortFinder
|
||||
from .._pages.chromium_base import ChromiumBase, get_mhtml, get_pdf, Timeout
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
@ -70,8 +71,9 @@ class ChromiumPage(ChromiumBase):
|
||||
def _run_browser(self):
|
||||
"""连接浏览器"""
|
||||
self._browser = Browser(self._chromium_options.address, self._browser_id, self)
|
||||
if (self._is_exist and self._chromium_options._headless is False and
|
||||
'headless' in self._browser.run_cdp('Browser.getVersion')['userAgent'].lower()):
|
||||
r = self._browser.run_cdp('Browser.getVersion')
|
||||
self._browser_version = r['product']
|
||||
if self._is_exist and self._chromium_options._headless is False and 'headless' in r['userAgent'].lower():
|
||||
self._browser.quit(3)
|
||||
connect_browser(self._chromium_options)
|
||||
ws = get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'})
|
||||
@ -124,20 +126,26 @@ class ChromiumPage(ChromiumBase):
|
||||
return self.browser.tabs_count
|
||||
|
||||
@property
|
||||
def tabs(self):
|
||||
def tab_ids(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
return self.browser.tabs
|
||||
return self.browser.tab_ids
|
||||
|
||||
@property
|
||||
def latest_tab(self):
|
||||
"""返回最新的标签页id,最新标签页指最后创建或最后被激活的"""
|
||||
return self.tabs[0]
|
||||
"""返回最新的标签页,最新标签页指最后创建或最后被激活的
|
||||
当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id"""
|
||||
return self.get_tab(self.tab_ids[0], as_id=not Settings.singleton_tab_obj)
|
||||
|
||||
@property
|
||||
def process_id(self):
|
||||
"""返回浏览器进程id"""
|
||||
return self.browser.process_id
|
||||
|
||||
@property
|
||||
def browser_version(self):
|
||||
"""返回所控制的浏览器版本号"""
|
||||
return self._browser_version
|
||||
|
||||
def save(self, path=None, name=None, as_pdf=False, **kwargs):
|
||||
"""把当前页面保存为文件,如果path和name参数都为None,只返回文本
|
||||
:param path: 保存路径,为None且name不为None时保存在当前路径
|
||||
@ -148,32 +156,56 @@ class ChromiumPage(ChromiumBase):
|
||||
"""
|
||||
return get_pdf(self, path, name, kwargs) if as_pdf else get_mhtml(self, path, name)
|
||||
|
||||
def get_tab(self, id_or_num=None):
|
||||
"""获取一个标签页对象
|
||||
:param id_or_num: 要获取的标签页id或序号,为None时获取当前tab,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:return: 标签页对象
|
||||
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
:param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象
|
||||
"""
|
||||
with self._lock:
|
||||
if id_or_num is not None:
|
||||
if isinstance(id_or_num, str):
|
||||
return ChromiumTab(self, id_or_num)
|
||||
id_or_num = id_or_num
|
||||
elif isinstance(id_or_num, int):
|
||||
return ChromiumTab(self, self.tabs[id_or_num - 1 if id_or_num > 0 else id_or_num])
|
||||
elif id_or_num is None:
|
||||
return ChromiumTab(self, self.tab_id)
|
||||
id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]
|
||||
elif isinstance(id_or_num, ChromiumTab):
|
||||
return id_or_num
|
||||
else:
|
||||
raise TypeError(f'id_or_num需传入tab id或序号,非{id_or_num}。')
|
||||
if as_id:
|
||||
return id_or_num.tab_id
|
||||
elif Settings.singleton_tab_obj:
|
||||
return id_or_num
|
||||
else:
|
||||
return self.get_tab(id_or_num.tab_id)
|
||||
|
||||
def find_tabs(self, title=None, url=None, tab_type=None, single=True):
|
||||
"""查找符合条件的tab,返回它们的id组成的列表
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param single: 是否返回首个结果的id,为False返回所有信息
|
||||
:return: tab id或tab列表
|
||||
elif title == url == tab_type is None:
|
||||
id_or_num = self.tab_id
|
||||
|
||||
else:
|
||||
id_or_num = self._browser.find_tabs(title, url, tab_type)
|
||||
if id_or_num:
|
||||
id_or_num = id_or_num[0]['id']
|
||||
else:
|
||||
return None
|
||||
|
||||
if as_id:
|
||||
return id_or_num
|
||||
|
||||
with self._lock:
|
||||
return ChromiumTab(self, id_or_num)
|
||||
|
||||
def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象组成的列表
|
||||
"""
|
||||
return self._browser.find_tabs(title, url, tab_type, single)
|
||||
if as_id:
|
||||
return [tab['id'] for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
with self._lock:
|
||||
return [ChromiumTab(self, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
|
||||
def new_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
@ -183,32 +215,11 @@ class ChromiumPage(ChromiumBase):
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
tab = ChromiumTab(self, tab_id=self._new_tab(new_window, background, new_context))
|
||||
tab = ChromiumTab(self, tab_id=self.browser.new_tab(new_window, background, new_context))
|
||||
if url:
|
||||
tab.get(url)
|
||||
return tab
|
||||
|
||||
def _new_tab(self, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
:param new_window: 是否在新窗口打开标签页
|
||||
:param background: 是否不激活新标签页,如new_window为True则无效
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
bid = None
|
||||
if new_context:
|
||||
bid = self.browser.run_cdp('Target.createBrowserContext')['browserContextId']
|
||||
|
||||
kwargs = {'url': ''}
|
||||
if new_window:
|
||||
kwargs['newWindow'] = True
|
||||
if background:
|
||||
kwargs['background'] = True
|
||||
if bid:
|
||||
kwargs['browserContextId'] = bid
|
||||
|
||||
return self.browser.run_cdp('Target.createTarget', **kwargs)['targetId']
|
||||
|
||||
def close(self):
|
||||
"""关闭Page管理的标签页"""
|
||||
self.close_tabs(self.tab_id)
|
||||
@ -219,7 +230,7 @@ class ChromiumPage(ChromiumBase):
|
||||
:param others: 是否关闭指定标签页之外的
|
||||
:return: None
|
||||
"""
|
||||
all_tabs = set(self.tabs)
|
||||
all_tabs = set(self.tab_ids)
|
||||
if isinstance(tabs_or_ids, str):
|
||||
tabs = {tabs_or_ids}
|
||||
elif isinstance(tabs_or_ids, ChromiumTab):
|
||||
@ -269,6 +280,22 @@ class ChromiumPage(ChromiumBase):
|
||||
"""
|
||||
self.close_tabs(tabs_or_ids, True)
|
||||
|
||||
@property
|
||||
def tabs(self):
|
||||
"""返回所有标签页id组成的列表"""
|
||||
return self.browser.tab_ids
|
||||
|
||||
def find_tabs(self, title=None, url=None, tab_type=None, single=True):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
:param title: 要匹配title的文本
|
||||
:param url: 要匹配url的文本
|
||||
:param tab_type: tab类型,可用列表输入多个
|
||||
:param single: 是否返回首个结果的id,为False返回所有信息
|
||||
:return: tab id或tab列表
|
||||
"""
|
||||
r = self._browser.find_tabs(title, url, tab_type)
|
||||
return r[0]['id'] if r and single else r
|
||||
|
||||
|
||||
def handle_options(addr_or_opts):
|
||||
"""设置浏览器启动属性
|
||||
|
@ -37,6 +37,7 @@ class ChromiumPage(ChromiumBase):
|
||||
self._rect: Optional[TabRect] = ...
|
||||
self._is_exist: bool = ...
|
||||
self._lock: Lock = ...
|
||||
self._browser_version: str = ...
|
||||
|
||||
def _handle_options(self, addr_or_opts: Union[str, ChromiumOptions]) -> str: ...
|
||||
|
||||
@ -51,17 +52,20 @@ class ChromiumPage(ChromiumBase):
|
||||
def tabs_count(self) -> int: ...
|
||||
|
||||
@property
|
||||
def tabs(self) -> List[str]: ...
|
||||
def tab_ids(self) -> List[str]: ...
|
||||
|
||||
@property
|
||||
def wait(self) -> PageWaiter: ...
|
||||
|
||||
@property
|
||||
def latest_tab(self) -> str: ...
|
||||
def latest_tab(self) -> Union[ChromiumTab, ChromiumPage, str]: ...
|
||||
|
||||
@property
|
||||
def process_id(self) -> Optional[int]: ...
|
||||
|
||||
@property
|
||||
def browser_version(self) -> str: ...
|
||||
|
||||
@property
|
||||
def set(self) -> ChromiumPageSetter: ...
|
||||
|
||||
@ -86,16 +90,22 @@ class ChromiumPage(ChromiumBase):
|
||||
generateTaggedPDF: bool = ...,
|
||||
generateDocumentOutline: bool = ...) -> Union[bytes, str]: ...
|
||||
|
||||
def get_tab(self, tab_id: Union[str, ChromiumTab, int] = None) -> ChromiumTab: ...
|
||||
def get_tab(self,
|
||||
id_or_num: Union[str, ChromiumTab, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[ChromiumTab, str, None]: ...
|
||||
|
||||
def find_tabs(self, title: str = None, url: str = None,
|
||||
tab_type: Union[str, list, tuple] = None, single: bool = True) -> Union[str, List[str]]: ...
|
||||
def get_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[List[ChromiumTab], List[str]]: ...
|
||||
|
||||
def new_tab(self, url: str = None, new_window: bool = False, background: bool = False,
|
||||
new_context: bool = False) -> ChromiumTab: ...
|
||||
|
||||
def _new_tab(self, new_window: bool = False, background: bool = False, new_context: bool = False) -> str: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
def close_tabs(self, tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],
|
||||
|
@ -106,6 +106,9 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
:param page: WebPage对象
|
||||
:param tab_id: 要控制的标签页id
|
||||
"""
|
||||
if Settings.singleton_tab_obj and hasattr(self, '_created'):
|
||||
return
|
||||
|
||||
self._mode = 'd'
|
||||
self._has_driver = True
|
||||
self._has_session = True
|
||||
@ -363,7 +366,7 @@ class WebPageTab(SessionPage, ChromiumTab, BasePage):
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 是否以字典方式返回
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
|
@ -5,6 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from copy import copy
|
||||
from pathlib import Path
|
||||
from re import search, DOTALL
|
||||
from time import sleep
|
||||
@ -17,7 +18,7 @@ from tldextract import extract
|
||||
from .._base.base import BasePage
|
||||
from .._configs.session_options import SessionOptions
|
||||
from .._elements.session_element import SessionElement, make_session_ele
|
||||
from .._functions.web import cookie_to_dict
|
||||
from .._functions.web import cookie_to_dict, format_headers
|
||||
from .._units.setter import SessionPageSetter
|
||||
|
||||
|
||||
@ -56,9 +57,9 @@ class SessionPage(BasePage):
|
||||
|
||||
elif isinstance(session_or_options, Session):
|
||||
self._session_options = SessionOptions()
|
||||
self._headers = session_or_options.headers
|
||||
session_or_options.headers = None
|
||||
self._session = session_or_options
|
||||
self._session = copy(session_or_options)
|
||||
self._headers = self._session.headers
|
||||
self._session.headers = None
|
||||
|
||||
def _s_set_runtime_settings(self):
|
||||
"""设置运行时用到的属性"""
|
||||
@ -200,7 +201,7 @@ class SessionPage(BasePage):
|
||||
:param index: 获取第几个,从1开始,可传入负数获取倒数第几个
|
||||
:return: SessionElement对象或属性、文本
|
||||
"""
|
||||
return make_session_ele(self.html) if locator is None else self._ele(locator, index=index, method='s_ele()')
|
||||
return make_session_ele(self) if locator is None else self._ele(locator, index=index, method='s_ele()')
|
||||
|
||||
def s_eles(self, locator):
|
||||
"""返回页面中符合条件的所有元素、属性或节点文本
|
||||
@ -221,7 +222,7 @@ class SessionPage(BasePage):
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 是否以字典方式返回,False则以list返回
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
@ -293,7 +294,7 @@ class SessionPage(BasePage):
|
||||
if 'headers' not in kwargs:
|
||||
kwargs['headers'] = {}
|
||||
else:
|
||||
kwargs['headers'] = CaseInsensitiveDict(kwargs['headers'])
|
||||
kwargs['headers'] = CaseInsensitiveDict(format_headers(kwargs['headers']))
|
||||
|
||||
# 设置referer和host值
|
||||
parsed_url = urlparse(url)
|
||||
|
@ -79,7 +79,7 @@ class SessionPage(BasePage):
|
||||
params: dict | None = ...,
|
||||
data: Union[dict, str, None] = ...,
|
||||
json: Union[dict, str, None] = ...,
|
||||
headers: dict | None = ...,
|
||||
headers: Union[dict, str, None] = ...,
|
||||
cookies: Any | None = ...,
|
||||
files: Any | None = ...,
|
||||
auth: Any | None = ...,
|
||||
@ -140,7 +140,7 @@ class SessionPage(BasePage):
|
||||
timeout: float | None = ...,
|
||||
params: dict | None = ...,
|
||||
json: Union[dict, str, None] = ...,
|
||||
headers: dict | None = ...,
|
||||
headers: Union[dict, str, None] = ...,
|
||||
cookies: Any | None = ...,
|
||||
files: Any | None = ...,
|
||||
auth: Any | None = ...,
|
||||
|
@ -31,7 +31,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
"""初始化函数
|
||||
:param mode: 'd' 或 's',即driver模式和session模式
|
||||
:param timeout: 超时时间(秒),d模式时为寻找元素时间,s模式时为连接时间,默认10秒
|
||||
:param chromium_options: Driver对象,只使用s模式时应传入False
|
||||
:param chromium_options: ChromiumOptions对象,只使用s模式时应传入False
|
||||
:param session_or_options: Session对象或SessionOptions对象,只使用d模式时应传入False
|
||||
"""
|
||||
if hasattr(self, '_created'):
|
||||
@ -298,7 +298,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
|
||||
def cookies(self, as_dict=False, all_domains=False, all_info=False):
|
||||
"""返回cookies
|
||||
:param as_dict: 是否以字典方式返回,False以list形式返回
|
||||
:param as_dict: 为True时以dict格式返回,为False时返回list且all_info无效
|
||||
:param all_domains: 是否返回所有域的cookies
|
||||
:param all_info: 是否返回所有信息,False则只返回name、value、domain
|
||||
:return: cookies信息
|
||||
@ -308,21 +308,51 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
elif self._mode == 'd':
|
||||
return super(SessionPage, self).cookies(as_dict, all_domains, all_info)
|
||||
|
||||
def get_tab(self, id_or_num=None):
|
||||
"""获取一个标签页对象
|
||||
:param id_or_num: 要获取的标签页id或序号,为None时获取当前tab,序号不是视觉排列顺序,而是激活顺序
|
||||
:return: 标签页对象
|
||||
def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""获取一个标签页对象,id_or_num不为None时,后面几个参数无效
|
||||
:param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: WebPageTab对象
|
||||
"""
|
||||
if isinstance(id_or_num, str):
|
||||
return WebPageTab(self, id_or_num)
|
||||
elif isinstance(id_or_num, int):
|
||||
return WebPageTab(self, self.tabs[id_or_num])
|
||||
elif id_or_num is None:
|
||||
return WebPageTab(self, self.tab_id)
|
||||
elif isinstance(id_or_num, WebPageTab):
|
||||
return id_or_num
|
||||
if id_or_num is not None:
|
||||
if isinstance(id_or_num, str):
|
||||
id_or_num = id_or_num
|
||||
elif isinstance(id_or_num, int):
|
||||
id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num]
|
||||
elif isinstance(id_or_num, WebPageTab):
|
||||
return id_or_num.tab_id if as_id else id_or_num
|
||||
|
||||
elif title == url == tab_type is None:
|
||||
id_or_num = self.tab_id
|
||||
|
||||
else:
|
||||
raise TypeError(f'id_or_num需传入tab id或序号,非{id_or_num}。')
|
||||
id_or_num = self._browser.find_tabs(title, url, tab_type)
|
||||
if id_or_num:
|
||||
id_or_num = id_or_num[0]['id']
|
||||
else:
|
||||
return None
|
||||
|
||||
if as_id:
|
||||
return id_or_num
|
||||
|
||||
with self._lock:
|
||||
return WebPageTab(self, id_or_num)
|
||||
|
||||
def get_tabs(self, title=None, url=None, tab_type='page', as_id=False):
|
||||
"""查找符合条件的tab,返回它们组成的列表
|
||||
:param title: 要匹配title的文本,模糊匹配,为None则匹配所有
|
||||
:param url: 要匹配url的文本,模糊匹配,为None则匹配所有
|
||||
:param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有
|
||||
:param as_id: 是否返回标签页id而不是标签页对象
|
||||
:return: ChromiumTab对象组成的列表
|
||||
"""
|
||||
if as_id:
|
||||
return [tab['id'] for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
with self._lock:
|
||||
return [WebPageTab(self, tab['id']) for tab in self._browser.find_tabs(title, url, tab_type)]
|
||||
|
||||
def new_tab(self, url=None, new_window=False, background=False, new_context=False):
|
||||
"""新建一个标签页
|
||||
@ -332,7 +362,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
:param new_context: 是否创建新的上下文
|
||||
:return: 新标签页对象
|
||||
"""
|
||||
tab = WebPageTab(self, tab_id=self._new_tab(new_window, background, new_context))
|
||||
tab = WebPageTab(self, tab_id=self.browser.new_tab(new_window, background, new_context))
|
||||
if url:
|
||||
tab.get(url)
|
||||
return tab
|
||||
|
@ -90,7 +90,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
params: dict | None = ...,
|
||||
data: Union[dict, str, None] = ...,
|
||||
json: Union[dict, str, None] = ...,
|
||||
headers: dict | None = ...,
|
||||
headers: Union[dict, str, None] = ...,
|
||||
cookies: Any | None = ...,
|
||||
files: Any | None = ...,
|
||||
auth: Any | None = ...,
|
||||
@ -127,7 +127,18 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
all_domains: bool = False,
|
||||
all_info: bool = False) -> Union[dict, list]: ...
|
||||
|
||||
def get_tab(self, id_or_num: Union[str, WebPageTab, int] = None) -> WebPageTab: ...
|
||||
def get_tab(self,
|
||||
id_or_num: Union[str, WebPageTab, int] = None,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[WebPageTab, str, None]: ...
|
||||
|
||||
def get_tabs(self,
|
||||
title: str = None,
|
||||
url: str = None,
|
||||
tab_type: Union[str, list, tuple] = 'page',
|
||||
as_id: bool = False) -> Union[List[WebPageTab], List[str]]: ...
|
||||
|
||||
def new_tab(self,
|
||||
url: str = None,
|
||||
@ -151,7 +162,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
timeout: float | None = ...,
|
||||
params: dict | None = ...,
|
||||
json: Union[dict, str, None] = ...,
|
||||
headers: dict | None = ...,
|
||||
headers: Union[dict, str, None] = ...,
|
||||
cookies: Any | None = ...,
|
||||
files: Any | None = ...,
|
||||
auth: Any | None = ...,
|
||||
@ -162,6 +173,9 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
|
||||
verify: Any | None = ...,
|
||||
cert: Any | None = ...) -> Union[bool, Response]: ...
|
||||
|
||||
@property
|
||||
def latest_tab(self) -> Union[WebPageTab, WebPage]: ...
|
||||
|
||||
@property
|
||||
def set(self) -> WebPageSetter: ...
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from ..errors import AlertExistsError
|
||||
from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys
|
||||
from .._functions.keys import modifierBit, keyDescriptionForString, input_text_or_keys, Keys, keyDefinitions
|
||||
from .._functions.web import location_in_viewport
|
||||
|
||||
|
||||
@ -274,18 +274,23 @@ class Actions:
|
||||
return self
|
||||
|
||||
def type(self, keys):
|
||||
"""用模拟键盘按键方式输入文本,可输入字符串,也可输入组合键,只能输入键盘上有的字符
|
||||
"""用模拟键盘按键方式输入文本,可输入字符串,也可输入组合键
|
||||
:param keys: 要按下的按键,特殊字符和多个文本可用list或tuple传入
|
||||
:return: self
|
||||
"""
|
||||
modifiers = []
|
||||
for i in keys:
|
||||
for character in i:
|
||||
self.key_down(character)
|
||||
if character in ('\ue009', '\ue008', '\ue00a', '\ue03d'):
|
||||
modifiers.append(character)
|
||||
if character in keyDefinitions:
|
||||
self.key_down(character)
|
||||
if character in ('\ue009', '\ue008', '\ue00a', '\ue03d'):
|
||||
modifiers.append(character)
|
||||
else:
|
||||
self.key_up(character)
|
||||
|
||||
else:
|
||||
self.key_up(character)
|
||||
self.owner.run_cdp('Input.dispatchKeyEvent', type='char', text=character)
|
||||
|
||||
for m in modifiers:
|
||||
self.key_up(m)
|
||||
return self
|
||||
@ -298,9 +303,13 @@ class Actions:
|
||||
input_text_or_keys(self.owner, text)
|
||||
return self
|
||||
|
||||
def wait(self, second):
|
||||
"""等待若干秒"""
|
||||
sleep(second)
|
||||
def wait(self, second, scope=None):
|
||||
"""等待若干秒,如传入两个参数,等待时间为这两个数间的一个随机数
|
||||
:param second: 秒数
|
||||
:param scope: 随机数范围
|
||||
:return: None
|
||||
"""
|
||||
self.owner.wait(second=second, scope=scope)
|
||||
return self
|
||||
|
||||
def _get_key_data(self, key, action):
|
||||
|
@ -100,7 +100,7 @@ class Actions:
|
||||
|
||||
def input(self, text: Any) -> Actions: ...
|
||||
|
||||
def wait(self, second: float) -> Actions: ...
|
||||
def wait(self, second: float, scope: float = None) -> Actions: ...
|
||||
|
||||
def _get_key_data(self, key: str, action: str) -> dict: ...
|
||||
|
||||
|
@ -37,10 +37,12 @@ class Clicker(object):
|
||||
:return: 是否点击成功
|
||||
"""
|
||||
if self._ele.tag == 'option':
|
||||
if self._ele.states.is_selected:
|
||||
self._ele.parent('t:select').select.cancel_by_option(self._ele)
|
||||
else:
|
||||
if not self._ele.states.is_selected:
|
||||
self._ele.parent('t:select').select.by_option(self._ele)
|
||||
else:
|
||||
select = self._ele.parent('t:select')
|
||||
if select.select.is_multi:
|
||||
self._ele.parent('t:select').select.cancel_by_option(self._ele)
|
||||
return
|
||||
|
||||
if not by_js: # 模拟点击
|
||||
@ -184,17 +186,6 @@ class Clicker(object):
|
||||
raise RuntimeError('没有出现新标签页。')
|
||||
return self._ele.page.get_tab(tid)
|
||||
|
||||
def for_new_tab(self, by_js=False):
|
||||
"""点击后等待新tab出现并返回其对象
|
||||
:param by_js: 是否使用js点击,逻辑与click()一致
|
||||
:return: 新标签页对象,如果没有等到新标签页出现则抛出异常
|
||||
"""
|
||||
self.left(by_js=by_js)
|
||||
tid = self._ele.page._page.wait.new_tab()
|
||||
if not tid:
|
||||
raise RuntimeError('没有出现新标签页。')
|
||||
return self._ele.page._page.get_tab(tid)
|
||||
|
||||
def _click(self, client_x, client_y, button='left', count=1):
|
||||
"""实施点击
|
||||
:param client_x: 视口中的x坐标
|
||||
|
@ -5,23 +5,22 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import Cookie
|
||||
|
||||
from .._functions.web import set_browser_cookies, set_session_cookies
|
||||
|
||||
|
||||
class CookiesSetter(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
self._owner = owner
|
||||
|
||||
def __call__(self, cookies):
|
||||
"""设置一个或多个cookie
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
if (isinstance(cookies, dict) and 'name' in cookies and 'value' in cookies) or isinstance(cookies, Cookie):
|
||||
cookies = [cookies]
|
||||
set_browser_cookies(self._page, cookies)
|
||||
set_browser_cookies(self._owner, cookies)
|
||||
|
||||
def remove(self, name, url=None, domain=None, path=None):
|
||||
"""删除一个cookie
|
||||
@ -36,38 +35,38 @@ class CookiesSetter(object):
|
||||
d['url'] = url
|
||||
if domain is not None:
|
||||
d['domain'] = domain
|
||||
if not url and not domain:
|
||||
d['url'] = self._owner.url
|
||||
if path is not None:
|
||||
d['path'] = path
|
||||
self._page.run_cdp('Network.deleteCookies', **d)
|
||||
self._owner.run_cdp('Network.deleteCookies', **d)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
self._page.run_cdp('Network.clearBrowserCookies')
|
||||
self._owner.run_cdp('Network.clearBrowserCookies')
|
||||
|
||||
|
||||
class SessionCookiesSetter(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
def __init__(self, owner):
|
||||
self._owner = owner
|
||||
|
||||
def __call__(self, cookies):
|
||||
"""设置多个cookie,注意不要传入单个
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
if (isinstance(cookies, dict) and 'name' in cookies and 'value' in cookies) or isinstance(cookies, Cookie):
|
||||
cookies = [cookies]
|
||||
set_session_cookies(self._page.session, cookies)
|
||||
set_session_cookies(self._owner.session, cookies)
|
||||
|
||||
def remove(self, name):
|
||||
"""删除一个cookie
|
||||
:param name: cookie的name字段
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.cookies.set(name, None)
|
||||
self._owner.session.cookies.set(name, None)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
self._page.session.cookies.clear()
|
||||
self._owner.session.cookies.clear()
|
||||
|
||||
|
||||
class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
@ -77,9 +76,9 @@ class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
:param cookies: cookies信息
|
||||
:return: None
|
||||
"""
|
||||
if self._page.mode == 'd' and self._page._has_driver:
|
||||
if self._owner.mode == 'd' and self._owner._has_driver:
|
||||
super().__call__(cookies)
|
||||
elif self._page.mode == 's' and self._page._has_session:
|
||||
elif self._owner.mode == 's' and self._owner._has_session:
|
||||
super(CookiesSetter, self).__call__(cookies)
|
||||
|
||||
def remove(self, name, url=None, domain=None, path=None):
|
||||
@ -90,16 +89,16 @@ class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
:param path: cookie的path字段,可选,d模式时才有效
|
||||
:return: None
|
||||
"""
|
||||
if self._page.mode == 'd' and self._page._has_driver:
|
||||
if self._owner.mode == 'd' and self._owner._has_driver:
|
||||
super().remove(name, url, domain, path)
|
||||
elif self._page.mode == 's' and self._page._has_session:
|
||||
elif self._owner.mode == 's' and self._owner._has_session:
|
||||
if url or domain or path:
|
||||
raise AttributeError('url、domain、path参数只有d模式下有效。')
|
||||
super(CookiesSetter, self).remove(name)
|
||||
|
||||
def clear(self):
|
||||
"""清除cookies"""
|
||||
if self._page.mode == 'd' and self._page._has_driver:
|
||||
if self._owner.mode == 'd' and self._owner._has_driver:
|
||||
super().clear()
|
||||
elif self._page.mode == 's' and self._page._has_session:
|
||||
elif self._owner.mode == 's' and self._owner._has_session:
|
||||
super(CookiesSetter, self).clear()
|
||||
|
@ -5,11 +5,9 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from http.cookiejar import Cookie
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from typing import Union
|
||||
|
||||
from requests.cookies import RequestsCookieJar
|
||||
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_tab import WebPageTab
|
||||
from .._pages.session_page import SessionPage
|
||||
@ -17,11 +15,11 @@ from .._pages.web_page import WebPage
|
||||
|
||||
|
||||
class CookiesSetter(object):
|
||||
_page: ChromiumBase
|
||||
_owner: ChromiumBase
|
||||
|
||||
def __init__(self, page: ChromiumBase): ...
|
||||
|
||||
def __call__(self, cookies: Union[RequestsCookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
def remove(self, name: str, url: str = None, domain: str = None, path: str = None) -> None: ...
|
||||
|
||||
@ -29,11 +27,11 @@ class CookiesSetter(object):
|
||||
|
||||
|
||||
class SessionCookiesSetter(object):
|
||||
_page: SessionPage
|
||||
_owner: SessionPage
|
||||
|
||||
def __init__(self, page: SessionPage): ...
|
||||
|
||||
def __call__(self, cookies: Union[RequestsCookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
def remove(self, name: str) -> None: ...
|
||||
|
||||
@ -41,11 +39,11 @@ class SessionCookiesSetter(object):
|
||||
|
||||
|
||||
class WebPageCookiesSetter(CookiesSetter, SessionCookiesSetter):
|
||||
_page: Union[WebPage, WebPageTab]
|
||||
_owner: Union[WebPage, WebPageTab]
|
||||
|
||||
def __init__(self, page: SessionPage): ...
|
||||
|
||||
def __call__(self, cookies: Union[RequestsCookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
def __call__(self, cookies: Union[CookieJar, Cookie, list, tuple, str, dict]) -> None: ...
|
||||
|
||||
def remove(self, name: str, url: str = None, domain: str = None, path: str = None) -> None: ...
|
||||
|
||||
|
@ -30,7 +30,7 @@ class DownloadManager(object):
|
||||
|
||||
def set_rename(self, tab_id: str, rename: str = None, suffix: str = None) -> None: ...
|
||||
|
||||
def set_file_exists(self, tab_id: str, mode: Literal['rename', 'skip', 'overwrite']) -> None: ...
|
||||
def set_file_exists(self, tab_id: str, mode: Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']) -> None: ...
|
||||
|
||||
def set_flag(self, tab_id: str, flag: Union[bool, DownloadMission, None]) -> None: ...
|
||||
|
||||
|
@ -21,13 +21,13 @@ from ..errors import WaitTimeoutError
|
||||
class Listener(object):
|
||||
"""监听器基类"""
|
||||
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: ChromiumBase对象
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
self._page = page
|
||||
self._address = page.address
|
||||
self._target_id = page._target_id
|
||||
self._owner = owner
|
||||
self._address = owner.address
|
||||
self._target_id = owner._target_id
|
||||
self._driver = None
|
||||
self._running_requests = 0
|
||||
self._running_targets = 0
|
||||
@ -167,7 +167,7 @@ class Listener(object):
|
||||
caught = 0
|
||||
end = perf_counter() + timeout if timeout else None
|
||||
while True:
|
||||
if timeout and perf_counter() > end:
|
||||
if (timeout and perf_counter() > end) or self._driver._stopped.is_set():
|
||||
return
|
||||
if self._caught.qsize() >= gap:
|
||||
yield self._caught.get_nowait() if gap == 1 else [self._caught.get_nowait() for _ in range(gap)]
|
||||
@ -216,37 +216,40 @@ class Listener(object):
|
||||
self._running_requests = 0
|
||||
self._running_targets = 0
|
||||
|
||||
def wait_silent(self, timeout=None, targets_only=False):
|
||||
def wait_silent(self, timeout=None, targets_only=False, limit=0):
|
||||
"""等待所有请求结束
|
||||
:param timeout: 超时,为None时无限等待
|
||||
:param targets_only: 是否只等待targets指定的请求结束
|
||||
:param limit: 剩下多少个连接时视为结束
|
||||
:return: 返回是否等待成功
|
||||
"""
|
||||
if not self.listening:
|
||||
raise RuntimeError('监听未启动或已暂停。')
|
||||
if timeout is None:
|
||||
while (not targets_only and self._running_requests > 0) or (targets_only and self._running_targets > 0):
|
||||
while ((not targets_only and self._running_requests > limit)
|
||||
or (targets_only and self._running_targets > limit)):
|
||||
sleep(.1)
|
||||
return True
|
||||
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if (not targets_only and self._running_requests <= 0) or (targets_only and self._running_targets <= 0):
|
||||
if ((not targets_only and self._running_requests <= limit)
|
||||
or (targets_only and self._running_targets <= limit)):
|
||||
return True
|
||||
sleep(.1)
|
||||
else:
|
||||
return False
|
||||
|
||||
def _to_target(self, target_id, address, page):
|
||||
def _to_target(self, target_id, address, owner):
|
||||
"""切换监听的页面对象
|
||||
:param target_id: 新页面对象_target_id
|
||||
:param address: 新页面对象address
|
||||
:param page: 新页面对象
|
||||
:param owner: 新页面对象
|
||||
:return: None
|
||||
"""
|
||||
self._target_id = target_id
|
||||
self._address = address
|
||||
self._page = page
|
||||
self._owner = owner
|
||||
debug = False
|
||||
if self._driver:
|
||||
debug = self._driver._debug
|
||||
@ -275,7 +278,7 @@ class Listener(object):
|
||||
and (self._res_type is True or kwargs.get('type', '').upper() in self._res_type)):
|
||||
self._running_targets += 1
|
||||
rid = kwargs['requestId']
|
||||
p = self._request_ids.setdefault(rid, DataPacket(self._page.tab_id, True))
|
||||
p = self._request_ids.setdefault(rid, DataPacket(self._owner.tab_id, True))
|
||||
p._raw_request = kwargs
|
||||
if kwargs['request'].get('hasPostData', None) and not kwargs['request'].get('postData', None):
|
||||
p._raw_post_data = self._driver.run('Network.getRequestPostData',
|
||||
@ -289,7 +292,7 @@ class Listener(object):
|
||||
and (self._method is True or kwargs['request']['method'] in self._method)
|
||||
and (self._res_type is True or kwargs.get('type', '').upper() in self._res_type)):
|
||||
self._running_targets += 1
|
||||
p = self._request_ids.setdefault(rid, DataPacket(self._page.tab_id, target))
|
||||
p = self._request_ids.setdefault(rid, DataPacket(self._owner.tab_id, target))
|
||||
p._raw_request = kwargs
|
||||
break
|
||||
|
||||
@ -390,13 +393,13 @@ class Listener(object):
|
||||
class FrameListener(Listener):
|
||||
def _requestWillBeSent(self, **kwargs):
|
||||
"""接收到请求时的回调函数"""
|
||||
if not self._page._is_diff_domain and kwargs.get('frameId', None) != self._page._frame_id:
|
||||
if not self._owner._is_diff_domain and kwargs.get('frameId', None) != self._owner._frame_id:
|
||||
return
|
||||
super()._requestWillBeSent(**kwargs)
|
||||
|
||||
def _response_received(self, **kwargs):
|
||||
"""接收到返回信息时处理方法"""
|
||||
if not self._page._is_diff_domain and kwargs.get('frameId', None) != self._page._frame_id:
|
||||
if not self._owner._is_diff_domain and kwargs.get('frameId', None) != self._owner._frame_id:
|
||||
return
|
||||
super()._response_received(**kwargs)
|
||||
|
||||
@ -528,8 +531,14 @@ class Request(object):
|
||||
self._postData = postData
|
||||
return self._postData
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""以list形式返回发送的cookies"""
|
||||
return [c['cookie'] for c in self.extra_info.associatedCookies if not c['blockedReasons']]
|
||||
|
||||
@property
|
||||
def extra_info(self):
|
||||
"""返回额外数据"""
|
||||
return RequestExtraInfo(self._data_packet._request_extra_info or {})
|
||||
|
||||
|
||||
|
@ -19,8 +19,8 @@ __RES_TYPE__ = Literal['Document', 'Stylesheet', 'Image', 'Media', 'Font', 'Scri
|
||||
|
||||
|
||||
class Listener(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
self._address: str = ...
|
||||
self._target_id: str = ...
|
||||
self._targets: Union[str, dict, None] = ...
|
||||
@ -62,14 +62,22 @@ class Listener(object):
|
||||
fit_count: bool = True,
|
||||
raise_err: bool = None) -> Union[List[DataPacket], DataPacket, None]: ...
|
||||
|
||||
def steps(self,
|
||||
count: int = None,
|
||||
timeout: float = None,
|
||||
gap=1) -> Iterable[Union[DataPacket, List[DataPacket]]]: ...
|
||||
|
||||
@property
|
||||
def results(self) -> Union[DataPacket, Dict[str, List[DataPacket]], False]: ...
|
||||
|
||||
def clear(self) -> None: ...
|
||||
|
||||
def wait_silent(self, timeout: float = None, targets_only: bool = False) -> bool: ...
|
||||
def wait_silent(self,
|
||||
timeout: float = None,
|
||||
targets_only: bool = False,
|
||||
limit: int = 0) -> bool: ...
|
||||
|
||||
def _to_target(self, target_id: str, address: str, page: ChromiumBase) -> None: ...
|
||||
def _to_target(self, target_id: str, address: str, owner: ChromiumBase) -> None: ...
|
||||
|
||||
def _requestWillBeSent(self, **kwargs) -> None: ...
|
||||
|
||||
@ -83,17 +91,12 @@ class Listener(object):
|
||||
|
||||
def _loading_failed(self, **kwargs) -> None: ...
|
||||
|
||||
def steps(self,
|
||||
count: int = None,
|
||||
timeout: float = None,
|
||||
gap=1) -> Iterable[Union[DataPacket, List[DataPacket]]]: ...
|
||||
|
||||
def _set_callback(self) -> None: ...
|
||||
|
||||
|
||||
class FrameListener(Listener):
|
||||
def __init__(self, page: ChromiumFrame):
|
||||
self._page: ChromiumFrame = ...
|
||||
def __init__(self, owner: ChromiumFrame):
|
||||
self._owner: ChromiumFrame = ...
|
||||
self._is_diff: bool = ...
|
||||
|
||||
|
||||
@ -174,6 +177,9 @@ class Request(object):
|
||||
@property
|
||||
def postData(self) -> Any: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> List[dict]: ...
|
||||
|
||||
@property
|
||||
def extra_info(self) -> Optional[RequestExtraInfo]: ...
|
||||
|
||||
|
@ -102,7 +102,8 @@ class ElementRect(object):
|
||||
:return: 四个角坐标
|
||||
"""
|
||||
return self._ele.owner.run_cdp('DOM.getBoxModel', backendNodeId=self._ele._backend_id,
|
||||
nodeId=self._ele._node_id, objectId=self._ele._obj_id)['model'][quad]
|
||||
# nodeId=self._ele._node_id, objectId=self._ele._obj_id
|
||||
)['model'][quad]
|
||||
|
||||
def _get_page_coord(self, x, y):
|
||||
"""根据视口坐标获取绝对坐标"""
|
||||
@ -113,12 +114,15 @@ class ElementRect(object):
|
||||
|
||||
|
||||
class TabRect(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: Page对象和Tab对象
|
||||
"""
|
||||
self._owner = owner
|
||||
|
||||
@property
|
||||
def window_state(self):
|
||||
"""返回窗口状态:normal、fullscreen、maximized、 minimized"""
|
||||
"""返回窗口状态:normal、fullscreen、maximized、minimized"""
|
||||
return self._get_window_rect()['windowState']
|
||||
|
||||
@property
|
||||
@ -170,23 +174,26 @@ class TabRect(object):
|
||||
@property
|
||||
def viewport_size_with_scrollbar(self):
|
||||
"""返回视口宽高,包括滚动条,格式:(宽, 高)"""
|
||||
r = self._page.run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();')
|
||||
r = self._owner.run_js('return window.innerWidth.toString() + " " + window.innerHeight.toString();')
|
||||
w, h = r.split(' ')
|
||||
return int(w), int(h)
|
||||
|
||||
def _get_page_rect(self):
|
||||
"""获取页面范围信息"""
|
||||
return self._page.run_cdp_loaded('Page.getLayoutMetrics')
|
||||
return self._owner.run_cdp_loaded('Page.getLayoutMetrics')
|
||||
|
||||
def _get_window_rect(self):
|
||||
"""获取窗口范围信息"""
|
||||
return self._page.browser.get_window_bounds(self._page.tab_id)
|
||||
return self._owner.browser.get_window_bounds(self._owner.tab_id)
|
||||
|
||||
|
||||
class FrameRect(object):
|
||||
"""异域iframe使用"""
|
||||
|
||||
def __init__(self, frame):
|
||||
"""
|
||||
:param frame: ChromiumFrame对象
|
||||
"""
|
||||
self._frame = frame
|
||||
|
||||
@property
|
||||
|
@ -62,8 +62,8 @@ class ElementRect(object):
|
||||
|
||||
|
||||
class TabRect(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: Union[ChromiumPage, ChromiumTab, WebPage, WebPageTab] = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: Union[ChromiumPage, ChromiumTab, WebPage, WebPageTab] = ...
|
||||
|
||||
@property
|
||||
def window_state(self) -> str: ...
|
||||
|
@ -16,8 +16,8 @@ from time import sleep, time
|
||||
|
||||
|
||||
class Screencast(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
def __init__(self, owner):
|
||||
self._owner = owner
|
||||
self._path = None
|
||||
self._tmp_path = None
|
||||
self._running = False
|
||||
@ -39,16 +39,16 @@ class Screencast(object):
|
||||
raise ValueError('save_path必须设置。')
|
||||
|
||||
if self._mode in ('frugal_video', 'video'):
|
||||
if self._page.browser.page._chromium_options.tmp_path:
|
||||
if self._owner.browser.page._chromium_options.tmp_path:
|
||||
self._tmp_path = Path(
|
||||
self._page.browser.page._chromium_options.tmp_path) / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
self._owner.browser.page._chromium_options.tmp_path) / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
else:
|
||||
self._tmp_path = Path(gettempdir()) / 'DrissionPage' / f'screencast_tmp_{time()}_{randint(0, 100)}'
|
||||
self._tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if self._mode.startswith('frugal'):
|
||||
self._page.driver.set_callback('Page.screencastFrame', self._onScreencastFrame)
|
||||
self._page.run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)
|
||||
self._owner.driver.set_callback('Page.screencastFrame', self._onScreencastFrame)
|
||||
self._owner.run_cdp('Page.startScreencast', everyNthFrame=1, quality=100)
|
||||
|
||||
elif not self._mode.startswith('js'):
|
||||
self._running = True
|
||||
@ -79,8 +79,8 @@ class Screencast(object):
|
||||
}
|
||||
'''
|
||||
print('请手动选择要录制的目标。')
|
||||
self._page.run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
|
||||
self._page.run_js(js)
|
||||
self._owner.run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
|
||||
self._owner.run_js(js)
|
||||
|
||||
def stop(self, video_name=None):
|
||||
"""停止录屏
|
||||
@ -93,19 +93,19 @@ class Screencast(object):
|
||||
path = f'{self._path}{sep}{name}'
|
||||
|
||||
if self._mode.startswith('js'):
|
||||
self._page.run_js('mediaRecorder.stop();', as_expr=True)
|
||||
while not self._page.run_js('return DrissionPage_Screencast_blob_ok;'):
|
||||
self._owner.run_js('mediaRecorder.stop();', as_expr=True)
|
||||
while not self._owner.run_js('return DrissionPage_Screencast_blob_ok;'):
|
||||
sleep(.1)
|
||||
blob = self._page.run_js('return DrissionPage_Screencast_blob;')
|
||||
uuid = self._page.run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid']
|
||||
data = self._page.run_cdp('IO.read', handle=f'blob:{uuid}')['data']
|
||||
blob = self._owner.run_js('return DrissionPage_Screencast_blob;')
|
||||
uuid = self._owner.run_cdp('IO.resolveBlob', objectId=blob['result']['objectId'])['uuid']
|
||||
data = self._owner.run_cdp('IO.read', handle=f'blob:{uuid}')['data']
|
||||
with open(path, 'wb') as f:
|
||||
f.write(b64decode(data))
|
||||
return path
|
||||
|
||||
if self._mode.startswith('frugal'):
|
||||
self._page.driver.set_callback('Page.screencastFrame', None)
|
||||
self._page.run_cdp('Page.stopScreencast')
|
||||
self._owner.driver.set_callback('Page.screencastFrame', None)
|
||||
self._owner.run_cdp('Page.stopScreencast')
|
||||
else:
|
||||
self._enable = False
|
||||
while self._running:
|
||||
@ -155,7 +155,7 @@ class Screencast(object):
|
||||
self._running = True
|
||||
path = self._tmp_path or self._path
|
||||
while self._enable:
|
||||
self._page.get_screenshot(path=path, name=f'{time()}.jpg')
|
||||
self._owner.get_screenshot(path=path, name=f'{time()}.jpg')
|
||||
sleep(.04)
|
||||
self._running = False
|
||||
|
||||
@ -164,7 +164,7 @@ class Screencast(object):
|
||||
path = self._tmp_path or self._path
|
||||
with open(f'{path}{sep}{kwargs["metadata"]["timestamp"]}.jpg', 'wb') as f:
|
||||
f.write(b64decode(kwargs['data']))
|
||||
self._page.run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
|
||||
self._owner.run_cdp('Page.screencastFrameAck', sessionId=kwargs['sessionId'])
|
||||
|
||||
|
||||
class ScreencastMode(object):
|
||||
|
@ -12,8 +12,8 @@ from .._pages.chromium_base import ChromiumBase
|
||||
|
||||
|
||||
class Screencast(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
self._path: Path = ...
|
||||
self._tmp_path: Path = ...
|
||||
self._running: bool = ...
|
||||
|
@ -87,15 +87,15 @@ class Scroller(object):
|
||||
if not self._wait_complete:
|
||||
return
|
||||
|
||||
page = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver
|
||||
r = page.run_cdp('Page.getLayoutMetrics')
|
||||
owner = self._driver.owner if self._driver._type == 'ChromiumElement' else self._driver
|
||||
r = owner.run_cdp('Page.getLayoutMetrics')
|
||||
x = r['layoutViewport']['pageX']
|
||||
y = r['layoutViewport']['pageY']
|
||||
|
||||
end_time = perf_counter() + page.timeout
|
||||
end_time = perf_counter() + owner.timeout
|
||||
while perf_counter() < end_time:
|
||||
sleep(.1)
|
||||
r = page.run_cdp('Page.getLayoutMetrics')
|
||||
r = owner.run_cdp('Page.getLayoutMetrics')
|
||||
x1 = r['layoutViewport']['pageX']
|
||||
y1 = r['layoutViewport']['pageY']
|
||||
|
||||
@ -120,11 +120,11 @@ class ElementScroller(Scroller):
|
||||
|
||||
|
||||
class PageScroller(Scroller):
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: 页面对象
|
||||
:param owner: 页面对象
|
||||
"""
|
||||
super().__init__(page)
|
||||
super().__init__(owner)
|
||||
self.t1 = 'window'
|
||||
self.t2 = 'document.documentElement'
|
||||
|
||||
@ -146,7 +146,7 @@ class PageScroller(Scroller):
|
||||
txt = 'true' if center else 'false'
|
||||
ele.run_js(f'this.scrollIntoViewIfNeeded({txt});')
|
||||
if center or (center is not False and ele.states.is_covered):
|
||||
ele.run_js('''function getWindowScrollTop() {var scroll_top = 0;
|
||||
ele.run_js('''function getWindowScrollTop() {let scroll_top = 0;
|
||||
if (document.documentElement && document.documentElement.scrollTop) {
|
||||
scroll_top = document.documentElement.scrollTop;
|
||||
} else if (document.body) {scroll_top = document.body.scrollTop;}
|
||||
|
@ -51,7 +51,7 @@ class ElementScroller(Scroller):
|
||||
|
||||
|
||||
class PageScroller(Scroller):
|
||||
def __init__(self, page: ChromiumBase): ...
|
||||
def __init__(self, owner: ChromiumBase): ...
|
||||
|
||||
def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: Union[bool, None] = None) -> None: ...
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from time import perf_counter
|
||||
from time import perf_counter, sleep
|
||||
|
||||
|
||||
class SelectElement(object):
|
||||
@ -215,6 +215,7 @@ class SelectElement(object):
|
||||
if len(eles) >= text_len:
|
||||
ok = True
|
||||
break
|
||||
sleep(.01)
|
||||
|
||||
if ok:
|
||||
self._select_options(eles, mode)
|
||||
@ -237,6 +238,7 @@ class SelectElement(object):
|
||||
if len(self.options) >= text_len:
|
||||
ok = True
|
||||
break
|
||||
sleep(.01)
|
||||
|
||||
if ok:
|
||||
eles = self.options
|
||||
|
@ -11,12 +11,18 @@ from time import sleep
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
from .cookies_setter import SessionCookiesSetter, CookiesSetter, WebPageCookiesSetter
|
||||
from .._functions.settings import Settings
|
||||
from .._functions.tools import show_or_hide_browser
|
||||
from .._functions.web import format_headers
|
||||
from ..errors import ElementLostError
|
||||
|
||||
|
||||
class BasePageSetter(object):
|
||||
def __init__(self, page):
|
||||
self._page = page
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: BasePage对象
|
||||
"""
|
||||
self._owner = owner
|
||||
|
||||
def NoneElement_value(self, value=None, on_off=True):
|
||||
"""设置空元素是否返回设定值
|
||||
@ -24,39 +30,42 @@ class BasePageSetter(object):
|
||||
:param on_off: 是否启用
|
||||
:return: None
|
||||
"""
|
||||
self._page._none_ele_return_value = on_off
|
||||
self._page._none_ele_value = value
|
||||
self._owner._none_ele_return_value = on_off
|
||||
self._owner._none_ele_value = value
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BasePageSetter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
self._cookies_setter = None
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
"""返回用于设置页面加载策略的对象"""
|
||||
return LoadMode(self._page)
|
||||
return LoadMode(self._owner)
|
||||
|
||||
@property
|
||||
def scroll(self):
|
||||
"""返回用于设置页面滚动设置的对象"""
|
||||
return PageScrollSetter(self._page.scroll)
|
||||
return PageScrollSetter(self._owner.scroll)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = CookiesSetter(self._page)
|
||||
self._cookies_setter = CookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def retry_times(self, times):
|
||||
"""设置连接失败重连次数"""
|
||||
self._page.retry_times = times
|
||||
self._owner.retry_times = times
|
||||
|
||||
def retry_interval(self, interval):
|
||||
"""设置连接失败重连间隔"""
|
||||
self._page.retry_interval = interval
|
||||
self._owner.retry_interval = interval
|
||||
|
||||
def timeouts(self, base=None, page_load=None, script=None, implicit=None):
|
||||
"""设置超时时间,单位为秒
|
||||
@ -67,14 +76,14 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
"""
|
||||
base = base if base is not None else implicit
|
||||
if base is not None:
|
||||
self._page.timeouts.base = base
|
||||
self._page._timeout = base
|
||||
self._owner.timeouts.base = base
|
||||
self._owner._timeout = base
|
||||
|
||||
if page_load is not None:
|
||||
self._page.timeouts.page_load = page_load
|
||||
self._owner.timeouts.page_load = page_load
|
||||
|
||||
if script is not None:
|
||||
self._page.timeouts.script = script
|
||||
self._owner.timeouts.script = script
|
||||
|
||||
def user_agent(self, ua, platform=None):
|
||||
"""为当前tab设置user agent,只在当前tab有效
|
||||
@ -85,7 +94,7 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
keys = {'userAgent': ua}
|
||||
if platform:
|
||||
keys['platform'] = platform
|
||||
self._page.run_cdp('Emulation.setUserAgentOverride', **keys)
|
||||
self._owner.run_cdp('Emulation.setUserAgentOverride', **keys)
|
||||
|
||||
def session_storage(self, item, value):
|
||||
"""设置或删除某项sessionStorage信息
|
||||
@ -93,15 +102,15 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._page.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._page.run_cdp('Storage.getStorageKeyForFrame', frameId=self._page._frame_id)['storageKey']
|
||||
self._owner.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._page.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': False}, key=item)
|
||||
self._owner.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': False}, key=item)
|
||||
else:
|
||||
self._page.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},
|
||||
key=item, value=value)
|
||||
self._page.run_cdp_loaded('DOMStorage.disable')
|
||||
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': False},
|
||||
key=item, value=value)
|
||||
self._owner.run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def local_storage(self, item, value):
|
||||
"""设置或删除某项localStorage信息
|
||||
@ -109,38 +118,38 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
:param value: 项的值,设置为False时,删除该项
|
||||
:return: None
|
||||
"""
|
||||
self._page.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._page.run_cdp('Storage.getStorageKeyForFrame', frameId=self._page._frame_id)['storageKey']
|
||||
self._owner.run_cdp_loaded('DOMStorage.enable')
|
||||
i = self._owner.run_cdp('Storage.getStorageKeyForFrame', frameId=self._owner._frame_id)['storageKey']
|
||||
if value is False:
|
||||
self._page.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': True}, key=item)
|
||||
self._owner.run_cdp('DOMStorage.removeDOMStorageItem',
|
||||
storageId={'storageKey': i, 'isLocalStorage': True}, key=item)
|
||||
else:
|
||||
self._page.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},
|
||||
key=item, value=value)
|
||||
self._page.run_cdp_loaded('DOMStorage.disable')
|
||||
self._owner.run_cdp('DOMStorage.setDOMStorageItem', storageId={'storageKey': i, 'isLocalStorage': True},
|
||||
key=item, value=value)
|
||||
self._owner.run_cdp_loaded('DOMStorage.disable')
|
||||
|
||||
def upload_files(self, files):
|
||||
"""等待上传的文件路径
|
||||
:param files: 文件路径列表或字符串,字符串时多个文件用回车分隔
|
||||
:return: None
|
||||
"""
|
||||
if not self._page._upload_list:
|
||||
self._page.driver.set_callback('Page.fileChooserOpened', self._page._onFileChooserOpened)
|
||||
self._page.run_cdp('Page.setInterceptFileChooserDialog', enabled=True)
|
||||
if not self._owner._upload_list:
|
||||
self._owner.driver.set_callback('Page.fileChooserOpened', self._owner._onFileChooserOpened)
|
||||
self._owner.run_cdp('Page.setInterceptFileChooserDialog', enabled=True)
|
||||
|
||||
if isinstance(files, str):
|
||||
files = files.split('\n')
|
||||
elif isinstance(files, Path):
|
||||
files = (files, )
|
||||
self._page._upload_list = [str(Path(i).absolute()) for i in files]
|
||||
files = (files,)
|
||||
self._owner._upload_list = [str(Path(i).absolute()) for i in files]
|
||||
|
||||
def headers(self, headers: dict) -> None:
|
||||
def headers(self, headers) -> None:
|
||||
"""设置固定发送的headers
|
||||
:param headers: dict格式的headers数据
|
||||
:return: None
|
||||
"""
|
||||
self._page.run_cdp('Network.enable')
|
||||
self._page.run_cdp('Network.setExtraHTTPHeaders', headers=headers)
|
||||
self._owner.run_cdp('Network.enable')
|
||||
self._owner.run_cdp('Network.setExtraHTTPHeaders', headers=format_headers(headers))
|
||||
|
||||
def auto_handle_alert(self, on_off=True, accept=True):
|
||||
"""设置是否启用自动处理弹窗
|
||||
@ -148,7 +157,7 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
:param accept: bool表示确定还是取消
|
||||
:return: None
|
||||
"""
|
||||
self._page._alert.auto = accept if on_off else None
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
def blocked_urls(self, urls):
|
||||
"""设置要忽略的url
|
||||
@ -161,25 +170,28 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
urls = (urls,)
|
||||
if not isinstance(urls, (list, tuple)):
|
||||
raise TypeError('urls需传入str、list或tuple类型。')
|
||||
self._page.run_cdp('Network.enable')
|
||||
self._page.run_cdp('Network.setBlockedURLs', urls=urls)
|
||||
self._owner.run_cdp('Network.enable')
|
||||
self._owner.run_cdp('Network.setBlockedURLs', urls=urls)
|
||||
|
||||
# --------------即将废弃---------------
|
||||
|
||||
@property
|
||||
def load_strategy(self):
|
||||
"""返回用于设置页面加载策略的对象"""
|
||||
return LoadMode(self._page)
|
||||
return LoadMode(self._owner)
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param owner: 标签页对象
|
||||
"""
|
||||
super().__init__(owner)
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
"""返回用于设置浏览器窗口的对象"""
|
||||
return WindowSetter(self._page)
|
||||
return WindowSetter(self._owner)
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
@ -187,10 +199,10 @@ class TabSetter(ChromiumBaseSetter):
|
||||
:return: None
|
||||
"""
|
||||
path = str(Path(path).absolute())
|
||||
self._page._download_path = path
|
||||
self._page.browser._dl_mgr.set_path(self._page, path)
|
||||
if self._page._DownloadKit:
|
||||
self._page._DownloadKit.set.goal_path(path)
|
||||
self._owner._download_path = path
|
||||
self._owner.browser._dl_mgr.set_path(self._owner, path)
|
||||
if self._owner._DownloadKit:
|
||||
self._owner._DownloadKit.set.goal_path(path)
|
||||
|
||||
def download_file_name(self, name=None, suffix=None):
|
||||
"""设置下一个被下载文件的名称
|
||||
@ -198,7 +210,7 @@ class TabSetter(ChromiumBaseSetter):
|
||||
:param suffix: 后缀名,显式设置后缀名,不使用远程文件后缀
|
||||
:return: None
|
||||
"""
|
||||
self._page.browser._dl_mgr.set_rename(self._page.tab_id, name, suffix)
|
||||
self._owner.browser._dl_mgr.set_rename(self._owner.tab_id, name, suffix)
|
||||
|
||||
def when_download_file_exists(self, mode):
|
||||
"""设置当存在同名文件时的处理方式
|
||||
@ -211,11 +223,11 @@ class TabSetter(ChromiumBaseSetter):
|
||||
if mode not in types:
|
||||
raise ValueError(f'''mode参数只能是 '{"', '".join(types.keys())}' 之一,现在是:{mode}''')
|
||||
|
||||
self._page.browser._dl_mgr.set_file_exists(self._page.tab_id, mode)
|
||||
self._owner.browser._dl_mgr.set_file_exists(self._owner.tab_id, mode)
|
||||
|
||||
def activate(self):
|
||||
"""使标签页处于最前面"""
|
||||
self._page.browser.activate_tab(self._page.tab_id)
|
||||
self._owner.browser.activate_tab(self._owner.tab_id)
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
@ -226,39 +238,46 @@ class ChromiumPageSetter(TabSetter):
|
||||
:return: None
|
||||
"""
|
||||
if not tab_or_id:
|
||||
tab_or_id = self._page.tab_id
|
||||
tab_or_id = self._owner.tab_id
|
||||
elif not isinstance(tab_or_id, str): # 传入Tab对象
|
||||
tab_or_id = tab_or_id.tab_id
|
||||
self._page.browser.activate_tab(tab_or_id)
|
||||
self._owner.browser.activate_tab(tab_or_id)
|
||||
|
||||
@property
|
||||
def window(self):
|
||||
"""返回用于设置浏览器窗口的对象"""
|
||||
return PageWindowSetter(self._page)
|
||||
def auto_handle_alert(self, on_off=True, accept=True, all_tabs=False):
|
||||
"""设置是否启用自动处理弹窗
|
||||
:param on_off: bool表示开或关
|
||||
:param accept: bool表示确定还是取消
|
||||
:param all_tabs: 是否为全局设置
|
||||
:return: None
|
||||
"""
|
||||
if all_tabs:
|
||||
Settings.auto_handle_alert = on_off
|
||||
else:
|
||||
self._owner._alert.auto = accept if on_off else None
|
||||
|
||||
|
||||
class SessionPageSetter(BasePageSetter):
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: SessionPage对象
|
||||
:param owner: SessionPage对象
|
||||
"""
|
||||
super().__init__(page)
|
||||
super().__init__(owner)
|
||||
self._cookies_setter = None
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = SessionCookiesSetter(self._page)
|
||||
self._cookies_setter = SessionCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def retry_times(self, times):
|
||||
"""设置连接失败时重连次数"""
|
||||
self._page.retry_times = times
|
||||
self._owner.retry_times = times
|
||||
|
||||
def retry_interval(self, interval):
|
||||
"""设置连接失败时重连间隔"""
|
||||
self._page.retry_interval = interval
|
||||
self._owner.retry_interval = interval
|
||||
|
||||
def download_path(self, path):
|
||||
"""设置下载路径
|
||||
@ -266,16 +285,16 @@ class SessionPageSetter(BasePageSetter):
|
||||
:return: None
|
||||
"""
|
||||
path = str(Path(path).absolute())
|
||||
self._page._download_path = path
|
||||
if self._page._DownloadKit:
|
||||
self._page._DownloadKit.set.goal_path(path)
|
||||
self._owner._download_path = path
|
||||
if self._owner._DownloadKit:
|
||||
self._owner._DownloadKit.set.goal_path(path)
|
||||
|
||||
def timeout(self, second):
|
||||
"""设置连接超时时间
|
||||
:param second: 秒数
|
||||
:return: None
|
||||
"""
|
||||
self._page.timeout = second
|
||||
self._owner.timeout = second
|
||||
|
||||
def encoding(self, encoding, set_all=True):
|
||||
"""设置编码
|
||||
@ -284,16 +303,16 @@ class SessionPageSetter(BasePageSetter):
|
||||
:return: None
|
||||
"""
|
||||
if set_all:
|
||||
self._page._encoding = encoding if encoding else None
|
||||
if self._page.response:
|
||||
self._page.response.encoding = encoding
|
||||
self._owner._encoding = encoding if encoding else None
|
||||
if self._owner.response:
|
||||
self._owner.response.encoding = encoding
|
||||
|
||||
def headers(self, headers):
|
||||
"""设置通用的headers
|
||||
:param headers: dict形式的headers
|
||||
:return: None
|
||||
"""
|
||||
self._page._headers = CaseInsensitiveDict(headers)
|
||||
self._owner._headers = CaseInsensitiveDict(format_headers(headers))
|
||||
|
||||
def header(self, name, value):
|
||||
"""设置headers中一个项
|
||||
@ -301,14 +320,14 @@ class SessionPageSetter(BasePageSetter):
|
||||
:param value: 设置值
|
||||
:return: None
|
||||
"""
|
||||
self._page._headers[name] = value
|
||||
self._owner._headers[name] = value
|
||||
|
||||
def user_agent(self, ua):
|
||||
"""设置user agent
|
||||
:param ua: user agent
|
||||
:return: None
|
||||
"""
|
||||
self._page._headers['user-agent'] = ua
|
||||
self._owner._headers['user-agent'] = ua
|
||||
|
||||
def proxies(self, http=None, https=None):
|
||||
"""设置proxies参数
|
||||
@ -316,63 +335,63 @@ class SessionPageSetter(BasePageSetter):
|
||||
:param https: https代理地址
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.proxies = {'http': http, 'https': https}
|
||||
self._owner.session.proxies = {'http': http, 'https': https}
|
||||
|
||||
def auth(self, auth):
|
||||
"""设置认证元组或对象
|
||||
:param auth: 认证元组或对象
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.auth = auth
|
||||
self._owner.session.auth = auth
|
||||
|
||||
def hooks(self, hooks):
|
||||
"""设置回调方法
|
||||
:param hooks: 回调方法
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.hooks = hooks
|
||||
self._owner.session.hooks = hooks
|
||||
|
||||
def params(self, params):
|
||||
"""设置查询参数字典
|
||||
:param params: 查询参数字典
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.params = params
|
||||
self._owner.session.params = params
|
||||
|
||||
def verify(self, on_off):
|
||||
"""设置是否验证SSL证书
|
||||
:param on_off: 是否验证 SSL 证书
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.verify = on_off
|
||||
self._owner.session.verify = on_off
|
||||
|
||||
def cert(self, cert):
|
||||
"""SSL客户端证书文件的路径(.pem格式),或(‘cert’, ‘key’)元组
|
||||
:param cert: 证书路径或元组
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.cert = cert
|
||||
self._owner.session.cert = cert
|
||||
|
||||
def stream(self, on_off):
|
||||
"""设置是否使用流式响应内容
|
||||
:param on_off: 是否使用流式响应内容
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.stream = on_off
|
||||
self._owner.session.stream = on_off
|
||||
|
||||
def trust_env(self, on_off):
|
||||
"""设置是否信任环境
|
||||
:param on_off: 是否信任环境
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.trust_env = on_off
|
||||
self._owner.session.trust_env = on_off
|
||||
|
||||
def max_redirects(self, times):
|
||||
"""设置最大重定向次数
|
||||
:param times: 最大重定向次数
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.max_redirects = times
|
||||
self._owner.session.max_redirects = times
|
||||
|
||||
def add_adapter(self, url, adapter):
|
||||
"""添加适配器
|
||||
@ -380,20 +399,20 @@ class SessionPageSetter(BasePageSetter):
|
||||
:param adapter: 适配器对象
|
||||
:return: None
|
||||
"""
|
||||
self._page.session.mount(url, adapter)
|
||||
self._owner.session.mount(url, adapter)
|
||||
|
||||
|
||||
class WebPageSetter(ChromiumPageSetter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
self._session_setter = SessionPageSetter(self._page)
|
||||
self._chromium_setter = ChromiumPageSetter(self._page)
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._session_setter = SessionPageSetter(self._owner)
|
||||
self._chromium_setter = ChromiumPageSetter(self._owner)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = WebPageCookiesSetter(self._page)
|
||||
self._cookies_setter = WebPageCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
@ -401,30 +420,30 @@ class WebPageSetter(ChromiumPageSetter):
|
||||
:param headers: dict格式的headers数据
|
||||
:return: None
|
||||
"""
|
||||
if self._page.mode == 's':
|
||||
if self._owner.mode == 's':
|
||||
self._session_setter.headers(headers)
|
||||
else:
|
||||
self._chromium_setter.headers(headers)
|
||||
|
||||
def user_agent(self, ua, platform=None):
|
||||
"""设置user agent,d模式下只有当前tab有效"""
|
||||
if self._page.mode == 's':
|
||||
if self._owner.mode == 's':
|
||||
self._session_setter.user_agent(ua)
|
||||
else:
|
||||
self._chromium_setter.user_agent(ua, platform)
|
||||
|
||||
|
||||
class WebPageTabSetter(TabSetter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
self._session_setter = SessionPageSetter(self._page)
|
||||
self._chromium_setter = ChromiumBaseSetter(self._page)
|
||||
def __init__(self, owner):
|
||||
super().__init__(owner)
|
||||
self._session_setter = SessionPageSetter(self._owner)
|
||||
self._chromium_setter = ChromiumBaseSetter(self._owner)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
"""返回用于设置cookies的对象"""
|
||||
if self._cookies_setter is None:
|
||||
self._cookies_setter = WebPageCookiesSetter(self._page)
|
||||
self._cookies_setter = WebPageCookiesSetter(self._owner)
|
||||
return self._cookies_setter
|
||||
|
||||
def headers(self, headers) -> None:
|
||||
@ -432,16 +451,16 @@ class WebPageTabSetter(TabSetter):
|
||||
:param headers: dict格式的headers数据
|
||||
:return: None
|
||||
"""
|
||||
if self._page._has_session:
|
||||
if self._owner._has_session:
|
||||
self._session_setter.headers(headers)
|
||||
if self._page._has_driver:
|
||||
if self._owner._has_driver:
|
||||
self._chromium_setter.headers(headers)
|
||||
|
||||
def user_agent(self, ua, platform=None):
|
||||
"""设置user agent,d模式下只有当前tab有效"""
|
||||
if self._page._has_session:
|
||||
if self._owner._has_session:
|
||||
self._session_setter.user_agent(ua)
|
||||
if self._page._has_driver:
|
||||
if self._owner._has_driver:
|
||||
self._chromium_setter.user_agent(ua, platform)
|
||||
|
||||
|
||||
@ -458,7 +477,13 @@ class ChromiumElementSetter(object):
|
||||
:param value: 属性值
|
||||
:return: None
|
||||
"""
|
||||
self._ele.owner.run_cdp('DOM.setAttributeValue', nodeId=self._ele._node_id, name=name, value=str(value))
|
||||
try:
|
||||
self._ele.owner.run_cdp('DOM.setAttributeValue',
|
||||
nodeId=self._ele._node_id, name=name, value=str(value))
|
||||
except ElementLostError:
|
||||
self._ele._refresh_id()
|
||||
self._ele.owner.run_cdp('DOM.setAttributeValue',
|
||||
nodeId=self._ele._node_id, name=name, value=str(value))
|
||||
|
||||
def property(self, name, value):
|
||||
"""设置元素property属性
|
||||
@ -491,17 +516,17 @@ class ChromiumFrameSetter(ChromiumBaseSetter):
|
||||
:param value: 属性值
|
||||
:return: None
|
||||
"""
|
||||
self._page.frame_ele.set.attr(name, value)
|
||||
self._owner.frame_ele.set.attr(name, value)
|
||||
|
||||
|
||||
class LoadMode(object):
|
||||
"""用于设置页面加载策略的类"""
|
||||
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: ChromiumBase对象
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
self._page = page
|
||||
self._owner = owner
|
||||
|
||||
def __call__(self, value):
|
||||
"""设置加载策略
|
||||
@ -510,23 +535,26 @@ class LoadMode(object):
|
||||
"""
|
||||
if value.lower() not in ('normal', 'eager', 'none'):
|
||||
raise ValueError("只能选择 'normal', 'eager', 'none'。")
|
||||
self._page._load_mode = value
|
||||
self._owner._load_mode = value
|
||||
|
||||
def normal(self):
|
||||
"""设置页面加载策略为normal"""
|
||||
self._page._load_mode = 'normal'
|
||||
self._owner._load_mode = 'normal'
|
||||
|
||||
def eager(self):
|
||||
"""设置页面加载策略为eager"""
|
||||
self._page._load_mode = 'eager'
|
||||
self._owner._load_mode = 'eager'
|
||||
|
||||
def none(self):
|
||||
"""设置页面加载策略为none"""
|
||||
self._page._load_mode = 'none'
|
||||
self._owner._load_mode = 'none'
|
||||
|
||||
|
||||
class PageScrollSetter(object):
|
||||
def __init__(self, scroll):
|
||||
"""
|
||||
:param scroll: PageScroller对象
|
||||
"""
|
||||
self._scroll = scroll
|
||||
|
||||
def wait_complete(self, on_off=True):
|
||||
@ -553,11 +581,11 @@ class PageScrollSetter(object):
|
||||
class WindowSetter(object):
|
||||
"""用于设置窗口大小的类"""
|
||||
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: 页面对象
|
||||
:param owner: 页面对象
|
||||
"""
|
||||
self._page = page
|
||||
self._owner = owner
|
||||
self._window_id = self._get_info()['windowId']
|
||||
|
||||
def max(self):
|
||||
@ -620,7 +648,7 @@ class WindowSetter(object):
|
||||
"""获取窗口位置及大小信息"""
|
||||
for _ in range(50):
|
||||
try:
|
||||
return self._page.run_cdp('Browser.getWindowForTarget')
|
||||
return self._owner.run_cdp('Browser.getWindowForTarget')
|
||||
except:
|
||||
sleep(.1)
|
||||
|
||||
@ -630,7 +658,7 @@ class WindowSetter(object):
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self._page.run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)
|
||||
self._owner.run_cdp('Browser.setWindowBounds', windowId=self._window_id, bounds=bounds)
|
||||
except:
|
||||
raise RuntimeError('浏览器全屏或最小化状态时请先调用set.window.normal()恢复正常状态。')
|
||||
|
||||
@ -648,12 +676,10 @@ class WindowSetter(object):
|
||||
"""设置窗口为全屏"""
|
||||
self.full()
|
||||
|
||||
|
||||
class PageWindowSetter(WindowSetter):
|
||||
def hide(self):
|
||||
"""隐藏浏览器窗口,只在Windows系统可用"""
|
||||
show_or_hide_browser(self._page, hide=True)
|
||||
show_or_hide_browser(self._owner, hide=True)
|
||||
|
||||
def show(self):
|
||||
"""显示浏览器窗口,只在Windows系统可用"""
|
||||
show_or_hide_browser(self._page, hide=False)
|
||||
show_or_hide_browser(self._owner, hide=False)
|
||||
|
@ -6,7 +6,7 @@
|
||||
@License : BSD 3-Clause.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple, Literal, Any
|
||||
from typing import Union, Tuple, Literal, Any, Optional
|
||||
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.auth import HTTPBasicAuth
|
||||
@ -18,7 +18,7 @@ from .._elements.chromium_element import ChromiumElement
|
||||
from .._pages.chromium_base import ChromiumBase
|
||||
from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
from .._pages.chromium_tab import ChromiumTab
|
||||
from .._pages.chromium_tab import ChromiumTab, WebPageTab
|
||||
from .._pages.session_page import SessionPage
|
||||
from .._pages.web_page import WebPage
|
||||
|
||||
@ -26,15 +26,15 @@ FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']
|
||||
|
||||
|
||||
class BasePageSetter(object):
|
||||
def __init__(self, page: BasePage):
|
||||
self._page: BasePage = ...
|
||||
def __init__(self, owner: BasePage):
|
||||
self._owner: BasePage = ...
|
||||
|
||||
def NoneElement_value(self, value: Any = None, on_off: bool = True) -> None: ...
|
||||
|
||||
|
||||
class ChromiumBaseSetter(BasePageSetter):
|
||||
def __init__(self, page):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner):
|
||||
self._owner: ChromiumBase = ...
|
||||
self._cookies_setter: CookiesSetter = ...
|
||||
|
||||
@property
|
||||
@ -58,7 +58,7 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
|
||||
def local_storage(self, item: str, value: Union[str, bool]) -> None: ...
|
||||
|
||||
def headers(self, headers: dict) -> None: ...
|
||||
def headers(self, headers: Union[dict, str]) -> None: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True) -> None: ...
|
||||
|
||||
@ -68,7 +68,9 @@ class ChromiumBaseSetter(BasePageSetter):
|
||||
|
||||
|
||||
class TabSetter(ChromiumBaseSetter):
|
||||
def __init__(self, page): ...
|
||||
_owner: ChromiumTab = ...
|
||||
|
||||
def __init__(self, owner: Union[ChromiumTab, WebPageTab, WebPage, ChromiumPage]): ...
|
||||
|
||||
@property
|
||||
def window(self) -> WindowSetter: ...
|
||||
@ -83,20 +85,18 @@ class TabSetter(ChromiumBaseSetter):
|
||||
|
||||
|
||||
class ChromiumPageSetter(TabSetter):
|
||||
_page: ChromiumPage = ...
|
||||
|
||||
@property
|
||||
def window(self) -> PageWindowSetter: ...
|
||||
|
||||
def main_tab(self, tab_id: str = None) -> None: ...
|
||||
_owner: ChromiumPage = ...
|
||||
|
||||
def tab_to_front(self, tab_or_id: Union[str, ChromiumTab] = None) -> None: ...
|
||||
|
||||
def auto_handle_alert(self, on_off: bool = True, accept: bool = True, all_tabs: bool = False) -> None: ...
|
||||
|
||||
|
||||
class SessionPageSetter(BasePageSetter):
|
||||
def __init__(self, page: SessionPage):
|
||||
self._page: SessionPage = ...
|
||||
self._cookies_setter: SessionCookiesSetter = ...
|
||||
_owner: SessionPage = ...
|
||||
_cookies_setter: Optional[SessionCookiesSetter] = ...
|
||||
|
||||
def __init__(self, owner: SessionPage): ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> SessionCookiesSetter: ...
|
||||
@ -111,7 +111,7 @@ class SessionPageSetter(BasePageSetter):
|
||||
|
||||
def encoding(self, encoding: Union[str, None], set_all: bool = True) -> None: ...
|
||||
|
||||
def headers(self, headers: dict) -> None: ...
|
||||
def headers(self, headers: Union[str, dict]) -> None: ...
|
||||
|
||||
def header(self, name: str, value: str) -> None: ...
|
||||
|
||||
@ -139,26 +139,26 @@ class SessionPageSetter(BasePageSetter):
|
||||
|
||||
|
||||
class WebPageSetter(ChromiumPageSetter):
|
||||
_page: WebPage = ...
|
||||
_owner: WebPage = ...
|
||||
_session_setter: SessionPageSetter = ...
|
||||
_chromium_setter: ChromiumPageSetter = ...
|
||||
|
||||
def user_agent(self, ua: str, platform: str = None) -> None: ...
|
||||
|
||||
def headers(self, headers: dict) -> None: ...
|
||||
def headers(self, headers: Union[str, dict]) -> None: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> WebPageCookiesSetter: ...
|
||||
|
||||
|
||||
class WebPageTabSetter(TabSetter):
|
||||
_page: WebPage = ...
|
||||
_owner: WebPageTab = ...
|
||||
_session_setter: SessionPageSetter = ...
|
||||
_chromium_setter: ChromiumBaseSetter = ...
|
||||
|
||||
def user_agent(self, ua: str, platform: str = None) -> None: ...
|
||||
|
||||
def headers(self, headers: dict) -> None: ...
|
||||
def headers(self, headers: Union[str, dict]) -> None: ...
|
||||
|
||||
@property
|
||||
def cookies(self) -> WebPageCookiesSetter: ...
|
||||
@ -178,14 +178,14 @@ class ChromiumElementSetter(object):
|
||||
|
||||
|
||||
class ChromiumFrameSetter(ChromiumBaseSetter):
|
||||
_page: ChromiumFrame = ...
|
||||
_owner: ChromiumFrame = ...
|
||||
|
||||
def attr(self, name: str, value: str) -> None: ...
|
||||
|
||||
|
||||
class LoadMode(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
|
||||
def __call__(self, value: str) -> None: ...
|
||||
|
||||
@ -206,8 +206,8 @@ class PageScrollSetter(object):
|
||||
|
||||
|
||||
class WindowSetter(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
self._window_id: str = ...
|
||||
|
||||
def max(self) -> None: ...
|
||||
@ -226,10 +226,6 @@ class WindowSetter(object):
|
||||
|
||||
def _perform(self, bounds: dict) -> None: ...
|
||||
|
||||
|
||||
class PageWindowSetter(WindowSetter):
|
||||
_page: ChromiumPage = ...
|
||||
|
||||
def hide(self) -> None: ...
|
||||
|
||||
def show(self) -> None: ...
|
||||
|
@ -105,22 +105,22 @@ class ShadowRootStates(object):
|
||||
class PageStates(object):
|
||||
"""Page对象、Tab对象使用"""
|
||||
|
||||
def __init__(self, page):
|
||||
def __init__(self, owner):
|
||||
"""
|
||||
:param page: ChromiumBase对象
|
||||
:param owner: ChromiumBase对象
|
||||
"""
|
||||
self._page = page
|
||||
self._owner = owner
|
||||
|
||||
@property
|
||||
def is_loading(self):
|
||||
"""返回页面是否在加载状态"""
|
||||
return self._page._is_loading
|
||||
return self._owner._is_loading
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""返回页面对象是否仍然可用"""
|
||||
try:
|
||||
self._page.run_cdp('Page.getLayoutMetrics')
|
||||
self._owner.run_cdp('Page.getLayoutMetrics')
|
||||
return True
|
||||
except PageDisconnectedError:
|
||||
return False
|
||||
@ -128,12 +128,12 @@ class PageStates(object):
|
||||
@property
|
||||
def ready_state(self):
|
||||
"""返回当前页面加载状态,'connecting' 'loading' 'interactive' 'complete'"""
|
||||
return self._page._ready_state
|
||||
return self._owner._ready_state
|
||||
|
||||
@property
|
||||
def has_alert(self):
|
||||
"""返回当前页面是否存在弹窗"""
|
||||
return self._page._has_alert
|
||||
return self._owner._has_alert
|
||||
|
||||
|
||||
class FrameStates(object):
|
||||
|
@ -59,8 +59,8 @@ class ShadowRootStates(object):
|
||||
|
||||
|
||||
class PageStates(object):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._page: ChromiumBase = ...
|
||||
def __init__(self, owner: ChromiumBase):
|
||||
self._owner: ChromiumBase = ...
|
||||
|
||||
@property
|
||||
def is_loading(self) -> bool: ...
|
||||
|
@ -7,17 +7,12 @@
|
||||
"""
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from .._functions.locator import get_loc
|
||||
from .._functions.settings import Settings
|
||||
from ..errors import WaitTimeoutError, NoRectError
|
||||
|
||||
|
||||
class BaseWaiter(object):
|
||||
def __init__(self, page_or_ele):
|
||||
"""
|
||||
:param page_or_ele: 页面对象或元素对象
|
||||
"""
|
||||
self._driver = page_or_ele
|
||||
|
||||
class OriginWaiter(object):
|
||||
def __call__(self, second, scope=None):
|
||||
"""等待若干秒,如传入两个参数,等待时间为这两个数间的一个随机数
|
||||
:param second: 秒数
|
||||
@ -30,6 +25,14 @@ class BaseWaiter(object):
|
||||
from random import uniform
|
||||
sleep(uniform(second, scope))
|
||||
|
||||
|
||||
class BaseWaiter(OriginWaiter):
|
||||
def __init__(self, page_or_ele):
|
||||
"""
|
||||
:param page_or_ele: 页面对象或元素对象
|
||||
"""
|
||||
self._driver = page_or_ele
|
||||
|
||||
def ele_deleted(self, loc_or_ele, timeout=None, raise_err=None):
|
||||
"""等待元素从DOM中删除
|
||||
:param loc_or_ele: 要等待的元素,可以是已有元素、定位符
|
||||
@ -78,18 +81,53 @@ class BaseWaiter(object):
|
||||
return False
|
||||
return ele.wait.hidden(timeout, raise_err=raise_err)
|
||||
|
||||
def ele_loaded(self, locator, timeout=None, raise_err=None):
|
||||
"""等待元素加载到DOM
|
||||
:param locator: 要等待的元素,输入定位符
|
||||
def eles_loaded(self, locators, timeout=None, any_one=False, raise_err=None):
|
||||
"""等待元素加载到DOM,可等待全部或任意一个
|
||||
:param locators: 要等待的元素,输入定位符,用list输入多个
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param any_one: 是否等待到一个就返回
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
:return: 成功返回True,失败返回False
|
||||
"""
|
||||
ele = self._driver._ele(locator, raise_err=False, timeout=timeout)
|
||||
if ele:
|
||||
return ele
|
||||
|
||||
def _find(loc, driver):
|
||||
r = driver.run('DOM.performSearch', query=loc, includeUserAgentShadowDOM=True)
|
||||
if not r or 'error' in r:
|
||||
return False
|
||||
elif r['resultCount'] == 0:
|
||||
driver.run('DOM.discardSearchResults', searchId=r['searchId'])
|
||||
return False
|
||||
searchId = r['searchId']
|
||||
ids = driver.run('DOM.getSearchResults', searchId=searchId, fromIndex=0,
|
||||
toIndex=r['resultCount'])
|
||||
if 'error' in ids:
|
||||
return False
|
||||
|
||||
ids = ids['nodeIds']
|
||||
res = False
|
||||
for i in ids:
|
||||
r = driver.run('DOM.describeNode', nodeId=i)
|
||||
if 'error' in r or r['node']['nodeName'] in ('#text', '#comment'):
|
||||
continue
|
||||
else:
|
||||
res = True
|
||||
break
|
||||
driver.run('DOM.discardSearchResults', searchId=searchId)
|
||||
return res
|
||||
|
||||
by = ('id', 'xpath', 'link text', 'partial link text', 'name', 'tag name', 'class name', 'css selector')
|
||||
locators = ((get_loc(locators)[1],) if (isinstance(locators, str) or isinstance(locators, tuple)
|
||||
and locators[0] in by and len(locators) == 2)
|
||||
else [get_loc(l)[1] for l in locators])
|
||||
timeout = self._driver.timeout if timeout is None else timeout
|
||||
end_time = perf_counter() + timeout
|
||||
method = any if any_one else all
|
||||
while perf_counter() < end_time:
|
||||
if method([_find(l, self._driver.driver) for l in locators]):
|
||||
return True
|
||||
sleep(.01)
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(f'等待元素加载失败(等待{timeout}秒)。')
|
||||
raise WaitTimeoutError(f'等待元素{locators}加载失败(等待{timeout}秒)。')
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -125,7 +163,7 @@ class BaseWaiter(object):
|
||||
:return: 成功返回任务对象,失败返回False
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
raise RuntimeError('使用下载管理功能前需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
self._driver.browser._dl_mgr.set_flag(self._driver.tab_id, False if cancel_it else True)
|
||||
if timeout is None:
|
||||
timeout = self._driver.timeout
|
||||
@ -137,6 +175,7 @@ class BaseWaiter(object):
|
||||
if not isinstance(v, bool):
|
||||
r = v
|
||||
break
|
||||
sleep(.005)
|
||||
|
||||
self._driver.browser._dl_mgr.set_flag(self._driver.tab_id, None)
|
||||
return r
|
||||
@ -230,6 +269,21 @@ class BaseWaiter(object):
|
||||
"""
|
||||
return self._loading(timeout=timeout, start=False, raise_err=raise_err)
|
||||
|
||||
def ele_loaded(self, locator, timeout=None, raise_err=None):
|
||||
"""等待元素加载到DOM
|
||||
:param locator: 要等待的元素,输入定位符
|
||||
:param timeout: 超时时间,默认读取页面超时时间
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 成功返回元素对象,失败返回False
|
||||
"""
|
||||
ele = self._driver._ele(locator, raise_err=False, timeout=timeout)
|
||||
if ele:
|
||||
return ele
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
raise WaitTimeoutError(f'等待元素加载失败(等待{timeout}秒)。')
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class TabWaiter(BaseWaiter):
|
||||
|
||||
@ -240,7 +294,7 @@ class TabWaiter(BaseWaiter):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
raise RuntimeError('使用下载管理功能前需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
if not timeout:
|
||||
while self._driver.browser._dl_mgr.get_tab_missions(self._driver.tab_id):
|
||||
sleep(.5)
|
||||
@ -272,7 +326,6 @@ class TabWaiter(BaseWaiter):
|
||||
class PageWaiter(TabWaiter):
|
||||
def __init__(self, page):
|
||||
super().__init__(page)
|
||||
# self._listener = None
|
||||
|
||||
def new_tab(self, timeout=None, raise_err=None):
|
||||
"""等待新标签页出现
|
||||
@ -283,9 +336,9 @@ class PageWaiter(TabWaiter):
|
||||
timeout = timeout if timeout is not None else self._driver.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
latest_tab = self._driver.latest_tab
|
||||
if self._driver.tab_id != latest_tab:
|
||||
return latest_tab
|
||||
latest_tid = self._driver.tab_ids[0]
|
||||
if self._driver.tab_id != latest_tid:
|
||||
return latest_tid
|
||||
sleep(.01)
|
||||
|
||||
if raise_err is True or Settings.raise_when_wait_failed is True:
|
||||
@ -300,7 +353,7 @@ class PageWaiter(TabWaiter):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if not self._driver.browser._dl_mgr._running:
|
||||
raise RuntimeError('使用下载管理功能前需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
raise RuntimeError('此功能需显式设置下载路径(使用set.download_path()方法、配置对象或ini文件均可)。')
|
||||
if not timeout:
|
||||
while self._driver.browser._dl_mgr._missions:
|
||||
sleep(.5)
|
||||
@ -322,29 +375,17 @@ class PageWaiter(TabWaiter):
|
||||
return True
|
||||
|
||||
|
||||
class ElementWaiter(object):
|
||||
class ElementWaiter(OriginWaiter):
|
||||
"""等待元素在dom中某种状态,如删除、显示、隐藏"""
|
||||
|
||||
def __init__(self, page, ele):
|
||||
def __init__(self, owner, ele):
|
||||
"""等待元素在dom中某种状态,如删除、显示、隐藏
|
||||
:param page: 元素所在页面
|
||||
:param owner: 元素所在页面
|
||||
:param ele: 要等待的元素
|
||||
"""
|
||||
self._page = page
|
||||
self._owner = owner
|
||||
self._ele = ele
|
||||
|
||||
def __call__(self, second, scope=None):
|
||||
"""等待若干秒,如传入两个参数,等待时间为这两个数间的一个随机数
|
||||
:param second: 秒数
|
||||
:param scope: 随机数范围
|
||||
:return: None
|
||||
"""
|
||||
if scope is None:
|
||||
sleep(second)
|
||||
else:
|
||||
from random import uniform
|
||||
sleep(uniform(second, scope))
|
||||
|
||||
def deleted(self, timeout=None, raise_err=None):
|
||||
"""等待元素从dom删除
|
||||
:param timeout: 超时时间,为None使用元素所在页面timeout属性
|
||||
@ -408,7 +449,7 @@ class ElementWaiter(object):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self._page.timeout
|
||||
timeout = self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
if not self._ele.states.is_enabled or not self._ele.states.is_alive:
|
||||
@ -428,7 +469,7 @@ class ElementWaiter(object):
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self._page.timeout
|
||||
timeout = self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
try:
|
||||
@ -437,6 +478,7 @@ class ElementWaiter(object):
|
||||
break
|
||||
except NoRectError:
|
||||
pass
|
||||
sleep(.005)
|
||||
else:
|
||||
raise NoRectError
|
||||
|
||||
@ -458,7 +500,7 @@ class ElementWaiter(object):
|
||||
:param raise_err: 等待失败时是否报错,为None时根据Settings设置
|
||||
:return: 是否等待成功
|
||||
"""
|
||||
return self._wait_state('has_rect', True, timeout, raise_err, err_text='等待元素拥有大小及位置属性失败(等待{}秒)。')
|
||||
return self._wait_state('has_rect', True, timeout, raise_err, err_text='等待元素拥有大小及位置失败(等{}秒)。')
|
||||
|
||||
def _wait_state(self, attr, mode=False, timeout=None, raise_err=None, err_text=None):
|
||||
"""等待元素某个元素状态到达指定状态
|
||||
@ -471,7 +513,7 @@ class ElementWaiter(object):
|
||||
"""
|
||||
err_text = err_text or '等待元素状态改变失败(等待{}秒)。'
|
||||
if timeout is None:
|
||||
timeout = self._page.timeout
|
||||
timeout = self._owner.timeout
|
||||
end_time = perf_counter() + timeout
|
||||
while perf_counter() < end_time:
|
||||
a = self._ele.states.__getattribute__(attr)
|
||||
|
@ -14,7 +14,11 @@ from .._pages.chromium_frame import ChromiumFrame
|
||||
from .._pages.chromium_page import ChromiumPage
|
||||
|
||||
|
||||
class BaseWaiter(object):
|
||||
class OriginWaiter(object):
|
||||
def __call__(self, second: float, scope: float = None) -> None: ...
|
||||
|
||||
|
||||
class BaseWaiter(OriginWaiter):
|
||||
def __init__(self, page: ChromiumBase):
|
||||
self._driver: ChromiumBase = ...
|
||||
|
||||
@ -33,10 +37,11 @@ class BaseWaiter(object):
|
||||
def ele_hidden(self, loc_or_ele: Union[str, tuple, ChromiumElement], timeout: float = None,
|
||||
raise_err: bool = None) -> bool: ...
|
||||
|
||||
def ele_loaded(self,
|
||||
locator: Union[Tuple[str, str], str],
|
||||
timeout: float = None,
|
||||
raise_err: bool = None) -> Union[bool, ChromiumElement]: ...
|
||||
def eles_loaded(self,
|
||||
locators: Union[Tuple[str, str], str, list, tuple],
|
||||
timeout: float = None,
|
||||
any_one: bool = False,
|
||||
raise_err: bool = None) -> bool: ...
|
||||
|
||||
def _loading(self, timeout: float = None, start: bool = True, gap: float = .01, raise_err: bool = None) -> bool: ...
|
||||
|
||||
@ -73,10 +78,10 @@ class PageWaiter(TabWaiter):
|
||||
def all_downloads_done(self, timeout: float = None, cancel_if_timeout: bool = True) -> bool: ...
|
||||
|
||||
|
||||
class ElementWaiter(object):
|
||||
def __init__(self, page: ChromiumBase, ele: ChromiumElement):
|
||||
class ElementWaiter(OriginWaiter):
|
||||
def __init__(self, owner: ChromiumBase, ele: ChromiumElement):
|
||||
self._ele: ChromiumElement = ...
|
||||
self._page: ChromiumBase = ...
|
||||
self._owner: ChromiumBase = ...
|
||||
|
||||
def __call__(self, second: float, scope: float = None) -> None: ...
|
||||
|
||||
|
@ -11,6 +11,41 @@ from ._functions.keys import Keys
|
||||
from ._functions.settings import Settings
|
||||
from ._functions.tools import wait_until, configs_to_here
|
||||
from ._functions.web import get_blob, tree
|
||||
from ._pages.chromium_page import ChromiumPage
|
||||
from ._units.actions import Actions
|
||||
|
||||
__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here', 'get_blob', 'tree']
|
||||
__all__ = ['make_session_ele', 'Actions', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here', 'get_blob',
|
||||
'tree', 'from_selenium', 'from_playwright']
|
||||
|
||||
|
||||
def from_selenium(driver):
|
||||
"""从selenium的WebDriver对象生成ChromiumPage对象"""
|
||||
address, port = driver.caps.get('goog:chromeOptions', {}).get('debuggerAddress', ':').split(':')
|
||||
if not address:
|
||||
raise RuntimeError('获取失败。')
|
||||
return ChromiumPage(f'{address}:{port}')
|
||||
|
||||
|
||||
def from_playwright(page_or_browser):
|
||||
"""从playwright的Page或Browser对象生成ChromiumPage对象"""
|
||||
if hasattr(page_or_browser, 'context'):
|
||||
page_or_browser = page_or_browser.context.browser
|
||||
try:
|
||||
processes = page_or_browser.new_browser_cdp_session().send('SystemInfo.getProcessInfo')['processInfo']
|
||||
for process in processes:
|
||||
if process['type'] == 'browser':
|
||||
pid = process['id']
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('获取失败。')
|
||||
except:
|
||||
raise RuntimeError('获取失败。')
|
||||
|
||||
from psutil import net_connections
|
||||
for con_info in net_connections():
|
||||
if con_info.pid == pid:
|
||||
port = con_info.laddr.port
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('获取失败。')
|
||||
return ChromiumPage(f'127.0.0.1:{port}')
|
||||
|
103
README.en.md
Normal file
103
README.en.md
Normal file
@ -0,0 +1,103 @@
|
||||
# ✨️ Overview
|
||||
|
||||
DrissionPage is a python-based web page automation tool.
|
||||
|
||||
It can control the browser, send and receive data packets, and combine the two into one.
|
||||
|
||||
It can take into account the convenience of browser automation and the high efficiency of requests.
|
||||
|
||||
It is powerful and has countless built-in user-friendly designs and convenient functions.
|
||||
|
||||
Its syntax is concise and elegant, the amount of code is small, and it is friendly to novices.
|
||||
|
||||
---
|
||||
|
||||
<a href='https://gitee.com/g1879/DrissionPage/stargazers'><img src='https://gitee.com/g1879/DrissionPage/badge/star.svg?theme=dark' alt=' star'></img></a>
|
||||
|
||||
Project address: [gitee](https://gitee.com/g1879/DrissionPage) | [github](https://github.com/g1879/DrissionPage)
|
||||
|
||||
Your star is my greatest support💖
|
||||
|
||||
---
|
||||
|
||||
Supported systems: Windows, Linux, Mac
|
||||
|
||||
python version: 3.6 and above
|
||||
|
||||
Supported browsers: Chromium core browsers (such as Chrome and Edge), electron applications
|
||||
|
||||
---
|
||||
|
||||
# 🛠 How to use
|
||||
|
||||
**📖 Usage documentation:** [Click to view](https://drissionpage.cn)
|
||||
|
||||
**Communication QQ group:** 636361957
|
||||
|
||||
---
|
||||
|
||||
# 📕 background
|
||||
|
||||
When using requests for data collection, when facing a website to log in to, you have to analyze data packets and JS source code, construct complex requests, and often have to deal with anti-crawling methods such as verification codes, JS obfuscation, and signature parameters. The threshold is high and the development efficiency is low. high.
|
||||
Using a browser can largely bypass these pitfalls, but the browser is not very efficient.
|
||||
|
||||
Therefore, the original intention of this library is to combine them into one and achieve "fast writing" and "fast running" at the same time. It can switch the corresponding mode when different needs are needed, and provide a humanized usage method to improve development and operation efficiency.
|
||||
In addition to merging the two, this library also encapsulates commonly used functions in web page units, providing very simple operations and statements, allowing users to reduce considerations of details and focus on function implementation. Implement powerful functions in a simple way and make your code more elegant.
|
||||
|
||||
The previous version was implemented by repackaging selenium. Starting from 3.0, the author started from scratch, redeveloped the bottom layer, got rid of the dependence on selenium, enhanced functions, and improved operating efficiency.
|
||||
|
||||
---
|
||||
|
||||
# 💡 Concept
|
||||
|
||||
Simple yet powerful!
|
||||
|
||||
---
|
||||
|
||||
# ☀️ Features and Highlights
|
||||
|
||||
After long-term practice, the author has stepped through countless pitfalls, and all the experiences he has summarized have been written down in this library.
|
||||
|
||||
## 🎇 Powerful self-developed core
|
||||
|
||||
This library uses a fully self-developed kernel, has built-in N number of practical functions, and has integrated and optimized common functions. Compared with selenium, it has the following advantages:
|
||||
|
||||
- Not base on webdriver
|
||||
- No need to download different drivers for different browser versions
|
||||
- Runs faster
|
||||
- Can find elements across `<iframe>` without switching in and out
|
||||
- Treat `<iframe>` as a normal element. After obtaining it, you can directly search for elements in it, making the logic clearer.
|
||||
- You can operate multiple tabs in the browser at the same time, even if the tab is inactive, no need to switch
|
||||
- Can directly read the browser cache to save images without using the GUI to click save
|
||||
- You can take screenshots of the entire web page, including parts outside the viewport (supported by browsers 90 and above)
|
||||
- Can handle shadow-root in non-open state
|
||||
|
||||
## 🎇 Highlighted features
|
||||
|
||||
In addition to the above advantages, this library also has numerous built-in humanized designs.
|
||||
|
||||
- Minimalist grammar rules. Integrate a large number of commonly used functions to make the code more elegant
|
||||
- Positioning elements is easier and the function is more powerful and stable
|
||||
- Ubiquitous wait and auto-retry functionality. Make unstable networks easier to control, programs more stable, and writing more worry-free
|
||||
- Provide powerful download tools. You can also enjoy fast and reliable download functions when operating the browser
|
||||
- Allows repeated use of already open browsers. No need to start the browser from scratch every time, making debugging very convenient
|
||||
- Use ini files to save commonly used configurations and call them automatically, providing convenient settings and staying away from complicated configuration items.
|
||||
- Built-in lxml as a parsing engine, the parsing speed is improved by several orders of magnitude
|
||||
- Encapsulated using POM mode, which can be directly used for testing and easy to expand.
|
||||
- Highly integrated convenient functions, reflected in every detail
|
||||
- There are many details, so I won’t list them all here. You are welcome to experience them in actual use:)
|
||||
|
||||
---
|
||||
|
||||
# 🖐🏻 Disclaimer
|
||||
|
||||
Please do not apply DrissionPage to any work that may violate legal regulations and moral constraints. Please use DrissionPage in a friendly manner, comply with the spider agreement, and do not use DrissionPage for any illegal purposes. If you choose to use DrissionPage
|
||||
This means that you abide by this agreement. The author does not bear any legal risks and losses caused by your violation of this agreement. You will be responsible for all consequences.
|
||||
|
||||
---
|
||||
|
||||
# ☕ Buy me coffee
|
||||
|
||||
If this project is helpful to you, why not buy the author a cup of coffee :)
|
||||
|
||||

|
49
README.md
49
README.md
@ -12,6 +12,8 @@ DrissionPage 是一个基于 python 的网页自动化工具。
|
||||
|
||||
---
|
||||
|
||||
官方网站:[https://drissionpage.cn](https://drissionpage.cn)
|
||||
|
||||
<a href='https://gitee.com/g1879/DrissionPage/stargazers'><img src='https://gitee.com/g1879/DrissionPage/badge/star.svg?theme=dark' alt='star'></img></a> <a href='https://gitee.com/g1879/DrissionPage/members'><img src='https://gitee.com/g1879/DrissionPage/badge/fork.svg?theme=dark' alt='fork'></img></a>
|
||||
|
||||
项目地址:[gitee](https://gitee.com/g1879/DrissionPage) | [github](https://github.com/g1879/DrissionPage)
|
||||
@ -36,18 +38,6 @@ python 版本:3.6 及以上
|
||||
|
||||
---
|
||||
|
||||
# 📕 背景
|
||||
|
||||
用 requests 做数据采集面对要登录的网站时,要分析数据包、JS 源码,构造复杂的请求,往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高,开发效率不高。
|
||||
使用浏览器,可以很大程度上绕过这些坑,但浏览器运行效率不高。
|
||||
|
||||
因此,这个库设计初衷,是将它们合而为一,同时实现“写得快”和“跑得快”。能够在不同需要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。
|
||||
除了合并两者,本库还以网页为单位封装了常用功能,提供非常简便的操作和语句,使用户可减少考虑细节,专注功能实现。 以简单的方式实现强大的功能,使代码更优雅。
|
||||
|
||||
以前的版本是对 selenium 进行重新封装实现的。从 3.0 开始,作者另起炉灶,对底层进行了重新开发,摆脱对 selenium 的依赖,增强了功能,提升了运行效率。
|
||||
|
||||
---
|
||||
|
||||
# 💡 理念
|
||||
|
||||
简洁而强大!
|
||||
@ -60,24 +50,16 @@ python 版本:3.6 及以上
|
||||
|
||||
## 🎇 强大的自研内核
|
||||
|
||||
本库采用全自研的内核,内置了 N 多实用功能,对常用功能作了整合和优化,对比 selenium,有以下优点:
|
||||
|
||||
- 无 webdriver 特征
|
||||
本库采用全自研的内核,内置无数实用功能,对常用功能作了整合和优化,对比 selenium,有以下优点:
|
||||
|
||||
- 不基于 webdriver
|
||||
- 无需为不同版本的浏览器下载不同的驱动
|
||||
|
||||
- 运行速度更快
|
||||
|
||||
- 可以跨`<iframe>`查找元素,无需切入切出
|
||||
|
||||
- 把`<iframe>`看作普通元素,获取后可直接在其中查找元素,逻辑更清晰
|
||||
|
||||
- 可以同时操作浏览器中的多个标签页,即使标签页为非激活状态,无需切换
|
||||
|
||||
- 可以直接读取浏览器缓存来保存图片,无需用 GUI 点击另存
|
||||
|
||||
- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持)
|
||||
|
||||
- 可处理非`open`状态的 shadow-root
|
||||
|
||||
## 🎇 亮点功能
|
||||
@ -85,37 +67,24 @@ python 版本:3.6 及以上
|
||||
除了以上优点,本库还内置了无数人性化设计。
|
||||
|
||||
- 极简的语法规则。集成大量常用功能,代码更优雅
|
||||
|
||||
- 定位元素更加容易,功能更强大稳定
|
||||
|
||||
- 无处不在的等待和自动重试功能。使不稳定的网络变得易于控制,程序更稳定,编写更省心
|
||||
|
||||
- 提供强大的下载工具。操作浏览器时也能享受快捷可靠的下载功能
|
||||
|
||||
- 允许反复使用已经打开的浏览器。无须每次运行从头启动浏览器,调试超方便
|
||||
|
||||
- 使用 ini 文件保存常用配置,自动调用,提供便捷的设置,远离繁杂的配置项
|
||||
|
||||
- 内置 lxml 作为解析引擎,解析速度成几个数量级提升
|
||||
|
||||
- 使用 POM 模式封装,可直接用于测试,便于扩展
|
||||
|
||||
- 高度集成的便利功能,从每个细节中体现
|
||||
|
||||
- 还有很多细节,这里不一一列举,欢迎实际使用中体验:)
|
||||
|
||||
---
|
||||
|
||||
# 🔖 版本历史
|
||||
|
||||
[点击查看版本历史](https://g1879.gitee.io/drissionpagedocs/history/introduction/)
|
||||
|
||||
---
|
||||
|
||||
# 🖐🏻 免责声明
|
||||
|
||||
请勿将 DrissionPage 应用到任何可能会违反法律规定和道德约束的工作中,请友善使用 DrissionPage,遵守蜘蛛协议,不要将 DrissionPage 用于任何非法用途。如您选择使用 DrissionPage
|
||||
即代表您遵守此协议,作者不承担任何由于您违反此协议带来任何的法律风险和损失,一切后果由您承担。
|
||||
禁止将 DrissionPage 应用到任何可能会违反法律规定和道德约束的项目中。
|
||||
友善使用 DrissionPage,遵守蜘蛛协议,禁止将 DrissionPage 用于任何可能有损他人的项目中。
|
||||
如您选择使用 DrissionPage 即代表您遵守此协议,作者不承担任何由于您违反此协议带来任何的法律风险和损失。
|
||||
同时,作者不对 DrissionPage 可能存在的缺陷导致的损失承担任何责任,一切后果由您承担。
|
||||
|
||||
---
|
||||
|
||||
@ -123,4 +92,4 @@ python 版本:3.6 及以上
|
||||
|
||||
如果本项目对您有所帮助,不妨请作者我喝杯咖啡 :)
|
||||
|
||||

|
||||

|
||||
|
Loading…
x
Reference in New Issue
Block a user