Compare commits

...

32 Commits

Author SHA1 Message Date
g1879
1de7c66f42
!53 4.1.0.13
Merge pull request !53 from g1879/dev
2024-12-06 11:08:20 +00:00
g1879
c84953c5de 修改readme 2024-12-06 19:00:40 +08:00
g1879
a962fbe9cd 4.1.0.13修复js录像报错问题 2024-12-06 18:29:35 +08:00
g1879
230615a128 修改LICENSE 2024-12-06 07:17:08 +08:00
g1879
e19b13734d 降低循环等待间隔 2024-12-03 18:13:14 +08:00
g1879
0d9951f516 ChromiumFrame补充style();DownloadKit指定2.0.7或以上 2024-12-01 22:37:38 +08:00
g1879
2db39d3176
update .gitee/ISSUE_TEMPLATE.zh-CN.md.
Signed-off-by: g1879 <g1879@qq.com>
2024-11-30 02:42:38 +00:00
g1879
ee3038c74d 微调 2024-11-28 21:27:44 +08:00
g1879
d6402c26c8 4.1.0.12修复无法处理连续弹框问题;修复新建tab可能出现的问题;指定tldextract版本 2024-11-13 16:13:57 +08:00
g1879
7a5cc465db 4.1.0.11(+)
适配130浏览器;
修复下载问题;
优化下载信息显示;
WebPage的post()会返回Response对象;
click.to_download()恢复new_tab参数,默认None;
2024-11-08 10:31:17 +08:00
g1879
9d3b7f3ec1 click.to_download()删除new_tab参数;优化下载逻辑 2024-10-24 18:14:11 +08:00
g1879
ce9b17e25d 优化下载逻辑,待测试 2024-10-23 22:46:36 +08:00
g1879
5423976bb2 优化下载逻辑,待测试 2024-10-23 18:30:03 +08:00
g1879
4407a0daa1 一些修改(+)
增加Settings.browser_connect_timeout属性;
优化关闭标签页逻辑;
修复下载路径设置问题;
点击产生的新标签页下载任务可用原标签页等待;
remove_attr()返回元素自身;
select各种方法返回元素本身,找不到项时报错;
2024-10-21 07:10:10 +08:00
g1879
8d37aa079e 4.1.0.10修复文件下载重命名设置;Settings.locate_suffixes_list改为Settings.suffixes_list_path 2024-10-18 00:24:55 +08:00
g1879
e4e1affd43 4.1.0.9优化下载逻辑 2024-10-17 17:35:50 +08:00
g1879
e2653ed3b0 优化下载逻辑,修复tab只设置文件名时会下载到根目录的问题 2024-10-16 17:56:27 +08:00
g1879
da171e49c9 调整下载逻辑,未完成 2024-10-15 23:12:02 +08:00
g1879
463bdaa912 优化ChromiumPage下载逻辑,未完成 2024-10-15 17:51:45 +08:00
g1879
b20018072f Merge remote-tracking branch 'origin/dev' into dev 2024-10-15 15:14:43 +08:00
g1879
421bbc470b 支持离线运行;动作链type()增加interval参数 2024-10-15 15:14:00 +08:00
g1879
6a3918756e 4.1.0.8修复一个headers设置问题;修复多线程关闭tab时可能报错问题 2024-10-14 23:44:56 +08:00
g1879
b854df764c close_tabs()的tabs_or_ids参数改为必填 2024-10-14 17:44:44 +08:00
g1879
e6df59e958 修复Page对象设置下载路径问题;
优化等待新tab逻辑;
接管来自selenium和playwright的浏览器时忽略无头设置;
优化关闭tab逻辑
2024-10-14 00:02:29 +08:00
g1879
65d561e079 修复Page对象设置下载路径问题;
优化等待新tab逻辑;
接管来自selenium和playwright的浏览器时忽略无头设置;
优化关闭tab逻辑
2024-10-14 00:01:59 +08:00
g1879
6961fa07ff Page对象加上几种浏览器状态 2024-10-11 16:54:58 +08:00
g1879
8d1d3b554d 优化css_path 2024-10-09 14:45:17 +08:00
g1879
25dd981b25 Merge remote-tracking branch 'origin/dev' into dev 2024-10-08 17:25:25 +08:00
g1879
c6941438d5 修复new_tab()时浏览器关闭导致的卡住 2024-10-08 17:24:57 +08:00
g1879
67e8ede874
!48 v4.0.5.4
Merge pull request !48 from g1879/dev
2024-06-28 03:31:49 +00:00
g1879
21b391ef94
!47 v4.0.5.3
Merge pull request !47 from g1879/dev
2024-06-25 08:25:34 +00:00
g1879
7455314639
!45 v4.0.4.21
Merge pull request !45 from g1879/dev
2024-04-07 11:42:27 +00:00
86 changed files with 12492 additions and 504 deletions

View File

@ -1,12 +1,15 @@
在提交issue前请确认已经给本库点了星星这对我来说很重要。
使用方法请查看[使用文档](http://drissionpage.cn),文档里都有。
也可在QQ群里提问636361957
使用问题作者可能不能及时处理,可在知识星球提问,会尽快回复。
![](https://drissionpage.cn/zsxq.png)
请围绕以下内容陈述您的问题:
1. 遇到了什么问题?什么场景下出现的?如何重现?
2. 请附上代码和报错信息(如有)
2. 请附上代码和报错信息
3. DrissionPage、浏览器、python版本号是多少
4. 有什么意见建议?

View File

@ -2,8 +2,21 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
允许任何人以个人身份使用或分发本项目源代码但仅限于学习和合法非盈利目的
个人或组织如未获得版权持有人授权不得将本项目以源代码或二进制形式用于商业行为
使用本项目需满足以下条款如使用过程中出现违反任意一项条款的情形授权自动失效
* 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中
* 禁止将DrissionPage用于任何可能有损他人利益的项目中
* 禁止将DrissionPage用于攻击与骚扰行为
* 遵守Robots协议禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据
使用DrissionPage发生的一切行为均由使用人自行负责
因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关
版权持有人不承担任何使用DrissionPage带来的风险和损失
版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任
"""
from ._base.chromium import Chromium
from ._configs.chromium_options import ChromiumOptions
@ -12,4 +25,4 @@ from ._pages.chromium_page import ChromiumPage
from ._pages.session_page import SessionPage
from ._pages.web_page import WebPage
__version__ = '4.1.0.7'
__version__ = '4.1.0.13'

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from ._base.chromium import Chromium
from ._configs.chromium_options import ChromiumOptions

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from abc import abstractmethod
from copy import copy
@ -296,7 +295,7 @@ class BasePage(BaseParser):
if self._DownloadKit is None:
if not self._session:
self._create_session()
self._DownloadKit = DownloadKit(driver=self, goal_path=self.download_path)
self._DownloadKit = DownloadKit(driver=self, save_path=self.download_path)
return self._DownloadKit
def _before_connect(self, url, retry, interval):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from abc import abstractmethod
from typing import Union, Tuple, List, Any, Optional, Dict

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from re import match
@ -45,7 +44,7 @@ class Chromium(object):
if browser_id in cls._BROWSERS:
r = cls._BROWSERS[browser_id]
while not hasattr(r, '_driver'):
sleep(.1)
sleep(.05)
return r
r = object.__new__(cls)
r._chromium_options = opt
@ -64,6 +63,8 @@ class Chromium(object):
self._frames = {}
self._drivers = {}
self._all_drivers = {}
self._relation = {}
self._newest_tab_id = None
self._set = None
self._wait = None
@ -189,34 +190,32 @@ class Chromium(object):
def get_tabs(self, title=None, url=None, tab_type='page'):
return self._get_tabs(title=title, url=url, tab_type=tab_type, mix=True, as_id=False)
def close_tabs(self, tabs_or_ids=None, others=False):
all_tabs = set(self.tab_ids)
def close_tabs(self, tabs_or_ids, others=False):
if isinstance(tabs_or_ids, str):
tabs = {tabs_or_ids}
elif isinstance(tabs_or_ids, ChromiumTab):
tabs = {tabs_or_ids.tab_id}
elif tabs_or_ids is None:
tabs = {self.tab_ids[0]}
elif isinstance(tabs_or_ids, (list, tuple)):
tabs = set(i.tab_id if isinstance(i, ChromiumTab) else i for i in tabs_or_ids)
else:
raise TypeError('tabs_or_ids参数只能传入标签页对象或id。')
all_tabs = set(self.tab_ids)
if others:
tabs = all_tabs - tabs
end_len = len(set(all_tabs) - set(tabs))
if end_len <= 0:
self.quit()
return
if len(all_tabs - tabs) > 0:
for tab in tabs:
self._onTargetDestroyed(targetId=tab)
self._driver.run('Target.closeTarget', targetId=tab)
sleep(.2)
end_time = perf_counter() + 3
while self.tabs_count != end_len and perf_counter() < end_time:
sleep(.1)
self._close_tab(tab=tab)
else:
self.quit()
def _close_tab(self, tab):
if isinstance(tab, str):
tab = self.get_tab(tab)
tab._run_cdp('Target.closeTarget', targetId=tab.tab_id)
while tab.driver.is_running and tab.tab_id in self._all_drivers:
sleep(.01)
def activate_tab(self, id_ind_tab):
if isinstance(id_ind_tab, int):
@ -294,7 +293,7 @@ class Chromium(object):
rmtree(path, True)
def _new_tab(self, mix=True, url=None, new_window=False, background=False, new_context=False):
obj = MixTab if mix else ChromiumTab
tab_type = MixTab if mix else ChromiumTab
tab = None
if new_context:
tab = self._run_cdp('Target.createBrowserContext')['browserContextId']
@ -308,16 +307,20 @@ class Chromium(object):
kwargs['browserContextId'] = tab
if self.states.is_incognito:
return _new_tab_by_js(self, url, obj, new_window)
return _new_tab_by_js(self, url, tab_type, new_window)
else:
try:
tab = self._run_cdp('Target.createTarget', **kwargs)['targetId']
except CDPError:
return _new_tab_by_js(self, url, obj, new_window)
return _new_tab_by_js(self, url, tab_type, new_window)
while tab not in self._drivers:
sleep(.1)
tab = obj(self, tab)
while self.states.is_alive:
if tab in self._drivers:
break
sleep(.01)
else:
raise BrowserConnectError('浏览器已关闭')
tab = tab_type(self, tab)
if url:
tab.get(url)
return tab
@ -357,7 +360,8 @@ class Chromium(object):
raise TypeError('tab_type只能是set、list、tuple、str、None。')
tabs = [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))]
and (tab_type is None or i['type'] in tab_type)
and i['title'] != 'chrome-extension://neajdppkdcdipfabeoofebfddakdcjhd/audio.html')]
if as_id:
return [tab['id'] for tab in tabs]
with self._lock:
@ -369,7 +373,7 @@ class Chromium(object):
def _run_cdp(self, cmd, **cmd_args):
ignore = cmd_args.pop('_ignore', None)
r = self._driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, ignore)
return r if __ERROR__ not in r else raise_error(r, self, ignore)
def _get_driver(self, tab_id, owner=None):
d = self._drivers.pop(tab_id, None)
@ -385,9 +389,12 @@ class Chromium(object):
and not kwargs['targetInfo']['url'].startswith('devtools://')):
try:
tab_id = kwargs['targetInfo']['targetId']
self._frames[tab_id] = tab_id
d = Driver(tab_id, 'page', self.address)
self._relation[tab_id] = kwargs['targetInfo'].get('openerId', None)
self._drivers[tab_id] = d
self._all_drivers.setdefault(tab_id, set()).add(d)
self._newest_tab_id = tab_id
except WebSocketBadStatusException:
pass
@ -400,6 +407,7 @@ class Chromium(object):
d.stop()
self._drivers.pop(tab_id, None)
self._all_drivers.pop(tab_id, None)
self._relation.pop(tab_id, None)
def _on_disconnect(self):
if not self._disconnect_flag:
@ -475,8 +483,8 @@ def run_browser(chromium_options):
return is_headless, browser_id, is_exists
def _new_tab_by_js(browser: Chromium, url, obj, new_window):
mix = isinstance(obj, MixTab)
def _new_tab_by_js(browser: Chromium, url, tab_type, new_window):
mix = tab_type == MixTab
tab = browser._get_tab(mix=mix)
if url and not match(r'^.*?://.*', url):
raise ValueError(f'url也许需要加上http://')

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from threading import Lock
from typing import List, Optional, Set, Dict, Union, Tuple, Literal, Any
@ -12,7 +11,7 @@ from .driver import BrowserDriver, Driver
from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions
from .._functions.cookies import CookiesList
from .._pages.chromium_base import Timeout
from .._pages.chromium_base import Timeout, ChromiumBase
from .._pages.chromium_tab import ChromiumTab
from .._pages.mix_tab import MixTab
from .._units.downloader import DownloadManager
@ -40,6 +39,7 @@ class Chromium(object):
_frames: dict = ...
_drivers: Dict[str, Driver] = ...
_all_drivers: Dict[str, Set[Driver]] = ...
_relation: Dict[str, Optional[str]] = ...
_process_id: Optional[int] = ...
_dl_mgr: DownloadManager = ...
_timeouts: Timeout = ...
@ -51,6 +51,7 @@ class Chromium(object):
_disconnect_flag: bool = ...
_none_ele_return_value: bool = ...
_none_ele_value: Any = ...
_newest_tab_id: Optional[str] = ...
def __new__(cls,
addr_or_opts: Union[str, int, ChromiumOptions] = None,
@ -192,6 +193,12 @@ class Chromium(object):
"""
...
def _close_tab(self, tab: Union[ChromiumBase, str]):
"""关闭一个标签页
:param tab: 标签页对象或id
:return: None
"""
def activate_tab(self, id_ind_tab: Union[int, str, ChromiumTab]) -> None:
"""使一个标签页显示到前端
:param id_ind_tab: 标签页idstrTab对象或标签页序号int序号从1开始

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from json import dumps, loads, JSONDecodeError
from queue import Queue, Empty
@ -199,7 +198,7 @@ class Driver(object):
def stop(self):
self._stop()
while self._handle_event_th.is_alive() or self._recv_th.is_alive():
sleep(.1)
sleep(.01)
return True
def _stop(self):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from queue import Queue
from threading import Thread

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from re import search
@ -336,7 +335,7 @@ class ChromiumOptions(object):
return self
def set_address(self, address):
address = address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://')
address = address.replace('localhost', '127.0.0.1').lstrip('htps:/')
self._address = address
return self

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Any, Literal, Optional, Tuple

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from configparser import RawConfigParser, NoSectionError, NoOptionError
from pathlib import Path

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from configparser import RawConfigParser
from pathlib import Path

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from copy import copy
from pathlib import Path

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from http.cookiejar import CookieJar, Cookie
from pathlib import Path

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from json import loads
from os.path import basename
@ -204,6 +203,8 @@ class ChromiumElement(DrissionElement):
if (is_checked and uncheck) or (not is_checked and not uncheck):
self.click()
return self
def parent(self, level_or_loc=1, index=1, timeout=0):
return super().parent(level_or_loc, index, timeout=timeout)
@ -287,7 +288,7 @@ class ChromiumElement(DrissionElement):
if ele and (loc_data is None or _check_ele(ele, loc_data)):
return ele
sleep(.1)
sleep(.01)
return NoneElement(page=self.owner, method='offset()',
args={'locator': locator, 'offset_x': x, 'offset_y': y, 'timeout': timeout})
@ -399,6 +400,7 @@ class ChromiumElement(DrissionElement):
def remove_attr(self, name):
self._run_js(f'this.removeAttribute("{name}");')
return self
def property(self, name):
try:
@ -448,7 +450,7 @@ class ChromiumElement(DrissionElement):
'&& this.naturalHeight > 0')
end_time = perf_counter() + timeout
while not self._run_js(js) and perf_counter() < end_time:
sleep(.1)
sleep(.05)
src = self.attr('href') if self.tag == 'link' else self.attr('src')
if not src:
@ -485,7 +487,7 @@ class ChromiumElement(DrissionElement):
break
except CDPError:
pass
sleep(.1)
sleep(.05)
if not result:
return None
@ -530,7 +532,7 @@ class ChromiumElement(DrissionElement):
'&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0')
end_time = perf_counter() + self.timeout
while not self._run_js(js) and perf_counter() < end_time:
sleep(.1)
sleep(.05)
if scroll_to_center:
self.scroll.to_see(center=True)
@ -555,7 +557,7 @@ class ChromiumElement(DrissionElement):
vals = ''.join([str(i) for i in vals])
self.set.property('value', str(vals))
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return
return self
self.wait.clickable(wait_moved=False, timeout=.5)
if clear and vals not in ('\n', '\ue007'):
@ -575,7 +577,7 @@ class ChromiumElement(DrissionElement):
if by_js:
self._run_js("this.value='';")
self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));')
return
return self
self._input_focus()
self.input(('\ue009', 'a', '\ue017'), clear=False)
@ -647,7 +649,8 @@ class ChromiumElement(DrissionElement):
txt1 = '''
let i = el.getAttribute("id");
if (i){path = '>' + el.tagName.toLowerCase() + "#" + i + path;
break;}
el = el.parentNode;
continue;}
'''
txt3 = ''
txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;'''
@ -655,7 +658,6 @@ class ChromiumElement(DrissionElement):
js = '''function(){
function e(el) {
//return el;
if (!(el instanceof Element)) return;
let path = '';
while (el.nodeType === Node.ELEMENT_NODE) {
@ -902,7 +904,7 @@ class ShadowRoot(BaseElement):
end_time = perf_counter() + timeout
result = do_find()
while result is None and perf_counter() <= end_time:
sleep(.1)
sleep(.01)
result = do_find()
if result:
@ -1001,7 +1003,7 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True):
end_time = perf_counter() + timeout
result = do_find()
while result is None and perf_counter() < end_time:
sleep(.1)
sleep(.01)
result = do_find()
if result:
@ -1040,7 +1042,7 @@ def find_by_css(ele, selector, index, timeout):
end_time = perf_counter() + timeout
result = do_find()
while result is None and perf_counter() < end_time:
sleep(.1)
sleep(.01)
result = do_find()
if result:
@ -1250,7 +1252,13 @@ def parse_js_result(page, ele, result, end_time):
r = page._run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result']
return [parse_js_result(page, ele, result=i['value'], end_time=end_time) for i in r if i['name'].isdigit()]
elif 'objectId' in result:
elif result.get('className') == 'Blob':
data = page._run_cdp('IO.read',
handle=f"blob:{page._run_cdp('IO.resolveBlob', objectId=result['objectId'])['uuid']}")[
'data']
return data
elif 'objectId' in result and result.get('className') == 'Blob':
timeout = end_time - perf_counter()
if timeout < 0:
return

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Tuple, List, Any, Literal, Optional
@ -377,7 +376,7 @@ class ChromiumElement(DrissionElement):
"""
...
def remove_attr(self, name: str) -> None:
def remove_attr(self, name: str) -> ChromiumElement:
"""删除元素一个attribute属性
:param name: 属性名
:return: None

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from .._functions.settings import Settings
from ..errors import ElementNotFoundError

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Any

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from html import unescape
from re import match, sub, DOTALL, search
@ -149,18 +148,19 @@ class SessionElement(DrissionElement):
path_str = ''
ele = self
while ele:
if xpath:
while ele:
brothers = len(ele.eles(f'xpath:./preceding-sibling::{ele.tag}'))
path_str = f'/{ele.tag}[{brothers + 1}]{path_str}'
ele = ele.parent()
else:
while ele:
id_ = ele.attr('id')
if id_:
path_str = f'>{ele.tag}#{id_}{path_str}'
break
brothers = len(ele.eles(f'xpath:./preceding-sibling::*'))
path_str = f'>{ele.tag}:nth-child({brothers + 1}){path_str}'
else:
path_str = f'>{ele.tag}:nth-child({len(ele.eles("xpath:./preceding-sibling::*")) + 1}){path_str}'
ele = ele.parent()
return path_str if xpath else f'{path_str[1:]}'

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, List, Tuple, Optional

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from json import load, dump, JSONDecodeError
from os import environ
@ -15,6 +14,7 @@ from time import perf_counter, sleep
from requests import Session
from .settings import Settings
from .tools import port_is_using
from .._configs.options_manage import OptionsManager
from ..errors import BrowserConnectError
@ -168,8 +168,8 @@ def set_flags(opt):
dump(states_dict, f)
def test_connect(ip, port, timeout=30):
end_time = perf_counter() + timeout
def test_connect(ip, port):
end_time = perf_counter() + Settings.browser_connect_timeout
s = Session()
s.trust_env = False
s.keep_alive = False

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from click import command, option

View File

@ -2,13 +2,14 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from datetime import datetime
from http.cookiejar import Cookie, CookieJar
from tldextract import extract
from tldextract import TLDExtract
from .settings import Settings
def cookie_to_dict(cookie):
@ -113,7 +114,8 @@ def set_tab_cookies(page, cookies):
url = page._browser_url
if not url.startswith('http'):
raise RuntimeError(f'未设置域名请设置cookie的domain参数或先访问一个网站。{cookie}')
ex_url = extract(url)
ex_url = TLDExtract(suffix_list_urls=["https://publicsuffix.org/list/public_suffix_list.dat",
f"file:///{Settings.suffixes_list_path}"]).extract_str(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)
@ -179,10 +181,8 @@ def format_cookie(cookie):
if 'sameSite' in cookie:
sameSite = cookie['sameSite']
if sameSite in (None, False):
if sameSite in (None, False) or sameSite not in ('None', 'Lax', 'Strict', 'no_restriction'):
cookie.pop('sameSite')
elif sameSite not in ('None', 'Lax', 'Strict'):
raise ValueError(f'{cookie}\nsameSite字段必须为"None""Lax""Strict"之一。')
if 'priority' in cookie:
priority = cookie['priority']

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from http.cookiejar import Cookie
from typing import Union

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import perf_counter, sleep

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, List, Optional, Iterable, Dict

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from platform import system
@ -236,7 +235,7 @@ keyDefinitions = {
'\ue020': {'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6'},
'\ue021': {'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7'},
'\ue022': {'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8'},
'\ue023': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': '\(', 'key': '9'},
'\ue023': {'keyCode': 57, 'code': 'Digit9', 'shiftKey': r'\(', 'key': '9'},
'\ue024': {'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3},
'\ue025': {'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3},
'\ue027': {'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3},

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Tuple, Union, Any

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from re import split
from .by import By
@ -136,14 +135,14 @@ def str_to_xpath_loc(loc):
# 根据文本查找
elif loc.startswith('text='):
loc_str = f'//*[text()={_make_search_str(loc[5:])}]'
loc_str = f'//*[text()={_quotes_escape(loc[5:])}]'
elif loc.startswith('text:') and loc != 'text:':
loc_str = f'//*/text()[contains(., {_make_search_str(loc[5:])})]/..'
loc_str = f'//*/text()[contains(., {_quotes_escape(loc[5:])})]/..'
elif loc.startswith('text^') and loc != 'text^':
loc_str = f'//*/text()[starts-with(., {_make_search_str(loc[5:])})]/..'
loc_str = f'//*/text()[starts-with(., {_quotes_escape(loc[5:])})]/..'
elif loc.startswith('text$') and loc != 'text$':
loc_str = f'//*/text()[substring(., string-length(.) - string-length({_make_search_str(loc[5:])}) +1) = ' \
f'{_make_search_str(loc[5:])}]/..'
loc_str = (f'//*/text()[substring(., string-length(.) - string-length({_quotes_escape(loc[5:])}) +1) = '
f'{_quotes_escape(loc[5:])}]/..')
# 用xpath查找
elif loc.startswith(('xpath:', 'xpath=')) and loc not in ('xpath:', 'xpath='):
@ -156,7 +155,7 @@ def str_to_xpath_loc(loc):
# 根据文本模糊查找
elif loc:
loc_str = f'//*/text()[contains(., {_make_search_str(loc)})]/..'
loc_str = f'//*/text()[contains(., {_quotes_escape(loc)})]/..'
else:
loc_str = '//*'
@ -226,30 +225,30 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple:
symbol = r[1]
if symbol == '=': # 精确查找
arg = 'text()' if r[0] in ('@text()', '@tx()') else r[0]
arg_str = f'{arg}={_make_search_str(r[2])}'
arg_str = f'{arg}={_quotes_escape(r[2])}'
elif symbol == '^': # 匹配开头
if r[0] in ('@text()', '@tx()'):
txt_str = f'/text()[starts-with(., {_make_search_str(r[2])})]/..'
txt_str = f'/text()[starts-with(., {_quotes_escape(r[2])})]/..'
arg_str = ''
else:
arg_str = f"starts-with({r[0]},{_make_search_str(r[2])})"
arg_str = f"starts-with({r[0]},{_quotes_escape(r[2])})"
elif symbol == '$': # 匹配结尾
if r[0] in ('@text()', '@tx()'):
txt_str = (f'/text()[substring(., string-length(.) - string-length({_make_search_str(r[2])}) '
f'+1) = {_make_search_str(r[2])}]/..')
txt_str = (f'/text()[substring(., string-length(.) - string-length('
f'{_quotes_escape(r[2])}) +1) = {_quotes_escape(r[2])}]/..')
arg_str = ''
else:
arg_str = (f'substring({r[0]}, string-length({r[0]}) - string-length({_make_search_str(r[2])}) '
f'+1) = {_make_search_str(r[2])}')
arg_str = (f'substring({r[0]}, string-length({r[0]}) - string-length('
f'{_quotes_escape(r[2])}) +1) = {_quotes_escape(r[2])}')
elif symbol == ':': # 模糊查找
if r[0] in ('@text()', '@tx()'):
txt_str = f'/text()[contains(., {_make_search_str(r[2])})]/..'
txt_str = f'/text()[contains(., {_quotes_escape(r[2])})]/..'
arg_str = ''
else:
arg_str = f"contains({r[0]},{_make_search_str(r[2])})"
arg_str = f"contains({r[0]},{_quotes_escape(r[2])})"
else:
raise ValueError(f'符号不正确:{symbol}')
@ -313,17 +312,17 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
txt = r[2]
if symbol == '=':
arg_str = f'{arg}={_make_search_str(txt)}'
arg_str = f'{arg}={_quotes_escape(txt)}'
elif symbol == ':':
arg_str = f'contains({arg},{_make_search_str(txt)})'
arg_str = f'contains({arg},{_quotes_escape(txt)})'
elif symbol == '^':
arg_str = f'starts-with({arg},{_make_search_str(txt)})'
arg_str = f'starts-with({arg},{_quotes_escape(txt)})'
elif symbol == '$':
arg_str = f'substring({arg}, string-length({arg}) - string-length({_make_search_str(txt)}) +1) ' \
f'= {_make_search_str(txt)}'
arg_str = (f'substring({arg}, string-length({arg}) - string-length('
f'{_quotes_escape(txt)}) +1) = {_quotes_escape(txt)}')
else:
raise ValueError(f'符号不正确:{symbol}')
@ -342,11 +341,14 @@ def _make_multi_xpath_str(tag: str, text: str) -> tuple:
return 'xpath', f'//*[{arg_str}]' if arg_str else f'//*'
def _make_search_str(search_str: str) -> str:
""""转义,不知何故不能直接用 \ 来转义
def _quotes_escape(search_str: str) -> str:
""""转义,不知何故不能直接用 斜杠 来转义
:param search_str: 查询字符串
:return: "转义后的字符串
"""
if '"' not in search_str:
return f'"{search_str}"'
parts = search_str.split('"')
parts_num = len(parts)
search_str = 'concat('

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union

View File

@ -2,9 +2,9 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
class Settings(object):
@ -13,4 +13,14 @@ class Settings(object):
raise_when_wait_failed = False
singleton_tab_obj = True
cdp_timeout = 30
browser_connect_timeout = 30
auto_handle_alert = None
_suffixes_list = str(Path(__file__).parent.absolute() / 'suffixes.dat').replace('\\', '/')
@property
def suffixes_list_path(self):
return Settings._suffixes_list
@suffixes_list_path.setter
def suffixes_list_path(self, path):
Settings._suffixes_list = str(Path(path).absolute()).replace('\\', '/')

View File

@ -0,0 +1,24 @@
# -*- coding:utf-8 -*-
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Optional, Union
class Settings(object):
raise_when_ele_not_found: bool = ...
raise_when_click_failed: bool = ...
raise_when_wait_failed: bool = ...
singleton_tab_obj: bool = ...
cdp_timeout: float = ...
browser_connect_timeout: float = ...
auto_handle_alert: Optional[bool] = ...
_suffixes_list: str = ...
@property
def suffixes_list_path(self) -> str:
"""设置用于识别域名后缀的文件路径"""
...

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from platform import system
@ -157,7 +156,7 @@ def configs_to_here(save_name=None):
om.save(save_name)
def raise_error(result, ignore=None, user=False):
def raise_error(result, browser, ignore=None, user=False):
error = result['error']
if error in ('Cannot find context with specified id', 'Inspected target navigated or closed',
'No frame with given id found'):
@ -183,7 +182,7 @@ def raise_error(result, ignore=None, user=False):
elif error == 'Given expression does not evaluate to a function':
r = JavaScriptError(f'传入的js无法解析成函数\n{result["args"]["functionDeclaration"]}')
elif error.endswith("' wasn't found"):
r = RuntimeError(f'没有找到对应功能,方法错误或你的浏览器太旧。\n方法:{result["method"]}\n参数:{result["args"]}')
r = RuntimeError(f'没有找到对应功能,方法错误或你的浏览器太旧。\n浏览器版本:{browser.version}\n方法:{result["method"]}')
elif result['type'] == 'timeout':
from DrissionPage import __version__
txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \

View File

@ -2,14 +2,14 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from os import popen
from pathlib import Path
from threading import Lock
from typing import Union, Tuple
from .._base.chromium import Chromium
from .._pages.chromium_base import ChromiumBase
@ -98,9 +98,10 @@ def configs_to_here(save_name: Union[Path, str] = None) -> None:
...
def raise_error(result: dict, ignore=None, user: bool = False) -> None:
def raise_error(result: dict, browser: Chromium, ignore=None, user: bool = False) -> None:
"""抛出error对应报错
:param result: 包含error的dict
:param browser: 浏览器对象
:param ignore: 要忽略的错误
:param user: 是否用户调用的
:return: None

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from html import unescape
from os.path import sep
@ -302,10 +301,10 @@ def tree(ele_or_page, text=False, show_js=False, show_css=False):
def format_headers(txt):
if isinstance(txt, (dict, CaseInsensitiveDict)):
for k, v in txt.items():
if k in (':method', ':scheme', ':authority', ':path'):
txt.pop(k)
elif v not in (None, False, True):
if v not in (None, False, True):
txt[k] = str(v)
for i in (':method', ':scheme', ':authority', ':path'):
txt.pop(i, None)
return txt
headers = {}
for header in txt.split('\n'):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Optional, Tuple

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from json import loads, JSONDecodeError
from os.path import sep
@ -33,7 +32,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, ElementLostError
from ..errors import ContextLostError, CDPError, PageDisconnectedError, ElementLostError, JavaScriptError
__ERROR__ = 'error'
@ -150,7 +149,7 @@ class ChromiumBase(BasePage):
except:
timeout = end_time - perf_counter()
timeout = .5 if timeout <= 0 else timeout
sleep(.1)
sleep(.05)
else:
result = False
@ -223,7 +222,7 @@ class ChromiumBase(BasePage):
def _wait_to_stop(self):
end_time = perf_counter() + self.timeouts.page_load
while perf_counter() < end_time:
sleep(.1)
sleep(.02)
if self._ready_state in ('interactive', 'complete') and self._is_loading:
self.stop_loading()
@ -370,17 +369,17 @@ class ChromiumBase(BasePage):
def run_cdp(self, cmd, **cmd_args):
r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, user=True)
return r if __ERROR__ not in r else raise_error(r, self.browser, user=True)
def run_cdp_loaded(self, cmd, **cmd_args):
self.wait.doc_loaded()
r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, user=True)
return r if __ERROR__ not in r else raise_error(r, self.browser, user=True)
def _run_cdp(self, cmd, **cmd_args):
ignore = cmd_args.pop('_ignore', None)
r = self.driver.run(cmd, **cmd_args)
return r if __ERROR__ not in r else raise_error(r, ignore)
return r if __ERROR__ not in r else raise_error(r, self.browser, ignore)
def _run_cdp_loaded(self, cmd, **cmd_args):
self.wait.doc_loaded()
@ -486,7 +485,7 @@ class ChromiumBase(BasePage):
if perf_counter() >= end_time:
return NoneElement(self) if index is not None else ChromiumElementsList(owner=self)
sleep(.1)
sleep(.01)
timeout = end_time - perf_counter()
timeout = .5 if timeout <= 0 else timeout
result = self.driver.run('DOM.performSearch', query=loc, _timeout=timeout, includeUserAgentShadowDOM=True)
@ -539,7 +538,7 @@ class ChromiumBase(BasePage):
self._run_cdp('Page.stopLoading')
end_time = perf_counter() + 5
while self._ready_state != 'complete' and perf_counter() < end_time:
sleep(.1)
sleep(.02)
except (PageDisconnectedError, CDPError):
pass
finally:
@ -600,7 +599,10 @@ class ChromiumBase(BasePage):
else:
raise TypeError('html_or_info参数必须是html文本或tupletuple格式为(tag, {name: value})。')
try:
ele = self._run_js(js, *args)
except JavaScriptError:
raise RuntimeError('此网页不支持html格式新建元素请用dict传入html_or_info参数。')
return ele
def get_frame(self, loc_ind_ele, timeout=None):
@ -673,11 +675,11 @@ class ChromiumBase(BasePage):
self._get_document()
def handle_alert(self, accept=True, send=None, timeout=None, next_one=False):
r = self._handle_alert(accept=accept, send=send, timeout=timeout, next_one=next_one)
if not isinstance(accept, bool):
return r
return self._handle_alert(accept=accept, send=send, timeout=timeout, next_one=next_one)
r = self._handle_alert(accept=accept, send=send, timeout=timeout, next_one=next_one)
while self._has_alert:
sleep(.1)
sleep(.0001)
return r
def _handle_alert(self, accept=True, send=None, timeout=None, next_one=False):
@ -690,7 +692,7 @@ class ChromiumBase(BasePage):
timeout = .1 if timeout <= 0 else timeout
end_time = perf_counter() + timeout
while not self._alert.activated and perf_counter() < end_time:
sleep(.1)
sleep(.01)
if not self._alert.activated:
return False
@ -741,7 +743,7 @@ class ChromiumBase(BasePage):
'complete') and not self._is_loading:
return True
sleep(.1)
sleep(.01)
try:
self.stop_loading()
@ -770,7 +772,7 @@ class ChromiumBase(BasePage):
print(f'重试{t + 1} {to_url}')
end_time1 = end_time - perf_counter()
while self._ready_state not in ('loading', 'complete') and perf_counter() < end_time1: # 等待出错信息显示
sleep(.1)
sleep(.01)
self.stop_loading()
continue

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Tuple, Any, Optional, Literal
@ -479,7 +478,8 @@ class ChromiumBase(BasePage):
def add_ele(self,
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:
before: Union[ChromiumElement, str, Tuple[str, str], None] = None) -> Union[
ChromiumElement, ChromiumFrame]:
"""新建一个元素
:param html_or_info: 新元素的html文本或信息信息格式为(tag, {attr1: value, ...})
:param insert_to: 插入到哪个元素中可接收元素对象和定位符为None且为html添加到body不为html不插入
@ -573,7 +573,10 @@ class ChromiumBase(BasePage):
"""
...
def handle_alert(self, accept: Optional[bool] = True, send: str = None, timeout: float = None,
def handle_alert(self,
accept: Optional[bool] = True,
send: str = None,
timeout: float = None,
next_one: bool = False) -> Union[str, False]:
"""处理提示框,可以自动等待提示框出现
:param accept: True表示确认False表示取消为None不会按按钮但依然返回文本值
@ -584,7 +587,10 @@ class ChromiumBase(BasePage):
"""
...
def _handle_alert(self, accept: bool = True, send: str = None, timeout: float = None,
def _handle_alert(self,
accept: Optional[bool] = True,
send: str = None,
timeout: float = None,
next_one: bool = False) -> Union[str, False]:
"""处理提示框,可以自动等待提示框出现
:param accept: True表示确认False表示取消其它值不会按按钮但依然返回文本值

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from copy import copy
from re import search, findall, DOTALL
@ -35,7 +34,7 @@ class ChromiumFrame(ChromiumBase):
if Settings.singleton_tab_obj and fid in cls._Frames:
r = cls._Frames[fid]
while not hasattr(r, '_type') or r._type != 'ChromiumFrame':
sleep(.1)
sleep(.01)
return r
r = object.__new__(cls)
cls._Frames[fid] = r
@ -333,6 +332,9 @@ class ChromiumFrame(ChromiumBase):
def remove_attr(self, name):
self.frame_ele.remove_attr(name)
def style(self, style, pseudo_ele=''):
return self.frame_ele.style(style=style, pseudo_ele=pseudo_ele)
def run_js(self, script, *args, as_expr=False, timeout=None):
return self._run_js(script, *args, as_expr=as_expr, timeout=timeout)

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Tuple, List, Any, Optional, Literal
@ -240,6 +239,14 @@ class ChromiumFrame(ChromiumBase):
"""
...
def style(self, style: str, pseudo_ele: str = '') -> str:
"""返回frame元素样式属性值可获取伪元素属性值
:param style: 样式属性名称
:param pseudo_ele: 伪元素名称如有
:return: 样式属性的值
"""
...
def run_js(self,
script: str,
*args,

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import sleep
@ -24,7 +23,7 @@ class ChromiumPage(ChromiumBase):
if browser.id in cls._PAGES:
r = cls._PAGES[browser.id]
while not hasattr(r, '_frame_id'):
sleep(.1)
sleep(.05)
return r
r = object.__new__(cls)
@ -43,6 +42,7 @@ class ChromiumPage(ChromiumBase):
self._type = 'ChromiumPage'
self.set.timeouts(base=timeout) # 即将废弃
self._tab = self
self._browser._dl_mgr._page_id = self.tab_id
def __repr__(self):
return f'<ChromiumPage browser_id={self.browser.id} tab_id={self.tab_id}>'
@ -96,6 +96,10 @@ class ChromiumPage(ChromiumBase):
def address(self):
return self.browser.address
@property
def download_path(self):
return self.browser.download_path
def save(self, path=None, name=None, as_pdf=False, **kwargs):
return save_page(self, path, name, as_pdf, kwargs)
@ -114,10 +118,10 @@ class ChromiumPage(ChromiumBase):
self.browser.activate_tab(id_ind_tab)
def close(self):
self.close_tabs(self.tab_id)
self.browser._close_tab(self)
def close_tabs(self, tabs_or_ids=None, others=False):
self.browser.close_tabs(tabs_or_ids=tabs_or_ids or self.tab_id, others=others)
def close_tabs(self, tabs_or_ids, others=False):
self.browser.close_tabs(tabs_or_ids=tabs_or_ids, others=others)
def quit(self, timeout=5, force=True, del_data=False):
self.browser.quit(timeout, force, del_data=del_data)

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Tuple, List, Optional
@ -198,10 +197,10 @@ class ChromiumPage(ChromiumBase):
def close_tabs(self,
tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]],
Tuple[Union[str, ChromiumTab]]] = None,
Tuple[Union[str, ChromiumTab]]],
others: bool = False) -> None:
"""关闭传入的标签页,默认关闭当前页。可传入多个
:param tabs_or_ids: 要关闭的标签页对象或id可传入列表或元组为None时关闭当前页
"""关闭传入的标签页,可传入多个
:param tabs_or_ids: 要关闭的标签页对象或id可传入列表或元组
:param others: 是否关闭指定标签页之外的
:return: None
"""

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from copy import copy
from time import sleep
@ -22,7 +21,7 @@ class ChromiumTab(ChromiumBase):
if Settings.singleton_tab_obj and tab_id in cls._TABS:
r = cls._TABS[tab_id]
while not hasattr(r, '_frame_id'):
sleep(.1)
sleep(.05)
return r
r = object.__new__(cls)
cls._TABS[tab_id] = r
@ -51,7 +50,10 @@ class ChromiumTab(ChromiumBase):
self._none_ele_value = self.browser._none_ele_value
def close(self, others=False):
self.browser.close_tabs(self.tab_id, others=others)
if others:
self.browser.close_tabs(self.tab_id, others=True)
else:
self.browser._close_tab(self)
@property
def set(self):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Optional

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from .chromium_tab import ChromiumTab
from .._base.base import BasePage
@ -179,7 +178,10 @@ class MixTab(SessionPage, ChromiumTab, BasePage):
else super().cookies(all_domains, all_info)
def close(self, others=False, session=False):
self.browser.close_tabs(self.tab_id, others=others)
if others:
self.browser.close_tabs(self.tab_id, others=True)
else:
self.browser._close_tab(self)
if session and self._session:
self._session.close()
if self._response is not None:

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from http.cookiejar import CookieJar
from typing import Union, Tuple, Any, Optional, Literal

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from re import search, DOTALL
@ -12,11 +11,12 @@ from urllib.parse import urlparse
from requests import Response
from requests.structures import CaseInsensitiveDict
from tldextract import extract
from tldextract import TLDExtract
from .._base.base import BasePage
from .._elements.session_element import SessionElement, make_session_ele
from .._functions.cookies import cookie_to_dict, CookiesList
from .._functions.settings import Settings
from .._functions.web import format_headers
from .._units.setter import SessionPageSetter
@ -155,7 +155,9 @@ class SessionPage(BasePage):
cookies = self.session.cookies
else:
if self.url:
ex_url = extract(self._session_url)
ex_url = TLDExtract(
suffix_list_urls=["https://publicsuffix.org/list/public_suffix_list.dat",
f"file:///{Settings.suffixes_list_path}"]).extract_str(self._session_url)
domain = f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain
cookies = tuple(c for c in self.session.cookies if domain in c.domain or c.domain == '')
else:

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Any, Union, Tuple, Optional

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from .chromium_page import ChromiumPage
from .session_page import SessionPage
@ -112,6 +111,10 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def timeout(self):
return self.timeouts.base if self._d_mode else self._timeout
@property
def download_path(self):
return self.browser.download_path
def get(self, url, show_errmsg=False, retry=None, interval=None, timeout=None, **kwargs):
if self._d_mode:
return super(SessionPage, self).get(url, show_errmsg, retry, interval, timeout)
@ -125,7 +128,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
self.cookies_to_session()
super().post(url, show_errmsg, retry, interval, **kwargs)
return self.response
return super().post(url, show_errmsg, retry, interval, **kwargs)
def ele(self, locator, index=1, timeout=None):
return (super(SessionPage, self).ele(locator, index=index, timeout=timeout)
@ -228,7 +230,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def close(self):
if self._has_driver:
self.close_tabs(self.tab_id)
self.browser._close_tab(self)
if self._session:
self._session.close()
if self._response is not None:

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from http.cookiejar import CookieJar
from typing import Union, Tuple, List, Any, Optional, Literal
@ -19,6 +18,7 @@ from .._configs.chromium_options import ChromiumOptions
from .._configs.session_options import SessionOptions
from .._elements.chromium_element import ChromiumElement
from .._elements.session_element import SessionElement
from .._functions.cookies import CookiesList
from .._functions.elements import SessionElementsList, ChromiumElementsList
from .._units.setter import WebPageSetter
from .._units.waiter import WebPageWaiter
@ -285,7 +285,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
def cookies(self,
all_domains: bool = False,
all_info: bool = False) -> Union[dict, list]:
all_info: bool = False) -> CookiesList:
"""返回cookies
:param all_domains: 是否返回所有域的cookies
:param all_info: 是否返回所有信息False则只返回namevaluedomain

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import sleep, perf_counter
@ -179,7 +178,7 @@ class Actions:
self.owner._run_cdp('Input.dispatchKeyEvent', **data)
return self
def type(self, keys):
def type(self, keys, interval=0):
modifiers = []
if not isinstance(keys, (str, tuple, list)):
keys = str(keys)
@ -197,6 +196,7 @@ class Actions:
else:
self.owner._run_cdp('Input.dispatchKeyEvent', type='char', text=character)
sleep(interval)
for m in modifiers:
self.key_up(m)

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, Tuple, Any, Literal
@ -66,7 +65,7 @@ class Actions:
:param offset_x: 偏移量x
:param offset_y: 偏移量y
:param duration: 拖动用时传入0即瞬间到达
:return: self
:return: 动作链对象本身
"""
...
@ -75,7 +74,7 @@ class Actions:
:param offset_x: 偏移量x
:param offset_y: 偏移量y
:param duration: 拖动用时传入0即瞬间到达
:return: self
:return: 动作链对象本身
"""
...
@ -83,7 +82,7 @@ class Actions:
"""点击鼠标左键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:param times: 点击次数
:return: self
:return: 动作链对象本身
"""
...
@ -91,7 +90,7 @@ class Actions:
"""点击鼠标右键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:param times: 点击次数
:return: self
:return: 动作链对象本身
"""
...
@ -99,49 +98,49 @@ class Actions:
"""点击鼠标中键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:param times: 点击次数
:return: self
:return: 动作链对象本身
"""
...
def hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""按住鼠标左键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
def release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""释放鼠标左键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
def r_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""按住鼠标右键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
def r_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""释放鼠标右键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
def m_hold(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""按住鼠标中键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
def m_release(self, on_ele: Union[ChromiumElement, str] = None) -> Actions:
"""释放鼠标中键,可先移动到元素上
:param on_ele: ChromiumElement元素或文本定位符
:return: self
:return: 动作链对象本身
"""
...
@ -153,14 +152,14 @@ class Actions:
:param on_ele: ChromiumElement元素或文本定位符
:param button: 要按下的按键
:param count: 点击次数
:return: self
:return: 动作链对象本身
"""
...
def _release(self, button: str) -> Actions:
"""释放鼠标按键
:param button: 要释放的按键
:return: self
:return: 动作链对象本身
"""
...
@ -170,63 +169,66 @@ class Actions:
:param delta_y: 滚轮变化值y
:param delta_x: 滚轮变化值x
:param on_ele: ChromiumElement元素
:return: self
:return: 动作链对象本身
"""
...
def up(self, pixel: int) -> Actions:
"""鼠标向上移动若干像素
:param pixel: 鼠标移动的像素值
:return: self
:return: 动作链对象本身
"""
...
def down(self, pixel: int) -> Actions:
"""鼠标向下移动若干像素
:param pixel: 鼠标移动的像素值
:return: self
:return: 动作链对象本身
"""
...
def left(self, pixel: int) -> Actions:
"""鼠标向左移动若干像素
:param pixel: 鼠标移动的像素值
:return: self
:return: 动作链对象本身
"""
...
def right(self, pixel: int) -> Actions:
"""鼠标向右移动若干像素
:param pixel: 鼠标移动的像素值
:return: self
:return: 动作链对象本身
"""
...
def key_down(self, key: Union[KEYS, str]) -> Actions:
"""按下键盘上的按键,
:param key: 使用Keys获取的按键 'DEL' 形式按键名称
:return: self
:return: 动作链对象本身
"""
...
def key_up(self, key: Union[KEYS, str]) -> Actions:
"""提起键盘上的按键
:param key: 按键特殊字符见Keys
:return: self
:return: 动作链对象本身
"""
...
def type(self, keys: Union[KEYS, str, list, tuple]) -> Actions:
def type(self,
keys: Union[KEYS, str, list, tuple],
interval: float = 0) -> Actions:
"""用模拟键盘按键方式输入文本,可输入字符串,也可输入组合键
:param keys: 要按下的按键特殊字符和多个文本可用list或tuple传入
:return: self
:param interval: 每个字符之间间隔时间
:return: 动作链对象本身
"""
...
def input(self, text: Any) -> Actions:
"""输入文本也可输入组合键组合键用tuple形式输入
:param text: 文本值或按键组合
:return: self
:return: 动作链对象本身
"""
...

View File

@ -2,14 +2,15 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from time import perf_counter, sleep
from .waiter import wait_mission
from .._functions.settings import Settings
from .._functions.web import offset_scroll
from .._units.downloader import TabDownloadSettings
from ..errors import CanNotClickError, CDPError, NoRectError, AlertExistsError
@ -118,25 +119,59 @@ class Clicker(object):
def multi(self, times=2):
return self.at(count=times)
def to_download(self, save_path=None, rename=None, suffix=None, new_tab=False, by_js=False, timeout=None):
tmp_save_path = None
def to_download(self, save_path=None, rename=None, suffix=None, new_tab=None, by_js=False, timeout=None):
if not self._ele.tab._browser._dl_mgr._running:
self._ele.tab._browser.set.download_path('.')
if save_path:
if new_tab:
tmp_save_path = str(Path(save_path).absolute())
else:
self._ele.tab.set.download_path(save_path)
obj = self._ele.tab._browser if new_tab else self._ele.owner._tab
when_file_exists = None
tmp_path = None
if self._ele.tab._type.endswith('Page'):
obj = browser = self._ele.owner._browser
tid = 'browser'
elif new_tab:
obj = browser = self._ele.owner._browser
tid = 'browser'
t_settings = TabDownloadSettings(self._ele.owner.tab_id)
b_settings = TabDownloadSettings('browser')
when_file_exists = b_settings.when_file_exists
b_settings.when_file_exists = t_settings.when_file_exists
b_settings.rename = t_settings.rename
b_settings.suffix = t_settings.suffix
t_settings.rename = None
t_settings.suffix = None
if not save_path and b_settings.path != t_settings.path:
tmp_path = b_settings.path
b_settings.path = t_settings.path
else:
obj = self._ele.owner._tab
browser = obj.browser
browser._dl_mgr._waiting_tab.add(self._ele.owner.tab_id)
tid = obj.tab_id
if save_path:
tmp_path = obj.download_path
TabDownloadSettings(tid).path = str(Path(save_path).absolute())
if rename or suffix:
obj.set.download_file_name(rename, suffix)
if timeout is None:
timeout = obj.timeout
browser._dl_mgr.set_flag(tid, True)
self.left(by_js=by_js)
r = obj.wait.download_begin(timeout=timeout)
if tmp_save_path:
r.path = tmp_save_path
return r
m = wait_mission(browser, tid, timeout)
if tmp_path:
TabDownloadSettings(tid).path = tmp_path
if when_file_exists:
browser.set.when_download_file_exists(when_file_exists)
if m and new_tab:
self._ele.owner.browser._dl_mgr._tab_missions.setdefault(self._ele.owner.tab_id, set()).add(m)
m.from_tab = self._ele.owner.tab_id
browser._dl_mgr._waiting_tab.discard(self._ele.owner.tab_id)
return m
def to_upload(self, file_paths, by_js=False):
self._ele.owner.set.upload_files(file_paths)
@ -144,7 +179,7 @@ class Clicker(object):
self._ele.owner.wait.upload_paths_inputted()
def for_new_tab(self, by_js=False, timeout=3):
curr_tid = self._ele.tab.browser.tab_ids[0]
curr_tid = self._ele.tab.browser._newest_tab_id
self.left(by_js=by_js)
tid = self._ele.tab.browser.wait.new_tab(timeout=timeout, curr_tab=curr_tid)
if not tid:

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union
@ -80,14 +79,14 @@ class Clicker(object):
save_path: Union[str, Path] = None,
rename: str = None,
suffix: str = None,
new_tab: bool = False,
new_tab: bool = None,
by_js: bool = False,
timeout: float = None) -> DownloadMission:
"""点击触发下载
:param save_path: 保存路径为None保存在原来设置的如未设置保存到当前路径
:param rename: 重命名文件名
:param suffix: 指定文件后缀
:param new_tab: 该下载是否在新tab中触发
:param new_tab: 下载任务是否从新标签页触发为None会自动获取如获取不到设为True
:param by_js: 是否用js方式点击逻辑与click()一致
:param timeout: 等待下载触发的超时时间为None则使用页面对象设置
:return: DownloadMission对象

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from .._functions.cookies import set_tab_cookies, set_session_cookies, set_browser_cookies

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from http.cookiejar import Cookie, CookieJar
from typing import Union

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from os.path import sep
from pathlib import Path
@ -25,8 +24,11 @@ class DownloadManager(object):
t.when_file_exists = 'rename'
self._missions = {} # {guid: DownloadMission}
self._tab_missions = {} # {tab_id: DownloadMission}
self._tab_missions = {} # {tab_id: [DownloadMission, ...]}
self._flags = {} # {tab_id: [bool, DownloadMission]}
self._waiting_tab = set() # click.to_download()专用
self._tmp_path = '.'
self._page_id = None
self._running = False
@ -42,6 +44,7 @@ class DownloadManager(object):
self._browser._driver.set_callback('Browser.downloadWillBegin', self._onDownloadWillBegin)
r = self._browser._run_cdp('Browser.setDownloadBehavior', downloadPath=self._browser._download_path,
behavior='allowAndName', eventsEnabled=True)
self._tmp_path = self._browser._download_path
if 'error' in r:
print('浏览器版本太低无法使用下载管理功能。')
self._running = True
@ -61,14 +64,17 @@ class DownloadManager(object):
return self._flags.get(tab_id, None)
def get_tab_missions(self, tab_id):
return self._tab_missions.get(tab_id, [])
return self._tab_missions.get(tab_id, set())
def set_done(self, mission, state, final_path=None):
if mission.state not in ('canceled', 'skipped'):
mission.state = state
mission.final_path = final_path
if mission.tab_id in self._tab_missions and mission.id in self._tab_missions[mission.tab_id]:
self._tab_missions[mission.tab_id].remove(mission.id)
if mission.tab_id in self._tab_missions and mission in self._tab_missions[mission.tab_id]:
self._tab_missions[mission.tab_id].discard(mission)
if (mission.from_tab and mission.from_tab in self._tab_missions
and mission in self._tab_missions[mission.from_tab]):
self._tab_missions[mission.from_tab].discard(mission)
self._missions.pop(mission.id, None)
mission._is_done = True
@ -92,12 +98,18 @@ class DownloadManager(object):
self._tab_missions.pop(tab_id, None)
self._flags.pop(tab_id, None)
TabDownloadSettings.TABS.pop(tab_id, None)
self._waiting_tab.discard(tab_id)
def _onDownloadWillBegin(self, **kwargs):
guid = kwargs['guid']
tab_id = self._browser._frames.get(kwargs['frameId'], 'browser')
tab = 'browser' if tab_id in ('browser', self._page_id) or self.get_flag('browser') is not None else tab_id
opener = self._browser._relation.get(tab_id, None)
from_tab = None
if opener and opener in self._waiting_tab:
tab = from_tab = opener
settings = TabDownloadSettings(tab_id if tab_id in TabDownloadSettings.TABS else 'browser')
settings = TabDownloadSettings(tab)
if settings.rename:
if settings.suffix is not None:
name = f'{settings.rename}.{settings.suffix}' if settings.suffix else settings.rename
@ -122,25 +134,33 @@ class DownloadManager(object):
name = kwargs['suggestedFilename']
skip = False
overwrite = None # 存在且重命名
goal_path = Path(settings.path) / name
if goal_path.exists():
if settings.when_file_exists == 'skip':
skip = True
elif settings.when_file_exists == 'overwrite':
goal_path.unlink()
overwrite = True # 存在且覆盖
else: # 不存在
overwrite = False
m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._browser.download_path)
m = DownloadMission(self, tab_id, guid, settings.path, name, kwargs['url'], self._tmp_path, overwrite)
if from_tab:
m.from_tab = from_tab
self._tab_missions.setdefault(from_tab, set()).add(m)
self._missions[guid] = m
if self.get_flag(tab_id) is False: # 取消该任务
if self.get_flag('browser') is False or self.get_flag(tab) is False: # 取消该任务
self.cancel(m)
elif skip:
self.skip(m)
else:
self._tab_missions.setdefault(tab_id, []).append(m)
self._tab_missions.setdefault(tab_id, set()).add(m)
if self.get_flag(tab_id) is not None:
self._flags[tab_id] = m
if self.get_flag('browser') is not None:
self._flags['browser'] = m
elif self.get_flag(tab) is not None:
self._flags[tab] = m
def _onDownloadProgress(self, **kwargs):
if kwargs['guid'] in self._missions:
@ -151,13 +171,17 @@ class DownloadManager(object):
elif kwargs['state'] == 'completed':
if mission.state == 'skipped':
Path(f'{mission.save_path}{sep}{mission.id}').unlink(True)
Path(f'{mission.tmp_path}{sep}{mission.id}').unlink(True)
self.set_done(mission, 'skipped')
return
mission.received_bytes = kwargs['receivedBytes']
mission.total_bytes = kwargs['totalBytes']
form_path = f'{mission.save_path}{sep}{mission.id}'
to_path = str(get_usable_path(f'{mission.path}{sep}{mission.name}'))
form_path = f'{mission.tmp_path}{sep}{mission.id}'
if mission._overwrite is None:
to_path = str(get_usable_path(f'{mission.folder}{sep}{mission.name}'))
else:
to_path = f'{mission.folder}{sep}{mission.name}'
Path(mission.folder).mkdir(parents=True, exist_ok=True)
not_moved = True
for _ in range(10):
try:
@ -193,25 +217,27 @@ class TabDownloadSettings(object):
self.tab_id = tab_id
self.rename = None
self.suffix = None
self.path = ''
self.when_file_exists = 'rename'
self.path = '' if tab_id == 'browser' else self.TABS['browser'].path
self.when_file_exists = 'rename' if tab_id == 'browser' else self.TABS['browser'].when_file_exists
TabDownloadSettings.TABS[tab_id] = self
class DownloadMission(object):
def __init__(self, mgr, tab_id, _id, path, name, url, save_path):
def __init__(self, mgr, tab_id, _id, folder, name, url, tmp_path, overwrite):
self._mgr = mgr
self.url = url
self.tab_id = tab_id
self.from_tab = None
self.id = _id
self.path = path
self.folder = folder
self.name = name
self.state = 'running'
self.total_bytes = None
self.received_bytes = 0
self.final_path = None
self.save_path = save_path
self.tmp_path = tmp_path
self._overwrite = overwrite
self._is_done = False
def __repr__(self):
@ -234,8 +260,8 @@ class DownloadMission(object):
end_time = perf_counter()
while self.name is None and perf_counter() < end_time:
sleep(0.01)
print(f'文件名:{self.name}')
print(f'标路径:{self.path}')
print(f'文件名:{self.name or "未知"}')
print(f'录路径:{self.folder}')
if timeout is None:
while not self.is_done:
@ -255,11 +281,17 @@ class DownloadMission(object):
if show:
if self.state == 'completed':
print('\r100% ', end='')
if self._overwrite is None:
print(f'完成并重命名 {self.final_path}')
elif self._overwrite is False:
print(f'下载完成 {self.final_path}')
else:
print(f'已覆盖 {self.final_path}')
elif self.state == 'canceled':
print(f'下载取消')
elif self.state == 'skipped':
print(f'已跳过')
print(f'已跳过 {self.folder}{sep}{self.name}')
print()
return self.final_path if self.final_path else False

View File

@ -2,10 +2,9 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Dict, Optional, Union, Literal
from typing import Dict, Optional, Union, Literal, Set
from .._base.chromium import Chromium
from .._pages.chromium_base import ChromiumBase
@ -16,9 +15,12 @@ FILE_EXISTS = Literal['skip', 'rename', 'overwrite', 's', 'r', 'o']
class DownloadManager(object):
_browser: Chromium = ...
_missions: Dict[str, DownloadMission] = ...
_tab_missions: dict = ...
_tab_missions: Dict[str, Set[DownloadMission]] = ...
_flags: dict = ...
_waiting_tab: set = ...
_running: bool = ...
_tmp_path: str = ...
_page_id: Optional[str] = ...
def __init__(self, browser: Chromium):
"""
@ -130,7 +132,7 @@ class TabDownloadSettings(object):
rename: Optional[str] = ...
suffix: Optional[str] = ...
path: Optional[str] = ...
when_file_exists: str = ...
when_file_exists: FILE_EXISTS = ...
def __init__(self, tab_id: str):
"""
@ -141,34 +143,38 @@ class TabDownloadSettings(object):
class DownloadMission(object):
tab_id: str = ...
from_tab: Optional[str] = ...
_mgr: DownloadManager = ...
url: str = ...
id: str = ...
path: str = ...
folder: str = ...
name: str = ...
state: str = ...
total_bytes: Optional[int] = ...
received_bytes: int = ...
final_path: Optional[str] = ...
save_path: str = ...
tmp_path: str = ...
_overwrite: bool = ...
_is_done: bool = ...
def __init__(self,
mgr: DownloadManager,
tab_id: str,
_id: str,
path: str,
folder: str,
name: str,
url: str,
save_path: str):
tmp_path: str,
overwrite: bool):
"""
:param mgr: BrowserDownloadManager对象
:param tab_id: 标签页id
:param _id: 任务id
:param path: 最终保存路径
:param folder: 最终保存文件夹路径
:param name: 文件名
:param url: url
:param save_path: 下载临时路径
:param tmp_path: 下载临时路径
:param overwrite: 是否已存在同名文件None表示重命名
"""
...

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from base64 import b64decode
from json import JSONDecodeError, loads
@ -191,7 +190,7 @@ class Listener(object):
if timeout is None:
while ((not targets_only and self._running_requests > limit)
or (targets_only and self._running_targets > limit)):
sleep(.1)
sleep(.01)
return True
end_time = perf_counter() + timeout
@ -199,7 +198,7 @@ class Listener(object):
if ((not targets_only and self._running_requests <= limit)
or (targets_only and self._running_targets <= limit)):
return True
sleep(.1)
sleep(.01)
else:
return False
@ -423,7 +422,7 @@ class DataPacket(object):
def wait_extra_info(self, timeout=None):
if timeout is None:
while self._responseExtraInfo is None:
sleep(.1)
sleep(.01)
return True
else:
@ -431,7 +430,7 @@ class DataPacket(object):
while perf_counter() < end_time:
if self._responseExtraInfo is not None:
return True
sleep(.1)
sleep(.01)
else:
return False

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from queue import Queue
from typing import Union, List, Iterable, Optional, Literal, Any

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Tuple, Union

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from base64 import b64decode
from os.path import sep
@ -58,7 +57,7 @@ class Screencast(object):
? "video/webm; codecs=vp9"
: "video/webm"
mediaRecorder = new MediaRecorder(stream, {mimeType: mime})
DrissionPage_Screencast_chunks = []
DrissionPage_Screencast_chunks = [];
mediaRecorder.addEventListener('dataavailable', function(e) {
DrissionPage_Screencast_blob_ok = false;
DrissionPage_Screencast_chunks.push(e.data);
@ -76,6 +75,7 @@ class Screencast(object):
print('请手动选择要录制的目标。')
self._owner._run_js('var DrissionPage_Screencast_blob;var DrissionPage_Screencast_blob_ok=false;')
self._owner._run_js(js)
print('开始录制')
def stop(self, video_name=None):
if video_name and not video_name.endswith('mp4'):
@ -86,12 +86,13 @@ class Screencast(object):
if self._mode.startswith('js'):
self._owner._run_js('mediaRecorder.stop();', as_expr=True)
while not self._owner._run_js('return DrissionPage_Screencast_blob_ok;'):
sleep(.1)
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']
sleep(.05)
with open(path, 'wb') as f:
f.write(b64decode(data))
f.write(b64decode(self._owner._run_js('return DrissionPage_Screencast_blob;')))
self._owner._run_js('DrissionPage_Screencast_blob_ok = false;'
'DrissionPage_Screencast_chunks = [];'
'DrissionPage_Screencast_blob = null', as_expr=True)
print('停止录制')
return path
if self._mode.startswith('frugal'):
@ -100,9 +101,10 @@ class Screencast(object):
else:
self._enable = False
while self._running:
sleep(.1)
sleep(.01)
if self._mode.endswith('imgs'):
print('停止录制')
return str(Path(self._path).absolute())
if not str(self._path).isascii():
@ -127,6 +129,7 @@ class Screencast(object):
rmtree(self._tmp_path)
self._tmp_path = None
print('停止录制')
return f'{self._path}{sep}{name}'
def set_save_path(self, save_path=None):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Optional

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import sleep, perf_counter
@ -77,7 +76,7 @@ class Scroller(object):
end_time = perf_counter() + owner.timeout
while perf_counter() < end_time:
sleep(.1)
sleep(.02)
r = owner._run_cdp('Page.getLayoutMetrics')
x1 = r['layoutViewport']['pageX']
y1 = r['layoutViewport']['pageY']

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import perf_counter, sleep
@ -32,8 +31,7 @@ class SelectElement(object):
@property
def selected_option(self):
ele = self._ele._run_js('return this.options[this.selectedIndex];')
return ele
return self._ele._run_js('return this.options[this.selectedIndex];')
@property
def selected_options(self):
@ -54,6 +52,7 @@ class SelectElement(object):
i._run_js(f'this.selected={mode};')
if change:
self._dispatch_change()
return self._ele
def clear(self):
if not self.is_multi:
@ -73,7 +72,7 @@ class SelectElement(object):
return self._by_loc(locator, timeout)
def by_option(self, option):
self._select_options(option, 'true')
return self._select_options(option, 'true')
def cancel_by_text(self, text, timeout=None):
return self._select(text, 'text', True, timeout)
@ -88,19 +87,17 @@ class SelectElement(object):
return self._by_loc(locator, timeout, True)
def cancel_by_option(self, option):
self._select_options(option, 'false')
return self._select_options(option, 'false')
def _by_loc(self, loc, timeout=None, cancel=False):
eles = self._ele.eles(loc, timeout)
if not eles:
return False
raise RuntimeError('没有找到指定选项。')
mode = 'false' if cancel else 'true'
if self.is_multi:
self._select_options(eles, mode)
else:
self._select_options(eles[0], mode)
return True
if not self.is_multi:
eles = eles[0]
return self._select_options(eles, mode)
def _select(self, condition, para_type='text', cancel=False, timeout=None):
if not self.is_multi and isinstance(condition, (list, tuple)):
@ -117,7 +114,6 @@ class SelectElement(object):
return self._index(condition, mode, timeout)
def _text_value(self, condition, para_type, mode, timeout):
ok = False
text_len = len(condition)
eles = []
end_time = perf_counter() + timeout
@ -128,34 +124,22 @@ class SelectElement(object):
eles = [i for i in self.options if i.attr('value') in condition]
if len(eles) >= text_len:
ok = True
break
return self._select_options(eles, mode)
sleep(.01)
if ok:
self._select_options(eles, mode)
return True
return False
raise RuntimeError('没有找到指定选项。')
def _index(self, condition, mode, timeout):
ok = False
condition = [int(i) for i in condition]
text_len = abs(max(condition, key=abs))
end_time = perf_counter() + timeout
while perf_counter() < end_time:
if len(self.options) >= text_len:
ok = True
break
sleep(.01)
if ok:
eles = self.options
eles = [eles[i - 1] if i > 0 else eles[i] for i in condition]
self._select_options(eles, mode)
return True
return False
return self._select_options(eles, mode)
sleep(.01)
raise RuntimeError('没有找到指定选项。')
def _select_options(self, option, mode):
if isinstance(option, (list, tuple, set)):
@ -167,6 +151,7 @@ class SelectElement(object):
else:
option._run_js(f'this.selected={mode};')
self._dispatch_change()
return self._ele
def _dispatch_change(self):
self._ele._run_js('this.dispatchEvent(new CustomEvent("change", {bubbles: true}));')

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, Tuple, List, Optional
@ -20,11 +19,11 @@ class SelectElement(object):
def __call__(self,
text_or_index: Union[str, int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""选定下拉列表中子元素
:param text_or_index: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param timeout: 超时时间不输入默认实用页面超时时间
:return: None
:return: <select>元素对象
"""
...
@ -48,122 +47,124 @@ class SelectElement(object):
"""返回所有被选中的<option>元素列表"""
...
def all(self) -> None:
def all(self) -> ChromiumElement:
"""全选"""
...
def invert(self) -> None:
def invert(self) -> ChromiumElement:
"""反选"""
...
def clear(self) -> None:
def clear(self) -> ChromiumElement:
"""清除所有已选项"""
...
def by_text(self,
text: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据text值选择项。当元素是多选列表时可以接收list或tuple
:param text: text属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_value(self,
value: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据value值选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_index(self,
index: Union[int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据index值选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号从1开始可传入负数获取倒数第几个传入list或tuple可选择多项
:param timeout: 超时时间为None默认使用页面超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_locator(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""用定位符选择指定的项
:param locator: 定位符
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def by_option(self, option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> None:
def by_option(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> ChromiumElement:
"""选中单个或多个<option>元素
:param option: <option>元素或它们组成的列表
:return: None
:return: <select>元素对象
"""
...
def cancel_by_text(self,
text: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据text值取消选择项。当元素是多选列表时可以接收list或tuple
:param text: 文本传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_value(self,
value: Union[str, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据value值取消选择项。当元素是多选列表时可以接收list或tuple
:param value: value属性值传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_index(self,
index: Union[int, list, tuple],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""此方法用于根据index值取消选择项。当元素是多选列表时可以接收list或tuple
:param index: 序号从1开始可传入负数获取倒数第几个传入list或tuple可取消多项
:param timeout: 超时时间不输入默认实用页面超时时间
:return: 是否取消成功
:return: <select>元素对象
"""
...
def cancel_by_locator(self,
locator: Union[Tuple[str, str], str],
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""用定位符取消选择指定的项
:param locator: 定位符
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def cancel_by_option(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]]) -> None:
option: Union[ChromiumElement, List[ChromiumElement],
Tuple[ChromiumElement]]) -> ChromiumElement:
"""取消选中单个或多个<option>元素
:param option: <option>元素或它们组成的列表
:return: None
:return: <select>元素对象
"""
...
def _by_loc(self,
loc: Union[str, Tuple[str, str]],
timeout: float = None,
cancel: bool = False) -> bool:
cancel: bool = False) -> ChromiumElement:
"""用定位符取消选择指定的项
:param loc: 定位符
:param timeout: 超时时间
:param cancel: 是否取消选择
:return: 是否选择成功
:return: <select>元素对象
"""
...
@ -171,12 +172,12 @@ class SelectElement(object):
condition: Union[str, int, list, tuple] = None,
para_type: str = 'text',
cancel: bool = False,
timeout: float = None) -> bool:
timeout: float = None) -> ChromiumElement:
"""选定或取消选定下拉列表中子元素
:param condition: 根据文本值选或序号择选项若允许多选传入list或tuple可多选
:param para_type: 参数类型可选 'text''value''index'
:param cancel: 是否取消选择
:return: 是否选择成功
:return: <select>元素对象
"""
...
@ -184,31 +185,32 @@ class SelectElement(object):
condition: Union[list, set],
para_type: str,
mode: str,
timeout: float) -> bool:
timeout: float) -> ChromiumElement:
"""执行text和value搜索
:param condition: 条件set
:param para_type: 参数类型可选 'text''value'
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def _index(self, condition: set, mode: str, timeout: float) -> bool:
def _index(self, condition: set, mode: str, timeout: float) -> ChromiumElement:
"""执行index搜索
:param condition: 条件set
:param mode: 'true' 'false'
:param timeout: 超时时间
:return: 是否选择成功
:return: <select>元素对象
"""
...
def _select_options(self, option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]],
mode: str) -> None:
def _select_options(self,
option: Union[ChromiumElement, List[ChromiumElement], Tuple[ChromiumElement]],
mode: str) -> ChromiumElement:
"""选中或取消某个选项
:param option: options元素对象
:param mode: 选中还是取消
:return: None
:return: <select>元素对象
"""
...

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from time import sleep
@ -51,7 +50,7 @@ class SessionPageSetter(BaseSetter):
def download_path(self, path):
super().download_path(path)
if self._owner._DownloadKit:
self._owner._DownloadKit.set.goal_path(self._owner._download_path)
self._owner._DownloadKit.set.save_path(self._owner._download_path)
def timeout(self, second):
self._owner._timeout = second
@ -230,7 +229,7 @@ class TabSetter(ChromiumBaseSetter):
super().download_path(path)
self._owner.browser._dl_mgr.set_path(self._owner, self._owner._download_path)
if self._owner._DownloadKit:
self._owner._DownloadKit.set.goal_path(self._owner._download_path)
self._owner._DownloadKit.set.save_path(self._owner._download_path)
def download_file_name(self, name=None, suffix=None):
self._owner.browser._dl_mgr.set_rename(self._owner.tab_id, name, suffix)
@ -263,8 +262,18 @@ class ChromiumPageSetter(TabSetter):
self._owner.browser.retry_interval = interval
def download_path(self, path):
super().download_path(path)
self._owner.browser._download_path = self._owner._download_path
if path is None:
path = '.'
self._owner._download_path = str(Path(path).absolute())
self._owner.browser.set.download_path(path)
if self._owner._DownloadKit:
self._owner._DownloadKit.set.save_path(path)
def download_file_name(self, name=None, suffix=None):
self._owner.browser.set.download_file_name(name, suffix)
def when_download_file_exists(self, mode):
self._owner.browser.set.when_download_file_exists(mode)
class WebPageSetter(ChromiumPageSetter):
@ -459,7 +468,7 @@ class WindowSetter(object):
try:
return self._owner._run_cdp('Browser.getWindowForTarget')
except:
sleep(.1)
sleep(.02)
raise RuntimeError('获取窗口信息失败。')
def _perform(self, bounds):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from pathlib import Path
from typing import Union, Tuple, Literal, Any, Optional

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from .._functions.web import location_in_viewport
from ..errors import CDPError, NoRectError, PageDisconnectedError, ElementLostError
@ -139,6 +138,18 @@ class PageStates(object):
def has_alert(self):
return self._owner._has_alert
@property
def is_headless(self):
return self._owner.browser.states.is_headless
@property
def is_existed(self):
return self._owner.browser.states.is_existed
@property
def is_incognito(self):
return self._owner.browser.states.is_incognito
class FrameStates(object):
def __init__(self, frame):

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, Tuple, List, Optional, Literal
@ -153,6 +152,21 @@ class PageStates(object):
"""返回当前页面是否存在弹窗"""
...
@property
def is_headless(self) -> bool:
"""返回浏览器是否无头模式"""
...
@property
def is_existed(self) -> bool:
"""返回浏览器是否接管的"""
...
@property
def is_incognito(self) -> bool:
"""返回浏览器是否无痕模式"""
...
class FrameStates(object):
_frame: ChromiumFrame = ...

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from time import sleep, perf_counter
@ -28,16 +27,15 @@ class OriginWaiter(object):
class BrowserWaiter(OriginWaiter):
def new_tab(self, timeout=None, curr_tab=None, raise_err=None):
if not curr_tab:
curr_tab = self._owner.tab_ids[0]
curr_tab = self._owner._newest_tab_id
elif hasattr(curr_tab, '_type'):
curr_tab = curr_tab.tab_id
if timeout is None:
timeout = self._owner.timeout
end_time = perf_counter() + timeout
while perf_counter() < end_time:
latest_tid = self._owner.tab_ids[0]
if curr_tab != latest_tid:
return latest_tid
if curr_tab != self._owner._newest_tab_id:
return self._owner._newest_tab_id
sleep(.01)
if raise_err is True or Settings.raise_when_wait_failed is True:
@ -51,18 +49,7 @@ class BrowserWaiter(OriginWaiter):
self._owner._dl_mgr.set_flag('browser', False if cancel_it else True)
if timeout is None:
timeout = self._owner.timeout
r = False
end_time = perf_counter() + timeout
while perf_counter() < end_time:
v = self._owner._dl_mgr.get_flag('browser')
if not isinstance(v, bool):
r = v
break
sleep(.005)
self._owner._dl_mgr.set_flag('browser', None)
return r
return wait_mission(self._owner, 'browser', timeout)
def downloads_done(self, timeout=None, cancel_if_timeout=True):
if not self._owner._dl_mgr._running:
@ -184,18 +171,7 @@ class BaseWaiter(OriginWaiter):
self._owner.browser._dl_mgr.set_flag(self._owner.tab_id, False if cancel_it else True)
if timeout is None:
timeout = self._owner.timeout
r = False
end_time = perf_counter() + timeout
while perf_counter() < end_time:
v = self._owner.browser._dl_mgr.get_flag(self._owner.tab_id)
if not isinstance(v, bool):
r = v
break
sleep(.005)
self._owner.browser._dl_mgr.set_flag(self._owner.tab_id, None)
return r
return wait_mission(self._owner.browser, self._owner.tab_id, timeout)
def url_change(self, text, exclude=False, timeout=None, raise_err=None):
return self._owner if self._change('url', text, exclude, timeout, raise_err) else False
@ -293,6 +269,9 @@ class ChromiumPageWaiter(TabWaiter):
def new_tab(self, timeout=None, raise_err=None):
return self._owner.browser.wait.new_tab(timeout=timeout, raise_err=raise_err)
def download_begin(self, timeout=None, cancel_it=False):
return self._owner.browser.wait.download_begin(timeout=timeout, cancel_it=cancel_it)
def all_downloads_done(self, timeout=None, cancel_if_timeout=True):
return self._owner.browser.wait.downloads_done(timeout=timeout, cancel_if_timeout=cancel_if_timeout)
@ -418,3 +397,17 @@ class FrameWaiter(BaseWaiter, ElementWaiter):
@property
def _timeout(self):
return self._owner.timeout
def wait_mission(browser, tid, timeout=None):
r = False
end_time = perf_counter() + timeout
while perf_counter() < end_time:
v = browser._dl_mgr.get_flag(tid)
if not isinstance(v, bool):
r = v
break
sleep(.005)
browser._dl_mgr.set_flag(tid, None)
return r

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from typing import Union, Tuple, Any
@ -768,3 +767,13 @@ class FrameWaiter(BaseWaiter, ElementWaiter):
:return: 成功返回元素对象失败返回False
"""
...
def wait_mission(browser: Chromium, tid: str, timeout: float = None) -> Union[DownloadMission, False]:
"""等待下载任务
:param browser: Chromium对象
:param tid: 标签页id
:param timeout: 超时时间
:return:
"""
...

View File

@ -2,10 +2,10 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from ._base.chromium import Chromium
from ._configs.chromium_options import ChromiumOptions
from ._elements.session_element import make_session_ele
from ._functions.by import By
from ._functions.keys import Keys
@ -23,7 +23,9 @@ def from_selenium(driver):
address, port = driver.caps.get('goog:chromeOptions', {}).get('debuggerAddress', ':').split(':')
if not address:
raise RuntimeError('获取失败。')
return Chromium(f'{address}:{port}')
co = ChromiumOptions().set_local_port(port)
co._ua_set = True
return Chromium(co)
def from_playwright(page_or_browser):
@ -48,4 +50,6 @@ def from_playwright(page_or_browser):
break
else:
raise RuntimeError('获取失败,请用管理员权限运行。')
return Chromium(f'127.0.0.1:{port}')
co = ChromiumOptions().set_local_port(f'127.0.0.1:{port}')
co._ua_set = True
return Chromium(co)

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""

View File

@ -2,8 +2,7 @@
"""
@Author : g1879
@Contact : g1879@qq.com
@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved.
@License : BSD 3-Clause.
@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved.
"""
from ._elements.chromium_element import ChromiumElement, ShadowRoot
from ._elements.none_element import NoneElement

64
LICENSE
View File

@ -1,29 +1,49 @@
BSD 3-Clause License
Copyright (c) 2020, g1879
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
允许任何人以个人身份使用或分发本项目源代码,但仅限于学习和合法非盈利目的。
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
个人或组织如未获得版权持有人授权,不得将本项目以源代码或二进制形式用于商业行为。
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
使用本项目需满足以下条款,如使用过程中出现违反任意一项条款的情形,授权自动失效。
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
* 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* 禁止将DrissionPage用于任何可能有损他人利益的项目中
* 禁止将DrissionPage用于攻击与骚扰行为
* 遵守Robots协议禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据
使用DrissionPage发生的一切行为均由使用人自行负责。
因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关
版权持有人不承担任何使用DrissionPage带来的风险和损失。
版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任。
---------------------------------------------------------
Anyone may use or distribute the source code of this project in their personal capacity,
but only for the purpose of learning and legal non-profit activities.
An individual or organization may not use the project's source code or binary form for
commercial purposes without authorization from the copyright holder.
The following terms and conditions must be met in order to use this project. Authorization
will automatically expire if any of the terms are violated during use.
* It is strictly prohibited to use the DrissionPage app for any project that may violate local
laws and ethical constraints.
* It is strictly prohibited to use DrissionPage for any project that may harm the interests of others.
* It is strictly prohibited to use DrissionPage for attack and harassment.
* Follow the Robots protocol and do not use the DrissionPage to collect data that is prohibited
by law or the system's Robots protocol.
All actions taken using DrissionPage are the responsibility of the user.
The copyright holder is not involved in any disputes or consequences arising from the use of
DrissionPage for any actions, and the copyright holder shall not bear any risks and losses arising
from the use of DrissionPage.
The copyright holder shall not bear any responsibility for any losses resulting from any defects in
DrissionPage.

View File

@ -1,4 +1,5 @@
include DrissionPage/_configs/configs.ini
include DrissionPage/_functions/suffixes.dat
include DrissionPage/*.pyi
include DrissionPage/*/*.py
include DrissionPage/*/*.pyi

View File

@ -14,9 +14,7 @@ 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)
项目地址:[gitee](https://gitee.com/g1879/DrissionPage) | [github](https://github.com/g1879/DrissionPage) | [gitcode](https://gitcode.com/g1879/DrissionPage)
您的星星是对我最大的支持💖
@ -34,7 +32,9 @@ python 版本3.6 及以上
**📖 使用文档:** [点击查看](https://DrissionPage.cn)
**交流 QQ 群:** 636361957
**交流 QQ 群:** 见使用文档
![](https://drissionpage.cn/codes.jpg)
---
@ -79,19 +79,28 @@ python 版本3.6 及以上
---
# 🖐🏻 免责声明
# 📝 使用条款
禁止将 DrissionPage 应用到任何可能会违反法律规定和道德约束的项目中。
友善使用 DrissionPage遵守蜘蛛协议禁止将 DrissionPage 用于任何可能有损他人的项目中。
如您选择使用 DrissionPage 即代表您遵守此协议,作者不承担任何由于您违反此协议带来任何的法律风险和损失。
同时,作者不对 DrissionPage 可能存在的缺陷导致的损失承担任何责任,一切后果由您承担。
允许任何人以个人身份使用或分发本项目源代码,但仅限于学习和合法非盈利目的。
个人或组织如未获得版权持有人授权,不得将本项目以源代码或二进制形式用于商业行为。
使用本项目需满足以下条款,如使用过程中出现违反任意一项条款的情形,授权自动失效。
- 禁止将DrissionPage应用到任何可能违反当地法律规定和道德约束的项目中
- 禁止将DrissionPage用于任何可能有损他人利益的项目中
- 禁止将DrissionPage用于攻击与骚扰行为
- 遵守Robots协议禁止将DrissionPage用于采集法律或系统Robots协议不允许的数据
使用DrissionPage发生的一切行为均由使用人自行负责。
因使用DrissionPage进行任何行为所产生的一切纠纷及后果均与版权持有人无关
版权持有人不承担任何使用DrissionPage带来的风险和损失。
版权持有人不对DrissionPage可能存在的缺陷导致的任何损失负任何责任。
---
# ☕ 请我喝咖啡
作者是单人开发者,开发和写文档工作量较为繁重。
作者是人开发者,开发和写文档工作量较为繁重。
如果本项目对您有所帮助,不妨打赏一下作者
![](https://gitee.com/g1879/DrissionPageDocs/raw/master/static/img/code.jpg)
![](https://drissionpage.cn/code2.jpg)

View File

@ -1,8 +1,8 @@
requests
lxml
cssselect
DownloadKit>=2.0.4
DownloadKit>=2.0.7
websocket-client
click
tldextract
tldextract>=3.4.4
psutil

View File

@ -13,7 +13,7 @@ setup(
description="Python based web automation tool. It can control the browser and send and receive data packets.",
long_description=long_description,
long_description_content_type="text/markdown",
license="BSD",
# license="BSD",
keywords="DrissionPage",
url="https://DrissionPage.cn",
include_package_data=True,
@ -23,17 +23,17 @@ setup(
'lxml',
'requests',
'cssselect',
'DownloadKit>=2.0.4',
'DownloadKit>=2.0.7',
'websocket-client',
'click',
'tldextract',
'tldextract>=3.4.4',
'psutil'
],
classifiers=[
"Programming Language :: Python :: 3.6",
"Development Status :: 4 - Beta",
"Topic :: Utilities",
"License :: OSI Approved :: BSD License",
# "License :: OSI Approved :: BSD License",
],
python_requires='>=3.6',
entry_points={