diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index 5e4d74e..c4bce69 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -13,4 +13,4 @@ from ._configs.chromium_options import ChromiumOptions from ._configs.session_options import SessionOptions __all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__'] -__version__ = '4.0.0b11' +__version__ = '4.0.0b12' diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 7735145..45a79f4 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -9,7 +9,7 @@ from urllib.parse import quote from DownloadKit import DownloadKit -from .._commons.constants import Settings +from .._commons.settings import Settings from .._commons.locator import get_loc from .._commons.web import format_html from .._elements.none_element import NoneElement diff --git a/DrissionPage/_commons/browser.py b/DrissionPage/_commons/browser.py index 8e3d6aa..f06f881 100644 --- a/DrissionPage/_commons/browser.py +++ b/DrissionPage/_commons/browser.py @@ -4,16 +4,19 @@ @Contact : g1879@qq.com """ from json import load, dump, JSONDecodeError +from os import popen from pathlib import Path +from platform import system +from re import search from subprocess import Popen, DEVNULL from tempfile import gettempdir from time import perf_counter, sleep -from platform import system from requests import get as requests_get -from ..errors import BrowserConnectError from .tools import port_is_using +from .._configs.options_manage import OptionsManager +from ..errors import BrowserConnectError def connect_browser(option): @@ -43,7 +46,6 @@ def connect_browser(option): # 传入的路径找不到,主动在ini文件、注册表、系统变量中找 except FileNotFoundError: - from ..easy_set import get_chrome_path chrome_path = get_chrome_path(show_msg=False) if not chrome_path: @@ -282,3 +284,91 @@ def _remove_arg_from_dict(target_dict: dict, arg: str) -> None: exec(src) except: pass + + +def get_chrome_path(ini_path=None, show_msg=True, from_ini=True, + from_regedit=True, from_system_path=True): + """从ini文件或系统变量中获取chrome.exe的路径 + :param ini_path: ini文件路径 + :param show_msg: 是否打印信息 + :param from_ini: 是否从ini文件获取 + :param from_regedit: 是否从注册表获取 + :param from_system_path: 是否从系统路径获取 + :return: chrome.exe路径 + """ + # -----------从ini文件中获取-------------- + if ini_path and from_ini: + try: + path = OptionsManager(ini_path).chrome_options['browser_path'] + except KeyError: + path = None + else: + path = None + + if path and Path(path).is_file(): + if show_msg: + print('ini文件中', end='') + return str(path) + + from platform import system + sys = system().lower() + if sys in ('macos', 'darwin'): + return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' + + elif sys == 'linux': + paths = ('/usr/bin/google-chrome', '/opt/google/chrome/google-chrome', + '/user/lib/chromium-browser/chromium-browser') + for p in paths: + if Path(p).exists(): + return p + return None + + elif sys != 'windows': + return None + + # -----------从注册表中获取-------------- + if from_regedit: + import winreg + try: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, + r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe', + reserved=0, access=winreg.KEY_READ) + k = winreg.EnumValue(key, 0) + winreg.CloseKey(key) + + if show_msg: + print('注册表中', end='') + + return k[1] + + except FileNotFoundError: + pass + + # -----------从系统变量中获取-------------- + if from_system_path: + try: + paths = popen('set path').read().lower() + except: + return None + r = search(r'[^;]*chrome[^;]*', paths) + + if r: + path = Path(r.group(0)) if 'chrome.exe' in r.group(0) else Path(r.group(0)) / 'chrome.exe' + + if path.exists(): + if show_msg: + print('系统变量中', end='') + return str(path) + + paths = paths.split(';') + + for path in paths: + path = Path(path) / 'chrome.exe' + + try: + if path.exists(): + if show_msg: + print('系统变量中', end='') + return str(path) + except OSError: + pass diff --git a/DrissionPage/_commons/browser.pyi b/DrissionPage/_commons/browser.pyi index 76a2008..b32952b 100644 --- a/DrissionPage/_commons/browser.pyi +++ b/DrissionPage/_commons/browser.pyi @@ -21,3 +21,10 @@ def set_flags(opt: ChromiumOptions) -> None: ... def test_connect(ip: str, port: Union[int, str], timeout: float = 30) -> None: ... + + +def get_chrome_path(ini_path: str = None, + show_msg: bool = True, + from_ini: bool = True, + from_regedit: bool = True, + from_system_path: bool = True, ) -> Union[str, None]: ... diff --git a/DrissionPage/_commons/cli.py b/DrissionPage/_commons/cli.py index 82e991a..ec3bec8 100644 --- a/DrissionPage/_commons/cli.py +++ b/DrissionPage/_commons/cli.py @@ -5,8 +5,9 @@ """ from click import command, option +from .._commons.tools import configs_to_here as ch +from .._configs.chromium_options import ChromiumOptions from .._pages.chromium_page import ChromiumPage -from ..easy_set import set_paths, configs_to_here as ch @command() @@ -29,5 +30,22 @@ def main(set_browser_path, set_user_path, configs_to_here, launch_browser): ChromiumPage(port) +def set_paths(browser_path=None, user_data_path=None): + """快捷的路径设置函数 + :param browser_path: 浏览器可执行文件路径 + :param user_data_path: 用户数据路径 + :return: None + """ + co = ChromiumOptions() + + if browser_path is not None: + co.set_browser_path(browser_path) + + if user_data_path is not None: + co.set_user_data_path(user_data_path) + + co.save() + + if __name__ == '__main__': main() diff --git a/DrissionPage/_commons/constants.py b/DrissionPage/_commons/settings.py similarity index 82% rename from DrissionPage/_commons/constants.py rename to DrissionPage/_commons/settings.py index 4208282..ee5cda9 100644 --- a/DrissionPage/_commons/constants.py +++ b/DrissionPage/_commons/settings.py @@ -3,8 +3,6 @@ @Author : g1879 @Contact : g1879@qq.com """ -FRAME_ELEMENT = ('iframe', 'frame') -ERROR = 'error' class Settings(object): diff --git a/DrissionPage/_commons/tools.py b/DrissionPage/_commons/tools.py index 5e7dd80..813c3d1 100644 --- a/DrissionPage/_commons/tools.py +++ b/DrissionPage/_commons/tools.py @@ -11,6 +11,8 @@ from time import perf_counter, sleep from psutil import process_iter, AccessDenied, NoSuchProcess, ZombieProcess +from .._configs.options_manage import OptionsManager + def get_usable_path(path, is_file=True, parents=True): """检查文件或文件夹是否有重名,并返回可以使用的路径 @@ -238,3 +240,13 @@ def stop_process_on_port(port): pass except Exception as e: print(f"{proc.pid} {port}: {e}") + + +def configs_to_here(save_name=None): + """把默认ini文件复制到当前目录 + :param save_name: 指定文件名,为None则命名为'dp_configs.ini' + :return: None + """ + om = OptionsManager('default') + save_name = f'{save_name}.ini' if save_name is not None else 'dp_configs.ini' + om.save(save_name) diff --git a/DrissionPage/_commons/tools.pyi b/DrissionPage/_commons/tools.pyi index d4294fb..03aae58 100644 --- a/DrissionPage/_commons/tools.pyi +++ b/DrissionPage/_commons/tools.pyi @@ -39,3 +39,6 @@ def wait_until(page, condition: Union[FunctionType, str, tuple], timeout: float, def stop_process_on_port(port: Union[int, str]) -> None: ... + + +def configs_to_here(file_name: Union[Path, str] = None) -> None: ... diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 3e1bc4e..115bdd0 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -10,7 +10,7 @@ from time import perf_counter, sleep from .none_element import NoneElement from .session_element import make_session_ele from .._base.base import DrissionElement, BaseElement -from .._commons.constants import FRAME_ELEMENT, Settings +from .._commons.settings import Settings from .._commons.keys import keys_to_typing, keyDescriptionForString, keyDefinitions from .._commons.locator import get_loc from .._commons.tools import get_usable_path @@ -25,6 +25,8 @@ from .._units.waiter import ElementWaiter from ..errors import (ContextLossError, ElementLossError, JavaScriptError, ElementNotFoundError, CDPError, NoResourceError, AlertExistsError) +__FRAME_ELEMENT__ = ('iframe', 'frame') + class ChromiumElement(DrissionElement): """控制浏览器元素的对象""" @@ -402,7 +404,7 @@ class ChromiumElement(DrissionElement): :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 :return: SessionElement对象或属性、文本 """ - if self.tag in FRAME_ELEMENT: + if self.tag in __FRAME_ELEMENT__: r = make_session_ele(self.inner_html, loc_or_str) else: r = make_session_ele(self, loc_or_str) @@ -419,7 +421,7 @@ class ChromiumElement(DrissionElement): :param loc_or_str: 定位符 :return: SessionElement或属性、文本组成的列表 """ - if self.tag in FRAME_ELEMENT: + if self.tag in __FRAME_ELEMENT__: return make_session_ele(self.inner_html, loc_or_str, single=False) return make_session_ele(self, loc_or_str, single=False) @@ -1103,7 +1105,7 @@ def find_by_xpath(ele, xpath, single, timeout, relative=True): :return: ChromiumElement或其组成的列表 """ type_txt = '9' if single else '7' - node_txt = 'this.contentDocument' if ele.tag in FRAME_ELEMENT and not relative else 'this' + node_txt = 'this.contentDocument' if ele.tag in __FRAME_ELEMENT__ and not relative else 'this' js = make_js_for_find_ele_by_xpath(xpath, type_txt, node_txt) r = ele.page.run_cdp_loaded('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, returnByValue=False, awaitPromise=True, userGesture=True) @@ -1200,7 +1202,7 @@ def make_chromium_ele(page, node_id=None, obj_id=None): raise ElementLossError ele = ChromiumElement(page, obj_id=obj_id, node_id=node_id, backend_id=backend_id) - if ele.tag in FRAME_ELEMENT: + if ele.tag in __FRAME_ELEMENT__: from .._pages.chromium_frame import ChromiumFrame ele = ChromiumFrame(page, ele) diff --git a/DrissionPage/_elements/none_element.py b/DrissionPage/_elements/none_element.py index 96c1555..0ef09d3 100644 --- a/DrissionPage/_elements/none_element.py +++ b/DrissionPage/_elements/none_element.py @@ -3,7 +3,7 @@ @Author : g1879 @Contact : g1879@qq.com """ -from .._commons.constants import Settings +from .._commons.settings import Settings from ..errors import ElementNotFoundError diff --git a/DrissionPage/_pages/chromium_base.py b/DrissionPage/_pages/chromium_base.py index 6557701..69f5ec7 100644 --- a/DrissionPage/_pages/chromium_base.py +++ b/DrissionPage/_pages/chromium_base.py @@ -10,7 +10,7 @@ from threading import Thread from time import perf_counter, sleep from .._base.base import BasePage -from .._commons.constants import ERROR, Settings +from .._commons.settings import Settings from .._commons.locator import get_loc, is_loc from .._commons.tools import get_usable_path from .._commons.web import location_in_viewport @@ -28,6 +28,8 @@ from .._units.waiter import BaseWaiter from ..errors import (ContextLossError, ElementLossError, CDPError, PageClosedError, NoRectError, AlertExistsError, GetDocumentError, ElementNotFoundError) +__ERROR__ = 'error' + class ChromiumBase(BasePage): """标签页、frame、页面基类""" @@ -439,10 +441,10 @@ class ChromiumBase(BasePage): :return: 执行的结果 """ r = self.driver.run(cmd, **cmd_args) - if ERROR not in r: + if __ERROR__ not in r: return r - error = r[ERROR] + error = r[__ERROR__] if error in ('Cannot find context with specified id', 'Inspected target navigated or closed'): raise ContextLossError elif error in ('Could not find node with given id', 'Could not find object with given id', @@ -725,7 +727,7 @@ class ChromiumBase(BasePage): if isinstance(loc_ind_ele, str): if not is_loc(loc_ind_ele): xpath = f'xpath://*[(name()="iframe" or name()="frame") and ' \ - f'(@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]' + f'(@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]' else: xpath = loc_ind_ele ele = self._ele(xpath, timeout=timeout) diff --git a/DrissionPage/_units/clicker.py b/DrissionPage/_units/clicker.py index c103004..18359ff 100644 --- a/DrissionPage/_units/clicker.py +++ b/DrissionPage/_units/clicker.py @@ -5,7 +5,7 @@ """ from time import perf_counter, sleep -from .._commons.constants import Settings +from .._commons.settings import Settings from .._commons.web import offset_scroll from ..errors import CanNotClickError, CDPError, NoRectError diff --git a/DrissionPage/_units/waiter.py b/DrissionPage/_units/waiter.py index 9360ba9..3cbca57 100644 --- a/DrissionPage/_units/waiter.py +++ b/DrissionPage/_units/waiter.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- from time import sleep, perf_counter -from .._commons.constants import Settings +from .._commons.settings import Settings from ..errors import WaitTimeoutError, NoRectError diff --git a/DrissionPage/common.py b/DrissionPage/common.py index 72a0deb..88f9e57 100644 --- a/DrissionPage/common.py +++ b/DrissionPage/common.py @@ -3,11 +3,11 @@ @Author : g1879 @Contact : g1879@qq.com """ +from ._commons.by import By +from ._commons.keys import Keys +from ._commons.settings import Settings +from ._commons.tools import wait_until, configs_to_here from ._elements.session_element import make_session_ele from ._units.action_chains import ActionChains -from ._commons.keys import Keys -from ._commons.by import By -from ._commons.constants import Settings -from ._commons.tools import wait_until -__all__ = ['make_session_ele', 'ActionChains', 'Keys', 'By', 'Settings', 'wait_until'] +__all__ = ['make_session_ele', 'ActionChains', 'Keys', 'By', 'Settings', 'wait_until', 'configs_to_here'] diff --git a/DrissionPage/common.pyi b/DrissionPage/common.pyi deleted file mode 100644 index f1d57c4..0000000 --- a/DrissionPage/common.pyi +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding:utf-8 -*- -""" -@Author : g1879 -@Contact : g1879@qq.com -""" -from ._commons.by import By as By -from ._commons.constants import Settings as Settings -from ._commons.keys import Keys as Keys -from ._elements.session_element import make_session_ele as make_session_ele -from ._units.action_chains import ActionChains as ActionChains diff --git a/DrissionPage/easy_set.py b/DrissionPage/easy_set.py deleted file mode 100644 index 2bf6df5..0000000 --- a/DrissionPage/easy_set.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding:utf-8 -*- -""" -@Author : g1879 -@Contact : g1879@qq.com -""" -from os import popen -from pathlib import Path -from re import search - -from ._configs.chromium_options import ChromiumOptions -from ._configs.options_manage import OptionsManager - - -def configs_to_here(save_name=None): - """把默认ini文件复制到当前目录 - :param save_name: 指定文件名,为None则命名为'dp_configs.ini' - :return: None - """ - om = OptionsManager('default') - save_name = f'{save_name}.ini' if save_name is not None else 'dp_configs.ini' - om.save(save_name) - - -def show_settings(ini_path=None): - """打印ini文件内容 - :param ini_path: ini文件路径 - :return: None - """ - OptionsManager(ini_path).show() - - -def set_paths(browser_path=None, - local_port=None, - debugger_address=None, - download_path=None, - user_data_path=None, - cache_path=None, - ini_path=None): - """快捷的路径设置函数 - :param browser_path: 浏览器可执行文件路径 - :param local_port: 本地端口号 - :param debugger_address: 调试浏览器地址,例:127.0.0.1:9222 - :param download_path: 下载文件路径 - :param user_data_path: 用户数据路径 - :param cache_path: 缓存路径 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - om = OptionsManager(ini_path) - - def format_path(path: str) -> str: - return str(path) if path else '' - - if browser_path is not None: - om.set_item('chrome_options', 'browser_path', format_path(browser_path)) - - if local_port is not None: - om.set_item('chrome_options', 'debugger_address', f'127.0.0.1:{local_port}') - - if debugger_address is not None: - address = debugger_address.replace('localhost', '127.0.0.1').lstrip('http://').lstrip('https://') - om.set_item('chrome_options', 'debugger_address', address) - - if download_path is not None: - om.set_item('paths', 'download_path', format_path(download_path)) - - om.save() - - if user_data_path is not None: - set_argument('--user-data-dir', format_path(user_data_path), ini_path) - - if cache_path is not None: - set_argument('--disk-cache-dir', format_path(cache_path), ini_path) - - -def use_auto_port(on_off=True, ini_path=None): - """设置启动浏览器时使用自动分配的端口和临时文件夹 - :param on_off: 是否开启自动端口 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - if not isinstance(on_off, bool): - raise TypeError('on_off参数只能输入bool值。') - om = OptionsManager(ini_path) - om.set_item('chrome_options', 'auto_port', on_off) - om.save() - - -def use_system_user_path(on_off=True, ini_path=None): - """设置是否使用系统安装的浏览器默认用户文件夹 - :param on_off: 开或关 - :param ini_path: 要修改的ini文件路径 - :return: 当前对象 - """ - if not isinstance(on_off, bool): - raise TypeError('on_off参数只能输入bool值。') - om = OptionsManager(ini_path) - om.set_item('chrome_options', 'system_user_path', on_off) - om.save() - - -def set_argument(arg, value=None, ini_path=None): - """设置浏览器配置argument属性 - :param arg: 属性名 - :param value: 属性值,有值的属性传入值,没有的传入None - :param ini_path: 要修改的ini文件路径 - :return: None - """ - co = ChromiumOptions(ini_path=ini_path) - co.set_argument(arg, value) - co.save() - - -def set_headless(on_off=True, ini_path=None): - """设置是否隐藏浏览器界面 - :param on_off: 开或关 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - on_off = 'new' if on_off else False - set_argument('--headless', on_off, ini_path) - - -def set_no_imgs(on_off=True, ini_path=None): - """设置是否禁止加载图片 - :param on_off: 开或关 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - on_off = None if on_off else False - set_argument('--blink-settings=imagesEnabled=false', on_off, ini_path) - - -def set_no_js(on_off=True, ini_path=None): - """设置是否禁用js - :param on_off: 开或关 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - on_off = None if on_off else False - set_argument('--disable-javascript', on_off, ini_path) - - -def set_mute(on_off=True, ini_path=None): - """设置是否静音 - :param on_off: 开或关 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - on_off = None if on_off else False - set_argument('--mute-audio', on_off, ini_path) - - -def set_user_agent(user_agent, ini_path=None): - """设置user agent - :param user_agent: user agent文本 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - set_argument('--user-agent', user_agent, ini_path) - - -def set_proxy(proxy, ini_path=None): - """设置代理 - :param proxy: 代理网址和端口 - :param ini_path: 要修改的ini文件路径 - :return: None - """ - set_argument('--proxy-server', proxy, ini_path) - - -def get_chrome_path(ini_path=None, - show_msg=True, - from_ini=True, - from_regedit=True, - from_system_path=True): - """从ini文件或系统变量中获取chrome.exe的路径 - :param ini_path: ini文件路径 - :param show_msg: 是否打印信息 - :param from_ini: 是否从ini文件获取 - :param from_regedit: 是否从注册表获取 - :param from_system_path: 是否从系统路径获取 - :return: chrome.exe路径 - """ - # -----------从ini文件中获取-------------- - if ini_path and from_ini: - try: - path = OptionsManager(ini_path).chrome_options['browser_path'] - except KeyError: - path = None - else: - path = None - - if path and Path(path).is_file(): - if show_msg: - print('ini文件中', end='') - return str(path) - - from platform import system - sys = system().lower() - if sys in ('macos', 'darwin'): - return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' - - elif sys == 'linux': - paths = ('/usr/bin/google-chrome', '/opt/google/chrome/google-chrome', - '/user/lib/chromium-browser/chromium-browser') - for p in paths: - if Path(p).exists(): - return p - return None - - elif sys != 'windows': - return None - - # -----------从注册表中获取-------------- - if from_regedit: - import winreg - try: - key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe', - reserved=0, access=winreg.KEY_READ) - k = winreg.EnumValue(key, 0) - winreg.CloseKey(key) - - if show_msg: - print('注册表中', end='') - - return k[1] - - except FileNotFoundError: - pass - - # -----------从系统变量中获取-------------- - if from_system_path: - try: - paths = popen('set path').read().lower() - except: - return None - r = search(r'[^;]*chrome[^;]*', paths) - - if r: - path = Path(r.group(0)) if 'chrome.exe' in r.group(0) else Path(r.group(0)) / 'chrome.exe' - - if path.exists(): - if show_msg: - print('系统变量中', end='') - return str(path) - - paths = paths.split(';') - - for path in paths: - path = Path(path) / 'chrome.exe' - - try: - if path.exists(): - if show_msg: - print('系统变量中', end='') - return str(path) - except OSError: - pass diff --git a/DrissionPage/easy_set.pyi b/DrissionPage/easy_set.pyi deleted file mode 100644 index 3e8fc47..0000000 --- a/DrissionPage/easy_set.pyi +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding:utf-8 -*- -""" -@Author : g1879 -@Contact : g1879@qq.com -""" -from pathlib import Path -from typing import Union - - -def configs_to_here(file_name: Union[Path, str] = None) -> None: ... - - -def show_settings(ini_path: Union[str, Path] = None) -> None: ... - - -def set_paths(browser_path: Union[str, Path] = None, - local_port: Union[int, str] = None, - debugger_address: str = None, - download_path: Union[str, Path] = None, - user_data_path: Union[str, Path] = None, - cache_path: Union[str, Path] = None, - ini_path: Union[str, Path] = None) -> None: ... - - -def use_auto_port(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def use_system_user_path(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def set_argument(arg: str, value: Union[bool, str] = None, ini_path: Union[str, Path] = None) -> None: ... - - -def set_headless(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def set_no_imgs(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def set_no_js(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def set_mute(on_off: bool = True, ini_path: Union[str, Path] = None) -> None: ... - - -def set_user_agent(user_agent: str, ini_path: Union[str, Path] = None) -> None: ... - - -def set_proxy(proxy: str, ini_path: Union[str, Path] = None) -> None: ... - - -def get_chrome_path(ini_path: str = None, - show_msg: bool = True, - from_ini: bool = True, - from_regedit: bool = True, - from_system_path: bool = True, ) -> Union[str, None]: ... diff --git a/setup.py b/setup.py index d3d84ce..1bd9af1 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh: setup( name="DrissionPage", - version="4.0.0b11", + version="4.0.0b12", author="g1879", author_email="g1879@qq.com", description="Python based web automation tool. It can control the browser and send and receive data packets.",