diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md index c6e48e4..77d01e0 100644 --- a/.gitee/ISSUE_TEMPLATE.zh-CN.md +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -1,11 +1,17 @@ 在提交issue前,请确认已经给本库点了星星,这对我来说很重要。 使用方法请查看[使用文档](http://drissionpage.cn),文档里都有。 -也可在QQ群里提问(636361957)。 + +使用问题作者可能不能及时处理,可在知识星球提问,会尽快回复。 + +![](https://drissionpage.cn/zsxq.png) 请围绕以下内容陈述您的问题: 1. 遇到了什么问题?什么场景下出现的?如何重现? -2. 请附上代码和报错信息(如有) +2. 请附上代码和报错信息 3. DrissionPage、浏览器、python版本号是多少? 4. 有什么意见建议? + +请在下方写正文,不要把内容插入到上面的问题中。 +--- \ No newline at end of file diff --git a/DrissionPage/__init__.py b/DrissionPage/__init__.py index e173e0d..d1679b2 100644 --- a/DrissionPage/__init__.py +++ b/DrissionPage/__init__.py @@ -2,16 +2,27 @@ """ @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 +from ._configs.session_options import SessionOptions from ._pages.chromium_page import ChromiumPage from ._pages.session_page import SessionPage from ._pages.web_page import WebPage -# 启动配置类 -from ._configs.chromium_options import ChromiumOptions -from ._configs.session_options import SessionOptions - -__all__ = ['ChromiumPage', 'ChromiumOptions', 'SessionOptions', 'SessionPage', 'WebPage', '__version__'] -__version__ = '4.0.5.4' +__version__ = '4.1.0.13' diff --git a/DrissionPage/__init__.pyi b/DrissionPage/__init__.pyi new file mode 100644 index 0000000..b7c09b3 --- /dev/null +++ b/DrissionPage/__init__.pyi @@ -0,0 +1,15 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved. +""" +from ._base.chromium import Chromium +from ._configs.chromium_options import ChromiumOptions +from ._configs.session_options import SessionOptions +from ._pages.chromium_page import ChromiumPage +from ._pages.session_page import SessionPage +from ._pages.web_page import WebPage + +__all__ = ['WebPage', 'ChromiumPage', 'Chromium', 'ChromiumOptions', 'SessionOptions', 'SessionPage', '__version__'] +__version__: str = ... diff --git a/DrissionPage/_base/base.py b/DrissionPage/_base/base.py index 1fcbd40..aa2dc14 100644 --- a/DrissionPage/_base/base.py +++ b/DrissionPage/_base/base.py @@ -2,20 +2,23 @@ """ @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 from pathlib import Path from re import sub from urllib.parse import quote from DownloadKit import DownloadKit +from requests import Session -from .._functions.settings import Settings -from .._functions.locator import get_loc -from .._functions.web import format_html +from .._configs.session_options import SessionOptions from .._elements.none_element import NoneElement +from .._functions.elements import get_frame, get_eles +from .._functions.locator import get_loc, is_selenium_loc +from .._functions.settings import Settings +from .._functions.web import format_html from ..errors import ElementNotFoundError @@ -31,6 +34,23 @@ class BaseParser(object): def eles(self, locator, timeout=None): return self._ele(locator, timeout, index=None) + def find(self, locators, any_one=True, first_ele=True, timeout=None): + if 'Session' in self._type: + timeout = 0 + if timeout is None: + timeout = self.timeout + if isinstance(locators, tuple) and not is_selenium_loc(locators): + raise ValueError(f"locators参数为tuple时必须是单独的定位符,即长度为2,且第一位是'id', 'xpath', 'link text', " + f"'partial link text','name', 'tag name', 'class name', 'css selector' 之一。\n" + f"现在是:{locators}") + r = get_eles(locators, self, any_one, first_ele, timeout) + if any_one: + for ele in r: + if r[ele]: + return ele, r[ele] + return None, None + return r + # ----------------以下属性或方法待后代实现---------------- @property def html(self): @@ -45,7 +65,7 @@ class BaseParser(object): def _ele(self, locator, timeout=None, index=1, raise_err=None, method=None): pass - def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): + def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None): pass @@ -54,9 +74,32 @@ class BaseElement(BaseParser): def __init__(self, owner=None): self.owner = owner - self.page = owner._page if owner else None self._type = 'BaseElement' + def get_frame(self, loc_or_ind, timeout=None): + if not isinstance(loc_or_ind, (int, str, tuple)): + raise TypeError('loc_or_ind参数是定位符或序号。') + return get_frame(self, loc_ind_ele=loc_or_ind, timeout=timeout) + + def _ele(self, locator, timeout=None, index=1, relative=False, raise_err=None, method=None): + if hasattr(locator, '_type'): + return locator + if timeout is None: + timeout = self.timeout + r = self._find_elements(locator, timeout=timeout, index=index, relative=relative, raise_err=raise_err) + if r or isinstance(r, list): + return r + if raise_err is True or (Settings.raise_when_ele_not_found and raise_err is None): + raise ElementNotFoundError(None, method, {'locator': locator, 'index': index, 'timeout': timeout}) + + r.method = method + r.args = {'locator': locator, 'index': index, 'timeout': timeout} + return r + + @property + def timeout(self): + return self.owner.timeout if self.owner else 10 + # ----------------以下属性或方法由后代实现---------------- @property def tag(self): @@ -71,92 +114,46 @@ class BaseElement(BaseParser): def nexts(self): pass - def _ele(self, locator, timeout=None, index=1, relative=False, raise_err=None, method=None): - """调用获取元素的方法 - :param locator: 定位符 - :param timeout: 超时时间(秒) - :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 - :param relative: 是否相对定位 - :param raise_err: 找不到时是否抛出异常 - :param method: 调用的方法名 - :return: 元素对象或它们组成的列表 - """ - r = self._find_elements(locator, timeout=timeout, index=index, relative=relative, raise_err=raise_err) - if r or isinstance(r, list): - return r - if Settings.raise_when_ele_not_found or raise_err is True: - raise ElementNotFoundError(None, method, {'locator': locator, 'index': index}) - - r.method = method - r.args = {'locator': locator, 'index': index} - return r - class DrissionElement(BaseElement): - """ChromiumElement 和 SessionElement的基类,但不是ShadowRoot的基类""" @property def link(self): - """返回href或src绝对url""" return self.attr('href') or self.attr('src') @property def css_path(self): - """返回css path路径""" - return self._get_ele_path('css') + return self._get_ele_path(xpath=False) @property def xpath(self): - """返回xpath路径""" - return self._get_ele_path('xpath') + return self._get_ele_path() @property def comments(self): - """返回元素注释文本组成的列表""" return self.eles('xpath:.//comment()') def texts(self, text_node_only=False): - """返回元素内所有直接子节点的文本,包括元素和文本节点 - :param text_node_only: 是否只返回文本节点 - :return: 文本列表 - """ - if text_node_only: - texts = self.eles('xpath:/text()') - else: - texts = [x if isinstance(x, str) else x.text for x in self.eles('xpath:./text() | *')] - + texts = self.eles('xpath:/text()') if text_node_only else [x if isinstance(x, str) else x.text + for x in self.eles('xpath:./text() | *')] return [format_html(x.strip(' ').rstrip('\n')) for x in texts if x and sub('[\r\n\t ]', '', x) != ''] - def parent(self, level_or_loc=1, index=1): - """返回上面某一级父元素,可指定层数或用查询语法定位 - :param level_or_loc: 第几级父元素,1开始,或定位符 - :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 - :return: 上级元素对象 - """ + def parent(self, level_or_loc=1, index=1, timeout=None): if isinstance(level_or_loc, int): loc = f'xpath:./ancestor::*[{level_or_loc}]' elif isinstance(level_or_loc, (tuple, str)): loc = get_loc(level_or_loc, True) - if loc[0] == 'css selector': raise ValueError('此css selector语法不受支持,请换成xpath。') - loc = f'xpath:./ancestor::{loc[1].lstrip(". / ")}[{index}]' else: raise TypeError('level_or_loc参数只能是tuple、int或str。') - return self._ele(loc, timeout=0, relative=True, raise_err=False, method='parent()') + return self._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()') def child(self, locator='', index=1, timeout=None, ele_only=True): - """返回直接子元素元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 直接子元素或节点文本组成的列表 - """ if isinstance(locator, int): index = locator locator = '' @@ -173,52 +170,18 @@ class DrissionElement(BaseElement): {'locator': locator, 'index': index, 'ele_only': ele_only}) def prev(self, locator='', index=1, timeout=None, ele_only=True): - """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素 - """ return self._get_relative('prev()', 'preceding', True, locator, index, timeout, ele_only) def next(self, locator='', index=1, timeout=None, ele_only=True): - """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 后面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素 - """ return self._get_relative('next()', 'following', True, locator, index, timeout, ele_only) def before(self, locator='', index=1, timeout=None, ele_only=True): - """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素前面的某个元素或节点 - """ return self._get_relative('before()', 'preceding', False, locator, index, timeout, ele_only) def after(self, locator='', index=1, timeout=None, ele_only=True): - """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 后面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素后面的某个元素或节点 - """ return self._get_relative('after()', 'following', False, locator, index, timeout, ele_only) def children(self, locator='', timeout=None, ele_only=True): - """返回直接子元素元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 直接子元素或节点文本组成的列表 - """ if not locator: loc = '*' if ele_only else 'node()' else: @@ -232,53 +195,20 @@ class DrissionElement(BaseElement): return [e for e in nodes if not (isinstance(e, str) and sub('[ \n\t\r]', '', e) == '')] def prevs(self, locator='', timeout=None, ele_only=True): - """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本组成的列表 - """ return self._get_relatives(locator=locator, direction='preceding', timeout=timeout, ele_only=ele_only) def nexts(self, locator='', timeout=None, ele_only=True): - """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本组成的列表 - """ return self._get_relatives(locator=locator, direction='following', timeout=timeout, ele_only=ele_only) def befores(self, locator='', timeout=None, ele_only=True): - """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素前面的元素或节点组成的列表 - """ return self._get_relatives(locator=locator, direction='preceding', brother=False, timeout=timeout, ele_only=ele_only) def afters(self, locator='', timeout=None, ele_only=True): - """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素后面的元素或节点组成的列表 - """ return self._get_relatives(locator=locator, direction='following', brother=False, timeout=timeout, ele_only=ele_only) def _get_relative(self, func, direction, brother, locator='', index=1, timeout=None, ele_only=True): - """获取一个亲戚元素或节点,可用查询语法筛选,可指定返回筛选结果的第几个 - :param func: 方法名称 - :param direction: 方向,'following' 或 'preceding' - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素前面的某个元素或节点 - """ if isinstance(locator, int): index = locator locator = '' @@ -287,14 +217,6 @@ class DrissionElement(BaseElement): {'locator': locator, 'index': index, 'ele_only': ele_only}) def _get_relatives(self, index=None, locator='', direction='following', brother=True, timeout=.5, ele_only=True): - """按要求返回兄弟元素或节点组成的列表 - :param index: 获取第几个,该参数不为None时只获取该编号的元素 - :param locator: 用于筛选的查询语法 - :param direction: 'following' 或 'preceding',查找的方向 - :param brother: 查找范围,在同级查找还是整个dom前后查找 - :param timeout: 查找等待时间(秒) - :return: 元素对象或字符串 - """ brother = '-sibling' if brother else '' if not locator: @@ -332,20 +254,17 @@ class DrissionElement(BaseElement): def attr(self, name: str): return '' - def _get_ele_path(self, mode): + def _get_ele_path(self, xpath=True): return '' - def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): + def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None): pass class BasePage(BaseParser): - """页面类的基类""" def __init__(self): - """初始化函数""" self._url = None - self._timeout = 10 self._url_available = None self.retry_times = 3 self.retry_interval = 2 @@ -353,48 +272,33 @@ class BasePage(BaseParser): self._download_path = None self._none_ele_return_value = False self._none_ele_value = None + self._session = None + self._headers = None + self._session_options = None self._type = 'BasePage' @property def title(self): - """返回网页title""" ele = self._ele('xpath://title', raise_err=False, method='title') return ele.text if ele else None - @property - def timeout(self): - """返回查找元素时等待的秒数""" - return self._timeout - - @timeout.setter - def timeout(self, second): - """设置查找元素时等待的秒数""" - self._timeout = second - @property def url_available(self): - """返回当前访问的url有效性""" return self._url_available @property def download_path(self): - """返回默认下载路径""" return self._download_path @property def download(self): - """返回下载器对象""" if self._DownloadKit is None: - self._DownloadKit = DownloadKit(driver=self, goal_path=self.download_path) + if not self._session: + self._create_session() + self._DownloadKit = DownloadKit(driver=self, save_path=self.download_path) return self._DownloadKit def _before_connect(self, url, retry, interval): - """连接前的准备 - :param url: 要访问的url - :param retry: 重试次数 - :param interval: 重试间隔 - :return: 重试次数、间隔、是否文件组成的tuple - """ is_file = False if isinstance(url, Path) or ('://' not in url and ':\\\\' not in url): p = Path(url) @@ -407,6 +311,24 @@ class BasePage(BaseParser): interval = interval if interval is not None else self.retry_interval return retry, interval, is_file + def _set_session_options(self, session_or_options=None): + if not session_or_options: + self._session_options = SessionOptions(session_or_options) + + elif isinstance(session_or_options, SessionOptions): + self._session_options = session_or_options + + elif isinstance(session_or_options, Session): + self._session_options = SessionOptions() + self._session = copy(session_or_options) + self._headers = self._session.headers + self._session.headers = None + + def _create_session(self): + if not self._session_options: + self._set_session_options() + self._session, self._headers = self._session_options.make_session() + # ----------------以下属性或方法由后代实现---------------- @property def url(self): @@ -420,33 +342,22 @@ class BasePage(BaseParser): def user_agent(self): return - @abstractmethod - def cookies(self, as_dict=False, all_info=False): - return {} - @abstractmethod def get(self, url, show_errmsg=False, retry=None, interval=None): pass def _ele(self, locator, timeout=None, index=1, raise_err=None, method=None): - """调用获取元素的方法 - :param locator: 定位符 - :param timeout: 超时时间(秒) - :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 - :param raise_err: 找不到时是否抛出异常 - :param method: 调用的方法名 - :return: 元素对象或它们组成的列表 - """ if not locator: raise ElementNotFoundError(None, method, {'locator': locator}) + if timeout is None: + timeout = self.timeout r = self._find_elements(locator, timeout=timeout, index=index, raise_err=raise_err) - if r or isinstance(r, list): return r - if Settings.raise_when_ele_not_found or raise_err is True: - raise ElementNotFoundError(None, method, {'locator': locator, 'index': index}) + if raise_err is True or (Settings.raise_when_ele_not_found and raise_err is None): + raise ElementNotFoundError(None, method, {'locator': locator, 'index': index, 'timeout': timeout}) r.method = method - r.args = {'locator': locator, 'index': index} + r.args = {'locator': locator, 'index': index, 'timeout': timeout} return r diff --git a/DrissionPage/_base/base.pyi b/DrissionPage/_base/base.pyi index 8de4f35..7c5424c 100644 --- a/DrissionPage/_base/base.pyi +++ b/DrissionPage/_base/base.pyi @@ -2,17 +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. """ from abc import abstractmethod -from typing import Union, Tuple, List, Any, Optional +from typing import Union, Tuple, List, Any, Optional, Dict from DownloadKit import DownloadKit +from requests import Session +from requests.structures import CaseInsensitiveDict +from .._configs.session_options import SessionOptions +from .._elements.chromium_element import ChromiumElement from .._elements.none_element import NoneElement from .._elements.session_element import SessionElement from .._functions.elements import SessionElementsList +from .._pages.chromium_frame import ChromiumFrame from .._pages.chromium_page import ChromiumPage from .._pages.session_page import SessionPage from .._pages.web_page import WebPage @@ -20,6 +24,7 @@ from .._pages.web_page import WebPage class BaseParser(object): _type: str + timeout: float def __call__(self, locator: Union[Tuple[str, str], str], index: int = 1): ... @@ -30,6 +35,23 @@ class BaseParser(object): def eles(self, locator: Union[Tuple[str, str], str], timeout=None): ... + def find(self, + locators: Union[str, List[str], tuple], + any_one: bool = True, + first_ele: bool = True, + timeout: float = None) -> Union[Dict[str, ChromiumElement], Dict[str, SessionElement], + Dict[str, List[ChromiumElement]], Dict[str, List[SessionElement]], Tuple[str, SessionElement], + Tuple[str, ChromiumElement]]: + """传入多个定位符,获取多个ele + :param locators: 定位符组成的列表 + :param any_one: 是否任何一个定位符找到结果即返回 + :param first_ele: 每个定位符是否只获取第一个元素 + :param timeout: 超时时间(秒) + :return: any_one为True时,返回一个找到的元素定位符和对象组成的元组,格式:(loc, ele),全都没找到返回(None, None) + any_one为False时,返回dict格式,key为定位符,value为找到的元素或列表 + """ + ... + # ----------------以下属性或方法待后代实现---------------- @property def html(self) -> str: ... @@ -49,30 +71,26 @@ class BaseParser(object): def _find_elements(self, locator: Union[Tuple[str, str], str], - timeout: float = None, + timeout: float, index: Optional[int] = 1, relative: bool = False, raise_err: bool = None): ... class BaseElement(BaseParser): + owner: BasePage = ... - def __init__(self, owner: BasePage = None): - self.owner: BasePage = ... - self.page: Union[ChromiumPage, SessionPage, WebPage] = ... + def __init__(self, owner: BasePage = None): ... + + @property + def timeout(self) -> float: + """返回其查找元素时超时时间""" + ... # ----------------以下属性或方法由后代实现---------------- @property def tag(self) -> str: ... - def _ele(self, - locator: Union[Tuple[str, str], str], - timeout: float = None, - index: Optional[int] = 1, - relative: bool = False, - raise_err: bool = None, - method: str = None): ... - def parent(self, level_or_loc: Union[tuple, str, int] = 1): ... def prev(self, index: int = 1) -> None: ... @@ -83,83 +101,206 @@ class BaseElement(BaseParser): def nexts(self): ... + def get_frame(self, loc_or_ind, timeout=None) -> ChromiumFrame: + """获取元素中一个frame对象 + :param loc_or_ind: 定位符、iframe序号,序号从1开始,可传入负数获取倒数第几个 + :param timeout: 查找元素超时时间(秒) + :return: ChromiumFrame对象 + """ + ... + + def _ele(self, + locator: Union[Tuple[str, str], str], + timeout: float = None, + index: Optional[int] = 1, + relative: bool = False, + raise_err: bool = None, + method: str = None): + """调用获取元素的方法 + :param locator: 定位符 + :param timeout: 超时时间(秒) + :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 + :param relative: 是否相对定位 + :param raise_err: 找不到时是否抛出异常 + :param method: 调用的方法名 + :return: 元素对象或它们组成的列表 + """ + ... + class DrissionElement(BaseElement): + """ChromiumElement 和 SessionElement的基类,但不是ShadowRoot的基类""" def __init__(self, owner: BasePage = None): ... @property - def link(self) -> str: ... + def link(self) -> str: + """返回href或src绝对url""" + ... @property - def css_path(self) -> str: ... + def css_path(self) -> str: + """返回css path路径""" + ... @property - def xpath(self) -> str: ... + def xpath(self) -> str: + """返回xpath路径""" + ... @property - def comments(self) -> list: ... + def comments(self) -> list: + """返回元素注释文本组成的列表""" + ... - def texts(self, text_node_only: bool = False) -> list: ... + def texts(self, text_node_only: bool = False) -> list: + """返回元素内所有直接子节点的文本,包括元素和文本节点 + :param text_node_only: 是否只返回文本节点 + :return: 文本列表 + """ + ... def parent(self, level_or_loc: Union[tuple, str, int] = 1, - index: int = 1) -> Union[DrissionElement, None]: ... + index: int = 1, + timeout: float = None) -> Union[DrissionElement, None]: + """返回上面某一级父元素,可指定层数或用查询语法定位 + :param level_or_loc: 第几级父元素,1开始,或定位符 + :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 + :param timeout: 时间(秒) + :return: 上级元素对象 + """ + ... def child(self, locator: Union[Tuple[str, str], str, int] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: ... + ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: + """返回直接子元素元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param index: 第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 直接子元素或节点文本组成的列表 + """ + ... def prev(self, locator: Union[Tuple[str, str], str, int] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: ... + ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: + """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 + :param locator: 用于筛选的查询语法 + :param index: 前面第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 兄弟元素 + """ + ... def next(self, locator: Union[Tuple[str, str], str, int] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: ... + ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: + """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 + :param locator: 用于筛选的查询语法 + :param index: 后面第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 兄弟元素 + """ + ... def before(self, locator: Union[Tuple[str, str], str, int] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: ... + ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: + """返回前面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 + :param locator: 用于筛选的查询语法 + :param index: 前面第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 本元素前面的某个元素或节点 + """ + ... def after(self, locator: Union[Tuple[str, str], str, int] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: ... + ele_only: bool = True) -> Union[DrissionElement, str, NoneElement]: + """返回后面的一个兄弟元素,可用查询语法筛选,可指定返回筛选结果的第几个 + :param locator: 用于筛选的查询语法 + :param index: 后面第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 本元素后面的某个元素或节点 + """ + ... def children(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """返回直接子元素元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 直接子元素或节点文本组成的列表 + """ + ... def prevs(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 兄弟元素或节点文本组成的列表 + """ + ... def nexts(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 兄弟元素或节点文本组成的列表 + """ + ... def befores(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """返回后面全部兄弟元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 本元素前面的元素或节点组成的列表 + """ + ... def afters(self, locator: Union[Tuple[str, str], str] = '', timeout: float = None, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """返回前面全部兄弟元素或节点组成的列表,可用查询语法筛选 + :param locator: 用于筛选的查询语法 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 本元素后面的元素或节点组成的列表 + """ + ... def _get_relative(self, func: str, @@ -168,7 +309,17 @@ class DrissionElement(BaseElement): locator: Union[Tuple[str, str], str] = '', index: int = 1, timeout: float = None, - ele_only: bool = True) -> DrissionElement: ... + ele_only: bool = True) -> DrissionElement: + """获取一个亲戚元素或节点,可用查询语法筛选,可指定返回筛选结果的第几个 + :param func: 方法名称 + :param direction: 方向,'following' 或 'preceding' + :param locator: 用于筛选的查询语法 + :param index: 前面第几个查询结果,1开始 + :param timeout: 查找节点的超时时间(秒) + :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 + :return: 本元素前面的某个元素或节点 + """ + ... def _get_relatives(self, index: int = None, @@ -176,7 +327,16 @@ class DrissionElement(BaseElement): direction: str = 'following', brother: bool = True, timeout: float = 0.5, - ele_only: bool = True) -> List[Union[DrissionElement, str]]: ... + ele_only: bool = True) -> List[Union[DrissionElement, str]]: + """按要求返回兄弟元素或节点组成的列表 + :param index: 获取第几个,该参数不为None时只获取该编号的元素 + :param locator: 用于筛选的查询语法 + :param direction: 'following' 或 'preceding',查找的方向 + :param brother: 查找范围,在同级查找还是整个dom前后查找 + :param timeout: 查找等待时间(秒) + :return: 元素对象或字符串 + """ + ... # ----------------以下属性或方法由后代实现---------------- @property @@ -191,41 +351,65 @@ class DrissionElement(BaseElement): @abstractmethod def attr(self, name: str) -> str: ... - def _get_ele_path(self, mode) -> str: ... + def _get_ele_path(self, xpath: bool = True) -> str: ... class BasePage(BaseParser): + """页面类的基类""" - def __init__(self): - self._url_available: bool = ... - self.retry_times: int = ... - self.retry_interval: float = ... - self._timeout: float = ... - self._download_path: str = ... - self._DownloadKit: DownloadKit = ... - self._none_ele_return_value: bool = ... - self._none_ele_value: Any = ... - self._page: Union[ChromiumPage, SessionPage, WebPage]=... + _url_available: Optional[bool] = ... + retry_times: int = ... + retry_interval: float = ... + _download_path: Optional[str] = ... + _DownloadKit: Optional[DownloadKit] = ... + _none_ele_return_value: bool = ... + _none_ele_value: Any = ... + _page: Union[ChromiumPage, SessionPage, WebPage] = ... + _session: Optional[Session] = ... + _headers: Optional[CaseInsensitiveDict] = ... + _session_options: Optional[SessionOptions] = ... + + def __init__(self): ... @property - def title(self) -> Union[str, None]: ... + def title(self) -> Union[str, None]: + """返回网页title""" + ... @property - def timeout(self) -> float: ... - - @timeout.setter - def timeout(self, second: float) -> None: ... + def url_available(self) -> bool: + """返回当前访问的url有效性""" + ... @property - def url_available(self) -> bool: ... + def download_path(self) -> str: + """返回默认下载路径""" + ... @property - def download_path(self) -> str: ... + def download(self) -> DownloadKit: + """返回下载器对象""" + ... - @property - def download(self) -> DownloadKit: ... + def _before_connect(self, url: str, retry: int, interval: float) -> tuple: + """连接前的准备 + :param url: 要访问的url + :param retry: 重试次数 + :param interval: 重试间隔 + :return: 重试次数、间隔、是否文件组成的tuple + """ + ... - def _before_connect(self, url: str, retry: int, interval: float) -> tuple: ... + def _set_session_options(self, session_or_options: Union[Session, SessionOptions] = None) -> None: + """启动配置 + :param session_or_options: Session、SessionOptions对象 + :return: None + """ + ... + + def _create_session(self) -> None: + """创建内建Session对象""" + ... # ----------------以下属性或方法由后代实现---------------- @property @@ -237,9 +421,6 @@ class BasePage(BaseParser): @property def user_agent(self) -> str: ... - @abstractmethod - def cookies(self, as_dict: bool = False, all_info: bool = False) -> Union[list, dict]: ... - @abstractmethod def get(self, url: str, show_errmsg: bool = False, retry: int = None, interval: float = None): ... @@ -248,4 +429,13 @@ class BasePage(BaseParser): timeout: float = None, index: Optional[int] = 1, raise_err: bool = None, - method: str = None): ... + method: str = None): + """调用获取元素的方法 + :param locator: 定位符 + :param timeout: 超时时间(秒) + :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 + :param raise_err: 找不到时是否抛出异常 + :param method: 调用的方法名 + :return: 元素对象或它们组成的列表 + """ + ... diff --git a/DrissionPage/_base/browser.py b/DrissionPage/_base/browser.py deleted file mode 100644 index 00649d5..0000000 --- a/DrissionPage/_base/browser.py +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding:utf-8 -*- -""" -@Author : g1879 -@Contact : g1879@qq.com -@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. -@License : BSD 3-Clause. -""" -from pathlib import Path -from shutil import rmtree -from time import perf_counter, sleep - -from websocket import WebSocketBadStatusException - -from .driver import BrowserDriver, Driver -from .._functions.tools import raise_error -from .._units.downloader import DownloadManager -from ..errors import PageDisconnectedError - -__ERROR__ = 'error' - - -class Browser(object): - BROWSERS = {} - - def __new__(cls, address, browser_id, page): - """ - :param address: 浏览器地址 - :param browser_id: 浏览器id - :param page: ChromiumPage对象 - """ - if browser_id in cls.BROWSERS: - return cls.BROWSERS[browser_id] - return object.__new__(cls) - - def __init__(self, address, browser_id, page): - """ - :param address: 浏览器地址 - :param browser_id: 浏览器id - :param page: ChromiumPage对象 - """ - if hasattr(self, '_created'): - return - self._created = True - Browser.BROWSERS[browser_id] = self - - self.page = page - self.address = address - self._driver = BrowserDriver(browser_id, 'browser', address, self) - self.id = browser_id - self._frames = {} - self._drivers = {} - self._all_drivers = {} - self._connected = False - - self._process_id = None - try: - r = self.run_cdp('SystemInfo.getProcessInfo') - for i in r.get('processInfo', []): - if i['type'] == 'browser': - self._process_id = i['id'] - break - except: - pass - - self.run_cdp('Target.setDiscoverTargets', discover=True) - self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) - self._driver.set_callback('Target.targetCreated', self._onTargetCreated) - - def _get_driver(self, tab_id, owner=None): - """新建并返回指定tab id的Driver - :param tab_id: 标签页id - :param owner: 使用该驱动的对象 - :return: Driver对象 - """ - d = self._drivers.pop(tab_id, None) - if not d: - d = Driver(tab_id, 'page', self.address) - d.owner = owner - self._all_drivers.setdefault(tab_id, set()).add(d) - return d - - def _onTargetCreated(self, **kwargs): - """标签页创建时执行""" - if (kwargs['targetInfo']['type'] in ('page', 'webview') - and kwargs['targetInfo']['targetId'] not in self._all_drivers - and not kwargs['targetInfo']['url'].startswith('devtools://')): - try: - tab_id = kwargs['targetInfo']['targetId'] - d = Driver(tab_id, 'page', self.address) - self._drivers[tab_id] = d - self._all_drivers.setdefault(tab_id, set()).add(d) - except WebSocketBadStatusException: - pass - - def _onTargetDestroyed(self, **kwargs): - """标签页关闭时执行""" - tab_id = kwargs['targetId'] - if hasattr(self, '_dl_mgr'): - self._dl_mgr.clear_tab_info(tab_id) - for key in [k for k, i in self._frames.items() if i == tab_id]: - self._frames.pop(key, None) - for d in self._all_drivers.get(tab_id, tuple()): - d.stop() - self._drivers.pop(tab_id, None) - self._all_drivers.pop(tab_id, None) - - def connect_to_page(self): - """执行与page相关的逻辑""" - if not self._connected: - self._dl_mgr = DownloadManager(self) - self._connected = True - - def run_cdp(self, cmd, **cmd_args): - """执行Chrome DevTools Protocol语句 - :param cmd: 协议项目 - :param cmd_args: 参数 - :return: 执行的结果 - """ - 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) - - @property - def driver(self): - return self._driver - - @property - def tabs_count(self): - """返回标签页数量""" - j = self.run_cdp('Target.getTargets')['targetInfos'] # 不要改用get,避免卡死 - return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]) - - @property - def tab_ids(self): - """返回所有标签页id组成的列表""" - j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp,因为顺序不对 - return [i['id'] for i in j if i['type'] in ('page', 'webview') - and not i['url'].startswith('devtools://')] - - @property - def process_id(self): - """返回浏览器进程id""" - return self._process_id - - def find_tabs(self, title=None, url=None, tab_type=None): - """查找符合条件的tab,返回它们组成的列表,title和url是与关系 - :param title: 要匹配title的文本 - :param url: 要匹配url的文本 - :param tab_type: tab类型,可用列表输入多个 - :return: dict格式的tab信息列表列表 - """ - tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp - - if isinstance(tab_type, str): - tab_type = {tab_type} - elif isinstance(tab_type, (list, tuple, set)): - tab_type = set(tab_type) - elif tab_type is not None: - raise TypeError('tab_type只能是set、list、tuple、str、None。') - - return [i for i in tabs if ((title is None or title in i['title']) and (url is None or url in i['url']) - and (tab_type is None or i['type'] in tab_type))] - - def close_tab(self, tab_id): - """关闭标签页 - :param tab_id: 标签页id - :return: None - """ - self._onTargetDestroyed(targetId=tab_id) - self.driver.run('Target.closeTarget', targetId=tab_id) - - def stop_driver(self, driver): - """停止一个Driver - :param driver: Driver对象 - :return: None - """ - driver.stop() - self._all_drivers.get(driver.id, set()).discard(driver) - - def activate_tab(self, tab_id): - """使标签页变为活动状态 - :param tab_id: 标签页id - :return: None - """ - self.run_cdp('Target.activateTarget', targetId=tab_id) - - def get_window_bounds(self, tab_id=None): - """返回浏览器窗口位置和大小信息 - :param tab_id: 标签页id - :return: 窗口大小字典 - """ - return self.run_cdp('Browser.getWindowForTarget', targetId=tab_id or self.id)['bounds'] - - def new_tab(self, new_window=False, background=False, new_context=False): - """新建一个标签页 - :param new_window: 是否在新窗口打开标签页 - :param background: 是否不激活新标签页,如new_window为True则无效 - :param new_context: 是否创建新的上下文 - :return: 新标签页id - """ - bid = None - if new_context: - bid = self.run_cdp('Target.createBrowserContext')['browserContextId'] - - kwargs = {'url': ''} - if new_window: - kwargs['newWindow'] = True - if background: - kwargs['background'] = True - if bid: - kwargs['browserContextId'] = bid - - tid = self.run_cdp('Target.createTarget', **kwargs)['targetId'] - while tid not in self._drivers: - sleep(.1) - return tid - - def reconnect(self): - """断开重连""" - self._driver.stop() - BrowserDriver.BROWSERS.pop(self.id) - self._driver = BrowserDriver(self.id, 'browser', self.address, self) - self.run_cdp('Target.setDiscoverTargets', discover=True) - self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) - self._driver.set_callback('Target.targetCreated', self._onTargetCreated) - - def quit(self, timeout=5, force=False): - """关闭浏览器 - :param timeout: 等待浏览器关闭超时时间(秒) - :param force: 是否立刻强制终止进程 - :return: None - """ - try: - self.run_cdp('Browser.close') - except PageDisconnectedError: - pass - self.driver.stop() - - drivers = list(self._all_drivers.values()) - for tab in drivers: - for driver in tab: - driver.stop() - - if not force: - return - - try: - pids = [pid['id'] for pid in self.run_cdp('SystemInfo.getProcessInfo')['processInfo']] - except: - return - - from psutil import Process - for pid in pids: - try: - Process(pid).kill() - except: - pass - - from os import popen - from platform import system - end_time = perf_counter() + timeout - while perf_counter() < end_time: - ok = True - for pid in pids: - txt = f'tasklist | findstr {pid}' if system().lower() == 'windows' else f'ps -ef | grep {pid}' - p = popen(txt) - sleep(.05) - try: - if f' {pid} ' in p.read(): - ok = False - break - except TypeError: - pass - - if ok: - break - - def _on_disconnect(self): - self.page._on_disconnect() - Browser.BROWSERS.pop(self.id, None) - if self.page._chromium_options.is_auto_port and self.page._chromium_options.user_data_path: - path = Path(self.page._chromium_options.user_data_path) - end_time = perf_counter() + 7 - while perf_counter() < end_time: - if not path.exists(): - break - try: - rmtree(path) - break - except (PermissionError, FileNotFoundError, OSError): - pass - sleep(.03) diff --git a/DrissionPage/_base/browser.pyi b/DrissionPage/_base/browser.pyi deleted file mode 100644 index 170f88b..0000000 --- a/DrissionPage/_base/browser.pyi +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding:utf-8 -*- -""" -@Author : g1879 -@Contact : g1879@qq.com -@Copyright: (c) 2024 by g1879, Inc. All Rights Reserved. -@License : BSD 3-Clause. -""" -from typing import List, Optional, Union, Set, Dict - -from .driver import BrowserDriver, Driver -from .._pages.chromium_page import ChromiumPage -from .._units.downloader import DownloadManager - - -class Browser(object): - BROWSERS: dict = ... - page: ChromiumPage = ... - _driver: BrowserDriver = ... - id: str = ... - address: str = ... - _frames: dict = ... - _drivers: Dict[str, Driver] = ... - _all_drivers: Dict[str, Set[Driver]] = ... - _process_id: Optional[int] = ... - _dl_mgr: DownloadManager = ... - _connected: bool = ... - - def __new__(cls, address: str, browser_id: str, page: ChromiumPage): ... - - def __init__(self, address: str, browser_id: str, page: ChromiumPage): ... - - def _get_driver(self, tab_id: str, owner=None) -> Driver: ... - - def run_cdp(self, cmd, **cmd_args) -> dict: ... - - @property - def driver(self) -> BrowserDriver: ... - - @property - def tabs_count(self) -> int: ... - - @property - def tab_ids(self) -> List[str]: ... - - @property - def process_id(self) -> Optional[int]: ... - - def find_tabs(self, title: str = None, url: str = None, - tab_type: Union[str, list, tuple] = None) -> List[dict]: ... - - def close_tab(self, tab_id: str) -> None: ... - - def stop_driver(self, driver: Driver) -> None: ... - - def activate_tab(self, tab_id: str) -> None: ... - - def get_window_bounds(self, tab_id: str = None) -> dict: ... - - def new_tab(self, new_window: bool = False, background: bool = False, new_context: bool = False) -> str: ... - - def reconnect(self) -> None: ... - - def connect_to_page(self) -> None: ... - - def _onTargetCreated(self, **kwargs) -> None: ... - - def _onTargetDestroyed(self, **kwargs) -> None: ... - - def quit(self, timeout: float = 5, force: bool = False) -> None: ... - - def _on_disconnect(self) -> None: ... diff --git a/DrissionPage/_base/chromium.py b/DrissionPage/_base/chromium.py new file mode 100644 index 0000000..887bb4b --- /dev/null +++ b/DrissionPage/_base/chromium.py @@ -0,0 +1,496 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved. +""" +from pathlib import Path +from re import match +from shutil import rmtree +from threading import Lock +from time import sleep, perf_counter + +from requests import Session +from websocket import WebSocketBadStatusException + +from .driver import BrowserDriver, Driver +from .._configs.chromium_options import ChromiumOptions +from .._functions.browser import connect_browser +from .._functions.cookies import CookiesList +from .._functions.settings import Settings +from .._functions.tools import PortFinder +from .._functions.tools import raise_error +from .._pages.chromium_base import Timeout +from .._pages.chromium_tab import ChromiumTab +from .._pages.mix_tab import MixTab +from .._units.downloader import DownloadManager +from .._units.setter import BrowserSetter +from .._units.states import BrowserStates +from .._units.waiter import BrowserWaiter +from ..errors import BrowserConnectError, CDPError +from ..errors import PageDisconnectedError + +__ERROR__ = 'error' + + +class Chromium(object): + _BROWSERS = {} + _lock = Lock() + + def __new__(cls, addr_or_opts=None, session_options=None): + opt = handle_options(addr_or_opts) + is_headless, browser_id, is_exists = run_browser(opt) + with cls._lock: + if browser_id in cls._BROWSERS: + r = cls._BROWSERS[browser_id] + while not hasattr(r, '_driver'): + sleep(.05) + return r + r = object.__new__(cls) + r._chromium_options = opt + r._is_headless = is_headless + r._is_exists = is_exists + r.id = browser_id + cls._BROWSERS[browser_id] = r + return r + + def __init__(self, addr_or_opts=None, session_options=None): + if hasattr(self, '_created'): + return + self._created = True + + self._type = 'Chromium' + self._frames = {} + self._drivers = {} + self._all_drivers = {} + self._relation = {} + self._newest_tab_id = None + + self._set = None + self._wait = None + self._states = None + self._timeouts = Timeout(**self._chromium_options.timeouts) + self._load_mode = self._chromium_options.load_mode + self._download_path = str(Path(self._chromium_options.download_path).absolute()) + self._auto_handle_alert = None + self._none_ele_return_value = False + self._none_ele_value = None + self.retry_times = self._chromium_options.retry_times + self.retry_interval = self._chromium_options.retry_interval + self.address = self._chromium_options.address + self._disconnect_flag = False + self._driver = BrowserDriver(self.id, 'browser', self.address, self) + + if ((not self._chromium_options._ua_set and self._is_headless != self._chromium_options.is_headless) + or (self._is_exists and self._chromium_options._new_env)): + self.quit(3, True) + connect_browser(self._chromium_options) + s = Session() + s.trust_env = False + s.keep_alive = False + ws = s.get(f'http://{self._chromium_options.address}/json/version', headers={'Connection': 'close'}) + self.id = ws.json()['webSocketDebuggerUrl'].split('/')[-1] + self._driver = BrowserDriver(self.id, 'browser', self.address, self) + ws.close() + s.close() + self._is_exists = False + self._frames = {} + self._drivers = {} + self._all_drivers = {} + + self.version = self._run_cdp('Browser.getVersion')['product'] + + self._process_id = None + try: + r = self._run_cdp('SystemInfo.getProcessInfo') + for i in r.get('processInfo', []): + if i['type'] == 'browser': + self._process_id = i['id'] + break + except: + pass + + self._run_cdp('Target.setDiscoverTargets', discover=True) + self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) + self._driver.set_callback('Target.targetCreated', self._onTargetCreated) + self._dl_mgr = DownloadManager(self) + + self._session_options = session_options + + @property + def user_data_path(self): + return self._chromium_options.user_data_path + + @property + def process_id(self): + return self._process_id + + @property + def timeout(self): + return self._timeouts.base + + @property + def timeouts(self): + return self._timeouts + + @property + def load_mode(self): + return self._load_mode + + @property + def download_path(self): + return self._download_path + + @property + def set(self): + if self._set is None: + self._set = BrowserSetter(self) + return self._set + + @property + def states(self): + if self._states is None: + self._states = BrowserStates(self) + return self._states + + @property + def wait(self): + if self._wait is None: + self._wait = BrowserWaiter(self) + return self._wait + + @property + def tabs_count(self): + j = self._run_cdp('Target.getTargets')['targetInfos'] # 不要改用get,避免卡死 + return len([i for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')]) + + @property + def tab_ids(self): + j = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp,因为顺序不对 + return [i['id'] for i in j if i['type'] in ('page', 'webview') and not i['url'].startswith('devtools://')] + + @property + def latest_tab(self): + return self._get_tab(id_or_num=self.tab_ids[0], as_id=not Settings.singleton_tab_obj) + + def cookies(self, all_info=False): + cks = self._run_cdp(f'Storage.getCookies')['cookies'] + r = cks if all_info else [{'name': c['name'], 'value': c['value'], 'domain': c['domain']} for c in cks] + return CookiesList(r) + + def new_tab(self, url=None, new_window=False, background=False, new_context=False): + return self._new_tab(True, url=url, new_window=new_window, background=background, new_context=new_context) + + def get_tab(self, id_or_num=None, title=None, url=None, tab_type='page'): + t = self._get_tab(id_or_num=id_or_num, title=title, url=url, tab_type=tab_type, mix=True, as_id=False) + if t._type != 'MixTab': + raise RuntimeError('该标签页已有非MixTab版本,如需多对象公用请用Settings设置singleton_tab_obj为False。') + return t + + 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, 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 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 + + if len(all_tabs - tabs) > 0: + for tab in tabs: + 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): + id_ind_tab += -1 if id_ind_tab else 1 + id_ind_tab = self.tab_ids[id_ind_tab] + elif isinstance(id_ind_tab, ChromiumTab): + id_ind_tab = id_ind_tab.tab_id + self._run_cdp('Target.activateTarget', targetId=id_ind_tab) + + def reconnect(self): + self._disconnect_flag = True + self._driver.stop() + BrowserDriver.BROWSERS.pop(self.id) + self._driver = BrowserDriver(self.id, 'browser', self.address, self) + self._run_cdp('Target.setDiscoverTargets', discover=True) + self._driver.set_callback('Target.targetDestroyed', self._onTargetDestroyed) + self._driver.set_callback('Target.targetCreated', self._onTargetCreated) + self._disconnect_flag = False + + def clear_cache(self, cache=True, cookies=True): + if cache: + self.latest_tab.run_cdp('Network.clearBrowserCache') + + if cookies: + self._run_cdp('Storage.clearCookies') + + def quit(self, timeout=5, force=False, del_data=False): + try: + self._run_cdp('Browser.close') + except PageDisconnectedError: + pass + self._driver.stop() + + drivers = list(self._all_drivers.values()) + for tab in drivers: + for driver in tab: + driver.stop() + + if force: + pids = None + try: + pids = [pid['id'] for pid in self._run_cdp('SystemInfo.getProcessInfo')['processInfo']] + except: + pass + + if pids: + from psutil import Process + for pid in pids: + try: + Process(pid).kill() + except: + pass + + from os import popen + from platform import system + end_time = perf_counter() + timeout + while perf_counter() < end_time: + ok = True + for pid in pids: + txt = f'tasklist | findstr {pid}' if system().lower() == 'windows' else f'ps -ef | grep {pid}' + p = popen(txt) + sleep(.05) + try: + if f' {pid} ' in p.read(): + ok = False + break + except TypeError: + pass + + if ok: + break + + if del_data and not self._chromium_options.is_auto_port and self._chromium_options.user_data_path: + path = Path(self._chromium_options.user_data_path) + rmtree(path, True) + + def _new_tab(self, mix=True, url=None, new_window=False, background=False, new_context=False): + tab_type = MixTab if mix else ChromiumTab + tab = None + if new_context: + tab = self._run_cdp('Target.createBrowserContext')['browserContextId'] + + kwargs = {'url': ''} + if new_window: + kwargs['newWindow'] = True + if background: + kwargs['background'] = True + if tab: + kwargs['browserContextId'] = tab + + if self.states.is_incognito: + 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, tab_type, new_window) + + 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 + + def _get_tab(self, id_or_num=None, title=None, url=None, tab_type='page', mix=True, as_id=False): + if id_or_num is not None: + if isinstance(id_or_num, int): + id_or_num = self.tab_ids[id_or_num - 1 if id_or_num > 0 else id_or_num] + elif isinstance(id_or_num, ChromiumTab): + return id_or_num.tab_id if as_id else ChromiumTab(self, id_or_num.tab_id) + elif id_or_num not in [i['id'] for i in self._driver.get(f'http://{self.address}/json').json()]: + raise ValueError(f'没有找到标签页{id_or_num},所有标签页:{self.tab_ids}') + + elif title == url is None and tab_type == 'page': + id_or_num = self.tab_ids[0] + + else: + tabs = self._get_tabs(title=title, url=url, tab_type=tab_type, as_id=True) + if tabs: + id_or_num = tabs[0] + else: + raise RuntimeError('没有找到指定标签页。') + + if as_id: + return id_or_num + with self._lock: + return MixTab(self, id_or_num) if mix else ChromiumTab(self, id_or_num) + + def _get_tabs(self, title=None, url=None, tab_type='page', mix=True, as_id=False): + tabs = self._driver.get(f'http://{self.address}/json').json() # 不要改用cdp + + if isinstance(tab_type, str): + tab_type = {tab_type} + elif isinstance(tab_type, (list, tuple, set)): + tab_type = set(tab_type) + elif tab_type is not None: + 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 i['title'] != 'chrome-extension://neajdppkdcdipfabeoofebfddakdcjhd/audio.html')] + if as_id: + return [tab['id'] for tab in tabs] + with self._lock: + if mix: + return [MixTab(self, tab['id']) for tab in tabs] + else: + return [ChromiumTab(self, tab['id']) for tab in tabs] + + 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, self, ignore) + + def _get_driver(self, tab_id, owner=None): + d = self._drivers.pop(tab_id, None) + if not d: + d = Driver(tab_id, 'page', self.address) + d.owner = owner + self._all_drivers.setdefault(tab_id, set()).add(d) + return d + + def _onTargetCreated(self, **kwargs): + if (kwargs['targetInfo']['type'] in ('page', 'webview') + and kwargs['targetInfo']['targetId'] not in self._all_drivers + 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 + + def _onTargetDestroyed(self, **kwargs): + tab_id = kwargs['targetId'] + self._dl_mgr.clear_tab_info(tab_id) + for key in [k for k, i in self._frames.items() if i == tab_id]: + self._frames.pop(key, None) + for d in self._all_drivers.get(tab_id, tuple()): + 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: + Chromium._BROWSERS.pop(self.id, None) + if self._chromium_options.is_auto_port and self._chromium_options.user_data_path: + path = Path(self._chromium_options.user_data_path) + end_time = perf_counter() + 7 + while perf_counter() < end_time: + if not path.exists(): + break + try: + rmtree(path) + break + except (PermissionError, FileNotFoundError, OSError): + pass + sleep(.03) + + +def handle_options(addr_or_opts): + """设置浏览器启动属性 + :param addr_or_opts: 'ip:port'、ChromiumOptions、Driver + :return: 返回ChromiumOptions对象 + """ + if not addr_or_opts: + _chromium_options = ChromiumOptions(addr_or_opts) + if _chromium_options.is_auto_port: + port, path = PortFinder(_chromium_options.tmp_path).get_port(_chromium_options.is_auto_port) + _chromium_options.set_address(f'127.0.0.1:{port}') + _chromium_options.set_user_data_path(path) + _chromium_options.auto_port(scope=_chromium_options.is_auto_port) + + elif isinstance(addr_or_opts, ChromiumOptions): + if addr_or_opts.is_auto_port: + port, path = PortFinder(addr_or_opts.tmp_path).get_port(addr_or_opts.is_auto_port) + addr_or_opts.set_address(f'127.0.0.1:{port}') + addr_or_opts.set_user_data_path(path) + addr_or_opts.auto_port(scope=addr_or_opts.is_auto_port) + _chromium_options = addr_or_opts + + elif isinstance(addr_or_opts, str): + _chromium_options = ChromiumOptions() + _chromium_options.set_address(addr_or_opts) + + elif isinstance(addr_or_opts, int): + _chromium_options = ChromiumOptions() + _chromium_options.set_local_port(addr_or_opts) + + else: + raise TypeError('只能接收ip:port格式或ChromiumOptions类型参数。') + + return _chromium_options + + +def run_browser(chromium_options): + """连接浏览器""" + is_exists = connect_browser(chromium_options) + try: + s = Session() + s.trust_env = False + s.keep_alive = False + ws = s.get(f'http://{chromium_options.address}/json/version', headers={'Connection': 'close'}) + if not ws: + raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') + json = ws.json() + browser_id = json['webSocketDebuggerUrl'].split('/')[-1] + is_headless = 'headless' in json['User-Agent'].lower() + ws.close() + s.close() + except KeyError: + raise BrowserConnectError('浏览器版本太旧或此浏览器不支持接管。') + except: + raise BrowserConnectError('\n浏览器连接失败,请确认浏览器是否启动。') + return is_headless, browser_id, is_exists + + +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://?') + url = f'"{url}"' if url else '""' + new = 'target="_new"' if new_window else 'target="_blank"' + tid = browser.latest_tab.tab_id + tab.run_js(f'window.open({url}, {new})') + tid = browser.wait.new_tab(curr_tab=tid) + return browser._get_tab(tid, mix=mix) diff --git a/DrissionPage/_base/chromium.pyi b/DrissionPage/_base/chromium.pyi new file mode 100644 index 0000000..afe1594 --- /dev/null +++ b/DrissionPage/_base/chromium.pyi @@ -0,0 +1,300 @@ +# -*- coding:utf-8 -*- +""" +@Author : g1879 +@Contact : g1879@qq.com +@Copyright: (c) 2020 by g1879, Inc. All Rights Reserved. +""" +from threading import Lock +from typing import List, Optional, Set, Dict, Union, Tuple, Literal, Any + +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, ChromiumBase +from .._pages.chromium_tab import ChromiumTab +from .._pages.mix_tab import MixTab +from .._units.downloader import DownloadManager +from .._units.setter import BrowserSetter +from .._units.states import BrowserStates +from .._units.waiter import BrowserWaiter + + +class Chromium(object): + _BROWSERS: dict = ... + _lock: Lock = ... + + id: str = ... + address: str = ... + version: str = ... + retry_times: int = ... + retry_interval: float = ... + + _set: Optional[BrowserSetter] = ... + _wait: Optional[BrowserWaiter] = ... + _states: Optional[BrowserStates] = ... + _chromium_options: ChromiumOptions = ... + _session_options: SessionOptions = ... + _driver: BrowserDriver = ... + _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 = ... + _load_mode: str = ... + _download_path: str = ... + _auto_handle_alert: Optional[bool] = ... + _is_exists: bool = ... + _is_headless: bool = ... + _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, + session_options: Union[SessionOptions, None, False] = None): + """ + :param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int) + :param session_options: 使用双模Tab时使用的默认Session配置,为None使用ini文件配置,为False不从ini读取 + """ + ... + + def __init__(self, addr_or_opts: Union[str, int, ChromiumOptions] = None, + session_options: Union[SessionOptions, None, False] = None): + """ + :param addr_or_opts: 浏览器地址:端口、ChromiumOptions对象或端口数字(int) + :param session_options: 使用双模Tab时使用的默认Session配置,为None使用ini文件配置,为False不从ini读取 + """ + ... + + @property + def user_data_path(self) -> str: + """返回用户文件夹路径""" + ... + + @property + def process_id(self) -> Optional[int]: + """返回浏览器进程id""" + ... + + @property + def timeout(self) -> float: + """返回基础超时设置""" + ... + + @property + def timeouts(self) -> Timeout: + """返回所有超时设置""" + ... + + @property + def load_mode(self) -> Literal['none', 'normal', 'eager']: + """返回页面加载模式,包括 'none', 'normal', 'eager' 三种""" + ... + + @property + def download_path(self) -> str: + """返回默认下载路径""" + ... + + @property + def set(self) -> BrowserSetter: + """返回用于设置的对象""" + ... + + @property + def states(self) -> BrowserStates: + """返回用于获取状态的对象""" + ... + + @property + def wait(self) -> BrowserWaiter: + """返回用于等待的对象""" + ... + + @property + def tabs_count(self) -> int: + """返回标签页数量,只统计page、webview类型""" + ... + + @property + def tab_ids(self) -> List[str]: + """返回所有标签页id组成的列表,只统计page、webview类型""" + ... + + @property + def latest_tab(self) -> Union[MixTab, str]: + """返回最新的标签页,最新标签页指最后创建或最后被激活的 + 当Settings.singleton_tab_obj==True时返回Tab对象,否则返回tab id""" + ... + + def cookies(self, all_info: bool = False) -> CookiesList: + """以list格式返回所有域名的cookies + :param all_info: 是否返回所有内容,False则只返回name, value, domain + :return: cookies组成的列表 + """ + ... + + def new_tab(self, + url: str = None, + new_window: bool = False, + background: bool = False, + new_context: bool = False) -> MixTab: + """新建一个标签页 + :param url: 新标签页跳转到的网址,为None时新建空标签页 + :param new_window: 是否在新窗口打开标签页,隐身模式下无效 + :param background: 是否不激活新标签页,隐身模式和访客模式及new_window为True时无效 + :param new_context: 是否创建独立环境,隐身模式和访客模式下无效 + :return: 新标签页对象 + """ + ... + + def get_tab(self, + id_or_num: Union[str, int] = None, + title: str = None, + url: str = None, + tab_type: Union[str, list, tuple] = 'page', + as_id: bool = False) -> Union[MixTab, str]: + """获取一个标签页对象,id_or_num不为None时,后面几个参数无效 + :param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序 + :param title: 要匹配title的文本,模糊匹配,为None则匹配所有 + :param url: 要匹配url的文本,模糊匹配,为None则匹配所有 + :param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有 + :param as_id: 是否返回标签页id而不是标签页对象 + :return: Tab对象 + """ + ... + + def get_tabs(self, + title: str = None, + url: str = None, + tab_type: Union[str, list, tuple] = 'page', + as_id: bool = False) -> List[MixTab, str]: + """查找符合条件的tab,返回它们组成的列表,title和url是与关系 + :param title: 要匹配title的文本 + :param url: 要匹配url的文本 + :param tab_type: tab类型,可用列表输入多个 + :param as_id: 是否返回标签页id而不是标签页对象 + :return: Tab对象列表 + """ + ... + + def close_tabs(self, + tabs_or_ids: Union[str, ChromiumTab, List[Union[str, ChromiumTab]], + Tuple[Union[str, ChromiumTab]]], + others: bool = False) -> None: + """关闭传入的标签页,可传入多个 + :param tabs_or_ids: 指定的标签页对象或id,可用列表或元组传入多个 + :param others: 是否关闭指定标签页之外的 + :return: None + """ + ... + + 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: 标签页id(str)、Tab对象或标签页序号(int),序号从1开始 + :return: None + """ + ... + + def reconnect(self) -> None: + """断开重连""" + ... + + def clear_cache(self, cache: bool = True, cookies: bool = True) -> None: + """清除缓存,可选要清除的项 + :param cache: 是否清除cache + :param cookies: 是否清除cookies + :return: None + """ + ... + + def quit(self, timeout: float = 5, force: bool = False, del_data: bool = False) -> None: + """关闭浏览器 + :param timeout: 等待浏览器关闭超时时间(秒) + :param force: 是否立刻强制终止进程 + :param del_data: 是否删除用户文件夹 + :return: None + """ + ... + + def _new_tab(self, + mix: bool = True, + url: str = None, + new_window: bool = False, + background: bool = False, + new_context: bool = False) -> Union[ChromiumTab, MixTab]: + """新建一个标签页 + :param mix: 是否创建MixTab + :param url: 新标签页跳转到的网址 + :param new_window: 是否在新窗口打开标签页 + :param background: 是否不激活新标签页,如new_window为True则无效 + :param new_context: 是否创建新的上下文 + :return: 新标签页对象 + """ + ... + + def _get_tab(self, + id_or_num: Union[str, int] = None, + title: str = None, + url: str = None, + tab_type: Union[str, list, tuple] = 'page', + mix: bool = True, + as_id: bool = False) -> Union[ChromiumTab, str]: + """获取一个标签页对象,id_or_num不为None时,后面几个参数无效 + :param id_or_num: 要获取的标签页id或序号,序号从1开始,可传入负数获取倒数第几个,不是视觉排列顺序,而是激活顺序 + :param title: 要匹配title的文本,模糊匹配,为None则匹配所有 + :param url: 要匹配url的文本,模糊匹配,为None则匹配所有 + :param tab_type: tab类型,可用列表输入多个,如 'page', 'iframe' 等,为None则匹配所有 + :param mix: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效 + :return: Tab对象 + """ + ... + + def _get_tabs(self, + title: str = None, + url: str = None, + tab_type: Union[str, list, tuple] = 'page', + mix: bool = True, + as_id: bool = False) -> List[ChromiumTab, str]: + """查找符合条件的tab,返回它们组成的列表,title和url是与关系 + :param title: 要匹配title的文本 + :param url: 要匹配url的文本 + :param tab_type: tab类型,可用列表输入多个 + :param mix: 是否返回可切换模式的Tab对象 + :param as_id: 是否返回标签页id而不是标签页对象,mix=False时无效 + :return: Tab对象列表 + """ + ... + + def _run_cdp(self, cmd, **cmd_args) -> dict: + """执行Chrome DevTools Protocol语句 + :param cmd: 协议项目 + :param cmd_args: 参数 + :return: 执行的结果 + """ + ... + + def _get_driver(self, tab_id: str, owner=None) -> Driver: + """新建并返回指定tab id的Driver + :param tab_id: 标签页id + :param owner: 使用该驱动的对象 + :return: Driver对象 + """ + ... + + def _onTargetCreated(self, **kwargs): ... + + def _onTargetDestroyed(self, **kwargs): ... + + def _on_disconnect(self): ... diff --git a/DrissionPage/_base/driver.py b/DrissionPage/_base/driver.py index 3044a87..f565e29 100644 --- a/DrissionPage/_base/driver.py +++ b/DrissionPage/_base/driver.py @@ -2,34 +2,31 @@ """ @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 -from threading import Thread, Event +from threading import Thread from time import perf_counter, sleep +from requests import adapters from requests import Session from websocket import (WebSocketTimeoutException, WebSocketConnectionClosedException, create_connection, WebSocketException, WebSocketBadStatusException) from .._functions.settings import Settings -from ..errors import PageDisconnectedError +from ..errors import PageDisconnectedError, BrowserConnectError + +adapters.DEFAULT_RETRIES = 5 class Driver(object): def __init__(self, tab_id, tab_type, address, owner=None): - """ - :param tab_id: 标签页id - :param tab_type: 标签页类型 - :param address: 浏览器连接地址 - :param owner: 创建这个驱动的对象 - """ self.id = tab_id self.address = address self.type = tab_type self.owner = owner + # self._debug = True # self._debug = False self.alert_flag = False # 标记alert出现,跳过一条请求后复原 @@ -43,7 +40,7 @@ class Driver(object): self._handle_event_th.daemon = True self._handle_immediate_event_th = None - self._stopped = Event() + self.is_running = False self.event_handlers = {} self.immediate_event_handlers = {} @@ -54,11 +51,6 @@ class Driver(object): self.start() def _send(self, message, timeout=None): - """发送信息到浏览器,并返回浏览器返回的信息 - :param message: 发送给浏览器的数据 - :param timeout: 超时时间,为None表示无限 - :return: 浏览器返回的数据 - """ self._cur_id += 1 ws_id = self._cur_id message['id'] = ws_id @@ -86,7 +78,7 @@ class Driver(object): self.method_results.pop(ws_id, None) return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'} - while not self._stopped.is_set(): + while self.is_running: try: result = self.method_results[ws_id].get(timeout=.2) self.method_results.pop(ws_id, None) @@ -106,8 +98,7 @@ class Driver(object): return {'error': {'message': 'connection disconnected'}, 'type': 'connection_error'} def _recv_loop(self): - """接收浏览器信息的守护线程方法""" - while not self._stopped.is_set(): + while self.is_running: try: # self._ws.settimeout(1) msg_json = self._ws.recv() @@ -144,8 +135,7 @@ class Driver(object): # print(f'未知信息:{msg}') def _handle_event_loop(self): - """当接收到浏览器信息,执行已绑定的方法""" - while not self._stopped.is_set(): + while self.is_running: try: event = self.event_queue.get(timeout=1) except Empty: @@ -158,7 +148,7 @@ class Driver(object): self.event_queue.task_done() def _handle_immediate_event_loop(self): - while not self._stopped.is_set() and not self.immediate_event_queue.empty(): + while not self.immediate_event_queue.empty(): function, kwargs = self.immediate_event_queue.get(timeout=1) try: function(**kwargs) @@ -166,11 +156,6 @@ class Driver(object): pass def _handle_immediate_event(self, function, kwargs): - """处理立即执行的动作 - :param function: 要运行下方法 - :param kwargs: 方法参数 - :return: None - """ self.immediate_event_queue.put((function, kwargs)) if self._handle_immediate_event_th is None or not self._handle_immediate_event_th.is_alive(): self._handle_immediate_event_th = Thread(target=self._handle_immediate_event_loop) @@ -183,7 +168,7 @@ class Driver(object): :param kwargs: cdp参数 :return: 执行结果 """ - if self._stopped.is_set(): + if not self.is_running: return {'error': 'connection disconnected', 'type': 'connection_error'} timeout = kwargs.pop('_timeout', Settings.cdp_timeout) @@ -191,13 +176,12 @@ class Driver(object): if 'result' not in result and 'error' in result: kwargs['_timeout'] = timeout return {'error': result['error']['message'], 'type': result.get('type', 'call_method_error'), - 'method': _method, 'args': kwargs} + 'method': _method, 'args': kwargs, 'data': result['error'].get('data')} else: return result['result'] def start(self): - """启动连接""" - self._stopped.clear() + self.is_running = True try: self._ws = create_connection(self._websocket_url, enable_multithread=True, suppress_origin=True) except WebSocketBadStatusException as e: @@ -205,28 +189,37 @@ class Driver(object): raise RuntimeError('请升级websocket-client库。') else: return + except ConnectionRefusedError: + raise BrowserConnectError('浏览器未开启或已关闭。') self._recv_th.start() self._handle_event_th.start() return True 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): - """中断连接""" - if self._stopped.is_set(): + if not self.is_running: return False - self._stopped.set() + self.is_running = False if self._ws: self._ws.close() self._ws = None # try: + # while not self.immediate_event_queue.empty(): + # function, kwargs = self.immediate_event_queue.get_nowait() + # try: + # function(**kwargs) + # except PageDisconnectedError: + # raise + # pass + # sleep(.1) + # # while not self.event_queue.empty(): # event = self.event_queue.get_nowait() # function = self.event_handlers.get(event['method']) @@ -244,12 +237,6 @@ class Driver(object): self.owner._on_disconnect() def set_callback(self, event, callback, immediate=False): - """绑定cdp event和回调方法 - :param event: cdp event - :param callback: 绑定到cdp event的回调方法 - :param immediate: 是否要立即处理的动作 - :return: None - """ handler = self.immediate_event_handlers if immediate else self.event_handlers if callback: handler[event] = callback @@ -271,13 +258,15 @@ class BrowserDriver(Driver): self._created = True BrowserDriver.BROWSERS[tab_id] = self super().__init__(tab_id, tab_type, address, owner) - self._control_session = Session() - self._control_session.trust_env = False def __repr__(self): return f'' def get(self, url): - r = self._control_session.get(url, headers={'Connection': 'close'}) + s = Session() + s.trust_env = False + s.keep_alive = False + r = s.get(url, headers={'Connection': 'close'}) r.close() + s.close() return r diff --git a/DrissionPage/_base/driver.pyi b/DrissionPage/_base/driver.pyi index b3f44f9..796b053 100644 --- a/DrissionPage/_base/driver.pyi +++ b/DrissionPage/_base/driver.pyi @@ -2,25 +2,16 @@ """ @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, Event +from threading import Thread from typing import Union, Callable, Dict, Optional -from requests import Response, Session +from requests import Response from websocket import WebSocket -from .browser import Browser - - -class GenericAttr(object): - def __init__(self, name: str, tab: Driver): ... - - def __getattr__(self, item: str) -> Callable: ... - - def __setattr__(self, key: str, value: Callable) -> None: ... +from .._base.chromium import Chromium class Driver(object): @@ -35,43 +26,89 @@ class Driver(object): _recv_th: Thread _handle_event_th: Thread _handle_immediate_event_th: Optional[Thread] - _stopped: Event + is_running: bool event_handlers: dict immediate_event_handlers: dict method_results: dict event_queue: Queue immediate_event_queue: Queue - def __init__(self, tab_id: str, tab_type: str, address: str, owner=None): ... + def __init__(self, tab_id: str, tab_type: str, address: str, owner=None): + """ + :param tab_id: 标签页id + :param tab_type: 标签页类型 + :param address: 浏览器连接地址 + :param owner: 创建这个驱动的对象 + """ + ... - def _send(self, message: dict, timeout: float = None) -> dict: ... + def _send(self, message: dict, timeout: float = None) -> dict: + """发送信息到浏览器,并返回浏览器返回的信息 + :param message: 发送给浏览器的数据 + :param timeout: 超时时间,为None表示无限 + :return: 浏览器返回的数据 + """ + ... - def _recv_loop(self) -> None: ... + def _recv_loop(self) -> None: + """接收浏览器信息的守护线程方法""" + ... - def _handle_event_loop(self) -> None: ... + def _handle_event_loop(self) -> None: + """当接收到浏览器信息,执行已绑定的方法""" + ... def _handle_immediate_event_loop(self): ... - def _handle_immediate_event(self, function: Callable, kwargs: dict): ... + def _handle_immediate_event(self, function: Callable, kwargs: dict): + """处理立即执行的动作 + :param function: 要运行下方法 + :param kwargs: 方法参数 + :return: None + """ + ... - def run(self, _method: str, **kwargs) -> dict: ... + def run(self, _method: str, **kwargs) -> dict: + """执行cdp方法 + :param _method: cdp方法名 + :param kwargs: cdp参数 + :return: 执行结果 + """ + ... - def start(self) -> bool: ... + def start(self) -> bool: + """启动连接""" + ... - def stop(self) -> bool: ... + def stop(self) -> bool: + """中断连接""" + ... - def _stop(self) -> None: ... + def _stop(self) -> None: + """中断连接""" + ... - def set_callback(self, event: str, callback: Union[Callable, None], immediate: bool = False) -> None: ... + def set_callback(self, event: str, callback: Union[Callable, None], immediate: bool = False) -> None: + """绑定cdp event和回调方法 + :param event: cdp event + :param callback: 绑定到cdp event的回调方法 + :param immediate: 是否要立即处理的动作 + :return: None + """ + ... class BrowserDriver(Driver): BROWSERS: Dict[str, Driver] = ... - owner: Browser = ... - _control_session: Session = ... + owner: Chromium = ... - def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Browser): ... + def __new__(cls, tab_id: str, tab_type: str, address: str, owner: Chromium): ... - def __init__(self, tab_id: str, tab_type: str, address: str, owner: Browser): ... + def __init__(self, tab_id: str, tab_type: str, address: str, owner: Chromium): ... - def get(self, url) -> Response: ... + def get(self, url) -> Response: + """ + :param url: 要访问的链接 + :return: Response对象 + """ + ... diff --git a/DrissionPage/_configs/chromium_options.py b/DrissionPage/_configs/chromium_options.py index 86e64d2..d41e947 100644 --- a/DrissionPage/_configs/chromium_options.py +++ b/DrissionPage/_configs/chromium_options.py @@ -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 @@ -13,15 +12,12 @@ from .options_manage import OptionsManager class ChromiumOptions(object): def __init__(self, read_file=True, ini_path=None): - """ - :param read_file: 是否从默认ini文件中读取配置信息 - :param ini_path: ini文件路径,为None则读取默认ini文件 - """ self._user_data_path = None self._user = 'Default' self._prefs_to_del = [] self.clear_file_flags = False - self._headless = None + self._is_headless = False + self._ua_set = False if read_file is False: ini_path = False @@ -33,10 +29,10 @@ class ChromiumOptions(object): self.ini_path = str(ini_path) else: self.ini_path = str(Path(__file__).parent / 'configs.ini') - om = OptionsManager(ini_path) + om = OptionsManager(ini_path) options = om.chromium_options - self._download_path = om.paths.get('download_path', None) or None + self._download_path = om.paths.get('download_path', '.') or '.' self._tmp_path = om.paths.get('tmp_path', None) or None self._arguments = options.get('arguments', []) self._browser_path = options.get('browser_path', '') @@ -47,6 +43,11 @@ class ChromiumOptions(object): self._load_mode = options.get('load_mode', 'normal') self._system_user_path = options.get('system_user_path', False) self._existing_only = options.get('existing_only', False) + self._new_env = options.get('new_env', False) + for i in self._arguments: + if i.startswith('--headless'): + self._is_headless = True + break self._proxy = om.proxies.get('http', None) or om.proxies.get('https', None) @@ -74,102 +75,86 @@ class ChromiumOptions(object): return + def __repr__(self): + return f'' + @property def download_path(self): - """默认下载路径文件路径""" return self._download_path @property def browser_path(self): - """浏览器启动文件路径""" return self._browser_path @property def user_data_path(self): - """返回用户数据文件夹路径""" return self._user_data_path @property def tmp_path(self): - """返回临时文件夹路径""" return self._tmp_path @property def user(self): - """返回用户配置文件夹名称""" return self._user @property def load_mode(self): - """返回页面加载策略,'normal', 'eager', 'none'""" return self._load_mode @property def timeouts(self): - """返回timeouts设置""" return self._timeouts @property def proxy(self): - """返回代理设置""" return self._proxy @property def address(self): - """返回浏览器地址,ip:port""" return self._address @property def arguments(self): - """返回浏览器命令行设置列表""" return self._arguments @property def extensions(self): - """以list形式返回要加载的插件路径""" return self._extensions @property def preferences(self): - """返回用户首选项配置""" return self._prefs @property def flags(self): - """返回实验项配置""" return self._flags @property def system_user_path(self): - """返回是否使用系统安装的浏览器所使用的用户数据文件夹""" return self._system_user_path @property def is_existing_only(self): - """返回是否只接管现有浏览器方式""" return self._existing_only @property def is_auto_port(self): - """返回是否使用自动端口和用户文件,如指定范围则返回范围tuple""" return self._auto_port @property def retry_times(self): - """返回连接失败时的重试次数""" return self._retry_times @property def retry_interval(self): - """返回连接失败时的重试间隔(秒)""" return self._retry_interval + @property + def is_headless(self): + return self._is_headless + def set_retry(self, times=None, interval=None): - """设置连接失败时的重试操作 - :param times: 重试次数 - :param interval: 重试间隔 - :return: 当前对象 - """ if times is not None: self._retry_times = times if interval is not None: @@ -177,41 +162,36 @@ class ChromiumOptions(object): return self def set_argument(self, arg, value=None): - """设置浏览器配置的argument属性 - :param arg: 属性名 - :param value: 属性值,有值的属性传入值,没有的传入None,如传入False,删除该项 - :return: 当前对象 - """ self.remove_argument(arg) if value is not False: - if arg == '--headless' and value is None: - self._arguments.append('--headless=new') + if arg == '--headless': + if value == 'false': + self._is_headless = False + else: + if value is None: + value = 'new' + self._arguments.append(f'--headless={value}') + self._is_headless = True else: arg_str = arg if value is None else f'{arg}={value}' self._arguments.append(arg_str) + elif arg == '--headless': + self._is_headless = False return self def remove_argument(self, value): - """移除一个argument项 - :param value: 设置项名,有值的设置项传入设置名称即可 - :return: 当前对象 - """ - del_list = [] + elements_to_delete = [arg for arg in self._arguments if arg == value or arg.startswith(f'{value}=')] + if not elements_to_delete: + return self - for argument in self._arguments: - if argument == value or argument.startswith(f'{value}='): - del_list.append(argument) - - for del_arg in del_list: - self._arguments.remove(del_arg) + if len(elements_to_delete) == 1: + self._arguments.remove(elements_to_delete[0]) + else: + self._arguments = [arg for arg in self._arguments if arg not in elements_to_delete] return self def add_extension(self, path): - """添加插件 - :param path: 插件路径,可指向文件夹 - :return: 当前对象 - """ path = Path(path) if not path.exists(): raise OSError('插件路径不存在。') @@ -219,43 +199,22 @@ class ChromiumOptions(object): return self def remove_extensions(self): - """移除所有插件 - :return: 当前对象 - """ self._extensions = [] return self def set_pref(self, arg, value): - """设置Preferences文件中的用户设置项 - :param arg: 设置项名称 - :param value: 设置项值 - :return: 当前对象 - """ self._prefs[arg] = value return self def remove_pref(self, arg): - """删除用户首选项设置,不能删除已设置到文件中的项 - :param arg: 设置项名称 - :return: 当前对象 - """ self._prefs.pop(arg, None) return self def remove_pref_from_file(self, arg): - """删除用户配置文件中已设置的项 - :param arg: 设置项名称 - :return: 当前对象 - """ self._prefs_to_del.append(arg) return self def set_flag(self, flag, value=None): - """设置实验项 - :param flag: 设置项名称 - :param value: 设置项的值,为False则删除该项 - :return: 当前对象 - """ if value is False: self._flags.pop(flag, None) else: @@ -263,33 +222,22 @@ class ChromiumOptions(object): return self def clear_flags_in_file(self): - """删除浏览器配置文件中已设置的实验项""" self.clear_file_flags = True return self def clear_flags(self): - """清空本对象已设置的flag参数""" self._flags = {} return self def clear_arguments(self): - """清空本对象已设置的argument参数""" self._arguments = [] return self def clear_prefs(self): - """清空本对象已设置的pref参数""" self._prefs = {} return self - def set_timeouts(self, base=None, page_load=None, script=None, implicit=None): - """设置超时时间,单位为秒 - :param base: 默认超时时间 - :param page_load: 页面加载超时时间 - :param script: 脚本运行超时时间 - :return: 当前对象 - """ - base = base if base is not None else implicit + def set_timeouts(self, base=None, page_load=None, script=None): if base is not None: self._timeouts['base'] = base if page_load is not None: @@ -300,74 +248,43 @@ class ChromiumOptions(object): return self def set_user(self, user='Default'): - """设置使用哪个用户配置文件夹 - :param user: 用户文件夹名称 - :return: 当前对象 - """ self.set_argument('--profile-directory', user) self._user = user return self def headless(self, on_off=True): - """设置是否隐藏浏览器界面 - :param on_off: 开或关 - :return: 当前对象 - """ - on_off = 'new' if on_off else 'false' + on_off = 'new' if on_off else on_off return self.set_argument('--headless', on_off) def no_imgs(self, on_off=True): - """设置是否加载图片 - :param on_off: 开或关 - :return: 当前对象 - """ on_off = None if on_off else False return self.set_argument('--blink-settings=imagesEnabled=false', on_off) def no_js(self, on_off=True): - """设置是否禁用js - :param on_off: 开或关 - :return: 当前对象 - """ on_off = None if on_off else False return self.set_argument('--disable-javascript', on_off) def mute(self, on_off=True): - """设置是否静音 - :param on_off: 开或关 - :return: 当前对象 - """ on_off = None if on_off else False return self.set_argument('--mute-audio', on_off) def incognito(self, on_off=True): - """设置是否使用无痕模式启动 - :param on_off: 开或关 - :return: 当前对象 - """ on_off = None if on_off else False - return self.set_argument('--incognito', on_off) + self.set_argument('--incognito', on_off) + return self.set_argument('--inprivate', on_off) # edge + + def new_env(self, on_off=True): + self._new_env = on_off + return self def ignore_certificate_errors(self, on_off=True): - """设置是否忽略证书错误 - :param on_off: 开或关 - :return: 当前对象 - """ on_off = None if on_off else False return self.set_argument('--ignore-certificate-errors', on_off) def set_user_agent(self, user_agent): - """设置user agent - :param user_agent: user agent文本 - :return: 当前对象 - """ return self.set_argument('--user-agent', user_agent) def set_proxy(self, proxy): - """设置代理 - :param proxy: 代理url和端口 - :return: 当前对象 - """ if search(r'.*?:.*?@.*?\..*', proxy): print('你似乎在设置使用账号密码的代理,暂时不支持这种代理,可自行用插件实现需求。') if proxy.lower().startswith('socks'): @@ -376,13 +293,6 @@ class ChromiumOptions(object): return self.set_argument('--proxy-server', proxy) def set_load_mode(self, value): - """设置load_mode,可接收 'normal', 'eager', 'none' - normal:默认情况下使用, 等待所有资源下载完成 - eager:DOM访问已准备就绪, 但其他资源 (如图像) 可能仍在加载中 - none:完全不阻塞 - :param value: 可接收 'normal', 'eager', 'none' - :return: 当前对象 - """ if value not in ('normal', 'eager', 'none'): raise ValueError("只能选择 'normal', 'eager', 'none'。") self._load_mode = value.lower() @@ -420,52 +330,28 @@ class ChromiumOptions(object): return self def set_local_port(self, port): - """设置本地启动端口 - :param port: 端口号 - :return: 当前对象 - """ self._address = f'127.0.0.1:{port}' self._auto_port = False return self def set_address(self, address): - """设置浏览器地址,格式'ip:port' - :param address: 浏览器地址 - :return: 当前对象 - """ - 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 def set_browser_path(self, path): - """设置浏览器可执行文件路径 - :param path: 浏览器路径 - :return: 当前对象 - """ self._browser_path = str(path) return self def set_download_path(self, path): - """设置下载文件保存路径 - :param path: 下载路径 - :return: 当前对象 - """ - self._download_path = str(path) + self._download_path = '.' if path is None else str(path) return self def set_tmp_path(self, path): - """设置临时文件文件保存路径 - :param path: 下载路径 - :return: 当前对象 - """ self._tmp_path = str(path) return self def set_user_data_path(self, path): - """设置用户文件夹路径 - :param path: 用户文件夹路径 - :return: 当前对象 - """ u = str(path) self.set_argument('--user-data-dir', u) self._user_data_path = u @@ -473,49 +359,25 @@ class ChromiumOptions(object): return self def set_cache_path(self, path): - """设置缓存路径 - :param path: 缓存路径 - :return: 当前对象 - """ self.set_argument('--disk-cache-dir', str(path)) return self def use_system_user_path(self, on_off=True): - """设置是否使用系统安装的浏览器默认用户文件夹 - :param on_off: 开或关 - :return: 当前对象 - """ self._system_user_path = on_off return self - def auto_port(self, on_off=True, tmp_path=None, scope=None): - """自动获取可用端口 - :param on_off: 是否开启自动获取端口号 - :param tmp_path: 临时文件保存路径,为None时保存到系统临时文件夹,on_off为False时此参数无效 - :param scope: 指定端口范围,不含最后的数字,为None则使用[9600-19600) - :return: 当前对象 - """ + def auto_port(self, on_off=True, scope=None): if on_off: - self._auto_port = scope if scope else True - if tmp_path: - self._tmp_path = str(tmp_path) + self._auto_port = scope if scope else (9600, 59600) else: self._auto_port = False return self def existing_only(self, on_off=True): - """设置只接管已有浏览器,不自动启动新的 - :param on_off: 是否开启自动获取端口号 - :return: 当前对象 - """ self._existing_only = on_off return self def save(self, path=None): - """保存设置到文件 - :param path: ini文件的路径, None 保存到当前读取的配置文件,传入 'default' 保存到默认ini文件 - :return: 保存文件的绝对路径 - """ if path == 'default': path = (Path(__file__).parent / 'configs.ini').absolute() @@ -537,7 +399,7 @@ class ChromiumOptions(object): # 设置chromium_options attrs = ('address', 'browser_path', 'arguments', 'extensions', 'user', 'load_mode', - 'auto_port', 'system_user_path', 'existing_only', 'flags') + 'auto_port', 'system_user_path', 'existing_only', 'flags', 'new_env') for i in attrs: om.set_item('chromium_options', i, self.__getattribute__(f'_{i}')) # 设置代理 @@ -562,8 +424,4 @@ class ChromiumOptions(object): return path def save_to_default(self): - """保存当前配置到默认ini文件""" return self.save('default') - - def __repr__(self): - return f'' diff --git a/DrissionPage/_configs/chromium_options.pyi b/DrissionPage/_configs/chromium_options.pyi index c682a27..c04e62b 100644 --- a/DrissionPage/_configs/chromium_options.pyi +++ b/DrissionPage/_configs/chromium_options.pyi @@ -2,171 +2,401 @@ """ @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 class ChromiumOptions(object): - def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): - self.ini_path: str = ... - self._driver_path: str = ... - self._user_data_path: str = ... - self._download_path: str = ... - self._tmp_path: str = ... - self._arguments: list = ... - self._browser_path: str = ... - self._user: str = ... - self._load_mode: str = ... - self._timeouts: dict = ... - self._proxy: str = ... - self._address: str = ... - self._extensions: list = ... - self._prefs: dict = ... - self._flags: dict = ... - self._prefs_to_del: list = ... - self.clear_file_flags: bool = ... - self._auto_port: bool = ... - self._system_user_path: bool = ... - self._existing_only: bool = ... - self._headless: bool = ... - self._retry_times: int = ... - self._retry_interval: float = ... + ini_path: Optional[str] = ... + _driver_path: str = ... + _user_data_path: Optional[str] = ... + _download_path: str = ... + _tmp_path: str = ... + _arguments: list = ... + _browser_path: str = ... + _user: str = ... + _load_mode: str = ... + _timeouts: dict = ... + _proxy: str = ... + _address: str = ... + _extensions: list = ... + _prefs: dict = ... + _flags: dict = ... + _prefs_to_del: list = ... + _new_env: bool = ... + clear_file_flags: bool = ... + _auto_port: Union[Tuple[int, int], False] = ... + _system_user_path: bool = ... + _existing_only: bool = ... + _retry_times: int = ... + _retry_interval: float = ... + _is_headless: bool = ... + _ua_set: bool = ... + + def __init__(self, + read_file: [bool, None] = True, + ini_path: Union[str, Path] = None): + """ + :param read_file: 是否从默认ini文件中读取配置信息 + :param ini_path: ini文件路径,为None则读取默认ini文件 + """ + ... @property - def download_path(self) -> str: ... + def download_path(self) -> str: + """默认下载路径文件路径""" + ... @property - def browser_path(self) -> str: ... + def browser_path(self) -> str: + """浏览器启动文件路径""" + ... @property - def user_data_path(self) -> str: ... + def user_data_path(self) -> str: + """返回用户数据文件夹路径""" + ... @property - def tmp_path(self) -> Optional[str]: ... + def tmp_path(self) -> Optional[str]: + """返回临时文件夹路径""" + ... @property - def user(self) -> str: ... + def user(self) -> str: + """返回用户配置文件夹名称""" + ... @property - def load_mode(self) -> str: ... + def load_mode(self) -> str: + """返回页面加载策略,'normal', 'eager', 'none'""" + ... @property - def timeouts(self) -> dict: ... + def timeouts(self) -> dict: + """返回timeouts设置""" + ... @property - def proxy(self) -> str: ... + def proxy(self) -> str: + """返回代理设置""" + ... @property - def address(self) -> str: ... + def address(self) -> str: + """返回浏览器地址,ip:port""" + ... @property - def arguments(self) -> list: ... + def arguments(self) -> list: + """返回浏览器命令行设置列表""" + ... @property - def extensions(self) -> list: ... + def extensions(self) -> list: + """以list形式返回要加载的插件路径""" + ... @property - def preferences(self) -> dict: ... + def preferences(self) -> dict: + """返回用户首选项配置""" + ... @property - def flags(self) -> dict: ... + def flags(self) -> dict: + """返回实验项配置""" + ... @property - def system_user_path(self) -> bool: ... + def system_user_path(self) -> bool: + """返回是否使用系统安装的浏览器所使用的用户数据文件夹""" + ... @property - def is_existing_only(self) -> bool: ... + def is_existing_only(self) -> bool: + """返回是否只接管现有浏览器方式""" + ... @property - def is_auto_port(self) -> Union[bool, Tuple[int, int]]: ... + def is_auto_port(self) -> Union[bool, Tuple[int, int]]: + """返回是否使用自动端口和用户文件,如指定范围则返回范围tuple""" + ... @property - def retry_times(self) -> int: ... + def retry_times(self) -> int: + """返回连接失败时的重试次数""" + ... @property - def retry_interval(self) -> float: ... + def retry_interval(self) -> float: + """返回连接失败时的重试间隔(秒)""" + ... - def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions: ... + @property + def is_headless(self) -> bool: + """返回是否无头模式""" + ... - def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions: ... + def set_retry(self, times: int = None, interval: float = None) -> ChromiumOptions: + """设置连接失败时的重试操作 + :param times: 重试次数 + :param interval: 重试间隔 + :return: 当前对象 + """ + ... - def remove_argument(self, value: str) -> ChromiumOptions: ... + def set_argument(self, arg: str, value: Union[str, None, bool] = None) -> ChromiumOptions: + """设置浏览器配置的argument属性 + :param arg: 属性名 + :param value: 属性值,有值的属性传入值,没有的传入None,如传入False,删除该项 + :return: 当前对象 + """ + ... - def add_extension(self, path: Union[str, Path]) -> ChromiumOptions: ... + def remove_argument(self, value: str) -> ChromiumOptions: + """移除一个argument项 + :param value: 设置项名,有值的设置项传入设置名称即可 + :return: 当前对象 + """ + ... - def remove_extensions(self) -> ChromiumOptions: ... + def add_extension(self, path: Union[str, Path]) -> ChromiumOptions: + """添加插件 + :param path: 插件路径,可指向文件夹 + :return: 当前对象 + """ + ... - def set_pref(self, arg: str, value: Any) -> ChromiumOptions: ... + def remove_extensions(self) -> ChromiumOptions: + """移除所有插件 + :return: 当前对象 + """ + ... - def remove_pref(self, arg: str) -> ChromiumOptions: ... + def set_pref(self, arg: str, value: Any) -> ChromiumOptions: + """设置Preferences文件中的用户设置项 + :param arg: 设置项名称 + :param value: 设置项值 + :return: 当前对象 + """ + ... - def remove_pref_from_file(self, arg: str) -> ChromiumOptions: ... + def remove_pref(self, arg: str) -> ChromiumOptions: + """删除用户首选项设置,不能删除已设置到文件中的项 + :param arg: 设置项名称 + :return: 当前对象 + """ + ... - def set_flag(self, flag: str, value: Union[int, str, bool] = None) -> ChromiumOptions: ... + def remove_pref_from_file(self, arg: str) -> ChromiumOptions: + """删除用户配置文件中已设置的项 + :param arg: 设置项名称 + :return: 当前对象 + """ + ... - def clear_flags_in_file(self) -> ChromiumOptions: ... + def set_flag(self, flag: str, value: Union[int, str, bool] = None) -> ChromiumOptions: + """设置实验项 + :param flag: 设置项名称 + :param value: 设置项的值,为False则删除该项 + :return: 当前对象 + """ + ... - def clear_flags(self) -> ChromiumOptions: ... + def clear_flags_in_file(self) -> ChromiumOptions: + """删除浏览器配置文件中已设置的实验项""" + ... - def clear_arguments(self) -> ChromiumOptions: ... + def clear_flags(self) -> ChromiumOptions: + """清空本对象已设置的flag参数""" + ... - def clear_prefs(self) -> ChromiumOptions: ... + def clear_arguments(self) -> ChromiumOptions: + """清空本对象已设置的argument参数""" + ... + + def clear_prefs(self) -> ChromiumOptions: + """清空本对象已设置的pref参数""" + ... def set_timeouts(self, base: float = None, page_load: float = None, - script: float = None) -> ChromiumOptions: ... + script: float = None) -> ChromiumOptions: + """设置超时时间,单位为秒 + :param base: 默认超时时间 + :param page_load: 页面加载超时时间 + :param script: 脚本运行超时时间 + :return: 当前对象 + """ + ... - def set_user(self, user: str = 'Default') -> ChromiumOptions: ... + def set_user(self, user: str = 'Default') -> ChromiumOptions: + """设置使用哪个用户配置文件夹 + :param user: 用户文件夹名称 + :return: 当前对象 + """ + ... - def headless(self, on_off: bool = True) -> ChromiumOptions: ... + def headless(self, on_off: bool = True) -> ChromiumOptions: + """设置是否隐藏浏览器界面 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def no_imgs(self, on_off: bool = True) -> ChromiumOptions: ... + def no_imgs(self, on_off: bool = True) -> ChromiumOptions: + """设置是否加载图片 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def no_js(self, on_off: bool = True) -> ChromiumOptions: ... + def no_js(self, on_off: bool = True) -> ChromiumOptions: + """设置是否禁用js + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def mute(self, on_off: bool = True) -> ChromiumOptions: ... + def mute(self, on_off: bool = True) -> ChromiumOptions: + """设置是否静音 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def incognito(self, on_off: bool = True) -> ChromiumOptions: ... + def incognito(self, on_off: bool = True) -> ChromiumOptions: + """设置是否使用无痕模式启动 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def set_user_agent(self, user_agent: str) -> ChromiumOptions: ... + def new_env(self, on_off: bool = True) -> ChromiumOptions: + """设置是否使用全新浏览器环境 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def set_proxy(self, proxy: str) -> ChromiumOptions: ... + def ignore_certificate_errors(self, on_off=True) -> ChromiumOptions: + """设置是否忽略证书错误 + :param on_off: 开或关 + :return: 当前对象 + """ + ... - def ignore_certificate_errors(self, on_off=True) -> ChromiumOptions: ... + def set_user_agent(self, user_agent: str) -> ChromiumOptions: + """设置user agent + :param user_agent: user agent文本 + :return: 当前对象 + """ + ... - def set_load_mode(self, value: Literal['normal', 'eager', 'none']) -> ChromiumOptions: ... + def set_proxy(self, proxy: str) -> ChromiumOptions: + """设置代理 + :param proxy: 代理url和端口 + :return: 当前对象 + """ + ... - def set_browser_path(self, path: Union[str, Path]) -> ChromiumOptions: ... + def set_load_mode(self, value: Literal['normal', 'eager', 'none']) -> ChromiumOptions: + """设置load_mode,可接收 'normal', 'eager', 'none' + normal:默认情况下使用, 等待所有资源下载完成 + eager:DOM访问已准备就绪, 但其他资源 (如图像) 可能仍在加载中 + none:完全不阻塞 + :param value: 可接收 'normal', 'eager', 'none' + :return: 当前对象 + """ + ... - def set_local_port(self, port: Union[str, int]) -> ChromiumOptions: ... + def set_local_port(self, port: Union[str, int]) -> ChromiumOptions: + """设置本地启动端口 + :param port: 端口号 + :return: 当前对象 + """ + ... - def set_address(self, address: str) -> ChromiumOptions: ... + def set_address(self, address: str) -> ChromiumOptions: + """设置浏览器地址,格式'ip:port' + :param address: 浏览器地址 + :return: 当前对象 + """ + ... - def set_download_path(self, path: Union[str, Path]) -> ChromiumOptions: ... + def set_browser_path(self, path: Union[str, Path]) -> ChromiumOptions: + """设置浏览器可执行文件路径 + :param path: 浏览器路径 + :return: 当前对象 + """ + ... - def set_tmp_path(self, path: Union[str, Path]) -> ChromiumOptions: ... + def set_download_path(self, path: Union[str, Path]) -> ChromiumOptions: + """设置下载文件保存路径 + :param path: 下载路径 + :return: 当前对象 + """ + ... - def set_user_data_path(self, path: Union[str, Path]) -> ChromiumOptions: ... + def set_tmp_path(self, path: Union[str, Path]) -> ChromiumOptions: + """设置临时文件文件保存路径 + :param path: 下载路径 + :return: 当前对象 + """ + ... - def set_cache_path(self, path: Union[str, Path]) -> ChromiumOptions: ... + def set_user_data_path(self, path: Union[str, Path]) -> ChromiumOptions: + """设置用户文件夹路径 + :param path: 用户文件夹路径 + :return: 当前对象 + """ + ... + + def set_cache_path(self, path: Union[str, Path]) -> ChromiumOptions: + """设置缓存路径 + :param path: 缓存路径 + :return: 当前对象 + """ + ... def set_paths(self, browser_path: Union[str, Path] = None, local_port: Union[int, str] = None, address: str = None, download_path: Union[str, Path] = None, user_data_path: Union[str, Path] = None, cache_path: Union[str, Path] = None) -> ChromiumOptions: ... - def use_system_user_path(self, on_off: bool = True) -> ChromiumOptions: ... + def use_system_user_path(self, on_off: bool = True) -> ChromiumOptions: + """设置是否使用系统安装的浏览器默认用户文件夹 + :param on_off: 开或关 + :return: 当前对象 + """ + ... def auto_port(self, on_off: bool = True, - tmp_path: Union[str, Path] = None, - scope: Tuple[int, int] = None) -> ChromiumOptions: ... + scope: Tuple[int, int] = None) -> ChromiumOptions: + """自动获取可用端口 + :param on_off: 是否开启自动获取端口号 + :param scope: 指定端口范围,不含最后的数字,为None则使用[9600-59600) + :return: 当前对象 + """ + ... - def existing_only(self, on_off: bool = True) -> ChromiumOptions: ... + def existing_only(self, on_off: bool = True) -> ChromiumOptions: + """设置只接管已有浏览器,不自动启动新的 + :param on_off: 是否开启自动获取端口号 + :return: 当前对象 + """ + ... - def save(self, path: Union[str, Path] = None) -> str: ... + def save(self, path: Union[str, Path] = None) -> str: + """保存设置到文件 + :param path: ini文件的路径, None 保存到当前读取的配置文件,传入 'default' 保存到默认ini文件 + :return: 保存文件的绝对路径 + """ + ... - def save_to_default(self) -> str: ... + def save_to_default(self) -> str: + """保存当前配置到默认ini文件""" + ... diff --git a/DrissionPage/_configs/configs.ini b/DrissionPage/_configs/configs.ini index f2de400..9e516e5 100644 --- a/DrissionPage/_configs/configs.ini +++ b/DrissionPage/_configs/configs.ini @@ -14,6 +14,7 @@ user = Default auto_port = False system_user_path = False existing_only = False +new_env = False [session_options] headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'connection': 'keep-alive', 'accept-charset': 'GB2312,utf-8;q=0.7,*;q=0.7'} diff --git a/DrissionPage/_configs/options_manage.py b/DrissionPage/_configs/options_manage.py index f9c85f4..f39bbf5 100644 --- a/DrissionPage/_configs/options_manage.py +++ b/DrissionPage/_configs/options_manage.py @@ -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 @@ -14,9 +13,6 @@ class OptionsManager(object): """管理配置文件内容的类""" def __init__(self, path=None): - """初始化,读取配置文件,如没有设置临时文件夹,则设置并新建 - :param path: ini文件的路径,为None则找项目文件夹下的,找不到则读取模块文件夹下的 - """ if path is False: self.ini_path = None else: @@ -64,6 +60,7 @@ class OptionsManager(object): self.set_item('chromium_options', 'auto_port', 'False') self.set_item('chromium_options', 'system_user_path', 'False') self.set_item('chromium_options', 'existing_only', 'False') + self.set_item('chromium_options', 'new_env', 'False') self.set_item('session_options', 'headers', "{'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X " "10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10." "1.2 Safari/603.3.8', 'accept': 'text/html,application/xhtml" @@ -78,18 +75,9 @@ class OptionsManager(object): self.set_item('others', 'retry_interval', '2') def __getattr__(self, item): - """以dict形似返回获取大项信息 - :param item: 项名 - :return: None - """ return self.get_option(item) def get_value(self, section, item): - """获取配置的值 - :param section: 段名 - :param item: 项名 - :return: 项值 - """ try: return eval(self._conf.get(section, item)) except (SyntaxError, NameError): @@ -98,10 +86,6 @@ class OptionsManager(object): return None def get_option(self, section): - """把section内容以字典方式返回 - :param section: 段名 - :return: 段内容生成的字典 - """ items = self._conf.items(section) option = dict() @@ -114,30 +98,15 @@ class OptionsManager(object): return option def set_item(self, section, item, value): - """设置配置值 - :param section: 段名 - :param item: 项名 - :param value: 项值 - :return: None - """ self._conf.set(section, item, str(value)) self.__setattr__(f'_{section}', None) return self def remove_item(self, section, item): - """删除配置值 - :param section: 段名 - :param item: 项名 - :return: None - """ self._conf.remove_option(section, item) return self def save(self, path=None): - """保存配置文件 - :param path: ini文件的路径,传入 'default' 保存到默认ini文件 - :return: 保存路径 - """ default_path = (Path(__file__).parent / 'configs.ini').absolute() if path == 'default': path = default_path @@ -162,11 +131,9 @@ class OptionsManager(object): return path def save_to_default(self): - """保存当前配置到默认ini文件""" return self.save('default') def show(self): - """打印所有设置信息""" for i in self._conf.sections(): print(f'[{i}]') pprint(self.get_option(i)) diff --git a/DrissionPage/_configs/options_manage.pyi b/DrissionPage/_configs/options_manage.pyi index f2b0483..b0d2348 100644 --- a/DrissionPage/_configs/options_manage.pyi +++ b/DrissionPage/_configs/options_manage.pyi @@ -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 @@ -15,20 +14,62 @@ class OptionsManager(object): file_exists: bool = ... _conf: RawConfigParser = ... - def __init__(self, path: Union[Path, str] = None): ... + def __init__(self, path: Union[Path, str] = None): + """初始化,读取配置文件,如没有设置临时文件夹,则设置并新建 + :param path: ini文件的路径,为None则找项目文件夹下的,找不到则读取模块文件夹下的 + """ + ... - def __getattr__(self, item) -> dict: ... + def __getattr__(self, item) -> dict: + """以dict形似返回获取大项信息 + :param item: 项名 + :return: None + """ + ... - def get_value(self, section: str, item: str) -> Any: ... + def get_value(self, section: str, item: str) -> Any: + """获取配置的值 + :param section: 段名 + :param item: 项名 + :return: 项值 + """ + ... - def get_option(self, section: str) -> dict: ... + def get_option(self, section: str) -> dict: + """把section内容以字典方式返回 + :param section: 段名 + :return: 段内容生成的字典 + """ + ... - def set_item(self, section: str, item: str, value: Any) -> None: ... + def set_item(self, section: str, item: str, value: Any) -> None: + """设置配置值 + :param section: 段名 + :param item: 项名 + :param value: 项值 + :return: None + """ + ... - def remove_item(self, section: str, item: str) -> None: ... + def remove_item(self, section: str, item: str) -> None: + """删除配置值 + :param section: 段名 + :param item: 项名 + :return: None + """ + ... - def save(self, path: str = None) -> str: ... + def save(self, path: str = None) -> str: + """保存配置文件 + :param path: ini文件的路径,传入 'default' 保存到默认ini文件 + :return: 保存路径 + """ + ... - def save_to_default(self) -> str: ... + def save_to_default(self) -> str: + """保存当前配置到默认ini文件""" + ... - def show(self) -> None: ... + def show(self) -> None: + """打印所有设置信息""" + ... diff --git a/DrissionPage/_configs/session_options.py b/DrissionPage/_configs/session_options.py index a533e4d..664acd8 100644 --- a/DrissionPage/_configs/session_options.py +++ b/DrissionPage/_configs/session_options.py @@ -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 @@ -12,19 +11,15 @@ from requests import Session from requests.structures import CaseInsensitiveDict from .options_manage import OptionsManager -from .._functions.web import cookies_to_tuple, set_session_cookies, format_headers +from .._functions.cookies import cookies_to_tuple, set_session_cookies +from .._functions.web import format_headers class SessionOptions(object): - """requests的Session对象配置类""" def __init__(self, read_file=True, ini_path=None): - """ - :param read_file: 是否从文件读取配置 - :param ini_path: ini文件路径 - """ self.ini_path = None - self._download_path = None + self._download_path = '.' self._timeout = 10 self._del_set = set() # 记录要从ini文件删除的参数 @@ -83,71 +78,51 @@ class SessionOptions(object): self.set_proxies(om.proxies.get('http', None), om.proxies.get('https', None)) self._timeout = om.timeouts.get('base', 10) - self._download_path = om.paths.get('download_path', None) or None + self._download_path = om.paths.get('download_path', '.') or '.' others = om.others self._retry_times = others.get('retry_times', 3) self._retry_interval = others.get('retry_interval', 2) + def __repr__(self): + return f'' + # ===========须独立处理的项开始============ @property def download_path(self): - """返回默认下载路径属性信息""" return self._download_path def set_download_path(self, path): - """设置默认下载路径 - :param path: 下载路径 - :return: 返回当前对象 - """ - self._download_path = str(path) + self._download_path = '.' if path is None else str(path) return self @property def timeout(self): - """返回timeout属性信息""" return self._timeout def set_timeout(self, second): - """设置超时信息 - :param second: 秒数 - :return: 返回当前对象 - """ self._timeout = second return self @property def proxies(self): - """返回proxies设置信息""" if self._proxies is None: self._proxies = {} return self._proxies def set_proxies(self, http=None, https=None): - """设置proxies参数 - :param http: http代理地址 - :param https: https代理地址 - :return: 返回当前对象 - """ self._sets('proxies', {'http': http, 'https': https}) return self @property def retry_times(self): - """返回连接失败时的重试次数""" return self._retry_times @property def retry_interval(self): - """返回连接失败时的重试间隔(秒)""" return self._retry_interval def set_retry(self, times=None, interval=None): - """设置连接失败时的重试操作 - :param times: 重试次数 - :param interval: 重试间隔 - :return: 当前对象 - """ if times is not None: self._retry_times = times if interval is not None: @@ -158,16 +133,11 @@ class SessionOptions(object): @property def headers(self): - """返回headers设置信息""" if self._headers is None: self._headers = {} return self._headers def set_headers(self, headers): - """设置headers参数 - :param headers: 参数值,传入None可在ini文件标记删除 - :return: 返回当前对象 - """ if headers is None: self._headers = None self._del_set.add('headers') @@ -177,11 +147,6 @@ class SessionOptions(object): return self def set_a_header(self, name, value): - """设置headers中一个项 - :param name: 设置名称 - :param value: 设置值 - :return: 返回当前对象 - """ if self._headers is None: self._headers = {} @@ -189,10 +154,6 @@ class SessionOptions(object): return self def remove_a_header(self, name): - """从headers中删除一个设置 - :param name: 要删除的设置 - :return: 返回当前对象 - """ if self._headers is None: return self @@ -201,156 +162,99 @@ class SessionOptions(object): return self def clear_headers(self): - """清空已设置的header参数""" self._headers = None self._del_set.add('headers') @property def cookies(self): - """以list形式返回cookies""" if self._cookies is None: self._cookies = [] return self._cookies def set_cookies(self, cookies): - """设置一个或多个cookies信息 - :param cookies: cookies,可为Cookie, CookieJar, list, tuple, str, dict,传入None可在ini文件标记删除 - :return: 返回当前对象 - """ cookies = cookies if cookies is None else list(cookies_to_tuple(cookies)) self._sets('cookies', cookies) return self @property def auth(self): - """返回认证设置信息""" return self._auth def set_auth(self, auth): - """设置认证元组或对象 - :param auth: 认证元组或对象 - :return: 返回当前对象 - """ self._sets('auth', auth) return self @property def hooks(self): - """返回回调方法""" if self._hooks is None: self._hooks = {} return self._hooks def set_hooks(self, hooks): - """设置回调方法 - :param hooks: 回调方法 - :return: 返回当前对象 - """ self._hooks = hooks return self @property def params(self): - """返回连接参数设置信息""" if self._params is None: self._params = {} return self._params def set_params(self, params): - """设置查询参数字典 - :param params: 查询参数字典 - :return: 返回当前对象 - """ self._sets('params', params) return self @property def verify(self): - """返回是否验证SSL证书设置""" return self._verify def set_verify(self, on_off): - """设置是否验证SSL证书 - :param on_off: 是否验证 SSL 证书 - :return: 返回当前对象 - """ self._sets('verify', on_off) return self @property def cert(self): - """返回SSL证书设置信息""" return self._cert def set_cert(self, cert): - """SSL客户端证书文件的路径(.pem格式),或(‘cert’, ‘key’)元组 - :param cert: 证书路径或元组 - :return: 返回当前对象 - """ self._sets('cert', cert) return self @property def adapters(self): - """返回适配器设置信息""" if self._adapters is None: self._adapters = [] return self._adapters def add_adapter(self, url, adapter): - """添加适配器 - :param url: 适配器对应url - :param adapter: 适配器对象 - :return: 返回当前对象 - """ self._adapters.append((url, adapter)) return self @property def stream(self): - """返回是否使用流式响应内容设置信息""" return self._stream def set_stream(self, on_off): - """设置是否使用流式响应内容 - :param on_off: 是否使用流式响应内容 - :return: 返回当前对象 - """ self._sets('stream', on_off) return self @property def trust_env(self): - """返回是否信任环境设置信息""" return self._trust_env def set_trust_env(self, on_off): - """设置是否信任环境 - :param on_off: 是否信任环境 - :return: 返回当前对象 - """ self._sets('trust_env', on_off) return self @property def max_redirects(self): - """返回最大重定向次数""" return self._max_redirects def set_max_redirects(self, times): - """设置最大重定向次数 - :param times: 最大重定向次数 - :return: 返回当前对象 - """ self._sets('max_redirects', times) return self def _sets(self, arg, val): - """给属性赋值或标记删除 - :param arg: 属性名称 - :param val: 参数值 - :return: None - """ if val is None: self.__setattr__(f'_{arg}', None) self._del_set.add(arg) @@ -360,10 +264,6 @@ class SessionOptions(object): self._del_set.remove(arg) def save(self, path=None): - """保存设置到文件 - :param path: ini文件的路径,传入 'default' 保存到默认ini文件 - :return: 保存文件的绝对路径 - """ if path == 'default': path = (Path(__file__).parent / 'configs.ini').absolute() @@ -411,15 +311,12 @@ class SessionOptions(object): return path def save_to_default(self): - """保存当前配置到默认ini文件""" return self.save('default') def as_dict(self): - """以字典形式返回本对象""" return session_options_to_dict(self) def make_session(self): - """根据内在的配置生成Session对象,ua从对象中分离""" s = Session() h = CaseInsensitiveDict(self.headers) if self.headers else CaseInsensitiveDict() @@ -437,11 +334,6 @@ class SessionOptions(object): return s, h def from_session(self, session, headers=None): - """从Session对象中读取配置 - :param session: Session对象 - :param headers: headers - :return: 当前对象 - """ self._headers = CaseInsensitiveDict(copy(session.headers).update(headers)) if headers else session.headers self._cookies = session.cookies self._auth = session.auth @@ -457,15 +349,8 @@ class SessionOptions(object): self._adapters = [(k, i) for k, i in session.adapters.items()] return self - def __repr__(self): - return f'' - def session_options_to_dict(options): - """把session配置对象转换为字典 - :param options: session配置对象或字典 - :return: 配置字典 - """ if options in (False, None): return SessionOptions(read_file=False).as_dict() diff --git a/DrissionPage/_configs/session_options.pyi b/DrissionPage/_configs/session_options.pyi index 54a88af..4098721 100644 --- a/DrissionPage/_configs/session_options.pyi +++ b/DrissionPage/_configs/session_options.pyi @@ -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 @@ -12,125 +11,287 @@ from typing import Any, Union, Tuple, Optional from requests import Session from requests.adapters import HTTPAdapter from requests.auth import HTTPBasicAuth +from requests.cookies import RequestsCookieJar from requests.structures import CaseInsensitiveDict class SessionOptions(object): - def __init__(self, read_file: [bool, None] = True, ini_path: Union[str, Path] = None): - self.ini_path: str = ... - self._download_path: str = ... - self._headers: dict = ... - self._cookies: list = ... - self._auth: tuple = ... - self._proxies: dict = ... - self._hooks: dict = ... - self._params: dict = ... - self._verify: bool = ... - self._cert: Union[str, tuple] = ... - self._adapters: list = ... - self._stream: bool = ... - self._trust_env: bool = ... - self._max_redirects: int = ... - self._timeout: float = ... - self._del_set: set = ... - self._retry_times: int = ... - self._retry_interval: float = ... + """requests的Session对象配置类""" + + ini_path: Optional[str] = ... + _download_path: str = ... + _headers: Union[dict, CaseInsensitiveDict, None] = ... + _cookies: Union[list, RequestsCookieJar, None] = ... + _auth: Optional[tuple] = ... + _proxies: Optional[dict] = ... + _hooks: Optional[dict] = ... + _params: Union[dict, None] = ... + _verify: Optional[bool] = ... + _cert: Union[str, tuple, None] = ... + _adapters: Optional[list] = ... + _stream: Optional[bool] = ... + _trust_env: Optional[bool] = ... + _max_redirects: Optional[int] = ... + _timeout: float = ... + _del_set: set = ... + _retry_times: int = ... + _retry_interval: float = ... + + def __init__(self, + read_file: [bool, None] = True, + ini_path: Union[str, Path] = None): + """ + :param read_file: 是否从文件读取配置 + :param ini_path: ini文件路径 + """ + ... @property - def download_path(self) -> str: ... + def download_path(self) -> str: + """返回默认下载路径属性信息""" + ... - def set_download_path(self, path: Union[str, Path]) -> SessionOptions: ... + def set_download_path(self, path: Union[str, Path]) -> SessionOptions: + """设置默认下载路径 + :param path: 下载路径 + :return: 返回当前对象 + """ + ... @property - def timeout(self) -> float: ... + def timeout(self) -> float: + """返回timeout属性信息""" + ... - def set_timeout(self, second: float) -> SessionOptions: ... + def set_timeout(self, second: float) -> SessionOptions: + """设置超时信息 + :param second: 秒数 + :return: 返回当前对象 + """ + ... @property - def headers(self) -> dict: ... + def proxies(self) -> dict: + """返回proxies设置信息""" + ... - def set_headers(self, headers: Union[dict, str, None]) -> SessionOptions: ... - - def set_a_header(self, name: str, value: str) -> SessionOptions: ... - - def remove_a_header(self, name: str) -> SessionOptions: ... - - def clear_headers(self) -> SessionOptions: ... + def set_proxies(self, http: Union[str, None], https: Union[str, None] = None) -> SessionOptions: + """设置proxies参数 + :param http: http代理地址 + :param https: https代理地址 + :return: 返回当前对象 + """ + ... @property - def cookies(self) -> list: ... - - def set_cookies(self, cookies: Union[Cookie, CookieJar, list, tuple, str, dict, None]) -> SessionOptions: ... + def retry_times(self) -> int: + """返回连接失败时的重试次数""" + ... @property - def auth(self) -> Union[Tuple[str, str], HTTPBasicAuth]: ... + def retry_interval(self) -> float: + """返回连接失败时的重试间隔(秒)""" + ... - def set_auth(self, auth: Union[Tuple[str, str], HTTPBasicAuth, None]) -> SessionOptions: ... + def set_retry(self, times: int = None, interval: float = None) -> SessionOptions: + """设置连接失败时的重试操作 + :param times: 重试次数 + :param interval: 重试间隔 + :return: 当前对象 + """ + ... @property - def proxies(self) -> dict: ... + def headers(self) -> dict: + """返回headers设置信息""" + ... - def set_proxies(self, http: Union[str, None], https: Union[str, None] = None) -> SessionOptions: ... + def set_headers(self, headers: Union[dict, str, None]) -> SessionOptions: + """设置headers参数 + :param headers: 参数值,传入None可在ini文件标记删除 + :return: 返回当前对象 + """ + ... + + def set_a_header(self, name: str, value: str) -> SessionOptions: + """设置headers中一个项 + :param name: 设置名称 + :param value: 设置值 + :return: 返回当前对象 + """ + ... + + def remove_a_header(self, name: str) -> SessionOptions: + """从headers中删除一个设置 + :param name: 要删除的设置 + :return: 返回当前对象 + """ + ... + + def clear_headers(self) -> SessionOptions: + """清空已设置的header参数""" + ... @property - def retry_times(self) -> int: ... + def cookies(self) -> list: + """以list形式返回cookies""" + ... + + def set_cookies(self, cookies: Union[Cookie, CookieJar, list, tuple, str, dict, None]) -> SessionOptions: + """设置一个或多个cookies信息 + :param cookies: cookies,可为Cookie, CookieJar, list, tuple, str, dict,传入None可在ini文件标记删除 + :return: 返回当前对象 + """ + ... @property - def retry_interval(self) -> float: ... + def auth(self) -> Union[Tuple[str, str], HTTPBasicAuth]: + """返回认证设置信息""" + ... - def set_retry(self, times: int = None, interval: float = None) -> SessionOptions: ... + def set_auth(self, auth: Union[Tuple[str, str], HTTPBasicAuth, None]) -> SessionOptions: + """设置认证元组或对象 + :param auth: 认证元组或对象 + :return: 返回当前对象 + """ + ... @property - def hooks(self) -> dict: ... + def hooks(self) -> dict: + """返回回调方法""" + ... - def set_hooks(self, hooks: Union[dict, None]) -> SessionOptions: ... + def set_hooks(self, hooks: Union[dict, None]) -> SessionOptions: + """设置回调方法 + :param hooks: 回调方法 + :return: 返回当前对象 + """ + ... @property - def params(self) -> dict: ... + def params(self) -> dict: + """返回连接参数设置信息""" + ... - def set_params(self, params: Union[dict, None]) -> SessionOptions: ... + def set_params(self, params: Union[dict, None]) -> SessionOptions: + """设置查询参数字典 + :param params: 查询参数字典 + :return: 返回当前对象 + """ + ... @property - def verify(self) -> bool: ... + def verify(self) -> bool: + """返回是否验证SSL证书设置""" + ... - def set_verify(self, on_off: Union[bool, None]) -> SessionOptions: ... + def set_verify(self, on_off: Union[bool, None]) -> SessionOptions: + """设置是否验证SSL证书 + :param on_off: 是否验证 SSL 证书 + :return: 返回当前对象 + """ + ... @property - def cert(self) -> Union[str, tuple]: ... + def cert(self) -> Union[str, tuple]: + """返回SSL证书设置信息""" + ... - def set_cert(self, cert: Union[str, Tuple[str, str], None]) -> SessionOptions: ... + def set_cert(self, cert: Union[str, Tuple[str, str], None]) -> SessionOptions: + """SSL客户端证书文件的路径(.pem格式),或('cert', 'key')元组 + :param cert: 证书路径或元组 + :return: 返回当前对象 + """ + ... @property - def adapters(self): list: ... + def adapters(self) -> list: + """返回适配器设置信息""" + ... - def add_adapter(self, url: str, adapter: HTTPAdapter) -> SessionOptions: ... + def add_adapter(self, url: str, adapter: HTTPAdapter) -> SessionOptions: + """添加适配器 + :param url: 适配器对应url + :param adapter: 适配器对象 + :return: 返回当前对象 + """ + ... @property - def stream(self) -> bool: ... + def stream(self) -> bool: + """返回是否使用流式响应内容设置信息""" + ... - def set_stream(self, on_off: Union[bool, None]) -> SessionOptions: ... + def set_stream(self, on_off: Union[bool, None]) -> SessionOptions: + """设置是否使用流式响应内容 + :param on_off: 是否使用流式响应内容 + :return: 返回当前对象 + """ + ... @property - def trust_env(self) -> bool: ... + def trust_env(self) -> bool: + """返回是否信任环境设置信息""" + ... - def set_trust_env(self, on_off: Union[bool, None]) -> SessionOptions: ... + def set_trust_env(self, on_off: Union[bool, None]) -> SessionOptions: + """设置是否信任环境 + :param on_off: 是否信任环境 + :return: 返回当前对象 + """ + ... @property - def max_redirects(self) -> int: ... + def max_redirects(self) -> int: + """返回最大重定向次数""" + ... - def set_max_redirects(self, times: Union[int, None]) -> SessionOptions: ... + def set_max_redirects(self, times: Union[int, None]) -> SessionOptions: + """设置最大重定向次数 + :param times: 最大重定向次数 + :return: 返回当前对象 + """ + ... - def _sets(self, arg: str, val: Any) -> None: ... + def _sets(self, arg: str, val: Any) -> None: + """给属性赋值或标记删除 + :param arg: 属性名称 + :param val: 参数值 + :return: None + """ + ... - def save(self, path: str = None) -> str: ... + def save(self, path: str = None) -> str: + """保存设置到文件 + :param path: ini文件的路径,传入 'default' 保存到默认ini文件 + :return: 保存文件的绝对路径 + """ + ... - def save_to_default(self) -> str: ... + def save_to_default(self) -> str: + """保存当前配置到默认ini文件""" + ... - def as_dict(self) -> dict: ... + def as_dict(self) -> dict: + """以字典形式返回本对象""" + ... - def make_session(self) -> Tuple[Session, Optional[CaseInsensitiveDict]]: ... + def make_session(self) -> Tuple[Session, Optional[CaseInsensitiveDict]]: + """根据内在的配置生成Session对象,headers从对象中分离""" + ... - def from_session(self, session: Session, headers: CaseInsensitiveDict = None) -> SessionOptions: ... + def from_session(self, session: Session, headers: CaseInsensitiveDict = None) -> SessionOptions: + """从Session对象中读取配置 + :param session: Session对象 + :param headers: headers + :return: 当前对象 + """ + ... -def session_options_to_dict(options: Union[dict, SessionOptions, None]) -> Union[dict, None]: ... +def session_options_to_dict(options: Union[dict, SessionOptions, None]) -> Union[dict, None]: + """把session配置对象转换为字典 + :param options: session配置对象或字典 + :return: 配置字典 + """ + ... diff --git a/DrissionPage/_elements/chromium_element.py b/DrissionPage/_elements/chromium_element.py index 3f42b5f..df085d6 100644 --- a/DrissionPage/_elements/chromium_element.py +++ b/DrissionPage/_elements/chromium_element.py @@ -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 @@ -16,10 +15,10 @@ from DataRecorder.tools import get_usable_path, make_valid_name from .none_element import NoneElement from .session_element import make_session_ele from .._base.base import DrissionElement, BaseElement +from .._functions.elements import ChromiumElementsList, SessionElementsList from .._functions.keys import input_text_or_keys from .._functions.locator import get_loc, locator_to_tuple -from .._functions.elements import ChromiumElementsList -from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, offset_scroll, get_blob +from .._functions.web import make_absolute_link, get_ele_txt, format_html, is_js_func, get_blob from .._units.clicker import Clicker from .._units.rect import ElementRect from .._units.scroller import ElementScroller @@ -27,24 +26,17 @@ from .._units.selector import SelectElement from .._units.setter import ChromiumElementSetter from .._units.states import ElementStates, ShadowRootStates from .._units.waiter import ElementWaiter -from ..errors import ContextLostError, ElementLostError, JavaScriptError, CDPError, NoResourceError, AlertExistsError, \ - NoRectError +from ..errors import (ContextLostError, ElementLostError, JavaScriptError, CDPError, NoResourceError, + AlertExistsError, NoRectError) __FRAME_ELEMENT__ = ('iframe', 'frame') class ChromiumElement(DrissionElement): - """控制浏览器元素的对象""" def __init__(self, owner, node_id=None, obj_id=None, backend_id=None): - """node_id、obj_id和backend_id必须至少传入一个 - :param owner: 元素所在页面对象 - :param node_id: cdp中的node id - :param obj_id: js中的object id - :param backend_id: backend id - """ super().__init__(owner) - self.tab = self.owner.tab + self.tab = self.owner._tab self._select = None self._scroll = None self._rect = None @@ -76,135 +68,114 @@ class ChromiumElement(DrissionElement): else: raise ElementLostError + def __call__(self, locator, index=1, timeout=None): + return self.ele(locator, index=index, timeout=timeout) + def __repr__(self): attrs = [f"{k}='{v}'" for k, v in self.attrs.items()] return f'' - def __call__(self, locator, index=1, timeout=None): - """在内部查找元素 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 超时时间(秒) - :return: ChromiumElement对象或属性、文本 - """ - return self.ele(locator, index=index, timeout=timeout) - def __eq__(self, other): return self._backend_id == getattr(other, '_backend_id', None) @property def tag(self): - """返回元素tag""" if self._tag is None: - self._tag = self.owner.run_cdp('DOM.describeNode', - backendNodeId=self._backend_id)['node']['localName'].lower() + self._tag = self.owner._run_cdp('DOM.describeNode', + backendNodeId=self._backend_id)['node']['localName'].lower() return self._tag @property def html(self): - """返回元素outerHTML文本""" - return self.owner.run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML'] + return self.owner._run_cdp('DOM.getOuterHTML', backendNodeId=self._backend_id)['outerHTML'] @property def inner_html(self): - """返回元素innerHTML文本""" - return self.run_js('return this.innerHTML;') + return self._run_js('return this.innerHTML;') @property def attrs(self): - """返回元素所有attribute属性""" try: - attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] + attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)} except ElementLostError: self._refresh_id() - attrs = self.owner.run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] + attrs = self.owner._run_cdp('DOM.getAttributes', nodeId=self._node_id)['attributes'] return {attrs[i]: attrs[i + 1] for i in range(0, len(attrs), 2)} except CDPError: # 文档根元素不能调用此方法 return {} @property def text(self): - """返回元素内所有文本,文本已格式化""" return get_ele_txt(make_session_ele(self.html)) @property def raw_text(self): - """返回未格式化处理的元素内文本""" return self.property('innerText') # -----------------d模式独有属性------------------- @property def set(self): - """返回用于设置元素属性的对象""" if self._set is None: self._set = ChromiumElementSetter(self) return self._set @property def states(self): - """返回用于获取元素状态的对象""" if self._states is None: self._states = ElementStates(self) return self._states @property def pseudo(self): - """返回用于获取伪元素内容的对象""" if self._pseudo is None: self._pseudo = Pseudo(self) return self._pseudo @property def rect(self): - """返回用于获取元素位置的对象""" if self._rect is None: self._rect = ElementRect(self) return self._rect @property - def shadow_root(self): - """返回当前元素的shadow_root元素对象""" - info = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] - if not info.get('shadowRoots', None): - return None - - return ShadowRoot(self, backend_id=info['shadowRoots'][0]['backendNodeId']) + def sr(self): + end_time = perf_counter() + self.timeout + while perf_counter() < end_time: + info = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] + if info.get('shadowRoots', None): + return ShadowRoot(self, backend_id=info['shadowRoots'][0]['backendNodeId']) + return None @property - def sr(self): - """返回当前元素的shadow_root元素对象""" - return self.shadow_root + def shadow_root(self): + return self.sr @property def scroll(self): - """用于滚动滚动条的对象""" if self._scroll is None: self._scroll = ElementScroller(self) return self._scroll @property def click(self): - """返回用于点击的对象""" if self._clicker is None: self._clicker = Clicker(self) return self._clicker @property def wait(self): - """返回用于等待的对象""" if self._wait is None: - self._wait = ElementWaiter(self.owner, self) + self._wait = ElementWaiter(self) return self._wait @property def select(self): - """返回专门处理下拉列表的Select类,非下拉列表元素返回False""" if self._select is None: if self.tag != 'select': self._select = False else: self._select = SelectElement(self) - return self._select @property @@ -225,185 +196,116 @@ class ChromiumElement(DrissionElement): elif not is_checked and not uncheck: js = 'this.checked=true' if js: - self.run_js(js) - self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') + self._run_js(js) + self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') else: if (is_checked and uncheck) or (not is_checked and not uncheck): self.click() - def parent(self, level_or_loc=1, index=1): - """返回上面某一级父元素,可指定层数或用查询语法定位 - :param level_or_loc: 第几级父元素,1开始,或定位符 - :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果,1开始 - :return: 上级元素对象 - """ - return super().parent(level_or_loc, index) + return self + + def parent(self, level_or_loc=1, index=1, timeout=0): + return super().parent(level_or_loc, index, timeout=timeout) def child(self, locator='', index=1, timeout=None, ele_only=True): - """返回当前元素的一个符合条件的直接子元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 直接子元素或节点文本 - """ return super().child(locator, index, timeout, ele_only=ele_only) def prev(self, locator='', index=1, timeout=None, ele_only=True): - """返回当前元素前面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本 - """ return super().prev(locator, index, timeout, ele_only=ele_only) def next(self, locator='', index=1, timeout=None, ele_only=True): - """返回当前元素后面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本 - """ return super().next(locator, index, timeout, ele_only=ele_only) def before(self, locator='', index=1, timeout=None, ele_only=True): - """返回文档中当前元素前面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素前面的某个元素或节点 - """ return super().before(locator, index, timeout, ele_only=ele_only) def after(self, locator='', index=1, timeout=None, ele_only=True): - """返回文档中此当前元素后面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素后面的某个元素或节点 - """ return super().after(locator, index, timeout, ele_only=ele_only) def children(self, locator='', timeout=None, ele_only=True): - """返回当前元素符合条件的直接子元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 直接子元素或节点文本组成的列表 - """ return ChromiumElementsList(self.owner, super().children(locator, timeout, ele_only=ele_only)) def prevs(self, locator='', timeout=None, ele_only=True): - """返回当前元素前面符合条件的同级元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本组成的列表 - """ return ChromiumElementsList(self.owner, super().prevs(locator, timeout, ele_only=ele_only)) def nexts(self, locator='', timeout=None, ele_only=True): - """返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 兄弟元素或节点文本组成的列表 - """ return ChromiumElementsList(self.owner, super().nexts(locator, timeout, ele_only=ele_only)) def befores(self, locator='', timeout=None, ele_only=True): - """返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素前面的元素或节点组成的列表 - """ return ChromiumElementsList(self.owner, super().befores(locator, timeout, ele_only=ele_only)) def afters(self, locator='', timeout=None, ele_only=True): - """返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param timeout: 查找节点的超时时间(秒) - :param ele_only: 是否只获取元素,为False时把文本、注释节点也纳入 - :return: 本元素后面的元素或节点组成的列表 - """ return ChromiumElementsList(self.owner, super().afters(locator, timeout, ele_only=ele_only)) def over(self, timeout=None): - """获取覆盖在本元素上最上层的元素 - :param timeout: 等待元素出现的超时时间(秒) - :return: 元素对象 - """ - timeout = timeout if timeout is None else self.owner.timeout - bid = self.wait.covered(timeout=timeout) - if bid: - return ChromiumElement(owner=self.owner, backend_id=bid) - else: - return NoneElement(page=self.owner, method='on()', args={'timeout': timeout}) + if timeout is None: + timeout = self.timeout + bid = self.states.is_covered + end_time = perf_counter() + timeout + while not bid and perf_counter() < end_time: + bid = self.states.is_covered + return (ChromiumElement(owner=self.owner, backend_id=bid) + if bid else NoneElement(page=self.owner, method='over()', args={'timeout': timeout})) - def offset(self, offset_x, offset_y): - """获取相对本元素左上角左边指定偏移量位置的元素 - :param offset_x: 横坐标偏移量,向右为正 - :param offset_y: 纵坐标偏移量,向下为正 - :return: 元素对象 - """ - x, y = self.rect.location + def offset(self, locator=None, x=None, y=None, timeout=None): + if locator and not (isinstance(locator, str) and not locator.startswith( + ('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css='))): + raise ValueError('locator参数只能是str格式且不支持xpath和css形式。') + + if x == y is None: + x, y = self.rect.midpoint + x = int(x) + y = int(y) + else: + nx, ny = self.rect.location + nx += x if x else 0 + ny += y if y else 0 + x = int(nx) + y = int(ny) + loc_data = locator_to_tuple(locator) if locator else None + if timeout is None: + timeout = self.timeout + end_time = perf_counter() + timeout try: - return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', x=x + offset_x, - y=y + offset_y, includeUserAgentShadowDOM=True, + ele = ChromiumElement(owner=self.owner, + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y, + includeUserAgentShadowDOM=True, ignorePointerEventsNone=False)['backendNodeId']) except CDPError: - return NoneElement(page=self.owner, method='offset()', args={'offset_x': offset_x, 'offset_y': offset_y}) + ele = False + if ele and (loc_data is None or _check_ele(ele, loc_data)): + return ele + + while perf_counter() < end_time: + try: + ele = ChromiumElement(owner=self.owner, + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', x=x, y=y, + includeUserAgentShadowDOM=True, + ignorePointerEventsNone=False)['backendNodeId']) + except CDPError: + ele = False + + if ele and (loc_data is None or _check_ele(ele, loc_data)): + return ele + sleep(.01) + + return NoneElement(page=self.owner, method='offset()', + args={'locator': locator, 'offset_x': x, 'offset_y': y, 'timeout': timeout}) def east(self, loc_or_pixel=None, index=1): - """获取元素右边某个指定元素 - :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 - :param index: 第几个,从1开始 - :return: 获取到的元素对象 - """ return self._get_relative_eles(mode='east', locator=loc_or_pixel, index=index) def south(self, loc_or_pixel=None, index=1): - """获取元素下方某个指定元素 - :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 - :param index: 第几个,从1开始 - :return: 获取到的元素对象 - """ return self._get_relative_eles(mode='south', locator=loc_or_pixel, index=index) def west(self, loc_or_pixel=None, index=1): - """获取元素左边某个指定元素 - :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 - :param index: 第几个,从1开始 - :return: 获取到的元素对象 - """ return self._get_relative_eles(mode='west', locator=loc_or_pixel, index=index) def north(self, loc_or_pixel=None, index=1): - """获取元素上方某个指定元素 - :param loc_or_pixel: 定位符,只支持str或int,且不支持xpath和css方式,传入int按像素距离获取 - :param index: 第几个,从1开始 - :return: 获取到的元素对象 - """ return self._get_relative_eles(mode='north', locator=loc_or_pixel, index=index) def _get_relative_eles(self, mode='north', locator=None, index=1): - """获取元素下方某个指定元素 - :param locator: 定位符,只支持str或int,且不支持xpath和css方式 - :param index: 第几个,从1开始 - :return: 获取到的元素对象 - """ if locator and not (isinstance(locator, str) and not locator.startswith( ('x:', 'xpath:', 'x=', 'xpath=', 'c:', 'css:', 'c=', 'css=')) or isinstance(locator, int)): raise ValueError('locator参数只能是str格式且不支持xpath和css形式。') @@ -439,8 +341,8 @@ class ChromiumElement(DrissionElement): cdp_data[variable] += locator try: return ChromiumElement(owner=self.owner, - backend_id=self.owner.run_cdp('DOM.getNodeForLocation', - **cdp_data)['backendNodeId']) + backend_id=self.owner._run_cdp('DOM.getNodeForLocation', + **cdp_data)['backendNodeId']) except CDPError: return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator}) @@ -453,7 +355,7 @@ class ChromiumElement(DrissionElement): while 0 < cdp_data[variable] < max_len: cdp_data[variable] += value try: - bid = self.owner.run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId'] + bid = self.owner._run_cdp('DOM.getNodeForLocation', **cdp_data)['backendNodeId'] if bid == curr_ele: continue else: @@ -470,10 +372,6 @@ class ChromiumElement(DrissionElement): return NoneElement(page=self.owner, method=f'{mode}()', args={'locator': locator}) def attr(self, attr): - """返回一个attribute属性值 - :param attr: 属性名 - :return: 属性值文本,没有该属性返回None - """ attrs = self.attrs if attr == 'href': # 获取href属性时返回绝对url link = attrs.get('href') @@ -501,111 +399,60 @@ class ChromiumElement(DrissionElement): return attrs.get(attr, None) def remove_attr(self, name): - """删除元素一个attribute属性 - :param name: 属性名 - :return: None - """ - self.run_js(f'this.removeAttribute("{name}");') + self._run_js(f'this.removeAttribute("{name}");') + return self def property(self, name): - """获取一个property属性值 - :param name: 属性名 - :return: 属性值文本 - """ try: - value = self.run_js(f'return this.{name};') + value = self._run_js(f'return this.{name};') return format_html(value) if isinstance(value, str) else value except: return None def run_js(self, script, *args, as_expr=False, timeout=None): - """对本元素执行javascript代码 - :param script: js文本,文本中用this表示本元素 - :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... - :param as_expr: 是否作为表达式运行,为True时args无效 - :param timeout: js超时时间(秒),为None则使用页面timeouts.script设置 - :return: 运行的结果 - """ + return self._run_js(script, *args, as_expr=as_expr, timeout=timeout) + + def _run_js(self, script, *args, as_expr=False, timeout=None): return run_js(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args) def run_async_js(self, script, *args, as_expr=False): - """以异步方式对本元素执行javascript代码 - :param script: js文本,文本中用this表示本元素 - :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... - :param as_expr: 是否作为表达式运行,为True时args无效 - :return: None - """ run_js(self, script, as_expr, 0, args) def ele(self, locator, index=1, timeout=None): - """返回当前元素下级符合条件的一个元素、属性或节点文本 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param index: 获取第几个元素,从1开始,可传入负数获取倒数第几个 - :param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致 - :return: ChromiumElement对象或属性、文本 - """ return self._ele(locator, timeout, index=index, method='ele()') def eles(self, locator, timeout=None): - """返回当前元素下级所有符合条件的子元素、属性或节点文本 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致 - :return: ChromiumElement对象或属性、文本组成的列表 - """ return self._ele(locator, timeout=timeout, index=None) - def s_ele(self, locator=None, index=1): - """查找一个符合条件的元素,以SessionElement形式返回 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 - :return: SessionElement对象或属性、文本 - """ - return make_session_ele(self, locator, index=index, method='s_ele()') + def s_ele(self, locator=None, index=1, timeout=None): + return (make_session_ele(self, locator, index=index, method='s_ele()') + if locator is None or self.ele(locator, index=index, timeout=timeout) + else NoneElement(self.owner, method='s_ele()', args={'locator': locator, 'index': index})) - def s_eles(self, locator=None): - """查找所有符合条件的元素,以SessionElement列表形式返回 - :param locator: 定位符 - :return: SessionElement或属性、文本组成的列表 - """ - return make_session_ele(self, locator, index=None) + def s_eles(self, locator=None, timeout=None): + return (make_session_ele(self, locator, index=None) + if self.ele(locator, timeout=timeout) else SessionElementsList()) - def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): - """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 查找元素超时时间(秒) - :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 - :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 - :return: ChromiumElement对象或文本、属性或其组成的列表 - """ + def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None): return find_in_chromium_ele(self, locator, index, timeout, relative=relative) def style(self, style, pseudo_ele=''): - """返回元素样式属性值,可获取伪元素属性值 - :param style: 样式属性名称 - :param pseudo_ele: 伪元素名称(如有) - :return: 样式属性的值 - """ if pseudo_ele: - pseudo_ele = f', "{pseudo_ele}"' if pseudo_ele.startswith(':') else f', "::{pseudo_ele}"' - return self.run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");') + pseudo_ele = f', "{pseudo_ele}"' + return self._run_js(f'return window.getComputedStyle(this{pseudo_ele}).getPropertyValue("{style}");') def src(self, timeout=None, base64_to_bytes=True): - """返回元素src资源,base64的可转为bytes返回,其它返回str - :param timeout: 等待资源加载的超时时间(秒) - :param base64_to_bytes: 为True时,如果是base64数据,转换为bytes格式 - :return: 资源内容 - """ - timeout = self.owner.timeout if timeout is None else timeout + if timeout is None: + timeout = self.timeout if self.tag == 'img': # 等待图片加载完成 js = ('return this.complete && typeof this.naturalWidth != "undefined" ' '&& this.naturalWidth > 0 && typeof this.naturalHeight != "undefined" ' '&& this.naturalHeight > 0') end_time = perf_counter() + timeout - while not self.run_js(js) and perf_counter() < end_time: - sleep(.1) + while not self._run_js(js) and perf_counter() < end_time: + sleep(.05) - src = self.attr('src') + src = self.attr('href') if self.tag == 'link' else self.attr('src') if not src: raise RuntimeError('元素没有src值或该值为空。') if src.lower().startswith('data:image'): @@ -627,19 +474,20 @@ class ChromiumElement(DrissionElement): else: while perf_counter() < end_time: - src = self.property('currentSrc') + src = self.attr('href') if self.tag == 'link' else self.property('currentSrc') or self.property('src') if not src: + sleep(.01) continue - node = self.owner.run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] + node = self.owner._run_cdp('DOM.describeNode', backendNodeId=self._backend_id)['node'] frame = node.get('frameId', None) or self.owner._frame_id try: - result = self.owner.run_cdp('Page.getResourceContent', frameId=frame, url=src) + result = self.owner._run_cdp('Page.getResourceContent', frameId=frame, url=src) break except CDPError: pass - sleep(.1) + sleep(.05) if not result: return None @@ -654,13 +502,6 @@ class ChromiumElement(DrissionElement): return result['content'] def save(self, path=None, name=None, timeout=None, rename=True): - """保存图片或其它有src属性的元素的资源 - :param path: 文件保存路径,为None时保存到当前文件夹 - :param name: 文件名称,为None时从资源url获取 - :param timeout: 等待资源加载的超时时间(秒) - :param rename: 遇到重名文件时是否自动重命名 - :return: 返回保存路径 - """ data = self.src(timeout=timeout) if not data: raise NoResourceError @@ -686,20 +527,12 @@ class ChromiumElement(DrissionElement): return str(path) def get_screenshot(self, path=None, name=None, as_bytes=None, as_base64=None, scroll_to_center=True): - """对当前元素截图,可保存到文件,或以字节方式返回 - :param path: 文件保存路径 - :param name: 完整文件名,后缀可选 'jpg','jpeg','png','webp' - :param as_bytes: 是否以字节形式返回图片,可选 'jpg','jpeg','png','webp',生效时path参数和as_base64参数无效 - :param as_base64: 是否以base64字符串形式返回图片,可选 'jpg','jpeg','png','webp',生效时path参数无效 - :param scroll_to_center: 截图前是否滚动到视口中央 - :return: 图片完整路径或字节文本 - """ if self.tag == 'img': # 等待图片加载完成 js = ('return this.complete && typeof this.naturalWidth != "undefined" && this.naturalWidth > 0 ' '&& typeof this.naturalHeight != "undefined" && this.naturalHeight > 0') - end_time = perf_counter() + self.owner.timeout - while not self.run_js(js) and perf_counter() < end_time: - sleep(.1) + end_time = perf_counter() + self.timeout + while not self._run_js(js) and perf_counter() < end_time: + sleep(.05) if scroll_to_center: self.scroll.to_see(center=True) @@ -714,12 +547,6 @@ class ChromiumElement(DrissionElement): left_top=left_top, right_bottom=right_bottom, ele=self) def input(self, vals, clear=False, by_js=False): - """输入文本或组合键,也可用于输入文件路径到input元素(路径间用\n间隔) - :param vals: 文本值或按键组合 - :param clear: 输入前是否清空文本框 - :param by_js: 是否用js方式输入,不能输入组合键 - :return: None - """ if self.tag == 'input' and self.attr('type') == 'file': return self._set_file_input(vals) @@ -729,8 +556,8 @@ class ChromiumElement(DrissionElement): if isinstance(vals, (list, tuple)): 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 + self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') + return self self.wait.clickable(wait_moved=False, timeout=.5) if clear and vals not in ('\n', '\ue007'): @@ -738,126 +565,97 @@ class ChromiumElement(DrissionElement): else: self._input_focus() - input_text_or_keys(self.owner, vals) + if isinstance(vals, str) and vals not in ('\ue003', '\ue017', '\ue010', '\ue011', + '\ue012', '\ue013', '\ue014', '\ue015',): + input_text_or_keys(self.owner, vals) + else: + self.owner.actions.type(vals) + + return self def clear(self, by_js=False): - """清空元素文本 - :param by_js: 是否用js方式清空,为False则用全选+del模拟输入删除 - :return: None - """ if by_js: - self.run_js("this.value='';") - self.run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') - return + self._run_js("this.value='';") + self._run_js('this.dispatchEvent(new Event("change", {bubbles: true}));') + return self self._input_focus() self.input(('\ue009', 'a', '\ue017'), clear=False) + return self def _input_focus(self): - """输入前使元素获取焦点""" try: - self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) + self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id) except Exception: self.click(by_js=None) def focus(self): - """使元素获取焦点""" try: - self.owner.run_cdp('DOM.focus', backendNodeId=self._backend_id) + self.owner._run_cdp('DOM.focus', backendNodeId=self._backend_id) except Exception: - self.run_js('this.focus();') + self._run_js('this.focus();') + return self def hover(self, offset_x=None, offset_y=None): - """鼠标悬停,可接受偏移量,偏移量相对于元素左上角坐标。不传入x或y值时悬停在元素中点 - :param offset_x: 相对元素左上角坐标的x轴偏移量 - :param offset_y: 相对元素左上角坐标的y轴偏移量 - :return: None - """ - self.owner.scroll.to_see(self) - x, y = offset_scroll(self, offset_x, offset_y) - self.owner.run_cdp('Input.dispatchMouseEvent', type='mouseMoved', x=x, y=y, _ignore=AlertExistsError) + self.owner.actions.move_to(self, offset_x=offset_x, offset_y=offset_y, duration=.1) + return self def drag(self, offset_x=0, offset_y=0, duration=.5): - """拖拽当前元素到相对位置 - :param offset_x: x变化值 - :param offset_y: y变化值 - :param duration: 拖动用时,传入0即瞬间到达 - :return: None - """ curr_x, curr_y = self.rect.midpoint offset_x += curr_x offset_y += curr_y self.drag_to((offset_x, offset_y), duration) + return self def drag_to(self, ele_or_loc, duration=.5): - """拖拽当前元素,目标为另一个元素或坐标元组(x, y) - :param ele_or_loc: 另一个元素或坐标元组,坐标为元素中点的坐标 - :param duration: 拖动用时,传入0即瞬间到达 - :return: None - """ if isinstance(ele_or_loc, ChromiumElement): ele_or_loc = ele_or_loc.rect.midpoint elif not isinstance(ele_or_loc, (list, tuple)): raise TypeError('需要ChromiumElement对象或坐标。') self.owner.actions.hold(self).move_to(ele_or_loc, duration=duration).release() + return self def _get_obj_id(self, node_id=None, backend_id=None): - """根据传入node id或backend id获取js中的object id - :param node_id: cdp中的node id - :param backend_id: backend id - :return: js中的object id - """ if node_id: - return self.owner.run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId'] + return self.owner._run_cdp('DOM.resolveNode', nodeId=node_id)['object']['objectId'] else: - return self.owner.run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId'] + return self.owner._run_cdp('DOM.resolveNode', backendNodeId=backend_id)['object']['objectId'] def _get_node_id(self, obj_id=None, backend_id=None): - """根据传入object id或backend id获取cdp中的node id - :param obj_id: js中的object id - :param backend_id: backend id - :return: cdp中的node id - """ if obj_id: - return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] + return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] else: - n = self.owner.run_cdp('DOM.describeNode', backendNodeId=backend_id)['node'] + n = self.owner._run_cdp('DOM.describeNode', backendNodeId=backend_id)['node'] self._tag = n['localName'] return n['nodeId'] def _get_backend_id(self, node_id): - """根据传入node id获取backend id - :param node_id: - :return: backend id - """ - n = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node'] + n = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node'] self._tag = n['localName'] return n['backendNodeId'] def _refresh_id(self): - """根据backend id刷新其它id""" self._obj_id = self._get_obj_id(backend_id=self._backend_id) self._node_id = self._get_node_id(obj_id=self._obj_id) - def _get_ele_path(self, mode): - """返获取绝对的css路径或xpath路径""" - if mode == 'xpath': + def _get_ele_path(self, xpath=True): + if xpath: txt1 = 'let tag = el.nodeName.toLowerCase();' - txt3 = ''' && sib.nodeName.toLowerCase()==tag''' - txt4 = ''' - if(nth>1){path = '/' + tag + '[' + nth + ']' + path;} - else{path = '/' + tag + path;}''' + txt3 = ''' && sib.nodeName.toLowerCase()===tag''' + txt4 = '''path = '/' + tag + '[' + nth + ']' + path;''' txt5 = '''return path;''' - elif mode == 'css': - txt1 = '' + else: + txt1 = ''' + let i = el.getAttribute("id"); + if (i){path = '>' + el.tagName.toLowerCase() + "#" + i + path; + el = el.parentNode; + continue;} + ''' txt3 = '' txt4 = '''path = '>' + el.tagName.toLowerCase() + ":nth-child(" + nth + ")" + path;''' txt5 = '''return path.substr(1);''' - else: - raise ValueError(f"mode参数只能是'xpath'或'css',现在是:'{mode}'。") - js = '''function(){ function e(el) { if (!(el instanceof Element)) return; @@ -876,31 +674,21 @@ class ChromiumElement(DrissionElement): } return e(this);} ''' - t = self.run_js(js) - return f'{t}' if mode == 'css' else t + return self._run_js(js) def _set_file_input(self, files): - """对上传控件写入路径 - :param files: 文件路径列表或字符串,字符串时多个文件用回车分隔 - :return: None - """ if isinstance(files, str): files = files.split('\n') files = [str(Path(i).absolute()) for i in files] - self.owner.run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id) + self.owner._run_cdp('DOM.setFileInputFiles', files=files, backendNodeId=self._backend_id) + return self class ShadowRoot(BaseElement): - """ShadowRoot是用于处理ShadowRoot的类,使用方法和ChromiumElement基本一致""" def __init__(self, parent_ele, obj_id=None, backend_id=None): - """ - :param parent_ele: shadow root 所在父元素 - :param obj_id: js中的object id - :param backend_id: cdp中的backend id - """ super().__init__(parent_ele.owner) - self.tab = self.owner.tab + self.tab = self.owner._tab self.parent_ele = parent_ele if backend_id: self._backend_id = backend_id @@ -913,72 +701,45 @@ class ShadowRoot(BaseElement): self._states = None self._type = 'ShadowRoot' + def __call__(self, locator, index=1, timeout=None): + return self.ele(locator, index=index, timeout=timeout) + def __repr__(self): return f'' - def __call__(self, locator, index=1, timeout=None): - """在内部查找元素 - 例:ele2 = ele1('@id=ele_id') - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 - :param timeout: 超时时间(秒) - :return: 元素对象或属性、文本 - """ - return self.ele(locator, index=index, timeout=timeout) - def __eq__(self, other): return self._backend_id == getattr(other, '_backend_id', None) @property def tag(self): - """返回元素标签名""" return 'shadow-root' @property def html(self): - """返回outerHTML文本""" return f'{self.inner_html}' @property def inner_html(self): - """返回内部的html文本""" - return self.run_js('return this.innerHTML;') + return self._run_js('return this.innerHTML;') @property def states(self): - """返回用于获取元素状态的对象""" if self._states is None: self._states = ShadowRootStates(self) return self._states def run_js(self, script, *args, as_expr=False, timeout=None): - """运行javascript代码 - :param script: js文本 - :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... - :param as_expr: 是否作为表达式运行,为True时args无效 - :param timeout: js超时时间(秒),为None则使用页面timeouts.script设置 - :return: 运行的结果 - """ + return self._run_js(script, *args, as_expr=as_expr, timeout=timeout) + + def _run_js(self, script, *args, as_expr=False, timeout=None): return run_js(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args) def run_async_js(self, script, *args, as_expr=False, timeout=None): - """以异步方式执行js代码 - :param script: js文本 - :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... - :param as_expr: 是否作为表达式运行,为True时args无效 - :param timeout: js超时时间(秒),为None则使用页面timeouts.script设置 - :return: None - """ from threading import Thread Thread(target=run_js, args=(self, script, as_expr, self.owner.timeouts.script if timeout is None else timeout, args)).start() - def parent(self, level_or_loc=1, index=1): - """返回上面某一级父元素,可指定层数或用查询语法定位 - :param level_or_loc: 第几级父元素,或定位符 - :param index: 当level_or_loc传入定位符,使用此参数选择第几个结果 - :return: ChromiumElement对象 - """ + def parent(self, level_or_loc=1, index=1, timeout=0): if isinstance(level_or_loc, int): loc = f'xpath:./ancestor-or-self::*[{level_or_loc}]' @@ -993,14 +754,9 @@ class ShadowRoot(BaseElement): else: raise TypeError('level_or_loc参数只能是tuple、int或str。') - return self.parent_ele._ele(loc, timeout=0, relative=True, raise_err=False, method='parent()') + return self.parent_ele._ele(loc, timeout=timeout, relative=True, raise_err=False, method='parent()') - def child(self, locator='', index=1): - """返回直接子元素元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :return: 直接子元素或节点文本组成的列表 - """ + def child(self, locator='', index=1, timeout=None): if not locator: loc = '*' else: @@ -1010,58 +766,41 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') loc = f'xpath:./{loc}' - ele = self._ele(loc, index=index, relative=True) + ele = self._ele(loc, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'child()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'child()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def next(self, locator='', index=1): - """返回当前元素后面一个符合条件的同级元素,可用查询语法筛选,可指定返回筛选结果的第几个 - :param locator: 用于筛选的查询语法 - :param index: 第几个查询结果,1开始 - :return: ChromiumElement对象 - """ + def next(self, locator='', index=1, timeout=None): loc = get_loc(locator, True) if loc[0] == 'css selector': raise ValueError('此css selector语法不受支持,请换成xpath。') loc = loc[1].lstrip('./') xpath = f'xpath:./{loc}' - ele = self.parent_ele._ele(xpath, index=index, relative=True) + ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'next()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'next()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def before(self, locator='', index=1): - """返回文档中当前元素前面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param index: 前面第几个查询结果,1开始 - :return: 本元素前面的某个元素或节点 - """ + def before(self, locator='', index=1, timeout=None): loc = get_loc(locator, True) if loc[0] == 'css selector': raise ValueError('此css selector语法不受支持,请换成xpath。') loc = loc[1].lstrip('./') xpath = f'xpath:./preceding::{loc}' - ele = self.parent_ele._ele(xpath, index=index, relative=True) + ele = self.parent_ele._ele(xpath, index=index, relative=True, timeout=timeout) - return ele if ele else NoneElement(self.owner, 'before()', {'locator': locator, 'index': index}) + return ele if ele else NoneElement(self.owner, 'before()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def after(self, locator='', index=1): - """返回文档中此当前元素后面符合条件的一个元素,可用查询语法筛选,可指定返回筛选结果的第几个 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :param index: 后面第几个查询结果,1开始 - :return: 本元素后面的某个元素或节点 - """ - nodes = self.afters(locator=locator) - return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', {'locator': locator, 'index': index}) + def after(self, locator='', index=1, timeout=None): + nodes = self.afters(locator=locator, timeout=timeout) + return nodes[index - 1] if nodes else NoneElement(self.owner, 'after()', + {'locator': locator, 'index': index, 'timeout': timeout}) - def children(self, locator=''): - """返回当前元素符合条件的直接子元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :return: 直接子元素或节点文本组成的列表 - """ + def children(self, locator='', timeout=None): if not locator: loc = '*' else: @@ -1071,91 +810,48 @@ class ShadowRoot(BaseElement): loc = loc[1].lstrip('./') loc = f'xpath:./{loc}' - return self._ele(loc, index=None, relative=True) + return self._ele(loc, index=None, relative=True, timeout=timeout) - def nexts(self, locator=''): - """返回当前元素后面符合条件的同级元素或节点组成的列表,可用查询语法筛选 - :param locator: 用于筛选的查询语法 - :return: ChromiumElement对象组成的列表 - """ + def nexts(self, locator='', timeout=None): loc = get_loc(locator, True) if loc[0] == 'css selector': raise ValueError('此css selector语法不受支持,请换成xpath。') loc = loc[1].lstrip('./') xpath = f'xpath:./{loc}' - return self.parent_ele._ele(xpath, index=None, relative=True) + return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) - def befores(self, locator=''): - """返回文档中当前元素前面符合条件的元素或节点组成的列表,可用查询语法筛选 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :return: 本元素前面的元素或节点组成的列表 - """ + def befores(self, locator='', timeout=None): loc = get_loc(locator, True) if loc[0] == 'css selector': raise ValueError('此css selector语法不受支持,请换成xpath。') loc = loc[1].lstrip('./') xpath = f'xpath:./preceding::{loc}' - return self.parent_ele._ele(xpath, index=None, relative=True) + return self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) - def afters(self, locator=''): - """返回文档中当前元素后面符合条件的元素或节点组成的列表,可用查询语法筛选 - 查找范围不限同级元素,而是整个DOM文档 - :param locator: 用于筛选的查询语法 - :return: 本元素后面的元素或节点组成的列表 - """ + def afters(self, locator='', timeout=None): eles1 = self.nexts(locator) loc = get_loc(locator, True)[1].lstrip('./') xpath = f'xpath:./following::{loc}' - return eles1 + self.parent_ele._ele(xpath, index=None, relative=True) + return eles1 + self.parent_ele._ele(xpath, index=None, relative=True, timeout=timeout) def ele(self, locator, index=1, timeout=None): - """返回当前元素下级符合条件的一个元素 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param index: 获取第几个元素,从1开始,可传入负数获取倒数第几个 - :param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致 - :return: ChromiumElement对象 - """ return self._ele(locator, timeout, index=index, method='ele()') def eles(self, locator, timeout=None): - """返回当前元素下级所有符合条件的子元素 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 查找元素超时时间(秒),默认与元素所在页面等待时间一致 - :return: ChromiumElement对象组成的列表 - """ return self._ele(locator, timeout=timeout, index=None) - def s_ele(self, locator=None, index=1): - """查找一个符合条件的元素以SessionElement形式返回,处理复杂页面时效率很高 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param index: 获取第几个,从1开始,可传入负数获取倒数第几个 - :return: SessionElement对象或属性、文本 - """ - r = make_session_ele(self, locator, index=index) - if isinstance(r, NoneElement): - r.method = 's_ele()' - r.args = {'locator': locator} - return r + def s_ele(self, locator=None, index=1, timeout=None): + return (make_session_ele(self, locator, index=index, method='s_ele()') + if locator is None or self.ele(locator, index=index, timeout=timeout) + else NoneElement(self.owner, method='s_ele()', args={'locator': locator, 'index': index})) - def s_eles(self, locator): - """查找所有符合条件的元素以SessionElement列表形式返回,处理复杂页面时效率很高 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :return: SessionElement对象 - """ - return make_session_ele(self, locator, index=None) + def s_eles(self, locator, timeout=None): + return (make_session_ele(self, locator, index=None) + if self.ele(locator, timeout=timeout) else SessionElementsList()) - def _find_elements(self, locator, timeout=None, index=1, relative=False, raise_err=None): - """返回当前元素下级符合条件的子元素、属性或节点文本,默认返回第一个 - :param locator: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 查找元素超时时间(秒) - :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param relative: WebPage用的表示是否相对定位的参数 - :param raise_err: 找不到元素是是否抛出异常,为None时根据全局设置 - :return: ChromiumElement对象或其组成的列表 - """ + def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None): loc = get_loc(locator, css_mode=False) if loc[0] == 'css selector' and str(loc[1]).startswith(':root'): loc = loc[0], loc[1][5:] @@ -1163,14 +859,14 @@ class ShadowRoot(BaseElement): def do_find(): if loc[0] == 'css selector': if index == 1: - nod_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId'] + nod_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=loc[1])['nodeId'] if nod_id: r = make_chromium_eles(self.owner, _ids=nod_id, is_obj_id=False) return None if r is False else r else: - nod_ids = self.owner.run_cdp('DOM.querySelectorAll', - nodeId=self._node_id, selector=loc[1])['nodeId'] + nod_ids = self.owner._run_cdp('DOM.querySelectorAll', + nodeId=self._node_id, selector=loc[1])['nodeIds'] r = make_chromium_eles(self.owner, _ids=nod_ids, index=index, is_obj_id=False) return None if r is False else r @@ -1179,58 +875,55 @@ class ShadowRoot(BaseElement): if not eles: return None - css = [i.css_path[61:] for i in eles] + css = [] + for i in eles: + c = i.css_path + if c in ('html:nth-child(1)', 'html:nth-child(1)>body:nth-child(1)', + 'html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'): + continue + elif c.startswith('html:nth-child(1)>body:nth-child(1)>shadow_root:nth-child(1)'): + c = c[61:] + css.append(c) if index is not None: try: - node_id = self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, - selector=css[index - 1])['nodeId'] + node_id = self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, + selector=css[index - 1])['nodeId'] except IndexError: return None r = make_chromium_eles(self.owner, _ids=node_id, is_obj_id=False) return None if r is False else r else: - node_ids = [self.owner.run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] + node_ids = [self.owner._run_cdp('DOM.querySelector', nodeId=self._node_id, selector=i)['nodeId'] for i in css] - if 0 in node_ids: + node_ids = [i for i in node_ids if i] + if not node_ids: return None r = make_chromium_eles(self.owner, _ids=node_ids, index=index, is_obj_id=False) return None if r is False else r - timeout = timeout if timeout is not None else self.owner.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: return result - return NoneElement(self.owner) if index is not None else [] + return NoneElement(self.owner) if index is not None else ChromiumElementsList(self.owner) def _get_node_id(self, obj_id): - """返回元素node id""" - return self.owner.run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] + return self.owner._run_cdp('DOM.requestNode', objectId=obj_id)['nodeId'] def _get_obj_id(self, back_id): - """返回元素object id""" - return self.owner.run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId'] + return self.owner._run_cdp('DOM.resolveNode', backendNodeId=back_id)['object']['objectId'] def _get_backend_id(self, node_id): - """返回元素object id""" - r = self.owner.run_cdp('DOM.describeNode', nodeId=node_id)['node'] + r = self.owner._run_cdp('DOM.describeNode', nodeId=node_id)['node'] self._tag = r['localName'].lower() return r['backendNodeId'] def find_in_chromium_ele(ele, locator, index=1, timeout=None, relative=True): - """在chromium元素中查找 - :param ele: ChromiumElement对象 - :param locator: 元素定位元组 - :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param timeout: 查找元素超时时间(秒) - :param relative: WebPage用于标记是否相对定位使用 - :return: 返回ChromiumElement元素或它们组成的列表 - """ # ---------------处理定位符--------------- if isinstance(locator, (str, tuple)): loc = get_loc(locator) @@ -1244,7 +937,8 @@ def find_in_chromium_ele(ele, locator, index=1, timeout=None, relative=True): loc_str = f'{ele.css_path}{loc[1]}' loc = loc[0], loc_str - timeout = timeout if timeout is not None else ele.owner.timeout + if timeout is None: + timeout = ele.timeout # ---------------执行查找----------------- if loc[0] == 'xpath': @@ -1255,29 +949,21 @@ def find_in_chromium_ele(ele, locator, index=1, timeout=None, relative=True): def find_by_xpath(ele, xpath, index, timeout, relative=True): - """执行用xpath在元素中查找元素 - :param ele: 在此元素中查找 - :param xpath: 查找语句 - :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param timeout: 超时时间(秒) - :param relative: 是否相对定位 - :return: ChromiumElement或其组成的列表 - """ type_txt = '9' if index == 1 else '7' 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) ele.owner.wait.doc_loaded() def do_find(): - res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) if res['result']['type'] == 'string': return res['result']['value'] if 'exceptionDetails' in res: if 'The result is not a node set' in res['result']['description']: js1 = make_js_for_find_ele_by_xpath(xpath, '1', node_txt) - res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js1, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) return res['result']['value'] else: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1290,10 +976,10 @@ def find_by_xpath(ele, xpath, index, timeout, relative=True): return None if r is False else r else: - res = ele.owner.run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], - ownProperties=True)['result'][:-1] + res = ele.owner._run_cdp('Runtime.getProperties', objectId=res['result']['objectId'], + ownProperties=True)['result'][:-1] if index is None: - r = ChromiumElementsList(page=ele.owner) + r = ChromiumElementsList(owner=ele.owner) for i in res: if i['value']['type'] == 'object': r.append(make_chromium_eles(ele.owner, _ids=i['value']['objectId'], is_obj_id=True)) @@ -1317,22 +1003,15 @@ 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: return result - return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner) + return NoneElement(ele.owner) if index is not None else ChromiumElementsList(owner=ele.owner) def find_by_css(ele, selector, index, timeout): - """执行用css selector在元素中查找元素 - :param ele: 在此元素中查找 - :param selector: 查找语句 - :param index: 第几个结果,从1开始,可传入负数获取倒数第几个,为None返回所有 - :param timeout: 超时时间(秒) - :return: ChromiumElement或其组成的列表 - """ selector = selector.replace('"', r'\"') find_all = '' if index == 1 else 'All' node_txt = 'this.contentDocument' if ele.tag in ('iframe', 'frame', 'shadow-root') else 'this' @@ -1341,8 +1020,8 @@ def find_by_css(ele, selector, index, timeout): ele.owner.wait.doc_loaded() def do_find(): - res = ele.owner.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, - returnByValue=False, awaitPromise=True, userGesture=True) + res = ele.owner._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=ele._obj_id, + returnByValue=False, awaitPromise=True, userGesture=True) if 'exceptionDetails' in res: raise SyntaxError(f'查询语句错误:\n{res}') @@ -1354,32 +1033,24 @@ def find_by_css(ele, selector, index, timeout): return None if r is False else r else: - obj_ids = [i['value']['objectId'] for i in ele.owner.run_cdp('Runtime.getProperties', - objectId=res['result']['objectId'], - ownProperties=True)['result']] + obj_ids = [i['value']['objectId'] for i in ele.owner._run_cdp('Runtime.getProperties', + objectId=res['result']['objectId'], + ownProperties=True)['result']] r = make_chromium_eles(ele.owner, _ids=obj_ids, index=index, is_obj_id=True) return None if r is False else r 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: return result - return NoneElement(ele.owner) if index is not None else ChromiumElementsList(page=ele.owner) + return NoneElement(ele.owner) if index is not None else ChromiumElementsList(owner=ele.owner) def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False): - """根据node id或object id生成相应元素对象 - :param page: ChromiumPage对象 - :param _ids: 元素的id列表 - :param index: 获取第几个,为None返回全部 - :param is_obj_id: 传入的id是obj id还是node id - :param ele_only: 是否只返回ele,在页面查找元素时生效 - :return: 浏览器元素对象或它们组成的列表,生成失败返回False - """ if is_obj_id: get_node_func = _get_node_by_obj_id else: @@ -1400,8 +1071,10 @@ def make_chromium_eles(page, _ids, index=1, is_obj_id=True, ele_only=False): return get_node_func(page, obj_id, ele_only) else: # 获取全部 - nodes = ChromiumElementsList(page=page) + nodes = ChromiumElementsList(owner=page) for obj_id in _ids: + # if obj_id == 0: + # continue tmp = get_node_func(page, obj_id, ele_only) if tmp is False: return False @@ -1456,12 +1129,6 @@ def _make_ele(page, obj_id, node): def make_js_for_find_ele_by_xpath(xpath, type_txt, node_txt): - """生成用xpath在元素中查找元素的js文本 - :param xpath: xpath文本 - :param type_txt: 查找类型 - :param node_txt: 节点类型 - :return: js文本 - """ for_txt = '' # 获取第一个元素、节点或属性 @@ -1498,14 +1165,6 @@ else{a.push(e.snapshotItem(i));}}""" def run_js(page_or_ele, script, as_expr, timeout, args=None): - """运行javascript代码 - :param page_or_ele: 页面对象或元素对象 - :param script: js文本 - :param as_expr: 是否作为表达式运行,为True时args无效 - :param timeout: 超时时间(秒) - :param args: 参数,按顺序在js文本中对应arguments[0]、arguments[1]... - :return: js执行结果 - """ if isinstance(page_or_ele, (ChromiumElement, ShadowRoot)): is_page = False page = page_or_ele.owner @@ -1535,16 +1194,16 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None): end_time = perf_counter() + timeout try: if as_expr: - res = page.run_cdp('Runtime.evaluate', expression=script, returnByValue=False, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + res = page._run_cdp('Runtime.evaluate', expression=script, returnByValue=False, + awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) else: args = args or () if not is_js_func(script): script = f'function(){{{script}}}' - res = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id, - arguments=[convert_argument(arg) for arg in args], returnByValue=False, - awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) + res = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=script, objectId=obj_id, + arguments=[convert_argument(arg) for arg in args], returnByValue=False, + awaitPromise=True, userGesture=True, _timeout=timeout, _ignore=AlertExistsError) except TimeoutError: raise TimeoutError(f'执行js超时(等待{timeout}秒)。') except ContextLostError: @@ -1568,7 +1227,6 @@ def run_js(page_or_ele, script, as_expr, timeout, args=None): def parse_js_result(page, ele, result, end_time): - """解析js返回的结果""" if 'unserializableValue' in result: return result['unserializableValue'] @@ -1591,17 +1249,23 @@ def parse_js_result(page, ele, result, end_time): return r elif sub_type == 'array': - r = page.run_cdp('Runtime.getProperties', objectId=result['objectId'], ownProperties=True)['result'] + 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 js = 'function(){return JSON.stringify(this);}' - r = page.run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'], - returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, - _timeout=timeout) + r = page._run_cdp('Runtime.callFunctionOn', functionDeclaration=js, objectId=result['objectId'], + returnByValue=False, awaitPromise=True, userGesture=True, _ignore=AlertExistsError, + _timeout=timeout) return loads(parse_js_result(page, ele, r['result'], end_time)) else: @@ -1610,12 +1274,14 @@ def parse_js_result(page, ele, result, end_time): elif the_type == 'undefined': return None + elif the_type == 'function': + return result['description'] + else: return result['value'] def convert_argument(arg): - """把参数转换成js能够接收的形式""" if isinstance(arg, ChromiumElement): return {'objectId': arg._obj_id} @@ -1633,20 +1299,23 @@ def convert_argument(arg): class Pseudo(object): def __init__(self, ele): - """ - :param ele: ChromiumElement - """ self._ele = ele @property def before(self): - """返回当前元素的::before伪元素内容""" - return self._ele.style('content', 'before') + for i in ('::before', ':before', 'before'): + r = self._ele.style('content', i) + if r != 'content': + return r + return r @property def after(self): - """返回当前元素的::after伪元素内容""" - return self._ele.style('content', 'after') + for i in ('::after', ':after', 'after'): + r = self._ele.style('content', i) + if r != 'content': + return r + return r def _check_ele(ele, loc_data): diff --git a/DrissionPage/_elements/chromium_element.pyi b/DrissionPage/_elements/chromium_element.pyi index 1be5553..3cb4054 100644 --- a/DrissionPage/_elements/chromium_element.pyi +++ b/DrissionPage/_elements/chromium_element.pyi @@ -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 @@ -28,229 +27,554 @@ PIC_TYPE = Literal['jpg', 'jpeg', 'png', 'webp', True] class ChromiumElement(DrissionElement): + _tag: Optional[str] = ... + owner: ChromiumBase = ... + page: Union[ChromiumPage, WebPage] = ... + tab: Union[ChromiumPage, ChromiumTab] = ... + _node_id: int = ... + _obj_id: str = ... + _backend_id: int = ... + _doc_id: Optional[str] = ... + _scroll: Optional[ElementScroller] = ... + _clicker: Optional[Clicker] = ... + _select: Union[SelectElement, None, False] = ... + _wait: Optional[ElementWaiter] = ... + _rect: Optional[ElementRect] = ... + _set: Optional[ChromiumElementSetter] = ... + _states: Optional[ElementStates] = ... + _pseudo: Optional[Pseudo] = ... - def __init__(self, owner: ChromiumBase, node_id: int = None, obj_id: str = None, backend_id: int = None): - self._tag: str = ... - # self.page: Union[ChromiumPage, WebPage] = ... - self.owner: ChromiumBase = ... - self.page: Union[ChromiumPage, WebPage] = ... - self.tab: Union[ChromiumPage, ChromiumTab] = ... - self._node_id: int = ... - self._obj_id: str = ... - self._backend_id: int = ... - self._doc_id: str = ... - self._scroll: ElementScroller = ... - self._clicker: Clicker = ... - self._select: SelectElement = ... - self._wait: ElementWaiter = ... - self._rect: ElementRect = ... - self._set: ChromiumElementSetter = ... - self._states: ElementStates = ... - self._pseudo: Pseudo = ... - - def __repr__(self) -> str: ... + def __init__(self, + owner: ChromiumBase, + node_id: int = None, + obj_id: str = None, + backend_id: int = None): + """node_id、obj_id和backend_id必须至少传入一个 + :param owner: 元素所在页面对象 + :param node_id: cdp中的node id + :param obj_id: js中的object id + :param backend_id: backend id + """ + ... def __call__(self, locator: Union[Tuple[str, str], str], index: int = 1, - timeout: float = None) -> ChromiumElement: ... + timeout: float = None) -> ChromiumElement: + """在内部查找元素 + :param locator: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 超时时间(秒) + :return: ChromiumElement对象或属性、文本 + """ + ... + + def __repr__(self) -> str: ... def __eq__(self, other: ChromiumElement) -> bool: ... @property - def tag(self) -> str: ... + def tag(self) -> str: + """返回元素tag""" + ... @property - def html(self) -> str: ... + def html(self) -> str: + """返回元素outerHTML文本""" + ... @property - def inner_html(self) -> str: ... + def inner_html(self) -> str: + """返回元素innerHTML文本""" + ... @property - def attrs(self) -> dict: ... + def attrs(self) -> dict: + """返回元素所有attribute属性""" + ... @property - def text(self) -> str: ... + def text(self) -> str: + """返回元素内所有文本,文本已格式化""" + ... @property - def raw_text(self) -> str: ... - - # -----------------d模式独有属性------------------- - @property - def set(self) -> ChromiumElementSetter: ... + def raw_text(self) -> str: + """返回未格式化处理的元素内文本""" + ... @property - def states(self) -> ElementStates: ... + def set(self) -> ChromiumElementSetter: + """返回用于设置元素属性的对象""" + ... @property - def rect(self) -> ElementRect: ... + def states(self) -> ElementStates: + """返回用于获取元素状态的对象""" + ... @property - def pseudo(self) -> Pseudo: ... + def pseudo(self) -> Pseudo: + """返回用于获取伪元素内容的对象""" + ... @property - def shadow_root(self) -> Union[None, ShadowRoot]: ... + def rect(self) -> ElementRect: + """返回用于获取元素位置的对象""" + ... @property - def sr(self) -> Union[None, ShadowRoot]: ... + def shadow_root(self) -> Union[None, ShadowRoot]: + """返回当前元素的shadow_root元素对象""" + ... @property - def scroll(self) -> ElementScroller: ... + def sr(self) -> Union[None, ShadowRoot]: + """返回当前元素的shadow_root元素对象""" + ... @property - def click(self) -> Clicker: ... + def scroll(self) -> ElementScroller: + """用于滚动滚动条的对象""" + ... + + @property + def click(self) -> Clicker: + """返回用于点击的对象""" + ... + + @property + def wait(self) -> ElementWaiter: + """返回用于等待的对象""" + ... + + @property + def select(self) -> Union[SelectElement, False]: + """返回专门处理下拉列表的Select类,非元素项目 - :param equal: 是否匹配被选择的元素,False匹配不被选择的 - :return: 筛选结果 - """ return self._any_state('is_selected', equal=equal) def enabled(self, equal=True): - """以是否可用为条件筛选元素 - :param equal: 是否匹配可用的元素,False表示匹配disabled状态的 - :return: 筛选结果 - """ return self._any_state('is_enabled', equal=equal) def clickable(self, equal=True): - """以是否可点击为条件筛选元素 - :param equal: 是否匹配可点击的元素,False表示匹配不是可点击的 - :return: 筛选结果 - """ return self._any_state('is_clickable', equal=equal) def have_rect(self, equal=True): - """以是否有大小为条件筛选元素 - :param equal: 是否匹配有大小的元素,False表示匹配没有大小的 - :return: 筛选结果 - """ return self._any_state('has_rect', equal=equal) def style(self, name, value, equal=True): - """以是否拥有某个style值为条件筛选元素 - :param name: 属性名称 - :param value: 属性值 - :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 - :return: 筛选结果 - """ return self._get_attr(name, value, 'style', equal=equal) def property(self, name, value, equal=True): - """以是否拥有某个property值为条件筛选元素 - :param name: 属性名称 - :param value: 属性值 - :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 - :return: 筛选结果 - """ return self._get_attr(name, value, 'property', equal=equal) def _any_state(self, name, equal=True): - """ - :param name: 状态名称 - :param equal: 是否是指定状态,False表示否定状态 - :return: 选中的元素 - """ num = 0 if equal: for i in self._list: @@ -263,7 +197,7 @@ class ChromiumFilterOne(SessionFilterOne): num += 1 if self._index == num: return i - return NoneElement(self._list._page, f'{name}()', args={'equal': equal, 'index': self._index}) + return NoneElement(self._list._owner, f'{name}()', args={'equal': equal, 'index': self._index}) class ChromiumFilter(ChromiumFilterOne): @@ -282,69 +216,34 @@ class ChromiumFilter(ChromiumFilterOne): @property def get(self): - """返回用于获取元素属性的对象""" return self._list.get def search_one(self, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None, - have_rect=None, have_text=None): - """或关系筛选元素,获取一个结果 - :param index: 元素序号,从1开始 - :param displayed: 是否显示,bool,None为忽略该项 - :param checked: 是否被选中,bool,None为忽略该项 - :param selected: 是否被选择,bool,None为忽略该项 - :param enabled: 是否可用,bool,None为忽略该项 - :param clickable: 是否可点击,bool,None为忽略该项 - :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 - :param have_text: 是否含有文本,bool,None为忽略该项 - :return: 筛选结果 - """ + have_rect=None, have_text=None, tag=None): return _search_one(self._list, index=index, displayed=displayed, checked=checked, selected=selected, - enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text) + enabled=enabled, clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag) def search(self, displayed=None, checked=None, selected=None, enabled=None, clickable=None, - have_rect=None, have_text=None): - """或关系筛选元素 - :param displayed: 是否显示,bool,None为忽略该项 - :param checked: 是否被选中,bool,None为忽略该项 - :param selected: 是否被选择,bool,None为忽略该项 - :param enabled: 是否可用,bool,None为忽略该项 - :param clickable: 是否可点击,bool,None为忽略该项 - :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 - :param have_text: 是否含有文本,bool,None为忽略该项 - :return: 筛选结果 - """ + have_rect=None, have_text=None, tag=None): return _search(self._list, displayed=displayed, checked=checked, selected=selected, enabled=enabled, - clickable=clickable, have_rect=have_rect, have_text=have_text) + clickable=clickable, have_rect=have_rect, have_text=have_text, tag=tag) + + def tag(self, name, equal=True): + self._list = _tag_all(self._list, ChromiumElementsList(owner=self._list._owner), name=name, equal=equal) + return self def text(self, text, fuzzy=True, contain=True): - """以是否含有指定文本为条件筛选元素 - :param text: 用于匹配的文本 - :param fuzzy: 是否模糊匹配 - :param contain: 是否包含该字符串,False表示不包含 - :return: 筛选结果 - """ - self._list = _text_all(self._list, ChromiumElementsList(page=self._list._page), + self._list = _text_all(self._list, ChromiumElementsList(owner=self._list._owner), text=text, fuzzy=fuzzy, contain=contain) return self def _get_attr(self, name, value, method, equal=True): - """返回通过某个方法可获得某个值的元素 - :param name: 属性名称 - :param value: 属性值 - :param method: 方法名称 - :return: 筛选结果 - """ - self._list = _get_attr_all(self._list, ChromiumElementsList(page=self._list._page), - name=name, value=value, method=method, equal=equal) + self._list = _attr_all(self._list, ChromiumElementsList(owner=self._list._owner), + name=name, value=value, method=method, equal=equal) return self def _any_state(self, name, equal=True): - """ - :param name: 状态名称 - :param equal: 是否是指定状态,False表示否定状态 - :return: 选中的列表 - """ - r = ChromiumElementsList(page=self._list._page) + r = ChromiumElementsList(owner=self._list._owner) if equal: for i in self._list: if not isinstance(i, str) and getattr(i.states, name): @@ -362,47 +261,81 @@ class Getter(object): self._list = _list def links(self): - """返回所有元素的link属性组成的列表""" return [e.link for e in self._list if not isinstance(e, str)] def texts(self): - """返回所有元素的text属性组成的列表""" return [e if isinstance(e, str) else e.text for e in self._list] def attrs(self, name): - """返回所有元素指定的attr属性组成的列表 - :param name: 属性名称 - :return: 属性文本组成的列表 - """ return [e.attr(name) for e in self._list if not isinstance(e, str)] def get_eles(locators, owner, any_one=False, first_ele=True, timeout=10): - """传入多个定位符,获取多个ele - :param locators: 定位符组成的列表 - :param owner: 页面或元素对象 - :param any_one: 是否找到任何一个即返回 - :param first_ele: 每个定位符是否只获取第一个元素 - :param timeout: 超时时间(秒) - :return: 多个定位符组成的dict - """ - res = {loc: False for loc in locators} + if isinstance(locators, (tuple, str)): + locators = (locators,) + res = {loc: None for loc in locators} + + if timeout == 0: + for loc in locators: + ele = owner._ele(loc, timeout=0, raise_err=False, index=1 if first_ele else None, method='find()') + res[loc] = ele + if ele and any_one: + return res + return res + end_time = perf_counter() + timeout while perf_counter() <= end_time: for loc in locators: - if res[loc] is not False: + if res[loc]: continue - ele = owner.ele(loc, timeout=0) if first_ele else owner.eles(loc, timeout=0) - if ele: - res[loc] = ele - if any_one: - return res - if False not in res.values(): - break + ele = owner._ele(loc, timeout=0, raise_err=False, index=1 if first_ele else None, method='find()') + res[loc] = ele + if ele and any_one: + return res + if all(res.values()): + return res + sleep(.05) + return res -def _get_attr_all(src_list, aim_list, name, value, method, equal=True): +def get_frame(owner, loc_ind_ele, timeout=None): + if isinstance(loc_ind_ele, str): + if not is_str_loc(loc_ind_ele): + xpath = f'xpath://*[(name()="iframe" or name()="frame") and ' \ + f'(@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]' + else: + xpath = loc_ind_ele + ele = owner._ele(xpath, timeout=timeout) + if ele and ele._type != 'ChromiumFrame': + raise TypeError('该定位符不是指向frame元素。') + r = ele + + elif isinstance(loc_ind_ele, tuple): + ele = owner._ele(loc_ind_ele, timeout=timeout) + if ele and ele._type != 'ChromiumFrame': + raise TypeError('该定位符不是指向frame元素。') + r = ele + + elif isinstance(loc_ind_ele, int): + ele = owner._ele('@|tag():iframe@|tag():frame', timeout=timeout, index=loc_ind_ele) + if ele and ele._type != 'ChromiumFrame': + raise TypeError('该定位符不是指向frame元素。') + r = ele + + elif getattr(loc_ind_ele, '_type', None) == 'ChromiumFrame': + r = loc_ind_ele + + else: + raise TypeError('必须传入定位符、iframe序号、id、name、ChromiumFrame对象其中之一。') + + if isinstance(r, NoneElement): + r.method = 'get_frame()' + r.args = {'loc_ind_ele': loc_ind_ele} + return r + + +def _attr_all(src_list, aim_list, name, value, method, equal=True): if equal: for i in src_list: if not isinstance(i, str) and getattr(i, method)(name) == value: @@ -414,6 +347,19 @@ def _get_attr_all(src_list, aim_list, name, value, method, equal=True): return aim_list +def _tag_all(src_list, aim_list, name, equal=True): + name = name.lower() + if equal: + for i in src_list: + if not isinstance(i, str) and i.tag == name: + aim_list.append(i) + else: + for i in src_list: + if not isinstance(i, str) and i.tag != name: + aim_list.append(i) + return aim_list + + def _text_all(src_list, aim_list, text, fuzzy=True, contain=True): """以是否含有指定文本为条件筛选元素 :param text: 用于匹配的文本 @@ -435,7 +381,7 @@ def _text_all(src_list, aim_list, text, fuzzy=True, contain=True): def _search(_list, displayed=None, checked=None, selected=None, enabled=None, clickable=None, - have_rect=None, have_text=None): + have_rect=None, have_text=None, tag=None): """或关系筛选元素 :param displayed: 是否显示,bool,None为忽略该项 :param checked: 是否被选中,bool,None为忽略该项 @@ -444,9 +390,10 @@ def _search(_list, displayed=None, checked=None, selected=None, enabled=None, cl :param clickable: 是否可点击,bool,None为忽略该项 :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 元素类型 :return: 筛选结果 """ - r = ChromiumElementsList(page=_list._page) + r = ChromiumElementsList(owner=_list._owner) for i in _list: if not isinstance(i, str) and ( (displayed is not None and (displayed is True and i.states.is_displayed) @@ -462,13 +409,14 @@ def _search(_list, displayed=None, checked=None, selected=None, enabled=None, cl or (have_rect is not None and (have_rect is True and i.states.has_rect) or (have_rect is False and not i.states.has_rect)) or (have_text is not None and (have_text is True and i.raw_text) - or (have_text is False and not i.raw_text))): + or (have_text is False and not i.raw_text)) + or (tag is not None and i.tag == tag.lower())): r.append(i) return ChromiumFilter(r) def _search_one(_list, index=1, displayed=None, checked=None, selected=None, enabled=None, clickable=None, - have_rect=None, have_text=None): + have_rect=None, have_text=None, tag=None): """或关系筛选元素,获取一个结果 :param index: 元素序号,从1开始 :param displayed: 是否显示,bool,None为忽略该项 @@ -478,6 +426,7 @@ def _search_one(_list, index=1, displayed=None, checked=None, selected=None, ena :param clickable: 是否可点击,bool,None为忽略该项 :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 元素类型 :return: 筛选结果 """ num = 0 @@ -496,12 +445,13 @@ def _search_one(_list, index=1, displayed=None, checked=None, selected=None, ena or (have_rect is not None and (have_rect is True and i.states.has_rect) or (have_rect is False and not i.states.has_rect)) or (have_text is not None and (have_text is True and i.raw_text) - or (have_text is False and not i.raw_text))): + or (have_text is False and not i.raw_text)) + or (tag is not None and i.tag == tag.lower())): num += 1 if num == index: return i - return NoneElement(_list._page, method='filter()', args={'displayed': displayed, - 'checked': checked, 'selected': selected, - 'enabled': enabled, 'clickable': clickable, - 'have_rect': have_rect, 'have_text': have_text}) + return NoneElement(_list._owner, method='filter()', args={'displayed': displayed, 'checked': checked, + 'selected': selected, 'enabled': enabled, + 'clickable': clickable, 'have_rect': have_rect, + 'have_text': have_text, 'tag': tag}) diff --git a/DrissionPage/_functions/elements.pyi b/DrissionPage/_functions/elements.pyi index 41562e5..6b2b422 100644 --- a/DrissionPage/_functions/elements.pyi +++ b/DrissionPage/_functions/elements.pyi @@ -2,47 +2,79 @@ """ @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 +from typing import Union, List, Optional, Iterable, Dict from .._base.base import BaseParser from .._elements.chromium_element import ChromiumElement from .._elements.session_element import SessionElement - - -def get_eles(locators: Union[List[str], tuple], - owner: BaseParser, - any_one: bool = False, - first_ele: bool = True, - timeout: float = 10) -> dict: ... +from .._pages.chromium_base import ChromiumBase +from .._pages.chromium_frame import ChromiumFrame +from .._pages.session_page import SessionPage class SessionElementsList(list): - _page = ... + _owner: SessionPage = ... - def __init__(self, page=None, *args): ... - - @property - def get(self) -> Getter: ... - - @property - def filter(self) -> SessionFilter: ... - - @property - def filter_one(self) -> SessionFilterOne: ... + def __init__(self, + owner: SessionPage = None, + *args): + """ + :param owner: 产生元素列表的页面 + :param args: + """ + ... def __next__(self) -> SessionElement: ... + def __getitem__(self, _i) -> Union[SessionElement, SessionElementsList]: ... + + def __iter__(self) -> List[SessionElement]: ... + + @property + def get(self) -> Getter: + """返回用于属性的对象""" + ... + + @property + def filter(self) -> SessionFilter: + """返回用于筛选多个元素的对象""" + ... + + @property + def filter_one(self) -> SessionFilterOne: + """用于筛选单个元素的对象""" + ... + class ChromiumElementsList(SessionElementsList): + _owner: ChromiumBase = ... + + def __init__(self, + owner: ChromiumBase = None, + *args): + """ + :param owner: 产生元素列表的页面 + :param args: + """ + ... + + def __next__(self) -> ChromiumElement: ... + + def __getitem__(self, _i) -> Union[ChromiumElement, ChromiumElementsList]: ... + + def __iter__(self) -> List[ChromiumElement]: ... @property - def filter(self) -> ChromiumFilter: ... + def filter(self) -> ChromiumFilter: + """返回用于筛选多个元素的对象""" + ... @property - def filter_one(self) -> ChromiumFilterOne: ... + def filter_one(self) -> ChromiumFilterOne: + """用于筛选单个元素的对象""" + ... def search(self, displayed: Optional[bool] = None, @@ -51,7 +83,20 @@ class ChromiumElementsList(SessionElementsList): enabled: Optional[bool] = None, clickable: Optional[bool] = None, have_rect: Optional[bool] = None, - have_text: Optional[bool] = None) -> ChromiumFilter: ... + have_text: Optional[bool] = None, + tag: str = None) -> ChromiumFilter: + """或关系筛选元素 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 指定的元素类型 + :return: 筛选结果 + """ + ... def search_one(self, index: int = 1, @@ -61,28 +106,78 @@ class ChromiumElementsList(SessionElementsList): enabled: Optional[bool] = None, clickable: Optional[bool] = None, have_rect: Optional[bool] = None, - have_text: Optional[bool] = None) -> ChromiumElement: ... - - def __next__(self) -> ChromiumElement: ... + have_text: Optional[bool] = None, + tag: str = None) -> ChromiumElement: + """或关系筛选元素,获取一个结果 + :param index: 元素序号,从1开始 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 指定的元素类型 + :return: 筛选结果 + """ + ... class SessionFilterOne(object): _list: SessionElementsList = ... _index: int = ... - def __init__(self, _list: SessionElementsList, index: int = 1): ... + def __init__(self, _list: SessionElementsList): + """ + :param _list: 元素列表对象 + """ + ... - def __call__(self, index: int = 1) -> SessionFilterOne: ... + def __call__(self, index: int = 1) -> SessionFilterOne: + """返回结果中第几个元素 + :param index: 元素序号,从1开始 + :return: 对象自身 + """ + ... - def attr(self, name: str, value: str, equal: bool = True) -> SessionElement: ... + def tag(self, name: str, equal: bool = True) -> SessionElement: + """筛选某种元素 + :param name: 标签页名称 + :param equal: True表示匹配这种元素,False表示匹配非这种元素 + :return: 筛选结果 + """ + ... - def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionElement: ... + def attr(self, name: str, value: str, equal: bool = True) -> SessionElement: + """以是否拥有某个attribute值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + ... + + def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionElement: + """以是否含有指定文本为条件筛选元素 + :param text: 用于匹配的文本 + :param fuzzy: 是否模糊匹配 + :param contain: 是否包含该字符串,False表示不包含 + :return: 筛选结果 + """ + ... def _get_attr(self, name: str, value: str, method: str, - equal: bool = True) -> SessionElement: ... + equal: bool = True) -> SessionElement: + """返回通过某个方法可获得某个值的元素 + :param name: 属性名称 + :param value: 属性值 + :param method: 方法名称 + :return: 筛选结果 + """ + ... class SessionFilter(SessionFilterOne): @@ -96,54 +191,176 @@ class SessionFilter(SessionFilterOne): def __getitem__(self, item: int) -> SessionElement: ... @property - def get(self) -> Getter: ... + def get(self) -> Getter: + """返回用于获取元素属性的对象""" + ... - def attr(self, name: str, value: str, equal: bool = True) -> SessionFilter: ... + def tag(self, name: str, equal: bool = True) -> SessionFilter: + """筛选某种元素 + :param name: 标签页名称 + :param equal: True表示匹配这种元素,False表示匹配非这种元素 + :return: 筛选结果 + """ + ... - def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionFilter: ... + def attr(self, name: str, value: str, equal: bool = True) -> SessionFilter: + """以是否拥有某个attribute值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + ... + + def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> SessionFilter: + """以是否含有指定文本为条件筛选元素 + :param text: 用于匹配的文本 + :param fuzzy: 是否模糊匹配 + :param contain: 是否包含该字符串,False表示不包含 + :return: 筛选结果 + """ + ... def _get_attr(self, name: str, value: str, method: str, - equal: bool = True) -> SessionFilter: ... + equal: bool = True) -> SessionFilter: + """返回通过某个方法可获得某个值的元素 + :param name: 属性名称 + :param value: 属性值 + :param method: 方法名称 + :return: 筛选结果 + """ + ... class ChromiumFilterOne(SessionFilterOne): _list: ChromiumElementsList = ... - def __init__(self, _list: ChromiumElementsList): ... + def __init__(self, _list: ChromiumElementsList): + """ + :param _list: 元素列表对象 + """ + ... - def __call__(self, index: int = 1) -> ChromiumFilterOne: ... + def __call__(self, index: int = 1) -> ChromiumFilterOne: + """返回结果中第几个元素 + :param index: 元素序号,从1开始 + :return: 对象自身 + """ + ... - def displayed(self, equal: bool = True) -> ChromiumElement: ... + def tag(self, name: str, equal: bool = True) -> SessionElement: + """筛选某种元素 + :param name: 标签页名称 + :param equal: True表示匹配这种元素,False表示匹配非这种元素 + :return: 筛选结果 + """ + ... - def checked(self, equal: bool = True) -> ChromiumElement: ... + def attr(self, name: str, value: str, equal: bool = True) -> ChromiumElement: + """以是否拥有某个attribute值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + ... - def selected(self, equal: bool = True) -> ChromiumElement: ... + def text(self, + text: str, + fuzzy: bool = True, + contain: bool = True) -> ChromiumElement: + """以是否含有指定文本为条件筛选元素 + :param text: 用于匹配的文本 + :param fuzzy: 是否模糊匹配 + :param contain: 是否包含该字符串,False表示不包含 + :return: 筛选结果 + """ + ... - def enabled(self, equal: bool = True) -> ChromiumElement: ... + def displayed(self, equal: bool = True) -> ChromiumElement: + """以是否显示为条件筛选元素 + :param equal: 是否匹配显示的元素,False匹配不显示的 + :return: 筛选结果 + """ + ... - def clickable(self, equal: bool = True) -> ChromiumElement: ... + def checked(self, equal: bool = True) -> ChromiumElement: + """以是否被选中为条件筛选元素 + :param equal: 是否匹配被选中的元素,False匹配不被选中的 + :return: 筛选结果 + """ + ... - def have_rect(self, equal: bool = True) -> ChromiumElement: ... + def selected(self, equal: bool = True) -> ChromiumElement: + """以是否被选择为条件筛选元素,用于元素项目 + :param equal: 是否匹配被选择的元素,False匹配不被选择的 + :return: 筛选结果 + """ + ... - def style(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ... + def enabled(self, equal: bool = True) -> ChromiumFilter: + """以是否可用为条件筛选元素 + :param equal: 是否匹配可用的元素,False表示匹配disabled状态的 + :return: 筛选结果 + """ + ... + + def clickable(self, equal: bool = True) -> ChromiumFilter: + """以是否可点击为条件筛选元素 + :param equal: 是否匹配可点击的元素,False表示匹配不是可点击的 + :return: 筛选结果 + """ + ... + + def have_rect(self, equal: bool = True) -> ChromiumFilter: + """以是否有大小为条件筛选元素 + :param equal: 是否匹配有大小的元素,False表示匹配没有大小的 + :return: 筛选结果 + """ + ... + + def style(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: + """以是否拥有某个style值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + ... def property(self, name: str, - value: str, equal: bool = True) -> ChromiumFilter: ... - - def attr(self, name: str, value: str, equal: bool = True) -> ChromiumFilter: ... - - def text(self, text: str, fuzzy: bool = True, contain: bool = True) -> ChromiumFilter: ... - - def search(self, - displayed: Optional[bool] = None, - checked: Optional[bool] = None, - selected: Optional[bool] = None, - enabled: Optional[bool] = None, - clickable: Optional[bool] = None, - have_rect: Optional[bool] = None, - have_text: Optional[bool] = None) -> ChromiumFilter: ... + value: str, equal: bool = True) -> ChromiumFilter: + """以是否拥有某个property值为条件筛选元素 + :param name: 属性名称 + :param value: 属性值 + :param equal: True表示匹配name值为value值的元素,False表示匹配name值不为value值的 + :return: 筛选结果 + """ + ... def search_one(self, index: int = 1, @@ -198,23 +474,114 @@ class ChromiumFilter(ChromiumFilterOne): enabled: Optional[bool] = None, clickable: Optional[bool] = None, have_rect: Optional[bool] = None, - have_text: Optional[bool] = None) -> ChromiumElement: ... + have_text: Optional[bool] = None, + tag: str = None) -> ChromiumElement: + """或关系筛选元素,获取一个结果 + :param index: 元素序号,从1开始 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 指定的元素类型 + :return: 筛选结果 + """ + ... + + def search(self, + displayed: Optional[bool] = None, + checked: Optional[bool] = None, + selected: Optional[bool] = None, + enabled: Optional[bool] = None, + clickable: Optional[bool] = None, + have_rect: Optional[bool] = None, + have_text: Optional[bool] = None, + tag: str = None) -> ChromiumFilter: + """或关系筛选元素 + :param displayed: 是否显示,bool,None为忽略该项 + :param checked: 是否被选中,bool,None为忽略该项 + :param selected: 是否被选择,bool,None为忽略该项 + :param enabled: 是否可用,bool,None为忽略该项 + :param clickable: 是否可点击,bool,None为忽略该项 + :param have_rect: 是否拥有大小和位置,bool,None为忽略该项 + :param have_text: 是否含有文本,bool,None为忽略该项 + :param tag: 指定的元素类型 + :return: 筛选结果 + """ + ... def _get_attr(self, name: str, value: str, - method: str, equal: bool = True) -> ChromiumFilter: ... + method: str, equal: bool = True) -> ChromiumFilter: + """返回通过某个方法可获得某个值的元素 + :param name: 属性名称 + :param value: 属性值 + :param method: 方法名称 + :return: 筛选结果 + """ + ... - def _any_state(self, name: str, equal: bool = True) -> ChromiumFilter: ... + def _any_state(self, name: str, equal: bool = True) -> ChromiumFilter: + """ + :param name: 状态名称 + :param equal: 是否是指定状态,False表示否定状态 + :return: 选中的列表 + """ + ... class Getter(object): _list: SessionElementsList = ... - def __init__(self, _list: SessionElementsList): ... + def __init__(self, _list: SessionElementsList): + """ + :param _list: 元素列表对象 + """ + ... - def links(self) -> List[str]: ... + def links(self) -> List[str]: + """返回所有元素的link属性组成的列表""" + ... - def texts(self) -> List[str]: ... + def texts(self) -> List[str]: + """返回所有元素的text属性组成的列表""" + ... - def attrs(self, name: str) -> List[str]: ... + def attrs(self, name: str) -> List[str]: + """返回所有元素指定的attr属性组成的列表 + :param name: 属性名称 + :return: 属性文本组成的列表 + """ + ... + + +def get_eles(locators: Union[str, tuple, List[Union[str, tuple]]], + owner: BaseParser, + any_one: bool = False, + first_ele: bool = True, + timeout: float = 10) -> Union[Dict[str, ChromiumElement], Dict[str, SessionElement], +Dict[str, List[ChromiumElement]], Dict[str, List[SessionElement]]]: + """传入多个定位符,获取多个ele + :param locators: 定位符或它们组成的列表 + :param owner: 页面或元素对象 + :param any_one: 是否找到任何一个即返回 + :param first_ele: 每个定位符是否只获取第一个元素 + :param timeout: 超时时间(秒) + :return: 多个定位符组成的dict,first_only为False返回列表,否则为元素,无结果的返回False + """ + ... + + +def get_frame(owner: BaseParser, + loc_ind_ele: Union[str, int, tuple, ChromiumFrame, ChromiumElement], + timeout: float = None) -> ChromiumFrame: + """获取页面中一个frame对象 + :param owner: 要在其中查找元素的对象 + :param loc_ind_ele: 定位符、iframe序号、ChromiumFrame对象,序号从1开始,可传入负数获取倒数第几个 + :param timeout: 查找元素超时时间(秒) + :return: ChromiumFrame对象 + """ + ... diff --git a/DrissionPage/_functions/keys.py b/DrissionPage/_functions/keys.py index d55267c..cbf2f30 100644 --- a/DrissionPage/_functions/keys.py +++ b/DrissionPage/_functions/keys.py @@ -2,9 +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 platform import system + from ..errors import AlertExistsError @@ -21,18 +22,14 @@ class Keys: CANCEL = '\ue001' # ^break HELP = '\ue002' BACKSPACE = '\ue003' - BACK_SPACE = BACKSPACE TAB = '\ue004' CLEAR = '\ue005' RETURN = '\ue006' ENTER = '\ue007' SHIFT = '\ue008' - LEFT_SHIFT = SHIFT CONTROL = '\ue009' CTRL = '\ue009' - LEFT_CONTROL = CONTROL ALT = '\ue00a' - LEFT_ALT = ALT PAUSE = '\ue00b' ESCAPE = '\ue00c' SPACE = '\ue00d' @@ -41,13 +38,9 @@ class Keys: END = '\ue010' HOME = '\ue011' LEFT = '\ue012' - ARROW_LEFT = LEFT UP = '\ue013' - ARROW_UP = UP RIGHT = '\ue014' - ARROW_RIGHT = RIGHT DOWN = '\ue015' - ARROW_DOWN = DOWN INSERT = '\ue016' DELETE = '\ue017' DEL = '\ue017' @@ -219,15 +212,15 @@ keyDefinitions = { '\ue005': {'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3}, '\ue006': {'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3}, '\ue00b': {'keyCode': 19, 'code': 'Pause', 'key': 'Pause'}, - 'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'}, + # 'CapsLock': {'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock'}, '\ue00c': {'keyCode': 27, 'code': 'Escape', 'key': 'Escape'}, - 'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'}, - 'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'}, + # 'Convert': {'keyCode': 28, 'code': 'Convert', 'key': 'Convert'}, + # 'NonConvert': {'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert'}, '\ue010': {'keyCode': 35, 'code': 'End', 'key': 'End'}, # 'Numpad1': {'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3}, - 'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'}, - 'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'}, - 'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'}, + # 'Select': {'keyCode': 41, 'code': 'Select', 'key': 'Select'}, + # 'Open': {'keyCode': 43, 'code': 'Open', 'key': 'Execute'}, + # 'PrintScreen': {'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen'}, '\ue016': {'keyCode': 45, 'code': 'Insert', 'key': 'Insert'}, # 'Numpad0': {'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3}, '\ue017': {'keyCode': 46, 'code': 'Delete', 'key': 'Delete'}, @@ -242,36 +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'}, - 'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'}, - 'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'}, - 'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'}, - 'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'}, - 'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'}, - 'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'}, - 'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'}, - 'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'}, - 'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'}, - 'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'}, - 'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'}, - 'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'}, - 'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'}, - 'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'}, - 'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'}, - 'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'}, - 'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'}, - 'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'}, - 'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'}, - 'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'}, - 'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'}, - 'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'}, - 'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'}, - 'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'}, - 'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'}, - 'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'}, - 'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'}, - 'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'}, - 'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'}, + '\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}, @@ -288,69 +252,94 @@ keyDefinitions = { '\ue03a': {'keyCode': 121, 'code': 'F10', 'key': 'F10'}, '\ue03b': {'keyCode': 122, 'code': 'F11', 'key': 'F11'}, '\ue03c': {'keyCode': 123, 'code': 'F12', 'key': 'F12'}, - 'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'}, - 'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'}, - 'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'}, - 'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'}, - 'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'}, - 'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'}, - 'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'}, - 'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'}, - 'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'}, - 'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'}, - 'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'}, - 'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'}, - 'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'}, - 'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'}, - 'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'}, - 'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'}, - 'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'}, - 'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'}, - 'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'}, - 'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'}, - 'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'}, '\ue018': {'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';'}, - 'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='}, '\ue019': {'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3}, - 'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','}, - 'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'}, - 'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'}, - 'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'}, - 'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'}, - 'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['}, - 'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'}, - 'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'}, - 'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''}, - 'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'}, - 'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'}, - 'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'}, - 'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3}, - 'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'}, - 'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'}, - 'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'}, - 'Accept': {'keyCode': 30, 'key': 'Accept'}, - 'ModeChange': {'keyCode': 31, 'key': 'ModeChange'}, - 'Print': {'keyCode': 42, 'key': 'Print'}, - 'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'}, '\u0000': {'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3}, - 'Attn': {'keyCode': 246, 'key': 'Attn'}, - 'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'}, - 'ExSel': {'keyCode': 248, 'key': 'ExSel'}, - 'EraseEof': {'keyCode': 249, 'key': 'EraseEof'}, - 'Play': {'keyCode': 250, 'key': 'Play'}, - 'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'}, - 'Power': {'key': 'Power', 'code': 'Power'}, - 'Eject': {'key': 'Eject', 'code': 'Eject'}, + # 'KeyA': {'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a'}, + # 'KeyB': {'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b'}, + # 'KeyC': {'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c'}, + # 'KeyD': {'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd'}, + # 'KeyE': {'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e'}, + # 'KeyF': {'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f'}, + # 'KeyG': {'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g'}, + # 'KeyH': {'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h'}, + # 'KeyI': {'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i'}, + # 'KeyJ': {'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j'}, + # 'KeyK': {'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k'}, + # 'KeyL': {'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l'}, + # 'KeyM': {'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm'}, + # 'KeyN': {'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n'}, + # 'KeyO': {'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o'}, + # 'KeyP': {'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p'}, + # 'KeyQ': {'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q'}, + # 'KeyR': {'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r'}, + # 'KeyS': {'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's'}, + # 'KeyT': {'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't'}, + # 'KeyU': {'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u'}, + # 'KeyV': {'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v'}, + # 'KeyW': {'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w'}, + # 'KeyX': {'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x'}, + # 'KeyY': {'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y'}, + # 'KeyZ': {'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z'}, + # 'MetaLeft': {'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta'}, + # 'MetaRight': {'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta'}, + # 'ContextMenu': {'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu'}, + # 'F13': {'keyCode': 124, 'code': 'F13', 'key': 'F13'}, + # 'F14': {'keyCode': 125, 'code': 'F14', 'key': 'F14'}, + # 'F15': {'keyCode': 126, 'code': 'F15', 'key': 'F15'}, + # 'F16': {'keyCode': 127, 'code': 'F16', 'key': 'F16'}, + # 'F17': {'keyCode': 128, 'code': 'F17', 'key': 'F17'}, + # 'F18': {'keyCode': 129, 'code': 'F18', 'key': 'F18'}, + # 'F19': {'keyCode': 130, 'code': 'F19', 'key': 'F19'}, + # 'F20': {'keyCode': 131, 'code': 'F20', 'key': 'F20'}, + # 'F21': {'keyCode': 132, 'code': 'F21', 'key': 'F21'}, + # 'F22': {'keyCode': 133, 'code': 'F22', 'key': 'F22'}, + # 'F23': {'keyCode': 134, 'code': 'F23', 'key': 'F23'}, + # 'F24': {'keyCode': 135, 'code': 'F24', 'key': 'F24'}, + # 'NumLock': {'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock'}, + # 'ScrollLock': {'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock'}, + # 'AudioVolumeMute': {'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute'}, + # 'AudioVolumeDown': {'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown'}, + # 'AudioVolumeUp': {'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp'}, + # 'MediaTrackNext': {'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext'}, + # 'MediaTrackPrevious': {'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious'}, + # 'MediaStop': {'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop'}, + # 'MediaPlayPause': {'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause'}, + # 'Equal': {'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '='}, + # 'Comma': {'keyCode': 188, 'code': 'Comma', 'shiftKey': '<', 'key': ','}, + # 'Minus': {'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-'}, + # 'Period': {'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.'}, + # 'Slash': {'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/'}, + # 'Backquote': {'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`'}, + # 'BracketLeft': {'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '['}, + # 'Backslash': {'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\'}, + # 'BracketRight': {'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']'}, + # 'Quote': {'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\''}, + # 'AltGraph': {'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph'}, + # 'Props': {'keyCode': 247, 'code': 'Props', 'key': 'CrSel'}, + # 'Cancel': {'keyCode': 3, 'key': 'Cancel', 'code': 'Abort'}, + # 'Clear': {'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3}, + # 'Shift': {'keyCode': 16, 'key': 'Shift', 'code': 'ShiftLeft'}, + # 'Control': {'keyCode': 17, 'key': 'Control', 'code': 'ControlLeft'}, + # 'Alt': {'keyCode': 18, 'key': 'Alt', 'code': 'AltLeft'}, + # 'Accept': {'keyCode': 30, 'key': 'Accept'}, + # 'ModeChange': {'keyCode': 31, 'key': 'ModeChange'}, + # 'Print': {'keyCode': 42, 'key': 'Print'}, + # 'Execute': {'keyCode': 43, 'key': 'Execute', 'code': 'Open'}, + # 'Attn': {'keyCode': 246, 'key': 'Attn'}, + # 'CrSel': {'keyCode': 247, 'key': 'CrSel', 'code': 'Props'}, + # 'ExSel': {'keyCode': 248, 'key': 'ExSel'}, + # 'EraseEof': {'keyCode': 249, 'key': 'EraseEof'}, + # 'Play': {'keyCode': 250, 'key': 'Play'}, + # 'ZoomOut': {'keyCode': 251, 'key': 'ZoomOut'}, + # 'Power': {'key': 'Power', 'code': 'Power'}, + # 'Eject': {'key': 'Eject', 'code': 'Eject'}, } -modifierBit = {'\ue00a': 1, - '\ue009': 2, - '\ue03d': 4, - '\ue008': 8} +modifierBit = {'\ue00a': 1, '\ue009': 2, '\ue03d': 4, '\ue008': 8} +sys = system().lower() def keys_to_typing(value): - """把要输入的内容连成字符串,去掉其中 ctrl 等键。 - 返回的modifier表示是否有按下组合键""" typing = [] modifier = 0 for val in value: @@ -368,79 +357,65 @@ def keys_to_typing(value): return modifier, ''.join(typing) -def keyDescriptionForString(_modifiers, keyString): # noqa: C901 - shift = _modifiers & 8 - description = {'key': '', - 'keyCode': 0, - 'code': '', - 'text': '', - 'location': 0} +def make_input_data(modifiers, key, key_up=False): + data = keyDefinitions.get(key) + if not data: + return None - definition = keyDefinitions.get(keyString) # type: ignore - if not definition: - raise ValueError(f'未知按键:{keyString}') + result = {'modifiers': modifiers, 'autoRepeat': False, '_ignore': AlertExistsError} + shift = modifiers & 8 - if 'key' in definition: - description['key'] = definition['key'] - if shift and definition.get('shiftKey'): - description['key'] = definition['shiftKey'] + if shift and data.get('shiftKey'): + result['key'] = data['shiftKey'] + result['text'] = data['shiftKey'] + elif 'key' in data: + result['key'] = data['key'] - if 'keyCode' in definition: - description['keyCode'] = definition['keyCode'] - if shift and definition.get('shiftKeyCode'): - description['keyCode'] = definition['shiftKeyCode'] + if len(result.get('key', '')) == 1: # type: ignore + result['text'] = data['key'] - if 'code' in definition: - description['code'] = definition['code'] + sys_text = 'windowsVirtualKeyCode' if sys == 'windows' else 'nativeVirtualKeyCode' + if shift and data.get('shiftKeyCode'): + result[sys_text] = data['shiftKeyCode'] + elif 'keyCode' in data: + result[sys_text] = data['keyCode'] - if 'location' in definition: - description['location'] = definition['location'] + if 'code' in data: + result['code'] = data['code'] - if len(description['key']) == 1: # type: ignore - description['text'] = description['key'] + if 'location' in data: + result['location'] = data['location'] + result['isKeypad'] = data['location'] == 3 + else: + result['location'] = 0 + result['isKeypad'] = False - if 'text' in definition: - description['text'] = definition['text'] - if shift and definition.get('shiftText'): - description['text'] = definition['shiftText'] + if shift and data.get('shiftText'): + result['text'] = data['shiftText'] + result['unmodifiedText'] = data['shiftText'] + elif 'text' in data: + result['text'] = data['text'] + result['unmodifiedText'] = data['text'] - if _modifiers & ~8: - description['text'] = '' + if modifiers & ~8: + result['text'] = '' - return description + result['type'] = 'keyUp' if key_up else ('keyDown' if result.get('text') else 'rawKeyDown') + return result def send_key(page, modifier, key): - """发送一个字,在键盘中的字符触发按键,其它直接发送文本""" - if key in keyDefinitions: - description = keyDescriptionForString(modifier, key) - text = description['text'] - data = {'type': 'keyDown' if text else 'rawKeyDown', - 'modifiers': modifier, - 'windowsVirtualKeyCode': description['keyCode'], - 'code': description['code'], - 'key': description['key'], - 'text': text, - 'autoRepeat': False, - 'unmodifiedText': text, - 'location': description['location'], - 'isKeypad': description['location'] == 3, - '_ignore': AlertExistsError} - - page.run_cdp('Input.dispatchKeyEvent', **data) + data = make_input_data(modifier, key) + if data: + page._run_cdp('Input.dispatchKeyEvent', **data) data['type'] = 'keyUp' - page.run_cdp('Input.dispatchKeyEvent', **data) + page._run_cdp('Input.dispatchKeyEvent', **data) else: - page.run_cdp('Input.insertText', text=key, _ignore=AlertExistsError) + page._run_cdp('Input.insertText', text=key, _ignore=AlertExistsError) def input_text_or_keys(page, text_or_keys): - """输入文本,也可输入组合键,组合键用tuple形式输入 - :param page: ChromiumBase对象 - :param text_or_keys: 文本值或按键组合 - :return: self - """ if not isinstance(text_or_keys, (tuple, list)): text_or_keys = (str(text_or_keys),) modifier, text_or_keys = keys_to_typing(text_or_keys) @@ -451,7 +426,7 @@ def input_text_or_keys(page, text_or_keys): return if text_or_keys.endswith(('\n', '\ue007')): - page.run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError) + page._run_cdp('Input.insertText', text=text_or_keys[:-1], _ignore=AlertExistsError) send_key(page, modifier, '\n') else: - page.run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError) + page._run_cdp('Input.insertText', text=text_or_keys, _ignore=AlertExistsError) diff --git a/DrissionPage/_functions/keys.pyi b/DrissionPage/_functions/keys.pyi index a06c8be..a2d42db 100644 --- a/DrissionPage/_functions/keys.pyi +++ b/DrissionPage/_functions/keys.pyi @@ -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 Tuple, Dict, Union, Any +from typing import Tuple, Union, Any from .._pages.chromium_base import ChromiumBase @@ -23,18 +22,14 @@ class Keys: CANCEL: str HELP: str BACKSPACE: str - BACK_SPACE: str TAB: str CLEAR: str RETURN: str ENTER: str SHIFT: str - LEFT_SHIFT: str CONTROL: str CTRL: str - LEFT_CONTROL: str ALT: str - LEFT_ALT: str PAUSE: str ESCAPE: str SPACE: str @@ -43,13 +38,9 @@ class Keys: END: str HOME: str LEFT: str - ARROW_LEFT: str UP: str - ARROW_UP: str RIGHT: str - ARROW_RIGHT: str DOWN: str - ARROW_DOWN: str INSERT: str DELETE: str DEL: str @@ -93,13 +84,38 @@ keyDefinitions: dict = ... modifierBit: dict = ... -def keys_to_typing(value: Union[str, int, list, tuple]) -> Tuple[int, str]: ... +def keys_to_typing(value: Union[str, int, list, tuple]) -> Tuple[int, str]: + """把要输入的内容连成字符串,去掉其中 ctrl 等键。 + 返回的modifier表示是否有按下组合键""" + ... -def keyDescriptionForString(_modifiers: int, keyString: str) -> Dict: ... +def make_input_data(modifiers: int, + key: str, + key_up: bool = False) -> dict: + """ + :param modifiers: 功能键设置 + :param key: 按键字符 + :param key_up: 是否提起 + :return: None + """ + ... -def send_key(page: ChromiumBase, modifier: int, key: str) -> None: ... +def send_key(page: ChromiumBase, modifier: int, key: str) -> None: + """发送一个字,在键盘中的字符触发按键,其它直接发送文本 + :param page: 动作所在页面 + :param modifier: 功能键信息 + :param key: 要是输入的按键 + :return: None + """ + ... -def input_text_or_keys(page: ChromiumBase, text_or_keys: Any) -> None: ... +def input_text_or_keys(page: ChromiumBase, text_or_keys: Any) -> None: + """输入文本,也可输入组合键,组合键用tuple形式输入 + :param page: ChromiumBase对象 + :param text_or_keys: 文本值或按键组合 + :return: self + """ + ... diff --git a/DrissionPage/_functions/locator.py b/DrissionPage/_functions/locator.py index e0aa381..be6a467 100644 --- a/DrissionPage/_functions/locator.py +++ b/DrissionPage/_functions/locator.py @@ -2,18 +2,13 @@ """ @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 def locator_to_tuple(loc): - """解析定位字符串生成dict格式数据 - :param loc: 待处理的字符串 - :return: 格式: {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]} - """ loc = _preprocess(loc) # 多属性查找 @@ -83,19 +78,18 @@ def _get_arg(text) -> list: return [name, None, None] if len(r) != 3 else [name, r[1], r[2]] -def is_loc(text): - """返回text是否定位符""" +def is_str_loc(text): return text.startswith(('.', '#', '@', 't:', 't=', 'tag:', 'tag=', 'tx:', 'tx=', 'tx^', 'tx$', 'text:', 'text=', 'text^', 'text$', 'xpath:', 'xpath=', 'x:', 'x=', 'css:', 'css=', 'c:', 'c=')) +def is_selenium_loc(loc): + return (isinstance(loc, tuple) and len(loc) == 2 and isinstance(loc[1], str) + and loc[0] in ('id', 'xpath', 'link text', 'partial link text', 'name', 'tag name', 'class name', + 'css selector')) + + def get_loc(loc, translate_css=False, css_mode=False): - """接收本库定位语法或selenium定位元组,转换为标准定位元组,可翻译css selector为xpath - :param loc: 本库定位语法或selenium定位元组 - :param translate_css: 是否翻译css selector为xpath,用于相对定位 - :param css_mode: 是否尽量用css selector方式 - :return: DrissionPage定位元组 - """ if isinstance(loc, tuple): loc = translate_css_loc(loc) if css_mode else translate_loc(loc) @@ -118,10 +112,6 @@ def get_loc(loc, translate_css=False, css_mode=False): def str_to_xpath_loc(loc): - """处理元素查找语句 - :param loc: 查找语法字符串 - :return: 匹配符元组 - """ loc_by = 'xpath' loc = _preprocess(loc) @@ -145,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='): @@ -165,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 = '//*' @@ -173,10 +163,6 @@ def str_to_xpath_loc(loc): def str_to_css_loc(loc): - """处理元素查找语句 - :param loc: 查找语法字符串 - :return: 匹配符元组 - """ loc_by = 'css selector' loc = _preprocess(loc) @@ -238,31 +224,31 @@ def _make_single_xpath_str(tag: str, text: str) -> tuple: else: symbol = r[1] if symbol == '=': # 精确查找 - arg = '.' if r[0] in ('@text()', '@tx()') else r[0] - arg_str = f'{arg}={_make_search_str(r[2])}' + arg = 'text()' if r[0] in ('@text()', '@tx()') else r[0] + 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}') @@ -326,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}') @@ -355,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(' @@ -444,10 +433,6 @@ def _make_single_css_str(tag: str, text: str) -> tuple: def translate_loc(loc): - """把By类型的loc元组转换为css selector或xpath类型的 - :param loc: By类型的loc元组 - :return: css selector或xpath类型的loc元组 - """ if len(loc) != 2: raise ValueError('定位符长度必须为2。') @@ -480,16 +465,12 @@ def translate_loc(loc): loc_str = f'//a[contains(text(),"{loc[1]}")]' else: - raise ValueError('无法识别的定位符。') + raise ValueError(f'无法识别的定位符:{loc}') return loc_by, loc_str def translate_css_loc(loc): - """把By类型的loc元组转换为css selector或xpath类型的 - :param loc: By类型的loc元组 - :return: css selector或xpath类型的loc元组 - """ if len(loc) != 2: raise ValueError('定位符长度必须为2。') diff --git a/DrissionPage/_functions/locator.pyi b/DrissionPage/_functions/locator.pyi index 991ff27..76837dd 100644 --- a/DrissionPage/_functions/locator.pyi +++ b/DrissionPage/_functions/locator.pyi @@ -2,31 +2,76 @@ """ @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 -def locator_to_tuple(loc: str) -> dict: ... +def locator_to_tuple(loc: str) -> dict: + """解析定位字符串生成dict格式数据 + :param loc: 待处理的字符串 + :return: 格式: {'and': bool, 'args': ['属性名称', '匹配方式', '属性值', 是否否定]} + """ + ... -def is_loc(text: str) -> bool: ... +def is_str_loc(text: str) -> bool: + """返回text是否定位符""" + ... -def get_loc(loc: Union[tuple, str], translate_css: bool = False, css_mode: bool = False) -> tuple: ... +def is_selenium_loc(loc: tuple) -> bool: + """返回tuple是否selenium的定位符""" + ... -def str_to_xpath_loc(loc: str) -> tuple: ... +def get_loc(loc: Union[tuple, str], + translate_css: bool = False, + css_mode: bool = False) -> tuple: + """接收本库定位语法或selenium定位元组,转换为标准定位元组,可翻译css selector为xpath + :param loc: 本库定位语法或selenium定位元组 + :param translate_css: 是否翻译css selector为xpath,用于相对定位 + :param css_mode: 是否尽量用css selector方式 + :return: DrissionPage定位元组 + """ + ... -def str_to_css_loc(loc: str) -> tuple: ... +def str_to_xpath_loc(loc: str) -> tuple: + """处理元素查找语句 + :param loc: 查找语法字符串 + :return: 匹配符元组 + """ + ... -def translate_loc(loc: tuple) -> tuple: ... +def str_to_css_loc(loc: str) -> tuple: + """处理元素查找语句 + :param loc: 查找语法字符串 + :return: 匹配符元组 + """ + ... -def translate_css_loc(loc: tuple) -> tuple: ... +def translate_loc(loc: tuple) -> tuple: + """把By类型的loc元组转换为css selector或xpath类型的 + :param loc: By类型的loc元组 + :return: css selector或xpath类型的loc元组 + """ + ... -def css_trans(txt: str) -> str: ... +def translate_css_loc(loc: tuple) -> tuple: + """把By类型的loc元组转换为css selector或xpath类型的 + :param loc: By类型的loc元组 + :return: css selector或xpath类型的loc元组 + """ + ... + + +def css_trans(txt: str) -> str: + """css字符串中特殊字符转义 + :param txt: 要处理的文本 + :return: 处理后的文本 + """ + ... diff --git a/DrissionPage/_functions/settings.py b/DrissionPage/_functions/settings.py index d454c80..1a7b8e8 100644 --- a/DrissionPage/_functions/settings.py +++ b/DrissionPage/_functions/settings.py @@ -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('\\', '/') diff --git a/DrissionPage/_functions/settings.pyi b/DrissionPage/_functions/settings.pyi new file mode 100644 index 0000000..4702194 --- /dev/null +++ b/DrissionPage/_functions/settings.pyi @@ -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: + """设置用于识别域名后缀的文件路径""" + ... diff --git a/DrissionPage/_functions/suffixes.dat b/DrissionPage/_functions/suffixes.dat new file mode 100644 index 0000000..1b3136b --- /dev/null +++ b/DrissionPage/_functions/suffixes.dat @@ -0,0 +1,11792 @@ +ac +com.ac +edu.ac +gov.ac +net.ac +mil.ac +org.ac + +ad +nom.ad + +ae +co.ae +net.ae +org.ae +sch.ae +ac.ae +gov.ae +mil.ae + +aero +airline.aero +airport.aero +accident-investigation.aero +accident-prevention.aero +aerobatic.aero +aeroclub.aero +aerodrome.aero +agents.aero +air-surveillance.aero +air-traffic-control.aero +aircraft.aero +airtraffic.aero +ambulance.aero +association.aero +author.aero +ballooning.aero +broker.aero +caa.aero +cargo.aero +catering.aero +certification.aero +championship.aero +charter.aero +civilaviation.aero +club.aero +conference.aero +consultant.aero +consulting.aero +control.aero +council.aero +crew.aero +design.aero +dgca.aero +educator.aero +emergency.aero +engine.aero +engineer.aero +entertainment.aero +equipment.aero +exchange.aero +express.aero +federation.aero +flight.aero +freight.aero +fuel.aero +gliding.aero +government.aero +groundhandling.aero +group.aero +hanggliding.aero +homebuilt.aero +insurance.aero +journal.aero +journalist.aero +leasing.aero +logistics.aero +magazine.aero +maintenance.aero +marketplace.aero +media.aero +microlight.aero +modelling.aero +navigation.aero +parachuting.aero +paragliding.aero +passenger-association.aero +pilot.aero +press.aero +production.aero +recreation.aero +repbody.aero +res.aero +research.aero +rotorcraft.aero +safety.aero +scientist.aero +services.aero +show.aero +skydiving.aero +software.aero +student.aero +taxi.aero +trader.aero +trading.aero +trainer.aero +union.aero +workinggroup.aero +works.aero + +af +gov.af +com.af +org.af +net.af +edu.af + +ag +com.ag +org.ag +net.ag +co.ag +nom.ag + +ai +off.ai +com.ai +net.ai +org.ai + +al +com.al +edu.al +gov.al +mil.al +net.al +org.al + +am +co.am +com.am +commune.am +net.am +org.am + +ao +ed.ao +gv.ao +og.ao +co.ao +pb.ao +it.ao + +aq + +ar +bet.ar +com.ar +coop.ar +edu.ar +gob.ar +gov.ar +int.ar +mil.ar +musica.ar +mutual.ar +net.ar +org.ar +senasa.ar +tur.ar + +arpa +e164.arpa +in-addr.arpa +ip6.arpa +iris.arpa +uri.arpa +urn.arpa + +as +gov.as + +asia + +at +ac.at +co.at +gv.at +or.at +sth.ac.at + +au +com.au +net.au +org.au +edu.au +gov.au +asn.au +id.au +info.au +conf.au +oz.au +act.au +nsw.au +nt.au +qld.au +sa.au +tas.au +vic.au +wa.au +act.edu.au +catholic.edu.au +nsw.edu.au +nt.edu.au +qld.edu.au +sa.edu.au +tas.edu.au +vic.edu.au +wa.edu.au +qld.gov.au +sa.gov.au +tas.gov.au +vic.gov.au +wa.gov.au +schools.nsw.edu.au + +aw +com.aw + +ax + +az +com.az +net.az +int.az +gov.az +org.az +edu.az +info.az +pp.az +mil.az +name.az +pro.az +biz.az + +ba +com.ba +edu.ba +gov.ba +mil.ba +net.ba +org.ba + +bb +biz.bb +co.bb +com.bb +edu.bb +gov.bb +info.bb +net.bb +org.bb +store.bb +tv.bb + +*.bd + +be +ac.be + +bf +gov.bf + +bg +a.bg +b.bg +c.bg +d.bg +e.bg +f.bg +g.bg +h.bg +i.bg +j.bg +k.bg +l.bg +m.bg +n.bg +o.bg +p.bg +q.bg +r.bg +s.bg +t.bg +u.bg +v.bg +w.bg +x.bg +y.bg +z.bg +0.bg +1.bg +2.bg +3.bg +4.bg +5.bg +6.bg +7.bg +8.bg +9.bg + +bh +com.bh +edu.bh +net.bh +org.bh +gov.bh + +bi +co.bi +com.bi +edu.bi +or.bi +org.bi + +biz + +bj +africa.bj +agro.bj +architectes.bj +assur.bj +avocats.bj +co.bj +com.bj +eco.bj +econo.bj +edu.bj +info.bj +loisirs.bj +money.bj +net.bj +org.bj +ote.bj +resto.bj +restaurant.bj +tourism.bj +univ.bj + +bm +com.bm +edu.bm +gov.bm +net.bm +org.bm + +bn +com.bn +edu.bn +gov.bn +net.bn +org.bn + +bo +com.bo +edu.bo +gob.bo +int.bo +org.bo +net.bo +mil.bo +tv.bo +web.bo +academia.bo +agro.bo +arte.bo +blog.bo +bolivia.bo +ciencia.bo +cooperativa.bo +democracia.bo +deporte.bo +ecologia.bo +economia.bo +empresa.bo +indigena.bo +industria.bo +info.bo +medicina.bo +movimiento.bo +musica.bo +natural.bo +nombre.bo +noticias.bo +patria.bo +politica.bo +profesional.bo +plurinacional.bo +pueblo.bo +revista.bo +salud.bo +tecnologia.bo +tksat.bo +transporte.bo +wiki.bo + +br +9guacu.br +abc.br +adm.br +adv.br +agr.br +aju.br +am.br +anani.br +aparecida.br +app.br +arq.br +art.br +ato.br +b.br +barueri.br +belem.br +bet.br +bhz.br +bib.br +bio.br +blog.br +bmd.br +boavista.br +bsb.br +campinagrande.br +campinas.br +caxias.br +cim.br +cng.br +cnt.br +com.br +contagem.br +coop.br +coz.br +cri.br +cuiaba.br +curitiba.br +def.br +des.br +det.br +dev.br +ecn.br +eco.br +edu.br +emp.br +enf.br +eng.br +esp.br +etc.br +eti.br +far.br +feira.br +flog.br +floripa.br +fm.br +fnd.br +fortal.br +fot.br +foz.br +fst.br +g12.br +geo.br +ggf.br +goiania.br +gov.br +ac.gov.br +al.gov.br +am.gov.br +ap.gov.br +ba.gov.br +ce.gov.br +df.gov.br +es.gov.br +go.gov.br +ma.gov.br +mg.gov.br +ms.gov.br +mt.gov.br +pa.gov.br +pb.gov.br +pe.gov.br +pi.gov.br +pr.gov.br +rj.gov.br +rn.gov.br +ro.gov.br +rr.gov.br +rs.gov.br +sc.gov.br +se.gov.br +sp.gov.br +to.gov.br +gru.br +imb.br +ind.br +inf.br +jab.br +jampa.br +jdf.br +joinville.br +jor.br +jus.br +leg.br +leilao.br +lel.br +log.br +londrina.br +macapa.br +maceio.br +manaus.br +maringa.br +mat.br +med.br +mil.br +morena.br +mp.br +mus.br +natal.br +net.br +niteroi.br +*.nom.br +not.br +ntr.br +odo.br +ong.br +org.br +osasco.br +palmas.br +poa.br +ppg.br +pro.br +psc.br +psi.br +pvh.br +qsl.br +radio.br +rec.br +recife.br +rep.br +ribeirao.br +rio.br +riobranco.br +riopreto.br +salvador.br +sampa.br +santamaria.br +santoandre.br +saobernardo.br +saogonca.br +seg.br +sjc.br +slg.br +slz.br +sorocaba.br +srv.br +taxi.br +tc.br +tec.br +teo.br +the.br +tmp.br +trd.br +tur.br +tv.br +udi.br +vet.br +vix.br +vlog.br +wiki.br +zlg.br + +bs +com.bs +net.bs +org.bs +edu.bs +gov.bs + +bt +com.bt +edu.bt +gov.bt +net.bt +org.bt + +bv + +bw +co.bw +org.bw + +by +gov.by +mil.by +com.by +of.by + +bz +com.bz +net.bz +org.bz +edu.bz +gov.bz + +ca +ab.ca +bc.ca +mb.ca +nb.ca +nf.ca +nl.ca +ns.ca +nt.ca +nu.ca +on.ca +pe.ca +qc.ca +sk.ca +yk.ca +gc.ca + +cat + +cc + +cd +gov.cd + +cf + +cg + +ch + +ci +org.ci +or.ci +com.ci +co.ci +edu.ci +ed.ci +ac.ci +net.ci +go.ci +asso.ci +aéroport.ci +int.ci +presse.ci +md.ci +gouv.ci + +*.ck +!www.ck + +cl +co.cl +gob.cl +gov.cl +mil.cl + +cm +co.cm +com.cm +gov.cm +net.cm + +cn +ac.cn +com.cn +edu.cn +gov.cn +net.cn +org.cn +mil.cn +公司.cn +网络.cn +網絡.cn +ah.cn +bj.cn +cq.cn +fj.cn +gd.cn +gs.cn +gz.cn +gx.cn +ha.cn +hb.cn +he.cn +hi.cn +hl.cn +hn.cn +jl.cn +js.cn +jx.cn +ln.cn +nm.cn +nx.cn +qh.cn +sc.cn +sd.cn +sh.cn +sn.cn +sx.cn +tj.cn +xj.cn +xz.cn +yn.cn +zj.cn +hk.cn +mo.cn +tw.cn + +co +arts.co +com.co +edu.co +firm.co +gov.co +info.co +int.co +mil.co +net.co +nom.co +org.co +rec.co +web.co + +com + +coop + +cr +ac.cr +co.cr +ed.cr +fi.cr +go.cr +or.cr +sa.cr + +cu +com.cu +edu.cu +gob.cu +gov.cu +inf.cu +nat.cu +net.cu +org.cu + +cv +com.cv +edu.cv +int.cv +nome.cv +org.cv + +cw +com.cw +edu.cw +net.cw +org.cw + +cx +gov.cx + +cy +ac.cy +biz.cy +com.cy +ekloges.cy +gov.cy +ltd.cy +mil.cy +net.cy +org.cy +press.cy +pro.cy +tm.cy + +cz + +de + +dj + +dk + +dm +com.dm +net.dm +org.dm +edu.dm +gov.dm + +do +art.do +com.do +edu.do +gob.do +gov.do +mil.do +net.do +org.do +sld.do +web.do + +dz +art.dz +asso.dz +com.dz +edu.dz +gov.dz +org.dz +net.dz +pol.dz +soc.dz +tm.dz + +ec +com.ec +info.ec +net.ec +fin.ec +k12.ec +med.ec +pro.ec +org.ec +edu.ec +gov.ec +gob.ec +mil.ec + +edu + +ee +edu.ee +gov.ee +riik.ee +lib.ee +med.ee +com.ee +pri.ee +aip.ee +org.ee +fie.ee + +eg +com.eg +edu.eg +eun.eg +gov.eg +mil.eg +name.eg +net.eg +org.eg +sci.eg + +*.er + +es +com.es +nom.es +org.es +gob.es +edu.es + +et +com.et +gov.et +org.et +edu.et +biz.et +name.et +info.et +net.et + +eu + +fi +aland.fi + +fj +ac.fj +biz.fj +com.fj +gov.fj +info.fj +mil.fj +name.fj +net.fj +org.fj +pro.fj + +*.fk + +com.fm +edu.fm +net.fm +org.fm +fm + +fo + +fr +asso.fr +com.fr +gouv.fr +nom.fr +prd.fr +tm.fr +avoues.fr +cci.fr +greta.fr +huissier-justice.fr + +ga + +gb + +edu.gd +gov.gd +gd + +ge +com.ge +edu.ge +gov.ge +org.ge +mil.ge +net.ge +pvt.ge + +gf + +gg +co.gg +net.gg +org.gg + +gh +com.gh +edu.gh +gov.gh +org.gh +mil.gh + +gi +com.gi +ltd.gi +gov.gi +mod.gi +edu.gi +org.gi + +gl +co.gl +com.gl +edu.gl +net.gl +org.gl + +gm + +gn +ac.gn +com.gn +edu.gn +gov.gn +org.gn +net.gn + +gov + +gp +com.gp +net.gp +mobi.gp +edu.gp +org.gp +asso.gp + +gq + +gr +com.gr +edu.gr +net.gr +org.gr +gov.gr + +gs + +gt +com.gt +edu.gt +gob.gt +ind.gt +mil.gt +net.gt +org.gt + +gu +com.gu +edu.gu +gov.gu +guam.gu +info.gu +net.gu +org.gu +web.gu + +gw + +gy +co.gy +com.gy +edu.gy +gov.gy +net.gy +org.gy + +hk +com.hk +edu.hk +gov.hk +idv.hk +net.hk +org.hk +公司.hk +教育.hk +敎育.hk +政府.hk +個人.hk +个人.hk +箇人.hk +網络.hk +网络.hk +组織.hk +網絡.hk +网絡.hk +组织.hk +組織.hk +組织.hk + +hm + +hn +com.hn +edu.hn +org.hn +net.hn +mil.hn +gob.hn + +hr +iz.hr +from.hr +name.hr +com.hr + +ht +com.ht +shop.ht +firm.ht +info.ht +adult.ht +net.ht +pro.ht +org.ht +med.ht +art.ht +coop.ht +pol.ht +asso.ht +edu.ht +rel.ht +gouv.ht +perso.ht + +hu +co.hu +info.hu +org.hu +priv.hu +sport.hu +tm.hu +2000.hu +agrar.hu +bolt.hu +casino.hu +city.hu +erotica.hu +erotika.hu +film.hu +forum.hu +games.hu +hotel.hu +ingatlan.hu +jogasz.hu +konyvelo.hu +lakas.hu +media.hu +news.hu +reklam.hu +sex.hu +shop.hu +suli.hu +szex.hu +tozsde.hu +utazas.hu +video.hu + +id +ac.id +biz.id +co.id +desa.id +go.id +mil.id +my.id +net.id +or.id +ponpes.id +sch.id +web.id + +ie +gov.ie + +il +ac.il +co.il +gov.il +idf.il +k12.il +muni.il +net.il +org.il +ישראל +אקדמיה.ישראל +ישוב.ישראל +צהל.ישראל +ממשל.ישראל + +im +ac.im +co.im +com.im +ltd.co.im +net.im +org.im +plc.co.im +tt.im +tv.im + +in +5g.in +6g.in +ac.in +ai.in +am.in +bihar.in +biz.in +business.in +ca.in +cn.in +co.in +com.in +coop.in +cs.in +delhi.in +dr.in +edu.in +er.in +firm.in +gen.in +gov.in +gujarat.in +ind.in +info.in +int.in +internet.in +io.in +me.in +mil.in +net.in +nic.in +org.in +pg.in +post.in +pro.in +res.in +travel.in +tv.in +uk.in +up.in +us.in + +info + +int +eu.int + +io +com.io + +iq +gov.iq +edu.iq +mil.iq +com.iq +org.iq +net.iq + +ir +ac.ir +co.ir +gov.ir +id.ir +net.ir +org.ir +sch.ir +ایران.ir +ايران.ir + +is +net.is +com.is +edu.is +gov.is +org.is +int.is + +it +gov.it +edu.it +abr.it +abruzzo.it +aosta-valley.it +aostavalley.it +bas.it +basilicata.it +cal.it +calabria.it +cam.it +campania.it +emilia-romagna.it +emiliaromagna.it +emr.it +friuli-v-giulia.it +friuli-ve-giulia.it +friuli-vegiulia.it +friuli-venezia-giulia.it +friuli-veneziagiulia.it +friuli-vgiulia.it +friuliv-giulia.it +friulive-giulia.it +friulivegiulia.it +friulivenezia-giulia.it +friuliveneziagiulia.it +friulivgiulia.it +fvg.it +laz.it +lazio.it +lig.it +liguria.it +lom.it +lombardia.it +lombardy.it +lucania.it +mar.it +marche.it +mol.it +molise.it +piedmont.it +piemonte.it +pmn.it +pug.it +puglia.it +sar.it +sardegna.it +sardinia.it +sic.it +sicilia.it +sicily.it +taa.it +tos.it +toscana.it +trentin-sud-tirol.it +trentin-süd-tirol.it +trentin-sudtirol.it +trentin-südtirol.it +trentin-sued-tirol.it +trentin-suedtirol.it +trentino-a-adige.it +trentino-aadige.it +trentino-alto-adige.it +trentino-altoadige.it +trentino-s-tirol.it +trentino-stirol.it +trentino-sud-tirol.it +trentino-süd-tirol.it +trentino-sudtirol.it +trentino-südtirol.it +trentino-sued-tirol.it +trentino-suedtirol.it +trentino.it +trentinoa-adige.it +trentinoaadige.it +trentinoalto-adige.it +trentinoaltoadige.it +trentinos-tirol.it +trentinostirol.it +trentinosud-tirol.it +trentinosüd-tirol.it +trentinosudtirol.it +trentinosüdtirol.it +trentinosued-tirol.it +trentinosuedtirol.it +trentinsud-tirol.it +trentinsüd-tirol.it +trentinsudtirol.it +trentinsüdtirol.it +trentinsued-tirol.it +trentinsuedtirol.it +tuscany.it +umb.it +umbria.it +val-d-aosta.it +val-daosta.it +vald-aosta.it +valdaosta.it +valle-aosta.it +valle-d-aosta.it +valle-daosta.it +valleaosta.it +valled-aosta.it +valledaosta.it +vallee-aoste.it +vallée-aoste.it +vallee-d-aoste.it +vallée-d-aoste.it +valleeaoste.it +valléeaoste.it +valleedaoste.it +valléedaoste.it +vao.it +vda.it +ven.it +veneto.it +ag.it +agrigento.it +al.it +alessandria.it +alto-adige.it +altoadige.it +an.it +ancona.it +andria-barletta-trani.it +andria-trani-barletta.it +andriabarlettatrani.it +andriatranibarletta.it +ao.it +aosta.it +aoste.it +ap.it +aq.it +aquila.it +ar.it +arezzo.it +ascoli-piceno.it +ascolipiceno.it +asti.it +at.it +av.it +avellino.it +ba.it +balsan-sudtirol.it +balsan-südtirol.it +balsan-suedtirol.it +balsan.it +bari.it +barletta-trani-andria.it +barlettatraniandria.it +belluno.it +benevento.it +bergamo.it +bg.it +bi.it +biella.it +bl.it +bn.it +bo.it +bologna.it +bolzano-altoadige.it +bolzano.it +bozen-sudtirol.it +bozen-südtirol.it +bozen-suedtirol.it +bozen.it +br.it +brescia.it +brindisi.it +bs.it +bt.it +bulsan-sudtirol.it +bulsan-südtirol.it +bulsan-suedtirol.it +bulsan.it +bz.it +ca.it +cagliari.it +caltanissetta.it +campidano-medio.it +campidanomedio.it +campobasso.it +carbonia-iglesias.it +carboniaiglesias.it +carrara-massa.it +carraramassa.it +caserta.it +catania.it +catanzaro.it +cb.it +ce.it +cesena-forli.it +cesena-forlì.it +cesenaforli.it +cesenaforlì.it +ch.it +chieti.it +ci.it +cl.it +cn.it +co.it +como.it +cosenza.it +cr.it +cremona.it +crotone.it +cs.it +ct.it +cuneo.it +cz.it +dell-ogliastra.it +dellogliastra.it +en.it +enna.it +fc.it +fe.it +fermo.it +ferrara.it +fg.it +fi.it +firenze.it +florence.it +fm.it +foggia.it +forli-cesena.it +forlì-cesena.it +forlicesena.it +forlìcesena.it +fr.it +frosinone.it +ge.it +genoa.it +genova.it +go.it +gorizia.it +gr.it +grosseto.it +iglesias-carbonia.it +iglesiascarbonia.it +im.it +imperia.it +is.it +isernia.it +kr.it +la-spezia.it +laquila.it +laspezia.it +latina.it +lc.it +le.it +lecce.it +lecco.it +li.it +livorno.it +lo.it +lodi.it +lt.it +lu.it +lucca.it +macerata.it +mantova.it +massa-carrara.it +massacarrara.it +matera.it +mb.it +mc.it +me.it +medio-campidano.it +mediocampidano.it +messina.it +mi.it +milan.it +milano.it +mn.it +mo.it +modena.it +monza-brianza.it +monza-e-della-brianza.it +monza.it +monzabrianza.it +monzaebrianza.it +monzaedellabrianza.it +ms.it +mt.it +na.it +naples.it +napoli.it +no.it +novara.it +nu.it +nuoro.it +og.it +ogliastra.it +olbia-tempio.it +olbiatempio.it +or.it +oristano.it +ot.it +pa.it +padova.it +padua.it +palermo.it +parma.it +pavia.it +pc.it +pd.it +pe.it +perugia.it +pesaro-urbino.it +pesarourbino.it +pescara.it +pg.it +pi.it +piacenza.it +pisa.it +pistoia.it +pn.it +po.it +pordenone.it +potenza.it +pr.it +prato.it +pt.it +pu.it +pv.it +pz.it +ra.it +ragusa.it +ravenna.it +rc.it +re.it +reggio-calabria.it +reggio-emilia.it +reggiocalabria.it +reggioemilia.it +rg.it +ri.it +rieti.it +rimini.it +rm.it +rn.it +ro.it +roma.it +rome.it +rovigo.it +sa.it +salerno.it +sassari.it +savona.it +si.it +siena.it +siracusa.it +so.it +sondrio.it +sp.it +sr.it +ss.it +suedtirol.it +südtirol.it +sv.it +ta.it +taranto.it +te.it +tempio-olbia.it +tempioolbia.it +teramo.it +terni.it +tn.it +to.it +torino.it +tp.it +tr.it +trani-andria-barletta.it +trani-barletta-andria.it +traniandriabarletta.it +tranibarlettaandria.it +trapani.it +trento.it +treviso.it +trieste.it +ts.it +turin.it +tv.it +ud.it +udine.it +urbino-pesaro.it +urbinopesaro.it +va.it +varese.it +vb.it +vc.it +ve.it +venezia.it +venice.it +verbania.it +vercelli.it +verona.it +vi.it +vibo-valentia.it +vibovalentia.it +vicenza.it +viterbo.it +vr.it +vs.it +vt.it +vv.it + +je +co.je +net.je +org.je + +*.jm + +jo +com.jo +org.jo +net.jo +edu.jo +sch.jo +gov.jo +mil.jo +name.jo + +jobs + +jp +ac.jp +ad.jp +co.jp +ed.jp +go.jp +gr.jp +lg.jp +ne.jp +or.jp +aichi.jp +akita.jp +aomori.jp +chiba.jp +ehime.jp +fukui.jp +fukuoka.jp +fukushima.jp +gifu.jp +gunma.jp +hiroshima.jp +hokkaido.jp +hyogo.jp +ibaraki.jp +ishikawa.jp +iwate.jp +kagawa.jp +kagoshima.jp +kanagawa.jp +kochi.jp +kumamoto.jp +kyoto.jp +mie.jp +miyagi.jp +miyazaki.jp +nagano.jp +nagasaki.jp +nara.jp +niigata.jp +oita.jp +okayama.jp +okinawa.jp +osaka.jp +saga.jp +saitama.jp +shiga.jp +shimane.jp +shizuoka.jp +tochigi.jp +tokushima.jp +tokyo.jp +tottori.jp +toyama.jp +wakayama.jp +yamagata.jp +yamaguchi.jp +yamanashi.jp +栃木.jp +愛知.jp +愛媛.jp +兵庫.jp +熊本.jp +茨城.jp +北海道.jp +千葉.jp +和歌山.jp +長崎.jp +長野.jp +新潟.jp +青森.jp +静岡.jp +東京.jp +石川.jp +埼玉.jp +三重.jp +京都.jp +佐賀.jp +大分.jp +大阪.jp +奈良.jp +宮城.jp +宮崎.jp +富山.jp +山口.jp +山形.jp +山梨.jp +岩手.jp +岐阜.jp +岡山.jp +島根.jp +広島.jp +徳島.jp +沖縄.jp +滋賀.jp +神奈川.jp +福井.jp +福岡.jp +福島.jp +秋田.jp +群馬.jp +香川.jp +高知.jp +鳥取.jp +鹿児島.jp +*.kawasaki.jp +!city.kawasaki.jp +*.kitakyushu.jp +!city.kitakyushu.jp +*.kobe.jp +!city.kobe.jp +*.nagoya.jp +!city.nagoya.jp +*.sapporo.jp +!city.sapporo.jp +*.sendai.jp +!city.sendai.jp +*.yokohama.jp +!city.yokohama.jp +aisai.aichi.jp +ama.aichi.jp +anjo.aichi.jp +asuke.aichi.jp +chiryu.aichi.jp +chita.aichi.jp +fuso.aichi.jp +gamagori.aichi.jp +handa.aichi.jp +hazu.aichi.jp +hekinan.aichi.jp +higashiura.aichi.jp +ichinomiya.aichi.jp +inazawa.aichi.jp +inuyama.aichi.jp +isshiki.aichi.jp +iwakura.aichi.jp +kanie.aichi.jp +kariya.aichi.jp +kasugai.aichi.jp +kira.aichi.jp +kiyosu.aichi.jp +komaki.aichi.jp +konan.aichi.jp +kota.aichi.jp +mihama.aichi.jp +miyoshi.aichi.jp +nishio.aichi.jp +nisshin.aichi.jp +obu.aichi.jp +oguchi.aichi.jp +oharu.aichi.jp +okazaki.aichi.jp +owariasahi.aichi.jp +seto.aichi.jp +shikatsu.aichi.jp +shinshiro.aichi.jp +shitara.aichi.jp +tahara.aichi.jp +takahama.aichi.jp +tobishima.aichi.jp +toei.aichi.jp +togo.aichi.jp +tokai.aichi.jp +tokoname.aichi.jp +toyoake.aichi.jp +toyohashi.aichi.jp +toyokawa.aichi.jp +toyone.aichi.jp +toyota.aichi.jp +tsushima.aichi.jp +yatomi.aichi.jp +akita.akita.jp +daisen.akita.jp +fujisato.akita.jp +gojome.akita.jp +hachirogata.akita.jp +happou.akita.jp +higashinaruse.akita.jp +honjo.akita.jp +honjyo.akita.jp +ikawa.akita.jp +kamikoani.akita.jp +kamioka.akita.jp +katagami.akita.jp +kazuno.akita.jp +kitaakita.akita.jp +kosaka.akita.jp +kyowa.akita.jp +misato.akita.jp +mitane.akita.jp +moriyoshi.akita.jp +nikaho.akita.jp +noshiro.akita.jp +odate.akita.jp +oga.akita.jp +ogata.akita.jp +semboku.akita.jp +yokote.akita.jp +yurihonjo.akita.jp +aomori.aomori.jp +gonohe.aomori.jp +hachinohe.aomori.jp +hashikami.aomori.jp +hiranai.aomori.jp +hirosaki.aomori.jp +itayanagi.aomori.jp +kuroishi.aomori.jp +misawa.aomori.jp +mutsu.aomori.jp +nakadomari.aomori.jp +noheji.aomori.jp +oirase.aomori.jp +owani.aomori.jp +rokunohe.aomori.jp +sannohe.aomori.jp +shichinohe.aomori.jp +shingo.aomori.jp +takko.aomori.jp +towada.aomori.jp +tsugaru.aomori.jp +tsuruta.aomori.jp +abiko.chiba.jp +asahi.chiba.jp +chonan.chiba.jp +chosei.chiba.jp +choshi.chiba.jp +chuo.chiba.jp +funabashi.chiba.jp +futtsu.chiba.jp +hanamigawa.chiba.jp +ichihara.chiba.jp +ichikawa.chiba.jp +ichinomiya.chiba.jp +inzai.chiba.jp +isumi.chiba.jp +kamagaya.chiba.jp +kamogawa.chiba.jp +kashiwa.chiba.jp +katori.chiba.jp +katsuura.chiba.jp +kimitsu.chiba.jp +kisarazu.chiba.jp +kozaki.chiba.jp +kujukuri.chiba.jp +kyonan.chiba.jp +matsudo.chiba.jp +midori.chiba.jp +mihama.chiba.jp +minamiboso.chiba.jp +mobara.chiba.jp +mutsuzawa.chiba.jp +nagara.chiba.jp +nagareyama.chiba.jp +narashino.chiba.jp +narita.chiba.jp +noda.chiba.jp +oamishirasato.chiba.jp +omigawa.chiba.jp +onjuku.chiba.jp +otaki.chiba.jp +sakae.chiba.jp +sakura.chiba.jp +shimofusa.chiba.jp +shirako.chiba.jp +shiroi.chiba.jp +shisui.chiba.jp +sodegaura.chiba.jp +sosa.chiba.jp +tako.chiba.jp +tateyama.chiba.jp +togane.chiba.jp +tohnosho.chiba.jp +tomisato.chiba.jp +urayasu.chiba.jp +yachimata.chiba.jp +yachiyo.chiba.jp +yokaichiba.chiba.jp +yokoshibahikari.chiba.jp +yotsukaido.chiba.jp +ainan.ehime.jp +honai.ehime.jp +ikata.ehime.jp +imabari.ehime.jp +iyo.ehime.jp +kamijima.ehime.jp +kihoku.ehime.jp +kumakogen.ehime.jp +masaki.ehime.jp +matsuno.ehime.jp +matsuyama.ehime.jp +namikata.ehime.jp +niihama.ehime.jp +ozu.ehime.jp +saijo.ehime.jp +seiyo.ehime.jp +shikokuchuo.ehime.jp +tobe.ehime.jp +toon.ehime.jp +uchiko.ehime.jp +uwajima.ehime.jp +yawatahama.ehime.jp +echizen.fukui.jp +eiheiji.fukui.jp +fukui.fukui.jp +ikeda.fukui.jp +katsuyama.fukui.jp +mihama.fukui.jp +minamiechizen.fukui.jp +obama.fukui.jp +ohi.fukui.jp +ono.fukui.jp +sabae.fukui.jp +sakai.fukui.jp +takahama.fukui.jp +tsuruga.fukui.jp +wakasa.fukui.jp +ashiya.fukuoka.jp +buzen.fukuoka.jp +chikugo.fukuoka.jp +chikuho.fukuoka.jp +chikujo.fukuoka.jp +chikushino.fukuoka.jp +chikuzen.fukuoka.jp +chuo.fukuoka.jp +dazaifu.fukuoka.jp +fukuchi.fukuoka.jp +hakata.fukuoka.jp +higashi.fukuoka.jp +hirokawa.fukuoka.jp +hisayama.fukuoka.jp +iizuka.fukuoka.jp +inatsuki.fukuoka.jp +kaho.fukuoka.jp +kasuga.fukuoka.jp +kasuya.fukuoka.jp +kawara.fukuoka.jp +keisen.fukuoka.jp +koga.fukuoka.jp +kurate.fukuoka.jp +kurogi.fukuoka.jp +kurume.fukuoka.jp +minami.fukuoka.jp +miyako.fukuoka.jp +miyama.fukuoka.jp +miyawaka.fukuoka.jp +mizumaki.fukuoka.jp +munakata.fukuoka.jp +nakagawa.fukuoka.jp +nakama.fukuoka.jp +nishi.fukuoka.jp +nogata.fukuoka.jp +ogori.fukuoka.jp +okagaki.fukuoka.jp +okawa.fukuoka.jp +oki.fukuoka.jp +omuta.fukuoka.jp +onga.fukuoka.jp +onojo.fukuoka.jp +oto.fukuoka.jp +saigawa.fukuoka.jp +sasaguri.fukuoka.jp +shingu.fukuoka.jp +shinyoshitomi.fukuoka.jp +shonai.fukuoka.jp +soeda.fukuoka.jp +sue.fukuoka.jp +tachiarai.fukuoka.jp +tagawa.fukuoka.jp +takata.fukuoka.jp +toho.fukuoka.jp +toyotsu.fukuoka.jp +tsuiki.fukuoka.jp +ukiha.fukuoka.jp +umi.fukuoka.jp +usui.fukuoka.jp +yamada.fukuoka.jp +yame.fukuoka.jp +yanagawa.fukuoka.jp +yukuhashi.fukuoka.jp +aizubange.fukushima.jp +aizumisato.fukushima.jp +aizuwakamatsu.fukushima.jp +asakawa.fukushima.jp +bandai.fukushima.jp +date.fukushima.jp +fukushima.fukushima.jp +furudono.fukushima.jp +futaba.fukushima.jp +hanawa.fukushima.jp +higashi.fukushima.jp +hirata.fukushima.jp +hirono.fukushima.jp +iitate.fukushima.jp +inawashiro.fukushima.jp +ishikawa.fukushima.jp +iwaki.fukushima.jp +izumizaki.fukushima.jp +kagamiishi.fukushima.jp +kaneyama.fukushima.jp +kawamata.fukushima.jp +kitakata.fukushima.jp +kitashiobara.fukushima.jp +koori.fukushima.jp +koriyama.fukushima.jp +kunimi.fukushima.jp +miharu.fukushima.jp +mishima.fukushima.jp +namie.fukushima.jp +nango.fukushima.jp +nishiaizu.fukushima.jp +nishigo.fukushima.jp +okuma.fukushima.jp +omotego.fukushima.jp +ono.fukushima.jp +otama.fukushima.jp +samegawa.fukushima.jp +shimogo.fukushima.jp +shirakawa.fukushima.jp +showa.fukushima.jp +soma.fukushima.jp +sukagawa.fukushima.jp +taishin.fukushima.jp +tamakawa.fukushima.jp +tanagura.fukushima.jp +tenei.fukushima.jp +yabuki.fukushima.jp +yamato.fukushima.jp +yamatsuri.fukushima.jp +yanaizu.fukushima.jp +yugawa.fukushima.jp +anpachi.gifu.jp +ena.gifu.jp +gifu.gifu.jp +ginan.gifu.jp +godo.gifu.jp +gujo.gifu.jp +hashima.gifu.jp +hichiso.gifu.jp +hida.gifu.jp +higashishirakawa.gifu.jp +ibigawa.gifu.jp +ikeda.gifu.jp +kakamigahara.gifu.jp +kani.gifu.jp +kasahara.gifu.jp +kasamatsu.gifu.jp +kawaue.gifu.jp +kitagata.gifu.jp +mino.gifu.jp +minokamo.gifu.jp +mitake.gifu.jp +mizunami.gifu.jp +motosu.gifu.jp +nakatsugawa.gifu.jp +ogaki.gifu.jp +sakahogi.gifu.jp +seki.gifu.jp +sekigahara.gifu.jp +shirakawa.gifu.jp +tajimi.gifu.jp +takayama.gifu.jp +tarui.gifu.jp +toki.gifu.jp +tomika.gifu.jp +wanouchi.gifu.jp +yamagata.gifu.jp +yaotsu.gifu.jp +yoro.gifu.jp +annaka.gunma.jp +chiyoda.gunma.jp +fujioka.gunma.jp +higashiagatsuma.gunma.jp +isesaki.gunma.jp +itakura.gunma.jp +kanna.gunma.jp +kanra.gunma.jp +katashina.gunma.jp +kawaba.gunma.jp +kiryu.gunma.jp +kusatsu.gunma.jp +maebashi.gunma.jp +meiwa.gunma.jp +midori.gunma.jp +minakami.gunma.jp +naganohara.gunma.jp +nakanojo.gunma.jp +nanmoku.gunma.jp +numata.gunma.jp +oizumi.gunma.jp +ora.gunma.jp +ota.gunma.jp +shibukawa.gunma.jp +shimonita.gunma.jp +shinto.gunma.jp +showa.gunma.jp +takasaki.gunma.jp +takayama.gunma.jp +tamamura.gunma.jp +tatebayashi.gunma.jp +tomioka.gunma.jp +tsukiyono.gunma.jp +tsumagoi.gunma.jp +ueno.gunma.jp +yoshioka.gunma.jp +asaminami.hiroshima.jp +daiwa.hiroshima.jp +etajima.hiroshima.jp +fuchu.hiroshima.jp +fukuyama.hiroshima.jp +hatsukaichi.hiroshima.jp +higashihiroshima.hiroshima.jp +hongo.hiroshima.jp +jinsekikogen.hiroshima.jp +kaita.hiroshima.jp +kui.hiroshima.jp +kumano.hiroshima.jp +kure.hiroshima.jp +mihara.hiroshima.jp +miyoshi.hiroshima.jp +naka.hiroshima.jp +onomichi.hiroshima.jp +osakikamijima.hiroshima.jp +otake.hiroshima.jp +saka.hiroshima.jp +sera.hiroshima.jp +seranishi.hiroshima.jp +shinichi.hiroshima.jp +shobara.hiroshima.jp +takehara.hiroshima.jp +abashiri.hokkaido.jp +abira.hokkaido.jp +aibetsu.hokkaido.jp +akabira.hokkaido.jp +akkeshi.hokkaido.jp +asahikawa.hokkaido.jp +ashibetsu.hokkaido.jp +ashoro.hokkaido.jp +assabu.hokkaido.jp +atsuma.hokkaido.jp +bibai.hokkaido.jp +biei.hokkaido.jp +bifuka.hokkaido.jp +bihoro.hokkaido.jp +biratori.hokkaido.jp +chippubetsu.hokkaido.jp +chitose.hokkaido.jp +date.hokkaido.jp +ebetsu.hokkaido.jp +embetsu.hokkaido.jp +eniwa.hokkaido.jp +erimo.hokkaido.jp +esan.hokkaido.jp +esashi.hokkaido.jp +fukagawa.hokkaido.jp +fukushima.hokkaido.jp +furano.hokkaido.jp +furubira.hokkaido.jp +haboro.hokkaido.jp +hakodate.hokkaido.jp +hamatonbetsu.hokkaido.jp +hidaka.hokkaido.jp +higashikagura.hokkaido.jp +higashikawa.hokkaido.jp +hiroo.hokkaido.jp +hokuryu.hokkaido.jp +hokuto.hokkaido.jp +honbetsu.hokkaido.jp +horokanai.hokkaido.jp +horonobe.hokkaido.jp +ikeda.hokkaido.jp +imakane.hokkaido.jp +ishikari.hokkaido.jp +iwamizawa.hokkaido.jp +iwanai.hokkaido.jp +kamifurano.hokkaido.jp +kamikawa.hokkaido.jp +kamishihoro.hokkaido.jp +kamisunagawa.hokkaido.jp +kamoenai.hokkaido.jp +kayabe.hokkaido.jp +kembuchi.hokkaido.jp +kikonai.hokkaido.jp +kimobetsu.hokkaido.jp +kitahiroshima.hokkaido.jp +kitami.hokkaido.jp +kiyosato.hokkaido.jp +koshimizu.hokkaido.jp +kunneppu.hokkaido.jp +kuriyama.hokkaido.jp +kuromatsunai.hokkaido.jp +kushiro.hokkaido.jp +kutchan.hokkaido.jp +kyowa.hokkaido.jp +mashike.hokkaido.jp +matsumae.hokkaido.jp +mikasa.hokkaido.jp +minamifurano.hokkaido.jp +mombetsu.hokkaido.jp +moseushi.hokkaido.jp +mukawa.hokkaido.jp +muroran.hokkaido.jp +naie.hokkaido.jp +nakagawa.hokkaido.jp +nakasatsunai.hokkaido.jp +nakatombetsu.hokkaido.jp +nanae.hokkaido.jp +nanporo.hokkaido.jp +nayoro.hokkaido.jp +nemuro.hokkaido.jp +niikappu.hokkaido.jp +niki.hokkaido.jp +nishiokoppe.hokkaido.jp +noboribetsu.hokkaido.jp +numata.hokkaido.jp +obihiro.hokkaido.jp +obira.hokkaido.jp +oketo.hokkaido.jp +okoppe.hokkaido.jp +otaru.hokkaido.jp +otobe.hokkaido.jp +otofuke.hokkaido.jp +otoineppu.hokkaido.jp +oumu.hokkaido.jp +ozora.hokkaido.jp +pippu.hokkaido.jp +rankoshi.hokkaido.jp +rebun.hokkaido.jp +rikubetsu.hokkaido.jp +rishiri.hokkaido.jp +rishirifuji.hokkaido.jp +saroma.hokkaido.jp +sarufutsu.hokkaido.jp +shakotan.hokkaido.jp +shari.hokkaido.jp +shibecha.hokkaido.jp +shibetsu.hokkaido.jp +shikabe.hokkaido.jp +shikaoi.hokkaido.jp +shimamaki.hokkaido.jp +shimizu.hokkaido.jp +shimokawa.hokkaido.jp +shinshinotsu.hokkaido.jp +shintoku.hokkaido.jp +shiranuka.hokkaido.jp +shiraoi.hokkaido.jp +shiriuchi.hokkaido.jp +sobetsu.hokkaido.jp +sunagawa.hokkaido.jp +taiki.hokkaido.jp +takasu.hokkaido.jp +takikawa.hokkaido.jp +takinoue.hokkaido.jp +teshikaga.hokkaido.jp +tobetsu.hokkaido.jp +tohma.hokkaido.jp +tomakomai.hokkaido.jp +tomari.hokkaido.jp +toya.hokkaido.jp +toyako.hokkaido.jp +toyotomi.hokkaido.jp +toyoura.hokkaido.jp +tsubetsu.hokkaido.jp +tsukigata.hokkaido.jp +urakawa.hokkaido.jp +urausu.hokkaido.jp +uryu.hokkaido.jp +utashinai.hokkaido.jp +wakkanai.hokkaido.jp +wassamu.hokkaido.jp +yakumo.hokkaido.jp +yoichi.hokkaido.jp +aioi.hyogo.jp +akashi.hyogo.jp +ako.hyogo.jp +amagasaki.hyogo.jp +aogaki.hyogo.jp +asago.hyogo.jp +ashiya.hyogo.jp +awaji.hyogo.jp +fukusaki.hyogo.jp +goshiki.hyogo.jp +harima.hyogo.jp +himeji.hyogo.jp +ichikawa.hyogo.jp +inagawa.hyogo.jp +itami.hyogo.jp +kakogawa.hyogo.jp +kamigori.hyogo.jp +kamikawa.hyogo.jp +kasai.hyogo.jp +kasuga.hyogo.jp +kawanishi.hyogo.jp +miki.hyogo.jp +minamiawaji.hyogo.jp +nishinomiya.hyogo.jp +nishiwaki.hyogo.jp +ono.hyogo.jp +sanda.hyogo.jp +sannan.hyogo.jp +sasayama.hyogo.jp +sayo.hyogo.jp +shingu.hyogo.jp +shinonsen.hyogo.jp +shiso.hyogo.jp +sumoto.hyogo.jp +taishi.hyogo.jp +taka.hyogo.jp +takarazuka.hyogo.jp +takasago.hyogo.jp +takino.hyogo.jp +tamba.hyogo.jp +tatsuno.hyogo.jp +toyooka.hyogo.jp +yabu.hyogo.jp +yashiro.hyogo.jp +yoka.hyogo.jp +yokawa.hyogo.jp +ami.ibaraki.jp +asahi.ibaraki.jp +bando.ibaraki.jp +chikusei.ibaraki.jp +daigo.ibaraki.jp +fujishiro.ibaraki.jp +hitachi.ibaraki.jp +hitachinaka.ibaraki.jp +hitachiomiya.ibaraki.jp +hitachiota.ibaraki.jp +ibaraki.ibaraki.jp +ina.ibaraki.jp +inashiki.ibaraki.jp +itako.ibaraki.jp +iwama.ibaraki.jp +joso.ibaraki.jp +kamisu.ibaraki.jp +kasama.ibaraki.jp +kashima.ibaraki.jp +kasumigaura.ibaraki.jp +koga.ibaraki.jp +miho.ibaraki.jp +mito.ibaraki.jp +moriya.ibaraki.jp +naka.ibaraki.jp +namegata.ibaraki.jp +oarai.ibaraki.jp +ogawa.ibaraki.jp +omitama.ibaraki.jp +ryugasaki.ibaraki.jp +sakai.ibaraki.jp +sakuragawa.ibaraki.jp +shimodate.ibaraki.jp +shimotsuma.ibaraki.jp +shirosato.ibaraki.jp +sowa.ibaraki.jp +suifu.ibaraki.jp +takahagi.ibaraki.jp +tamatsukuri.ibaraki.jp +tokai.ibaraki.jp +tomobe.ibaraki.jp +tone.ibaraki.jp +toride.ibaraki.jp +tsuchiura.ibaraki.jp +tsukuba.ibaraki.jp +uchihara.ibaraki.jp +ushiku.ibaraki.jp +yachiyo.ibaraki.jp +yamagata.ibaraki.jp +yawara.ibaraki.jp +yuki.ibaraki.jp +anamizu.ishikawa.jp +hakui.ishikawa.jp +hakusan.ishikawa.jp +kaga.ishikawa.jp +kahoku.ishikawa.jp +kanazawa.ishikawa.jp +kawakita.ishikawa.jp +komatsu.ishikawa.jp +nakanoto.ishikawa.jp +nanao.ishikawa.jp +nomi.ishikawa.jp +nonoichi.ishikawa.jp +noto.ishikawa.jp +shika.ishikawa.jp +suzu.ishikawa.jp +tsubata.ishikawa.jp +tsurugi.ishikawa.jp +uchinada.ishikawa.jp +wajima.ishikawa.jp +fudai.iwate.jp +fujisawa.iwate.jp +hanamaki.iwate.jp +hiraizumi.iwate.jp +hirono.iwate.jp +ichinohe.iwate.jp +ichinoseki.iwate.jp +iwaizumi.iwate.jp +iwate.iwate.jp +joboji.iwate.jp +kamaishi.iwate.jp +kanegasaki.iwate.jp +karumai.iwate.jp +kawai.iwate.jp +kitakami.iwate.jp +kuji.iwate.jp +kunohe.iwate.jp +kuzumaki.iwate.jp +miyako.iwate.jp +mizusawa.iwate.jp +morioka.iwate.jp +ninohe.iwate.jp +noda.iwate.jp +ofunato.iwate.jp +oshu.iwate.jp +otsuchi.iwate.jp +rikuzentakata.iwate.jp +shiwa.iwate.jp +shizukuishi.iwate.jp +sumita.iwate.jp +tanohata.iwate.jp +tono.iwate.jp +yahaba.iwate.jp +yamada.iwate.jp +ayagawa.kagawa.jp +higashikagawa.kagawa.jp +kanonji.kagawa.jp +kotohira.kagawa.jp +manno.kagawa.jp +marugame.kagawa.jp +mitoyo.kagawa.jp +naoshima.kagawa.jp +sanuki.kagawa.jp +tadotsu.kagawa.jp +takamatsu.kagawa.jp +tonosho.kagawa.jp +uchinomi.kagawa.jp +utazu.kagawa.jp +zentsuji.kagawa.jp +akune.kagoshima.jp +amami.kagoshima.jp +hioki.kagoshima.jp +isa.kagoshima.jp +isen.kagoshima.jp +izumi.kagoshima.jp +kagoshima.kagoshima.jp +kanoya.kagoshima.jp +kawanabe.kagoshima.jp +kinko.kagoshima.jp +kouyama.kagoshima.jp +makurazaki.kagoshima.jp +matsumoto.kagoshima.jp +minamitane.kagoshima.jp +nakatane.kagoshima.jp +nishinoomote.kagoshima.jp +satsumasendai.kagoshima.jp +soo.kagoshima.jp +tarumizu.kagoshima.jp +yusui.kagoshima.jp +aikawa.kanagawa.jp +atsugi.kanagawa.jp +ayase.kanagawa.jp +chigasaki.kanagawa.jp +ebina.kanagawa.jp +fujisawa.kanagawa.jp +hadano.kanagawa.jp +hakone.kanagawa.jp +hiratsuka.kanagawa.jp +isehara.kanagawa.jp +kaisei.kanagawa.jp +kamakura.kanagawa.jp +kiyokawa.kanagawa.jp +matsuda.kanagawa.jp +minamiashigara.kanagawa.jp +miura.kanagawa.jp +nakai.kanagawa.jp +ninomiya.kanagawa.jp +odawara.kanagawa.jp +oi.kanagawa.jp +oiso.kanagawa.jp +sagamihara.kanagawa.jp +samukawa.kanagawa.jp +tsukui.kanagawa.jp +yamakita.kanagawa.jp +yamato.kanagawa.jp +yokosuka.kanagawa.jp +yugawara.kanagawa.jp +zama.kanagawa.jp +zushi.kanagawa.jp +aki.kochi.jp +geisei.kochi.jp +hidaka.kochi.jp +higashitsuno.kochi.jp +ino.kochi.jp +kagami.kochi.jp +kami.kochi.jp +kitagawa.kochi.jp +kochi.kochi.jp +mihara.kochi.jp +motoyama.kochi.jp +muroto.kochi.jp +nahari.kochi.jp +nakamura.kochi.jp +nankoku.kochi.jp +nishitosa.kochi.jp +niyodogawa.kochi.jp +ochi.kochi.jp +okawa.kochi.jp +otoyo.kochi.jp +otsuki.kochi.jp +sakawa.kochi.jp +sukumo.kochi.jp +susaki.kochi.jp +tosa.kochi.jp +tosashimizu.kochi.jp +toyo.kochi.jp +tsuno.kochi.jp +umaji.kochi.jp +yasuda.kochi.jp +yusuhara.kochi.jp +amakusa.kumamoto.jp +arao.kumamoto.jp +aso.kumamoto.jp +choyo.kumamoto.jp +gyokuto.kumamoto.jp +kamiamakusa.kumamoto.jp +kikuchi.kumamoto.jp +kumamoto.kumamoto.jp +mashiki.kumamoto.jp +mifune.kumamoto.jp +minamata.kumamoto.jp +minamioguni.kumamoto.jp +nagasu.kumamoto.jp +nishihara.kumamoto.jp +oguni.kumamoto.jp +ozu.kumamoto.jp +sumoto.kumamoto.jp +takamori.kumamoto.jp +uki.kumamoto.jp +uto.kumamoto.jp +yamaga.kumamoto.jp +yamato.kumamoto.jp +yatsushiro.kumamoto.jp +ayabe.kyoto.jp +fukuchiyama.kyoto.jp +higashiyama.kyoto.jp +ide.kyoto.jp +ine.kyoto.jp +joyo.kyoto.jp +kameoka.kyoto.jp +kamo.kyoto.jp +kita.kyoto.jp +kizu.kyoto.jp +kumiyama.kyoto.jp +kyotamba.kyoto.jp +kyotanabe.kyoto.jp +kyotango.kyoto.jp +maizuru.kyoto.jp +minami.kyoto.jp +minamiyamashiro.kyoto.jp +miyazu.kyoto.jp +muko.kyoto.jp +nagaokakyo.kyoto.jp +nakagyo.kyoto.jp +nantan.kyoto.jp +oyamazaki.kyoto.jp +sakyo.kyoto.jp +seika.kyoto.jp +tanabe.kyoto.jp +uji.kyoto.jp +ujitawara.kyoto.jp +wazuka.kyoto.jp +yamashina.kyoto.jp +yawata.kyoto.jp +asahi.mie.jp +inabe.mie.jp +ise.mie.jp +kameyama.mie.jp +kawagoe.mie.jp +kiho.mie.jp +kisosaki.mie.jp +kiwa.mie.jp +komono.mie.jp +kumano.mie.jp +kuwana.mie.jp +matsusaka.mie.jp +meiwa.mie.jp +mihama.mie.jp +minamiise.mie.jp +misugi.mie.jp +miyama.mie.jp +nabari.mie.jp +shima.mie.jp +suzuka.mie.jp +tado.mie.jp +taiki.mie.jp +taki.mie.jp +tamaki.mie.jp +toba.mie.jp +tsu.mie.jp +udono.mie.jp +ureshino.mie.jp +watarai.mie.jp +yokkaichi.mie.jp +furukawa.miyagi.jp +higashimatsushima.miyagi.jp +ishinomaki.miyagi.jp +iwanuma.miyagi.jp +kakuda.miyagi.jp +kami.miyagi.jp +kawasaki.miyagi.jp +marumori.miyagi.jp +matsushima.miyagi.jp +minamisanriku.miyagi.jp +misato.miyagi.jp +murata.miyagi.jp +natori.miyagi.jp +ogawara.miyagi.jp +ohira.miyagi.jp +onagawa.miyagi.jp +osaki.miyagi.jp +rifu.miyagi.jp +semine.miyagi.jp +shibata.miyagi.jp +shichikashuku.miyagi.jp +shikama.miyagi.jp +shiogama.miyagi.jp +shiroishi.miyagi.jp +tagajo.miyagi.jp +taiwa.miyagi.jp +tome.miyagi.jp +tomiya.miyagi.jp +wakuya.miyagi.jp +watari.miyagi.jp +yamamoto.miyagi.jp +zao.miyagi.jp +aya.miyazaki.jp +ebino.miyazaki.jp +gokase.miyazaki.jp +hyuga.miyazaki.jp +kadogawa.miyazaki.jp +kawaminami.miyazaki.jp +kijo.miyazaki.jp +kitagawa.miyazaki.jp +kitakata.miyazaki.jp +kitaura.miyazaki.jp +kobayashi.miyazaki.jp +kunitomi.miyazaki.jp +kushima.miyazaki.jp +mimata.miyazaki.jp +miyakonojo.miyazaki.jp +miyazaki.miyazaki.jp +morotsuka.miyazaki.jp +nichinan.miyazaki.jp +nishimera.miyazaki.jp +nobeoka.miyazaki.jp +saito.miyazaki.jp +shiiba.miyazaki.jp +shintomi.miyazaki.jp +takaharu.miyazaki.jp +takanabe.miyazaki.jp +takazaki.miyazaki.jp +tsuno.miyazaki.jp +achi.nagano.jp +agematsu.nagano.jp +anan.nagano.jp +aoki.nagano.jp +asahi.nagano.jp +azumino.nagano.jp +chikuhoku.nagano.jp +chikuma.nagano.jp +chino.nagano.jp +fujimi.nagano.jp +hakuba.nagano.jp +hara.nagano.jp +hiraya.nagano.jp +iida.nagano.jp +iijima.nagano.jp +iiyama.nagano.jp +iizuna.nagano.jp +ikeda.nagano.jp +ikusaka.nagano.jp +ina.nagano.jp +karuizawa.nagano.jp +kawakami.nagano.jp +kiso.nagano.jp +kisofukushima.nagano.jp +kitaaiki.nagano.jp +komagane.nagano.jp +komoro.nagano.jp +matsukawa.nagano.jp +matsumoto.nagano.jp +miasa.nagano.jp +minamiaiki.nagano.jp +minamimaki.nagano.jp +minamiminowa.nagano.jp +minowa.nagano.jp +miyada.nagano.jp +miyota.nagano.jp +mochizuki.nagano.jp +nagano.nagano.jp +nagawa.nagano.jp +nagiso.nagano.jp +nakagawa.nagano.jp +nakano.nagano.jp +nozawaonsen.nagano.jp +obuse.nagano.jp +ogawa.nagano.jp +okaya.nagano.jp +omachi.nagano.jp +omi.nagano.jp +ookuwa.nagano.jp +ooshika.nagano.jp +otaki.nagano.jp +otari.nagano.jp +sakae.nagano.jp +sakaki.nagano.jp +saku.nagano.jp +sakuho.nagano.jp +shimosuwa.nagano.jp +shinanomachi.nagano.jp +shiojiri.nagano.jp +suwa.nagano.jp +suzaka.nagano.jp +takagi.nagano.jp +takamori.nagano.jp +takayama.nagano.jp +tateshina.nagano.jp +tatsuno.nagano.jp +togakushi.nagano.jp +togura.nagano.jp +tomi.nagano.jp +ueda.nagano.jp +wada.nagano.jp +yamagata.nagano.jp +yamanouchi.nagano.jp +yasaka.nagano.jp +yasuoka.nagano.jp +chijiwa.nagasaki.jp +futsu.nagasaki.jp +goto.nagasaki.jp +hasami.nagasaki.jp +hirado.nagasaki.jp +iki.nagasaki.jp +isahaya.nagasaki.jp +kawatana.nagasaki.jp +kuchinotsu.nagasaki.jp +matsuura.nagasaki.jp +nagasaki.nagasaki.jp +obama.nagasaki.jp +omura.nagasaki.jp +oseto.nagasaki.jp +saikai.nagasaki.jp +sasebo.nagasaki.jp +seihi.nagasaki.jp +shimabara.nagasaki.jp +shinkamigoto.nagasaki.jp +togitsu.nagasaki.jp +tsushima.nagasaki.jp +unzen.nagasaki.jp +ando.nara.jp +gose.nara.jp +heguri.nara.jp +higashiyoshino.nara.jp +ikaruga.nara.jp +ikoma.nara.jp +kamikitayama.nara.jp +kanmaki.nara.jp +kashiba.nara.jp +kashihara.nara.jp +katsuragi.nara.jp +kawai.nara.jp +kawakami.nara.jp +kawanishi.nara.jp +koryo.nara.jp +kurotaki.nara.jp +mitsue.nara.jp +miyake.nara.jp +nara.nara.jp +nosegawa.nara.jp +oji.nara.jp +ouda.nara.jp +oyodo.nara.jp +sakurai.nara.jp +sango.nara.jp +shimoichi.nara.jp +shimokitayama.nara.jp +shinjo.nara.jp +soni.nara.jp +takatori.nara.jp +tawaramoto.nara.jp +tenkawa.nara.jp +tenri.nara.jp +uda.nara.jp +yamatokoriyama.nara.jp +yamatotakada.nara.jp +yamazoe.nara.jp +yoshino.nara.jp +aga.niigata.jp +agano.niigata.jp +gosen.niigata.jp +itoigawa.niigata.jp +izumozaki.niigata.jp +joetsu.niigata.jp +kamo.niigata.jp +kariwa.niigata.jp +kashiwazaki.niigata.jp +minamiuonuma.niigata.jp +mitsuke.niigata.jp +muika.niigata.jp +murakami.niigata.jp +myoko.niigata.jp +nagaoka.niigata.jp +niigata.niigata.jp +ojiya.niigata.jp +omi.niigata.jp +sado.niigata.jp +sanjo.niigata.jp +seiro.niigata.jp +seirou.niigata.jp +sekikawa.niigata.jp +shibata.niigata.jp +tagami.niigata.jp +tainai.niigata.jp +tochio.niigata.jp +tokamachi.niigata.jp +tsubame.niigata.jp +tsunan.niigata.jp +uonuma.niigata.jp +yahiko.niigata.jp +yoita.niigata.jp +yuzawa.niigata.jp +beppu.oita.jp +bungoono.oita.jp +bungotakada.oita.jp +hasama.oita.jp +hiji.oita.jp +himeshima.oita.jp +hita.oita.jp +kamitsue.oita.jp +kokonoe.oita.jp +kuju.oita.jp +kunisaki.oita.jp +kusu.oita.jp +oita.oita.jp +saiki.oita.jp +taketa.oita.jp +tsukumi.oita.jp +usa.oita.jp +usuki.oita.jp +yufu.oita.jp +akaiwa.okayama.jp +asakuchi.okayama.jp +bizen.okayama.jp +hayashima.okayama.jp +ibara.okayama.jp +kagamino.okayama.jp +kasaoka.okayama.jp +kibichuo.okayama.jp +kumenan.okayama.jp +kurashiki.okayama.jp +maniwa.okayama.jp +misaki.okayama.jp +nagi.okayama.jp +niimi.okayama.jp +nishiawakura.okayama.jp +okayama.okayama.jp +satosho.okayama.jp +setouchi.okayama.jp +shinjo.okayama.jp +shoo.okayama.jp +soja.okayama.jp +takahashi.okayama.jp +tamano.okayama.jp +tsuyama.okayama.jp +wake.okayama.jp +yakage.okayama.jp +aguni.okinawa.jp +ginowan.okinawa.jp +ginoza.okinawa.jp +gushikami.okinawa.jp +haebaru.okinawa.jp +higashi.okinawa.jp +hirara.okinawa.jp +iheya.okinawa.jp +ishigaki.okinawa.jp +ishikawa.okinawa.jp +itoman.okinawa.jp +izena.okinawa.jp +kadena.okinawa.jp +kin.okinawa.jp +kitadaito.okinawa.jp +kitanakagusuku.okinawa.jp +kumejima.okinawa.jp +kunigami.okinawa.jp +minamidaito.okinawa.jp +motobu.okinawa.jp +nago.okinawa.jp +naha.okinawa.jp +nakagusuku.okinawa.jp +nakijin.okinawa.jp +nanjo.okinawa.jp +nishihara.okinawa.jp +ogimi.okinawa.jp +okinawa.okinawa.jp +onna.okinawa.jp +shimoji.okinawa.jp +taketomi.okinawa.jp +tarama.okinawa.jp +tokashiki.okinawa.jp +tomigusuku.okinawa.jp +tonaki.okinawa.jp +urasoe.okinawa.jp +uruma.okinawa.jp +yaese.okinawa.jp +yomitan.okinawa.jp +yonabaru.okinawa.jp +yonaguni.okinawa.jp +zamami.okinawa.jp +abeno.osaka.jp +chihayaakasaka.osaka.jp +chuo.osaka.jp +daito.osaka.jp +fujiidera.osaka.jp +habikino.osaka.jp +hannan.osaka.jp +higashiosaka.osaka.jp +higashisumiyoshi.osaka.jp +higashiyodogawa.osaka.jp +hirakata.osaka.jp +ibaraki.osaka.jp +ikeda.osaka.jp +izumi.osaka.jp +izumiotsu.osaka.jp +izumisano.osaka.jp +kadoma.osaka.jp +kaizuka.osaka.jp +kanan.osaka.jp +kashiwara.osaka.jp +katano.osaka.jp +kawachinagano.osaka.jp +kishiwada.osaka.jp +kita.osaka.jp +kumatori.osaka.jp +matsubara.osaka.jp +minato.osaka.jp +minoh.osaka.jp +misaki.osaka.jp +moriguchi.osaka.jp +neyagawa.osaka.jp +nishi.osaka.jp +nose.osaka.jp +osakasayama.osaka.jp +sakai.osaka.jp +sayama.osaka.jp +sennan.osaka.jp +settsu.osaka.jp +shijonawate.osaka.jp +shimamoto.osaka.jp +suita.osaka.jp +tadaoka.osaka.jp +taishi.osaka.jp +tajiri.osaka.jp +takaishi.osaka.jp +takatsuki.osaka.jp +tondabayashi.osaka.jp +toyonaka.osaka.jp +toyono.osaka.jp +yao.osaka.jp +ariake.saga.jp +arita.saga.jp +fukudomi.saga.jp +genkai.saga.jp +hamatama.saga.jp +hizen.saga.jp +imari.saga.jp +kamimine.saga.jp +kanzaki.saga.jp +karatsu.saga.jp +kashima.saga.jp +kitagata.saga.jp +kitahata.saga.jp +kiyama.saga.jp +kouhoku.saga.jp +kyuragi.saga.jp +nishiarita.saga.jp +ogi.saga.jp +omachi.saga.jp +ouchi.saga.jp +saga.saga.jp +shiroishi.saga.jp +taku.saga.jp +tara.saga.jp +tosu.saga.jp +yoshinogari.saga.jp +arakawa.saitama.jp +asaka.saitama.jp +chichibu.saitama.jp +fujimi.saitama.jp +fujimino.saitama.jp +fukaya.saitama.jp +hanno.saitama.jp +hanyu.saitama.jp +hasuda.saitama.jp +hatogaya.saitama.jp +hatoyama.saitama.jp +hidaka.saitama.jp +higashichichibu.saitama.jp +higashimatsuyama.saitama.jp +honjo.saitama.jp +ina.saitama.jp +iruma.saitama.jp +iwatsuki.saitama.jp +kamiizumi.saitama.jp +kamikawa.saitama.jp +kamisato.saitama.jp +kasukabe.saitama.jp +kawagoe.saitama.jp +kawaguchi.saitama.jp +kawajima.saitama.jp +kazo.saitama.jp +kitamoto.saitama.jp +koshigaya.saitama.jp +kounosu.saitama.jp +kuki.saitama.jp +kumagaya.saitama.jp +matsubushi.saitama.jp +minano.saitama.jp +misato.saitama.jp +miyashiro.saitama.jp +miyoshi.saitama.jp +moroyama.saitama.jp +nagatoro.saitama.jp +namegawa.saitama.jp +niiza.saitama.jp +ogano.saitama.jp +ogawa.saitama.jp +ogose.saitama.jp +okegawa.saitama.jp +omiya.saitama.jp +otaki.saitama.jp +ranzan.saitama.jp +ryokami.saitama.jp +saitama.saitama.jp +sakado.saitama.jp +satte.saitama.jp +sayama.saitama.jp +shiki.saitama.jp +shiraoka.saitama.jp +soka.saitama.jp +sugito.saitama.jp +toda.saitama.jp +tokigawa.saitama.jp +tokorozawa.saitama.jp +tsurugashima.saitama.jp +urawa.saitama.jp +warabi.saitama.jp +yashio.saitama.jp +yokoze.saitama.jp +yono.saitama.jp +yorii.saitama.jp +yoshida.saitama.jp +yoshikawa.saitama.jp +yoshimi.saitama.jp +aisho.shiga.jp +gamo.shiga.jp +higashiomi.shiga.jp +hikone.shiga.jp +koka.shiga.jp +konan.shiga.jp +kosei.shiga.jp +koto.shiga.jp +kusatsu.shiga.jp +maibara.shiga.jp +moriyama.shiga.jp +nagahama.shiga.jp +nishiazai.shiga.jp +notogawa.shiga.jp +omihachiman.shiga.jp +otsu.shiga.jp +ritto.shiga.jp +ryuoh.shiga.jp +takashima.shiga.jp +takatsuki.shiga.jp +torahime.shiga.jp +toyosato.shiga.jp +yasu.shiga.jp +akagi.shimane.jp +ama.shimane.jp +gotsu.shimane.jp +hamada.shimane.jp +higashiizumo.shimane.jp +hikawa.shimane.jp +hikimi.shimane.jp +izumo.shimane.jp +kakinoki.shimane.jp +masuda.shimane.jp +matsue.shimane.jp +misato.shimane.jp +nishinoshima.shimane.jp +ohda.shimane.jp +okinoshima.shimane.jp +okuizumo.shimane.jp +shimane.shimane.jp +tamayu.shimane.jp +tsuwano.shimane.jp +unnan.shimane.jp +yakumo.shimane.jp +yasugi.shimane.jp +yatsuka.shimane.jp +arai.shizuoka.jp +atami.shizuoka.jp +fuji.shizuoka.jp +fujieda.shizuoka.jp +fujikawa.shizuoka.jp +fujinomiya.shizuoka.jp +fukuroi.shizuoka.jp +gotemba.shizuoka.jp +haibara.shizuoka.jp +hamamatsu.shizuoka.jp +higashiizu.shizuoka.jp +ito.shizuoka.jp +iwata.shizuoka.jp +izu.shizuoka.jp +izunokuni.shizuoka.jp +kakegawa.shizuoka.jp +kannami.shizuoka.jp +kawanehon.shizuoka.jp +kawazu.shizuoka.jp +kikugawa.shizuoka.jp +kosai.shizuoka.jp +makinohara.shizuoka.jp +matsuzaki.shizuoka.jp +minamiizu.shizuoka.jp +mishima.shizuoka.jp +morimachi.shizuoka.jp +nishiizu.shizuoka.jp +numazu.shizuoka.jp +omaezaki.shizuoka.jp +shimada.shizuoka.jp +shimizu.shizuoka.jp +shimoda.shizuoka.jp +shizuoka.shizuoka.jp +susono.shizuoka.jp +yaizu.shizuoka.jp +yoshida.shizuoka.jp +ashikaga.tochigi.jp +bato.tochigi.jp +haga.tochigi.jp +ichikai.tochigi.jp +iwafune.tochigi.jp +kaminokawa.tochigi.jp +kanuma.tochigi.jp +karasuyama.tochigi.jp +kuroiso.tochigi.jp +mashiko.tochigi.jp +mibu.tochigi.jp +moka.tochigi.jp +motegi.tochigi.jp +nasu.tochigi.jp +nasushiobara.tochigi.jp +nikko.tochigi.jp +nishikata.tochigi.jp +nogi.tochigi.jp +ohira.tochigi.jp +ohtawara.tochigi.jp +oyama.tochigi.jp +sakura.tochigi.jp +sano.tochigi.jp +shimotsuke.tochigi.jp +shioya.tochigi.jp +takanezawa.tochigi.jp +tochigi.tochigi.jp +tsuga.tochigi.jp +ujiie.tochigi.jp +utsunomiya.tochigi.jp +yaita.tochigi.jp +aizumi.tokushima.jp +anan.tokushima.jp +ichiba.tokushima.jp +itano.tokushima.jp +kainan.tokushima.jp +komatsushima.tokushima.jp +matsushige.tokushima.jp +mima.tokushima.jp +minami.tokushima.jp +miyoshi.tokushima.jp +mugi.tokushima.jp +nakagawa.tokushima.jp +naruto.tokushima.jp +sanagochi.tokushima.jp +shishikui.tokushima.jp +tokushima.tokushima.jp +wajiki.tokushima.jp +adachi.tokyo.jp +akiruno.tokyo.jp +akishima.tokyo.jp +aogashima.tokyo.jp +arakawa.tokyo.jp +bunkyo.tokyo.jp +chiyoda.tokyo.jp +chofu.tokyo.jp +chuo.tokyo.jp +edogawa.tokyo.jp +fuchu.tokyo.jp +fussa.tokyo.jp +hachijo.tokyo.jp +hachioji.tokyo.jp +hamura.tokyo.jp +higashikurume.tokyo.jp +higashimurayama.tokyo.jp +higashiyamato.tokyo.jp +hino.tokyo.jp +hinode.tokyo.jp +hinohara.tokyo.jp +inagi.tokyo.jp +itabashi.tokyo.jp +katsushika.tokyo.jp +kita.tokyo.jp +kiyose.tokyo.jp +kodaira.tokyo.jp +koganei.tokyo.jp +kokubunji.tokyo.jp +komae.tokyo.jp +koto.tokyo.jp +kouzushima.tokyo.jp +kunitachi.tokyo.jp +machida.tokyo.jp +meguro.tokyo.jp +minato.tokyo.jp +mitaka.tokyo.jp +mizuho.tokyo.jp +musashimurayama.tokyo.jp +musashino.tokyo.jp +nakano.tokyo.jp +nerima.tokyo.jp +ogasawara.tokyo.jp +okutama.tokyo.jp +ome.tokyo.jp +oshima.tokyo.jp +ota.tokyo.jp +setagaya.tokyo.jp +shibuya.tokyo.jp +shinagawa.tokyo.jp +shinjuku.tokyo.jp +suginami.tokyo.jp +sumida.tokyo.jp +tachikawa.tokyo.jp +taito.tokyo.jp +tama.tokyo.jp +toshima.tokyo.jp +chizu.tottori.jp +hino.tottori.jp +kawahara.tottori.jp +koge.tottori.jp +kotoura.tottori.jp +misasa.tottori.jp +nanbu.tottori.jp +nichinan.tottori.jp +sakaiminato.tottori.jp +tottori.tottori.jp +wakasa.tottori.jp +yazu.tottori.jp +yonago.tottori.jp +asahi.toyama.jp +fuchu.toyama.jp +fukumitsu.toyama.jp +funahashi.toyama.jp +himi.toyama.jp +imizu.toyama.jp +inami.toyama.jp +johana.toyama.jp +kamiichi.toyama.jp +kurobe.toyama.jp +nakaniikawa.toyama.jp +namerikawa.toyama.jp +nanto.toyama.jp +nyuzen.toyama.jp +oyabe.toyama.jp +taira.toyama.jp +takaoka.toyama.jp +tateyama.toyama.jp +toga.toyama.jp +tonami.toyama.jp +toyama.toyama.jp +unazuki.toyama.jp +uozu.toyama.jp +yamada.toyama.jp +arida.wakayama.jp +aridagawa.wakayama.jp +gobo.wakayama.jp +hashimoto.wakayama.jp +hidaka.wakayama.jp +hirogawa.wakayama.jp +inami.wakayama.jp +iwade.wakayama.jp +kainan.wakayama.jp +kamitonda.wakayama.jp +katsuragi.wakayama.jp +kimino.wakayama.jp +kinokawa.wakayama.jp +kitayama.wakayama.jp +koya.wakayama.jp +koza.wakayama.jp +kozagawa.wakayama.jp +kudoyama.wakayama.jp +kushimoto.wakayama.jp +mihama.wakayama.jp +misato.wakayama.jp +nachikatsuura.wakayama.jp +shingu.wakayama.jp +shirahama.wakayama.jp +taiji.wakayama.jp +tanabe.wakayama.jp +wakayama.wakayama.jp +yuasa.wakayama.jp +yura.wakayama.jp +asahi.yamagata.jp +funagata.yamagata.jp +higashine.yamagata.jp +iide.yamagata.jp +kahoku.yamagata.jp +kaminoyama.yamagata.jp +kaneyama.yamagata.jp +kawanishi.yamagata.jp +mamurogawa.yamagata.jp +mikawa.yamagata.jp +murayama.yamagata.jp +nagai.yamagata.jp +nakayama.yamagata.jp +nanyo.yamagata.jp +nishikawa.yamagata.jp +obanazawa.yamagata.jp +oe.yamagata.jp +oguni.yamagata.jp +ohkura.yamagata.jp +oishida.yamagata.jp +sagae.yamagata.jp +sakata.yamagata.jp +sakegawa.yamagata.jp +shinjo.yamagata.jp +shirataka.yamagata.jp +shonai.yamagata.jp +takahata.yamagata.jp +tendo.yamagata.jp +tozawa.yamagata.jp +tsuruoka.yamagata.jp +yamagata.yamagata.jp +yamanobe.yamagata.jp +yonezawa.yamagata.jp +yuza.yamagata.jp +abu.yamaguchi.jp +hagi.yamaguchi.jp +hikari.yamaguchi.jp +hofu.yamaguchi.jp +iwakuni.yamaguchi.jp +kudamatsu.yamaguchi.jp +mitou.yamaguchi.jp +nagato.yamaguchi.jp +oshima.yamaguchi.jp +shimonoseki.yamaguchi.jp +shunan.yamaguchi.jp +tabuse.yamaguchi.jp +tokuyama.yamaguchi.jp +toyota.yamaguchi.jp +ube.yamaguchi.jp +yuu.yamaguchi.jp +chuo.yamanashi.jp +doshi.yamanashi.jp +fuefuki.yamanashi.jp +fujikawa.yamanashi.jp +fujikawaguchiko.yamanashi.jp +fujiyoshida.yamanashi.jp +hayakawa.yamanashi.jp +hokuto.yamanashi.jp +ichikawamisato.yamanashi.jp +kai.yamanashi.jp +kofu.yamanashi.jp +koshu.yamanashi.jp +kosuge.yamanashi.jp +minami-alps.yamanashi.jp +minobu.yamanashi.jp +nakamichi.yamanashi.jp +nanbu.yamanashi.jp +narusawa.yamanashi.jp +nirasaki.yamanashi.jp +nishikatsura.yamanashi.jp +oshino.yamanashi.jp +otsuki.yamanashi.jp +showa.yamanashi.jp +tabayama.yamanashi.jp +tsuru.yamanashi.jp +uenohara.yamanashi.jp +yamanakako.yamanashi.jp +yamanashi.yamanashi.jp + +ke +ac.ke +co.ke +go.ke +info.ke +me.ke +mobi.ke +ne.ke +or.ke +sc.ke + +kg +org.kg +net.kg +com.kg +edu.kg +gov.kg +mil.kg + +*.kh + +ki +edu.ki +biz.ki +net.ki +org.ki +gov.ki +info.ki +com.ki + +km +org.km +nom.km +gov.km +prd.km +tm.km +edu.km +mil.km +ass.km +com.km +coop.km +asso.km +presse.km +medecin.km +notaires.km +pharmaciens.km +veterinaire.km +gouv.km + +kn +net.kn +org.kn +edu.kn +gov.kn + +kp +com.kp +edu.kp +gov.kp +org.kp +rep.kp +tra.kp + +kr +ac.kr +co.kr +es.kr +go.kr +hs.kr +kg.kr +mil.kr +ms.kr +ne.kr +or.kr +pe.kr +re.kr +sc.kr +busan.kr +chungbuk.kr +chungnam.kr +daegu.kr +daejeon.kr +gangwon.kr +gwangju.kr +gyeongbuk.kr +gyeonggi.kr +gyeongnam.kr +incheon.kr +jeju.kr +jeonbuk.kr +jeonnam.kr +seoul.kr +ulsan.kr + +kw +com.kw +edu.kw +emb.kw +gov.kw +ind.kw +net.kw +org.kw + +ky +com.ky +edu.ky +net.ky +org.ky + +kz +org.kz +edu.kz +net.kz +gov.kz +mil.kz +com.kz + +la +int.la +net.la +info.la +edu.la +gov.la +per.la +com.la +org.la + +lb +com.lb +edu.lb +gov.lb +net.lb +org.lb + +lc +com.lc +net.lc +co.lc +org.lc +edu.lc +gov.lc + +li + +lk +gov.lk +sch.lk +net.lk +int.lk +com.lk +org.lk +edu.lk +ngo.lk +soc.lk +web.lk +ltd.lk +assn.lk +grp.lk +hotel.lk +ac.lk + +lr +com.lr +edu.lr +gov.lr +org.lr +net.lr + +ls +ac.ls +biz.ls +co.ls +edu.ls +gov.ls +info.ls +net.ls +org.ls +sc.ls + +lt +gov.lt + +lu + +lv +com.lv +edu.lv +gov.lv +org.lv +mil.lv +id.lv +net.lv +asn.lv +conf.lv + +ly +com.ly +net.ly +gov.ly +plc.ly +edu.ly +sch.ly +med.ly +org.ly +id.ly + +ma +co.ma +net.ma +gov.ma +org.ma +ac.ma +press.ma + +mc +tm.mc +asso.mc + +md + +me +co.me +net.me +org.me +edu.me +ac.me +gov.me +its.me +priv.me + +mg +org.mg +nom.mg +gov.mg +prd.mg +tm.mg +edu.mg +mil.mg +com.mg +co.mg + +mh + +mil + +mk +com.mk +org.mk +net.mk +edu.mk +gov.mk +inf.mk +name.mk + +ml +com.ml +edu.ml +gouv.ml +gov.ml +net.ml +org.ml +presse.ml + +*.mm + +mn +gov.mn +edu.mn +org.mn + +mo +com.mo +net.mo +org.mo +edu.mo +gov.mo + +mobi + +mp + +mq + +mr +gov.mr + +ms +com.ms +edu.ms +gov.ms +net.ms +org.ms + +mt +com.mt +edu.mt +net.mt +org.mt + +mu +com.mu +net.mu +org.mu +gov.mu +ac.mu +co.mu +or.mu + +museum + +mv +aero.mv +biz.mv +com.mv +coop.mv +edu.mv +gov.mv +info.mv +int.mv +mil.mv +museum.mv +name.mv +net.mv +org.mv +pro.mv + +mw +ac.mw +biz.mw +co.mw +com.mw +coop.mw +edu.mw +gov.mw +int.mw +museum.mw +net.mw +org.mw + +mx +com.mx +org.mx +gob.mx +edu.mx +net.mx + +my +biz.my +com.my +edu.my +gov.my +mil.my +name.my +net.my +org.my + +mz +ac.mz +adv.mz +co.mz +edu.mz +gov.mz +mil.mz +net.mz +org.mz + +na +info.na +pro.na +name.na +school.na +or.na +dr.na +us.na +mx.na +ca.na +in.na +cc.na +tv.na +ws.na +mobi.na +co.na +com.na +org.na + +name + +nc +asso.nc +nom.nc + +ne + +net + +nf +com.nf +net.nf +per.nf +rec.nf +web.nf +arts.nf +firm.nf +info.nf +other.nf +store.nf + +ng +com.ng +edu.ng +gov.ng +i.ng +mil.ng +mobi.ng +name.ng +net.ng +org.ng +sch.ng + +ni +ac.ni +biz.ni +co.ni +com.ni +edu.ni +gob.ni +in.ni +info.ni +int.ni +mil.ni +net.ni +nom.ni +org.ni +web.ni + +nl + +no +fhs.no +vgs.no +fylkesbibl.no +folkebibl.no +museum.no +idrett.no +priv.no +mil.no +stat.no +dep.no +kommune.no +herad.no +aa.no +ah.no +bu.no +fm.no +hl.no +hm.no +jan-mayen.no +mr.no +nl.no +nt.no +of.no +ol.no +oslo.no +rl.no +sf.no +st.no +svalbard.no +tm.no +tr.no +va.no +vf.no +gs.aa.no +gs.ah.no +gs.bu.no +gs.fm.no +gs.hl.no +gs.hm.no +gs.jan-mayen.no +gs.mr.no +gs.nl.no +gs.nt.no +gs.of.no +gs.ol.no +gs.oslo.no +gs.rl.no +gs.sf.no +gs.st.no +gs.svalbard.no +gs.tm.no +gs.tr.no +gs.va.no +gs.vf.no +akrehamn.no +åkrehamn.no +algard.no +ålgård.no +arna.no +brumunddal.no +bryne.no +bronnoysund.no +brønnøysund.no +drobak.no +drøbak.no +egersund.no +fetsund.no +floro.no +florø.no +fredrikstad.no +hokksund.no +honefoss.no +hønefoss.no +jessheim.no +jorpeland.no +jørpeland.no +kirkenes.no +kopervik.no +krokstadelva.no +langevag.no +langevåg.no +leirvik.no +mjondalen.no +mjøndalen.no +mo-i-rana.no +mosjoen.no +mosjøen.no +nesoddtangen.no +orkanger.no +osoyro.no +osøyro.no +raholt.no +råholt.no +sandnessjoen.no +sandnessjøen.no +skedsmokorset.no +slattum.no +spjelkavik.no +stathelle.no +stavern.no +stjordalshalsen.no +stjørdalshalsen.no +tananger.no +tranby.no +vossevangen.no +afjord.no +åfjord.no +agdenes.no +al.no +ål.no +alesund.no +ålesund.no +alstahaug.no +alta.no +áltá.no +alaheadju.no +álaheadju.no +alvdal.no +amli.no +åmli.no +amot.no +åmot.no +andebu.no +andoy.no +andøy.no +andasuolo.no +ardal.no +årdal.no +aremark.no +arendal.no +ås.no +aseral.no +åseral.no +asker.no +askim.no +askvoll.no +askoy.no +askøy.no +asnes.no +åsnes.no +audnedaln.no +aukra.no +aure.no +aurland.no +aurskog-holand.no +aurskog-høland.no +austevoll.no +austrheim.no +averoy.no +averøy.no +balestrand.no +ballangen.no +balat.no +bálát.no +balsfjord.no +bahccavuotna.no +báhccavuotna.no +bamble.no +bardu.no +beardu.no +beiarn.no +bajddar.no +bájddar.no +baidar.no +báidár.no +berg.no +bergen.no +berlevag.no +berlevåg.no +bearalvahki.no +bearalváhki.no +bindal.no +birkenes.no +bjarkoy.no +bjarkøy.no +bjerkreim.no +bjugn.no +bodo.no +bodø.no +badaddja.no +bådåddjå.no +budejju.no +bokn.no +bremanger.no +bronnoy.no +brønnøy.no +bygland.no +bykle.no +barum.no +bærum.no +bo.telemark.no +bø.telemark.no +bo.nordland.no +bø.nordland.no +bievat.no +bievát.no +bomlo.no +bømlo.no +batsfjord.no +båtsfjord.no +bahcavuotna.no +báhcavuotna.no +dovre.no +drammen.no +drangedal.no +dyroy.no +dyrøy.no +donna.no +dønna.no +eid.no +eidfjord.no +eidsberg.no +eidskog.no +eidsvoll.no +eigersund.no +elverum.no +enebakk.no +engerdal.no +etne.no +etnedal.no +evenes.no +evenassi.no +evenášši.no +evje-og-hornnes.no +farsund.no +fauske.no +fuossko.no +fuoisku.no +fedje.no +fet.no +finnoy.no +finnøy.no +fitjar.no +fjaler.no +fjell.no +flakstad.no +flatanger.no +flekkefjord.no +flesberg.no +flora.no +fla.no +flå.no +folldal.no +forsand.no +fosnes.no +frei.no +frogn.no +froland.no +frosta.no +frana.no +fræna.no +froya.no +frøya.no +fusa.no +fyresdal.no +forde.no +førde.no +gamvik.no +gangaviika.no +gáŋgaviika.no +gaular.no +gausdal.no +gildeskal.no +gildeskål.no +giske.no +gjemnes.no +gjerdrum.no +gjerstad.no +gjesdal.no +gjovik.no +gjøvik.no +gloppen.no +gol.no +gran.no +grane.no +granvin.no +gratangen.no +grimstad.no +grong.no +kraanghke.no +kråanghke.no +grue.no +gulen.no +hadsel.no +halden.no +halsa.no +hamar.no +hamaroy.no +habmer.no +hábmer.no +hapmir.no +hápmir.no +hammerfest.no +hammarfeasta.no +hámmárfeasta.no +haram.no +hareid.no +harstad.no +hasvik.no +aknoluokta.no +ákŋoluokta.no +hattfjelldal.no +aarborte.no +haugesund.no +hemne.no +hemnes.no +hemsedal.no +heroy.more-og-romsdal.no +herøy.møre-og-romsdal.no +heroy.nordland.no +herøy.nordland.no +hitra.no +hjartdal.no +hjelmeland.no +hobol.no +hobøl.no +hof.no +hol.no +hole.no +holmestrand.no +holtalen.no +holtålen.no +hornindal.no +horten.no +hurdal.no +hurum.no +hvaler.no +hyllestad.no +hagebostad.no +hægebostad.no +hoyanger.no +høyanger.no +hoylandet.no +høylandet.no +ha.no +hå.no +ibestad.no +inderoy.no +inderøy.no +iveland.no +jevnaker.no +jondal.no +jolster.no +jølster.no +karasjok.no +karasjohka.no +kárášjohka.no +karlsoy.no +galsa.no +gálsá.no +karmoy.no +karmøy.no +kautokeino.no +guovdageaidnu.no +klepp.no +klabu.no +klæbu.no +kongsberg.no +kongsvinger.no +kragero.no +kragerø.no +kristiansand.no +kristiansund.no +krodsherad.no +krødsherad.no +kvalsund.no +rahkkeravju.no +ráhkkerávju.no +kvam.no +kvinesdal.no +kvinnherad.no +kviteseid.no +kvitsoy.no +kvitsøy.no +kvafjord.no +kvæfjord.no +giehtavuoatna.no +kvanangen.no +kvænangen.no +navuotna.no +návuotna.no +kafjord.no +kåfjord.no +gaivuotna.no +gáivuotna.no +larvik.no +lavangen.no +lavagis.no +loabat.no +loabát.no +lebesby.no +davvesiida.no +leikanger.no +leirfjord.no +leka.no +leksvik.no +lenvik.no +leangaviika.no +leaŋgaviika.no +lesja.no +levanger.no +lier.no +lierne.no +lillehammer.no +lillesand.no +lindesnes.no +lindas.no +lindås.no +lom.no +loppa.no +lahppi.no +láhppi.no +lund.no +lunner.no +luroy.no +lurøy.no +luster.no +lyngdal.no +lyngen.no +ivgu.no +lardal.no +lerdal.no +lærdal.no +lodingen.no +lødingen.no +lorenskog.no +lørenskog.no +loten.no +løten.no +malvik.no +masoy.no +måsøy.no +muosat.no +muosát.no +mandal.no +marker.no +marnardal.no +masfjorden.no +meland.no +meldal.no +melhus.no +meloy.no +meløy.no +meraker.no +meråker.no +moareke.no +moåreke.no +midsund.no +midtre-gauldal.no +modalen.no +modum.no +molde.no +moskenes.no +moss.no +mosvik.no +malselv.no +målselv.no +malatvuopmi.no +málatvuopmi.no +namdalseid.no +aejrie.no +namsos.no +namsskogan.no +naamesjevuemie.no +nååmesjevuemie.no +laakesvuemie.no +nannestad.no +narvik.no +narviika.no +naustdal.no +nedre-eiker.no +nes.akershus.no +nes.buskerud.no +nesna.no +nesodden.no +nesseby.no +unjarga.no +unjárga.no +nesset.no +nissedal.no +nittedal.no +nord-aurdal.no +nord-fron.no +nord-odal.no +norddal.no +nordkapp.no +davvenjarga.no +davvenjárga.no +nordre-land.no +nordreisa.no +raisa.no +ráisa.no +nore-og-uvdal.no +notodden.no +naroy.no +nærøy.no +notteroy.no +nøtterøy.no +odda.no +oksnes.no +øksnes.no +oppdal.no +oppegard.no +oppegård.no +orkdal.no +orland.no +ørland.no +orskog.no +ørskog.no +orsta.no +ørsta.no +os.hedmark.no +os.hordaland.no +osen.no +osteroy.no +osterøy.no +ostre-toten.no +østre-toten.no +overhalla.no +ovre-eiker.no +øvre-eiker.no +oyer.no +øyer.no +oygarden.no +øygarden.no +oystre-slidre.no +øystre-slidre.no +porsanger.no +porsangu.no +porsáŋgu.no +porsgrunn.no +radoy.no +radøy.no +rakkestad.no +rana.no +ruovat.no +randaberg.no +rauma.no +rendalen.no +rennebu.no +rennesoy.no +rennesøy.no +rindal.no +ringebu.no +ringerike.no +ringsaker.no +rissa.no +risor.no +risør.no +roan.no +rollag.no +rygge.no +ralingen.no +rælingen.no +rodoy.no +rødøy.no +romskog.no +rømskog.no +roros.no +røros.no +rost.no +røst.no +royken.no +røyken.no +royrvik.no +røyrvik.no +rade.no +råde.no +salangen.no +siellak.no +saltdal.no +salat.no +sálát.no +sálat.no +samnanger.no +sande.more-og-romsdal.no +sande.møre-og-romsdal.no +sande.vestfold.no +sandefjord.no +sandnes.no +sandoy.no +sandøy.no +sarpsborg.no +sauda.no +sauherad.no +sel.no +selbu.no +selje.no +seljord.no +sigdal.no +siljan.no +sirdal.no +skaun.no +skedsmo.no +ski.no +skien.no +skiptvet.no +skjervoy.no +skjervøy.no +skierva.no +skiervá.no +skjak.no +skjåk.no +skodje.no +skanland.no +skånland.no +skanit.no +skánit.no +smola.no +smøla.no +snillfjord.no +snasa.no +snåsa.no +snoasa.no +snaase.no +snåase.no +sogndal.no +sokndal.no +sola.no +solund.no +songdalen.no +sortland.no +spydeberg.no +stange.no +stavanger.no +steigen.no +steinkjer.no +stjordal.no +stjørdal.no +stokke.no +stor-elvdal.no +stord.no +stordal.no +storfjord.no +omasvuotna.no +strand.no +stranda.no +stryn.no +sula.no +suldal.no +sund.no +sunndal.no +surnadal.no +sveio.no +svelvik.no +sykkylven.no +sogne.no +søgne.no +somna.no +sømna.no +sondre-land.no +søndre-land.no +sor-aurdal.no +sør-aurdal.no +sor-fron.no +sør-fron.no +sor-odal.no +sør-odal.no +sor-varanger.no +sør-varanger.no +matta-varjjat.no +mátta-várjjat.no +sorfold.no +sørfold.no +sorreisa.no +sørreisa.no +sorum.no +sørum.no +tana.no +deatnu.no +time.no +tingvoll.no +tinn.no +tjeldsund.no +dielddanuorri.no +tjome.no +tjøme.no +tokke.no +tolga.no +torsken.no +tranoy.no +tranøy.no +tromso.no +tromsø.no +tromsa.no +romsa.no +trondheim.no +troandin.no +trysil.no +trana.no +træna.no +trogstad.no +trøgstad.no +tvedestrand.no +tydal.no +tynset.no +tysfjord.no +divtasvuodna.no +divttasvuotna.no +tysnes.no +tysvar.no +tysvær.no +tonsberg.no +tønsberg.no +ullensaker.no +ullensvang.no +ulvik.no +utsira.no +vadso.no +vadsø.no +cahcesuolo.no +čáhcesuolo.no +vaksdal.no +valle.no +vang.no +vanylven.no +vardo.no +vardø.no +varggat.no +várggát.no +vefsn.no +vaapste.no +vega.no +vegarshei.no +vegårshei.no +vennesla.no +verdal.no +verran.no +vestby.no +vestnes.no +vestre-slidre.no +vestre-toten.no +vestvagoy.no +vestvågøy.no +vevelstad.no +vik.no +vikna.no +vindafjord.no +volda.no +voss.no +varoy.no +værøy.no +vagan.no +vågan.no +voagat.no +vagsoy.no +vågsøy.no +vaga.no +vågå.no +valer.ostfold.no +våler.østfold.no +valer.hedmark.no +våler.hedmark.no + +*.np + +nr +biz.nr +info.nr +gov.nr +edu.nr +org.nr +net.nr +com.nr + +nu + +nz +ac.nz +co.nz +cri.nz +geek.nz +gen.nz +govt.nz +health.nz +iwi.nz +kiwi.nz +maori.nz +mil.nz +māori.nz +net.nz +org.nz +parliament.nz +school.nz + +om +co.om +com.om +edu.om +gov.om +med.om +museum.om +net.om +org.om +pro.om + +onion + +org + +pa +ac.pa +gob.pa +com.pa +org.pa +sld.pa +edu.pa +net.pa +ing.pa +abo.pa +med.pa +nom.pa + +pe +edu.pe +gob.pe +nom.pe +mil.pe +org.pe +com.pe +net.pe + +pf +com.pf +org.pf +edu.pf + +*.pg + +ph +com.ph +net.ph +org.ph +gov.ph +edu.ph +ngo.ph +mil.ph +i.ph + + +pk +ac.pk +biz.pk +com.pk +edu.pk +fam.pk +gkp.pk +gob.pk +gog.pk +gok.pk +gon.pk +gop.pk +gos.pk +gov.pk +net.pk +org.pk +web.pk + +pl +com.pl +net.pl +org.pl +aid.pl +agro.pl +atm.pl +auto.pl +biz.pl +edu.pl +gmina.pl +gsm.pl +info.pl +mail.pl +miasta.pl +media.pl +mil.pl +nieruchomosci.pl +nom.pl +pc.pl +powiat.pl +priv.pl +realestate.pl +rel.pl +sex.pl +shop.pl +sklep.pl +sos.pl +szkola.pl +targi.pl +tm.pl +tourism.pl +travel.pl +turystyka.pl +gov.pl +ap.gov.pl +griw.gov.pl +ic.gov.pl +is.gov.pl +kmpsp.gov.pl +konsulat.gov.pl +kppsp.gov.pl +kwp.gov.pl +kwpsp.gov.pl +mup.gov.pl +mw.gov.pl +oia.gov.pl +oirm.gov.pl +oke.gov.pl +oow.gov.pl +oschr.gov.pl +oum.gov.pl +pa.gov.pl +pinb.gov.pl +piw.gov.pl +po.gov.pl +pr.gov.pl +psp.gov.pl +psse.gov.pl +pup.gov.pl +rzgw.gov.pl +sa.gov.pl +sdn.gov.pl +sko.gov.pl +so.gov.pl +sr.gov.pl +starostwo.gov.pl +ug.gov.pl +ugim.gov.pl +um.gov.pl +umig.gov.pl +upow.gov.pl +uppo.gov.pl +us.gov.pl +uw.gov.pl +uzs.gov.pl +wif.gov.pl +wiih.gov.pl +winb.gov.pl +wios.gov.pl +witd.gov.pl +wiw.gov.pl +wkz.gov.pl +wsa.gov.pl +wskr.gov.pl +wsse.gov.pl +wuoz.gov.pl +wzmiuw.gov.pl +zp.gov.pl +zpisdn.gov.pl +augustow.pl +babia-gora.pl +bedzin.pl +beskidy.pl +bialowieza.pl +bialystok.pl +bielawa.pl +bieszczady.pl +boleslawiec.pl +bydgoszcz.pl +bytom.pl +cieszyn.pl +czeladz.pl +czest.pl +dlugoleka.pl +elblag.pl +elk.pl +glogow.pl +gniezno.pl +gorlice.pl +grajewo.pl +ilawa.pl +jaworzno.pl +jelenia-gora.pl +jgora.pl +kalisz.pl +kazimierz-dolny.pl +karpacz.pl +kartuzy.pl +kaszuby.pl +katowice.pl +kepno.pl +ketrzyn.pl +klodzko.pl +kobierzyce.pl +kolobrzeg.pl +konin.pl +konskowola.pl +kutno.pl +lapy.pl +lebork.pl +legnica.pl +lezajsk.pl +limanowa.pl +lomza.pl +lowicz.pl +lubin.pl +lukow.pl +malbork.pl +malopolska.pl +mazowsze.pl +mazury.pl +mielec.pl +mielno.pl +mragowo.pl +naklo.pl +nowaruda.pl +nysa.pl +olawa.pl +olecko.pl +olkusz.pl +olsztyn.pl +opoczno.pl +opole.pl +ostroda.pl +ostroleka.pl +ostrowiec.pl +ostrowwlkp.pl +pila.pl +pisz.pl +podhale.pl +podlasie.pl +polkowice.pl +pomorze.pl +pomorskie.pl +prochowice.pl +pruszkow.pl +przeworsk.pl +pulawy.pl +radom.pl +rawa-maz.pl +rybnik.pl +rzeszow.pl +sanok.pl +sejny.pl +slask.pl +slupsk.pl +sosnowiec.pl +stalowa-wola.pl +skoczow.pl +starachowice.pl +stargard.pl +suwalki.pl +swidnica.pl +swiebodzin.pl +swinoujscie.pl +szczecin.pl +szczytno.pl +tarnobrzeg.pl +tgory.pl +turek.pl +tychy.pl +ustka.pl +walbrzych.pl +warmia.pl +warszawa.pl +waw.pl +wegrow.pl +wielun.pl +wlocl.pl +wloclawek.pl +wodzislaw.pl +wolomin.pl +wroclaw.pl +zachpomor.pl +zagan.pl +zarow.pl +zgora.pl +zgorzelec.pl + +pm + +pn +gov.pn +co.pn +org.pn +edu.pn +net.pn + +post + +pr +com.pr +net.pr +org.pr +gov.pr +edu.pr +isla.pr +pro.pr +biz.pr +info.pr +name.pr +est.pr +prof.pr +ac.pr + +pro +aaa.pro +aca.pro +acct.pro +avocat.pro +bar.pro +cpa.pro +eng.pro +jur.pro +law.pro +med.pro +recht.pro + +ps +edu.ps +gov.ps +sec.ps +plo.ps +com.ps +org.ps +net.ps + +pt +net.pt +gov.pt +org.pt +edu.pt +int.pt +publ.pt +com.pt +nome.pt + +pw +co.pw +ne.pw +or.pw +ed.pw +go.pw +belau.pw + +py +com.py +coop.py +edu.py +gov.py +mil.py +net.py +org.py + +qa +com.qa +edu.qa +gov.qa +mil.qa +name.qa +net.qa +org.qa +sch.qa + +re +asso.re +com.re +nom.re + +ro +arts.ro +com.ro +firm.ro +info.ro +nom.ro +nt.ro +org.ro +rec.ro +store.ro +tm.ro +www.ro + +rs +ac.rs +co.rs +edu.rs +gov.rs +in.rs +org.rs + +ru + +rw +ac.rw +co.rw +coop.rw +gov.rw +mil.rw +net.rw +org.rw + +sa +com.sa +net.sa +org.sa +gov.sa +med.sa +pub.sa +edu.sa +sch.sa + +sb +com.sb +edu.sb +gov.sb +net.sb +org.sb + +sc +com.sc +gov.sc +net.sc +org.sc +edu.sc + +sd +com.sd +net.sd +org.sd +edu.sd +med.sd +tv.sd +gov.sd +info.sd + +se +a.se +ac.se +b.se +bd.se +brand.se +c.se +d.se +e.se +f.se +fh.se +fhsk.se +fhv.se +g.se +h.se +i.se +k.se +komforb.se +kommunalforbund.se +komvux.se +l.se +lanbib.se +m.se +n.se +naturbruksgymn.se +o.se +org.se +p.se +parti.se +pp.se +press.se +r.se +s.se +t.se +tm.se +u.se +w.se +x.se +y.se +z.se + +sg +com.sg +net.sg +org.sg +gov.sg +edu.sg +per.sg + +sh +com.sh +net.sh +gov.sh +org.sh +mil.sh + +si + +sj + +sk + +sl +com.sl +net.sl +edu.sl +gov.sl +org.sl + +sm + +sn +art.sn +com.sn +edu.sn +gouv.sn +org.sn +perso.sn +univ.sn + +so +com.so +edu.so +gov.so +me.so +net.so +org.so + +sr + +ss +biz.ss +com.ss +edu.ss +gov.ss +me.ss +net.ss +org.ss +sch.ss + +st +co.st +com.st +consulado.st +edu.st +embaixada.st +mil.st +net.st +org.st +principe.st +saotome.st +store.st + +su + +sv +com.sv +edu.sv +gob.sv +org.sv +red.sv + +sx +gov.sx + +sy +edu.sy +gov.sy +net.sy +mil.sy +com.sy +org.sy + +sz +co.sz +ac.sz +org.sz + +tc + +td + +tel + +tf + +tg + +th +ac.th +co.th +go.th +in.th +mi.th +net.th +or.th + +tj +ac.tj +biz.tj +co.tj +com.tj +edu.tj +go.tj +gov.tj +int.tj +mil.tj +name.tj +net.tj +nic.tj +org.tj +test.tj +web.tj + +tk + +tl +gov.tl + +tm +com.tm +co.tm +org.tm +net.tm +nom.tm +gov.tm +mil.tm +edu.tm + +tn +com.tn +ens.tn +fin.tn +gov.tn +ind.tn +info.tn +intl.tn +mincom.tn +nat.tn +net.tn +org.tn +perso.tn +tourism.tn + +to +com.to +gov.to +net.to +org.to +edu.to +mil.to + +tr +av.tr +bbs.tr +bel.tr +biz.tr +com.tr +dr.tr +edu.tr +gen.tr +gov.tr +info.tr +mil.tr +k12.tr +kep.tr +name.tr +net.tr +org.tr +pol.tr +tel.tr +tsk.tr +tv.tr +web.tr +nc.tr +gov.nc.tr + +tt +co.tt +com.tt +org.tt +net.tt +biz.tt +info.tt +pro.tt +int.tt +coop.tt +jobs.tt +mobi.tt +travel.tt +museum.tt +aero.tt +name.tt +gov.tt +edu.tt + +tv + +tw +edu.tw +gov.tw +mil.tw +com.tw +net.tw +org.tw +idv.tw +game.tw +ebiz.tw +club.tw +網路.tw +組織.tw +商業.tw + +tz +ac.tz +co.tz +go.tz +hotel.tz +info.tz +me.tz +mil.tz +mobi.tz +ne.tz +or.tz +sc.tz +tv.tz + +ua +com.ua +edu.ua +gov.ua +in.ua +net.ua +org.ua +cherkassy.ua +cherkasy.ua +chernigov.ua +chernihiv.ua +chernivtsi.ua +chernovtsy.ua +ck.ua +cn.ua +cr.ua +crimea.ua +cv.ua +dn.ua +dnepropetrovsk.ua +dnipropetrovsk.ua +donetsk.ua +dp.ua +if.ua +ivano-frankivsk.ua +kh.ua +kharkiv.ua +kharkov.ua +kherson.ua +khmelnitskiy.ua +khmelnytskyi.ua +kiev.ua +kirovograd.ua +km.ua +kr.ua +kropyvnytskyi.ua +krym.ua +ks.ua +kv.ua +kyiv.ua +lg.ua +lt.ua +lugansk.ua +luhansk.ua +lutsk.ua +lv.ua +lviv.ua +mk.ua +mykolaiv.ua +nikolaev.ua +od.ua +odesa.ua +odessa.ua +pl.ua +poltava.ua +rivne.ua +rovno.ua +rv.ua +sb.ua +sebastopol.ua +sevastopol.ua +sm.ua +sumy.ua +te.ua +ternopil.ua +uz.ua +uzhgorod.ua +uzhhorod.ua +vinnica.ua +vinnytsia.ua +vn.ua +volyn.ua +yalta.ua +zakarpattia.ua +zaporizhzhe.ua +zaporizhzhia.ua +zhitomir.ua +zhytomyr.ua +zp.ua +zt.ua + +ug +co.ug +or.ug +ac.ug +sc.ug +go.ug +ne.ug +com.ug +org.ug + +uk +ac.uk +co.uk +gov.uk +ltd.uk +me.uk +net.uk +nhs.uk +org.uk +plc.uk +police.uk +*.sch.uk + +us +dni.us +fed.us +isa.us +kids.us +nsn.us +ak.us +al.us +ar.us +as.us +az.us +ca.us +co.us +ct.us +dc.us +de.us +fl.us +ga.us +gu.us +hi.us +ia.us +id.us +il.us +in.us +ks.us +ky.us +la.us +ma.us +md.us +me.us +mi.us +mn.us +mo.us +ms.us +mt.us +nc.us +nd.us +ne.us +nh.us +nj.us +nm.us +nv.us +ny.us +oh.us +ok.us +or.us +pa.us +pr.us +ri.us +sc.us +sd.us +tn.us +tx.us +ut.us +vi.us +vt.us +va.us +wa.us +wi.us +wv.us +wy.us +k12.ak.us +k12.al.us +k12.ar.us +k12.as.us +k12.az.us +k12.ca.us +k12.co.us +k12.ct.us +k12.dc.us +k12.fl.us +k12.ga.us +k12.gu.us +k12.ia.us +k12.id.us +k12.il.us +k12.in.us +k12.ks.us +k12.ky.us +k12.la.us +k12.ma.us +k12.md.us +k12.me.us +k12.mi.us +k12.mn.us +k12.mo.us +k12.ms.us +k12.mt.us +k12.nc.us +k12.ne.us +k12.nh.us +k12.nj.us +k12.nm.us +k12.nv.us +k12.ny.us +k12.oh.us +k12.ok.us +k12.or.us +k12.pa.us +k12.pr.us +k12.sc.us +k12.tn.us +k12.tx.us +k12.ut.us +k12.vi.us +k12.vt.us +k12.va.us +k12.wa.us +k12.wi.us +k12.wy.us +cc.ak.us +cc.al.us +cc.ar.us +cc.as.us +cc.az.us +cc.ca.us +cc.co.us +cc.ct.us +cc.dc.us +cc.de.us +cc.fl.us +cc.ga.us +cc.gu.us +cc.hi.us +cc.ia.us +cc.id.us +cc.il.us +cc.in.us +cc.ks.us +cc.ky.us +cc.la.us +cc.ma.us +cc.md.us +cc.me.us +cc.mi.us +cc.mn.us +cc.mo.us +cc.ms.us +cc.mt.us +cc.nc.us +cc.nd.us +cc.ne.us +cc.nh.us +cc.nj.us +cc.nm.us +cc.nv.us +cc.ny.us +cc.oh.us +cc.ok.us +cc.or.us +cc.pa.us +cc.pr.us +cc.ri.us +cc.sc.us +cc.sd.us +cc.tn.us +cc.tx.us +cc.ut.us +cc.vi.us +cc.vt.us +cc.va.us +cc.wa.us +cc.wi.us +cc.wv.us +cc.wy.us +lib.ak.us +lib.al.us +lib.ar.us +lib.as.us +lib.az.us +lib.ca.us +lib.co.us +lib.ct.us +lib.dc.us +lib.fl.us +lib.ga.us +lib.gu.us +lib.hi.us +lib.ia.us +lib.id.us +lib.il.us +lib.in.us +lib.ks.us +lib.ky.us +lib.la.us +lib.ma.us +lib.md.us +lib.me.us +lib.mi.us +lib.mn.us +lib.mo.us +lib.ms.us +lib.mt.us +lib.nc.us +lib.nd.us +lib.ne.us +lib.nh.us +lib.nj.us +lib.nm.us +lib.nv.us +lib.ny.us +lib.oh.us +lib.ok.us +lib.or.us +lib.pa.us +lib.pr.us +lib.ri.us +lib.sc.us +lib.sd.us +lib.tn.us +lib.tx.us +lib.ut.us +lib.vi.us +lib.vt.us +lib.va.us +lib.wa.us +lib.wi.us +lib.wy.us +pvt.k12.ma.us +chtr.k12.ma.us +paroch.k12.ma.us +ann-arbor.mi.us +cog.mi.us +dst.mi.us +eaton.mi.us +gen.mi.us +mus.mi.us +tec.mi.us +washtenaw.mi.us + +uy +com.uy +edu.uy +gub.uy +mil.uy +net.uy +org.uy + +uz +co.uz +com.uz +net.uz +org.uz + +va + +vc +com.vc +net.vc +org.vc +gov.vc +mil.vc +edu.vc + +ve +arts.ve +bib.ve +co.ve +com.ve +e12.ve +edu.ve +firm.ve +gob.ve +gov.ve +info.ve +int.ve +mil.ve +net.ve +nom.ve +org.ve +rar.ve +rec.ve +store.ve +tec.ve +web.ve + +vg + +vi +co.vi +com.vi +k12.vi +net.vi +org.vi + +vn +ac.vn +ai.vn +biz.vn +com.vn +edu.vn +gov.vn +health.vn +id.vn +info.vn +int.vn +io.vn +name.vn +net.vn +org.vn +pro.vn + +angiang.vn +bacgiang.vn +backan.vn +baclieu.vn +bacninh.vn +baria-vungtau.vn +bentre.vn +binhdinh.vn +binhduong.vn +binhphuoc.vn +binhthuan.vn +camau.vn +cantho.vn +caobang.vn +daklak.vn +daknong.vn +danang.vn +dienbien.vn +dongnai.vn +dongthap.vn +gialai.vn +hagiang.vn +haiduong.vn +haiphong.vn +hanam.vn +hanoi.vn +hatinh.vn +haugiang.vn +hoabinh.vn +hungyen.vn +khanhhoa.vn +kiengiang.vn +kontum.vn +laichau.vn +lamdong.vn +langson.vn +laocai.vn +longan.vn +namdinh.vn +nghean.vn +ninhbinh.vn +ninhthuan.vn +phutho.vn +phuyen.vn +quangbinh.vn +quangnam.vn +quangngai.vn +quangninh.vn +quangtri.vn +soctrang.vn +sonla.vn +tayninh.vn +thaibinh.vn +thainguyen.vn +thanhhoa.vn +thanhphohochiminh.vn +thuathienhue.vn +tiengiang.vn +travinh.vn +tuyenquang.vn +vinhlong.vn +vinhphuc.vn +yenbai.vn + +vu +com.vu +edu.vu +net.vu +org.vu + +wf + +ws +com.ws +net.ws +org.ws +gov.ws +edu.ws + +yt + + +امارات + +հայ + +বাংলা + +бг + +البحرين + +бел + +中国 + +中國 + +الجزائر + +مصر + +ею + +ευ + +موريتانيا + +გე + +ελ + +香港 +公司.香港 +教育.香港 +政府.香港 +個人.香港 +網絡.香港 +組織.香港 + +ಭಾರತ + +ଭାରତ + +ভাৰত + +भारतम् + +भारोत + +ڀارت + +ഭാരതം + +भारत + +بارت + +بھارت + +భారత్ + +ભારત + +ਭਾਰਤ + +ভারত + +இந்தியா + +ایران + +ايران + +عراق + +الاردن + +한국 + +қаз + +ລາວ + +ලංකා + +இலங்கை + +المغرب + +мкд + +мон + +澳門 + +澳门 + +مليسيا + +عمان + +پاکستان + +پاكستان + +فلسطين + +срб +пр.срб +орг.срб +обр.срб +од.срб +упр.срб +ак.срб + +рф + +قطر + +السعودية + +السعودیة + +السعودیۃ + +السعوديه + +سودان + +新加坡 + +சிங்கப்பூர் + +سورية + +سوريا + +ไทย +ศึกษา.ไทย +ธุรกิจ.ไทย +รัฐบาล.ไทย +ทหาร.ไทย +เน็ต.ไทย +องค์กร.ไทย + +تونس + +台灣 + +台湾 + +臺灣 + +укр + +اليمن + +xxx + +ye +com.ye +edu.ye +gov.ye +net.ye +mil.ye +org.ye + +ac.za +agric.za +alt.za +co.za +edu.za +gov.za +grondar.za +law.za +mil.za +net.za +ngo.za +nic.za +nis.za +nom.za +org.za +school.za +tm.za +web.za + +zm +ac.zm +biz.zm +co.zm +com.zm +edu.zm +gov.zm +info.zm +mil.zm +net.zm +org.zm +sch.zm + +zw +ac.zw +co.zw +gov.zw +mil.zw +org.zw + + +aaa + +aarp + +abb + +abbott + +abbvie + +abc + +able + +abogado + +abudhabi + +academy + +accenture + +accountant + +accountants + +aco + +actor + +ads + +adult + +aeg + +aetna + +afl + +africa + +agakhan + +agency + +aig + +airbus + +airforce + +airtel + +akdn + +alibaba + +alipay + +allfinanz + +allstate + +ally + +alsace + +alstom + +amazon + +americanexpress + +americanfamily + +amex + +amfam + +amica + +amsterdam + +analytics + +android + +anquan + +anz + +aol + +apartments + +app + +apple + +aquarelle + +arab + +aramco + +archi + +army + +art + +arte + +asda + +associates + +athleta + +attorney + +auction + +audi + +audible + +audio + +auspost + +author + +auto + +autos + +aws + +axa + +azure + +baby + +baidu + +banamex + +band + +bank + +bar + +barcelona + +barclaycard + +barclays + +barefoot + +bargains + +baseball + +basketball + +bauhaus + +bayern + +bbc + +bbt + +bbva + +bcg + +bcn + +beats + +beauty + +beer + +bentley + +berlin + +best + +bestbuy + +bet + +bharti + +bible + +bid + +bike + +bing + +bingo + +bio + +black + +blackfriday + +blockbuster + +blog + +bloomberg + +blue + +bms + +bmw + +bnpparibas + +boats + +boehringer + +bofa + +bom + +bond + +boo + +book + +booking + +bosch + +bostik + +boston + +bot + +boutique + +box + +bradesco + +bridgestone + +broadway + +broker + +brother + +brussels + +build + +builders + +business + +buy + +buzz + +bzh + +cab + +cafe + +cal + +call + +calvinklein + +cam + +camera + +camp + +canon + +capetown + +capital + +capitalone + +car + +caravan + +cards + +care + +career + +careers + +cars + +casa + +case + +cash + +casino + +catering + +catholic + +cba + +cbn + +cbre + +center + +ceo + +cern + +cfa + +cfd + +chanel + +channel + +charity + +chase + +chat + +cheap + +chintai + +christmas + +chrome + +church + +cipriani + +circle + +cisco + +citadel + +citi + +citic + +city + +claims + +cleaning + +click + +clinic + +clinique + +clothing + +cloud + +club + +clubmed + +coach + +codes + +coffee + +college + +cologne + +commbank + +community + +company + +compare + +computer + +comsec + +condos + +construction + +consulting + +contact + +contractors + +cooking + +cool + +corsica + +country + +coupon + +coupons + +courses + +cpa + +credit + +creditcard + +creditunion + +cricket + +crown + +crs + +cruise + +cruises + +cuisinella + +cymru + +cyou + +dad + +dance + +data + +date + +dating + +datsun + +day + +dclk + +dds + +deal + +dealer + +deals + +degree + +delivery + +dell + +deloitte + +delta + +democrat + +dental + +dentist + +desi + +design + +dev + +dhl + +diamonds + +diet + +digital + +direct + +directory + +discount + +discover + +dish + +diy + +dnp + +docs + +doctor + +dog + +domains + +dot + +download + +drive + +dtv + +dubai + +dunlop + +dupont + +durban + +dvag + +dvr + +earth + +eat + +eco + +edeka + +education + +email + +emerck + +energy + +engineer + +engineering + +enterprises + +epson + +equipment + +ericsson + +erni + +esq + +estate + +eurovision + +eus + +events + +exchange + +expert + +exposed + +express + +extraspace + +fage + +fail + +fairwinds + +faith + +family + +fan + +fans + +farm + +farmers + +fashion + +fast + +fedex + +feedback + +ferrari + +ferrero + +fidelity + +fido + +film + +final + +finance + +financial + +fire + +firestone + +firmdale + +fish + +fishing + +fit + +fitness + +flickr + +flights + +flir + +florist + +flowers + +fly + +foo + +food + +football + +ford + +forex + +forsale + +forum + +foundation + +fox + +free + +fresenius + +frl + +frogans + +frontier + +ftr + +fujitsu + +fun + +fund + +furniture + +futbol + +fyi + +gal + +gallery + +gallo + +gallup + +game + +games + +gap + +garden + +gay + +gbiz + +gdn + +gea + +gent + +genting + +george + +ggee + +gift + +gifts + +gives + +giving + +glass + +gle + +global + +globo + +gmail + +gmbh + +gmo + +gmx + +godaddy + +gold + +goldpoint + +golf + +goo + +goodyear + +goog + +google + +gop + +got + +grainger + +graphics + +gratis + +green + +gripe + +grocery + +group + +gucci + +guge + +guide + +guitars + +guru + +hair + +hamburg + +hangout + +haus + +hbo + +hdfc + +hdfcbank + +health + +healthcare + +help + +helsinki + +here + +hermes + +hiphop + +hisamitsu + +hitachi + +hiv + +hkt + +hockey + +holdings + +holiday + +homedepot + +homegoods + +homes + +homesense + +honda + +horse + +hospital + +host + +hosting + +hot + +hotels + +hotmail + +house + +how + +hsbc + +hughes + +hyatt + +hyundai + +ibm + +icbc + +ice + +icu + +ieee + +ifm + +ikano + +imamat + +imdb + +immo + +immobilien + +inc + +industries + +infiniti + +ing + +ink + +institute + +insurance + +insure + +international + +intuit + +investments + +ipiranga + +irish + +ismaili + +ist + +istanbul + +itau + +itv + +jaguar + +java + +jcb + +jeep + +jetzt + +jewelry + +jio + +jll + +jmp + +jnj + +joburg + +jot + +joy + +jpmorgan + +jprs + +juegos + +juniper + +kaufen + +kddi + +kerryhotels + +kerrylogistics + +kerryproperties + +kfh + +kia + +kids + +kim + +kindle + +kitchen + +kiwi + +koeln + +komatsu + +kosher + +kpmg + +kpn + +krd + +kred + +kuokgroup + +kyoto + +lacaixa + +lamborghini + +lamer + +lancaster + +land + +landrover + +lanxess + +lasalle + +lat + +latino + +latrobe + +law + +lawyer + +lds + +lease + +leclerc + +lefrak + +legal + +lego + +lexus + +lgbt + +lidl + +life + +lifeinsurance + +lifestyle + +lighting + +like + +lilly + +limited + +limo + +lincoln + +link + +lipsy + +live + +living + +llc + +llp + +loan + +loans + +locker + +locus + +lol + +london + +lotte + +lotto + +love + +lpl + +lplfinancial + +ltd + +ltda + +lundbeck + +luxe + +luxury + +madrid + +maif + +maison + +makeup + +man + +management + +mango + +map + +market + +marketing + +markets + +marriott + +marshalls + +mattel + +mba + +mckinsey + +med + +media + +meet + +melbourne + +meme + +memorial + +men + +menu + +merck + +merckmsd + +miami + +microsoft + +mini + +mint + +mit + +mitsubishi + +mlb + +mls + +mma + +mobile + +moda + +moe + +moi + +mom + +monash + +money + +monster + +mormon + +mortgage + +moscow + +moto + +motorcycles + +mov + +movie + +msd + +mtn + +mtr + +music + +nab + +nagoya + +navy + +nba + +nec + +netbank + +netflix + +network + +neustar + +new + +news + +next + +nextdirect + +nexus + +nfl + +ngo + +nhk + +nico + +nike + +nikon + +ninja + +nissan + +nissay + +nokia + +norton + +now + +nowruz + +nowtv + +nra + +nrw + +ntt + +nyc + +obi + +observer + +office + +okinawa + +olayan + +olayangroup + +ollo + +omega + +one + +ong + +onl + +online + +ooo + +open + +oracle + +orange + +organic + +origins + +osaka + +otsuka + +ott + +ovh + +page + +panasonic + +paris + +pars + +partners + +parts + +party + +pay + +pccw + +pet + +pfizer + +pharmacy + +phd + +philips + +phone + +photo + +photography + +photos + +physio + +pics + +pictet + +pictures + +pid + +pin + +ping + +pink + +pioneer + +pizza + +place + +play + +playstation + +plumbing + +plus + +pnc + +pohl + +poker + +politie + +porn + +pramerica + +praxi + +press + +prime + +prod + +productions + +prof + +progressive + +promo + +properties + +property + +protection + +pru + +prudential + +pub + +pwc + +qpon + +quebec + +quest + +racing + +radio + +read + +realestate + +realtor + +realty + +recipes + +red + +redstone + +redumbrella + +rehab + +reise + +reisen + +reit + +reliance + +ren + +rent + +rentals + +repair + +report + +republican + +rest + +restaurant + +review + +reviews + +rexroth + +rich + +richardli + +ricoh + +ril + +rio + +rip + +rocks + +rodeo + +rogers + +room + +rsvp + +rugby + +ruhr + +run + +rwe + +ryukyu + +saarland + +safe + +safety + +sakura + +sale + +salon + +samsclub + +samsung + +sandvik + +sandvikcoromant + +sanofi + +sap + +sarl + +sas + +save + +saxo + +sbi + +sbs + +scb + +schaeffler + +schmidt + +scholarships + +school + +schule + +schwarz + +science + +scot + +search + +seat + +secure + +security + +seek + +select + +sener + +services + +seven + +sew + +sex + +sexy + +sfr + +shangrila + +sharp + +shell + +shia + +shiksha + +shoes + +shop + +shopping + +shouji + +show + +silk + +sina + +singles + +site + +ski + +skin + +sky + +skype + +sling + +smart + +smile + +sncf + +soccer + +social + +softbank + +software + +sohu + +solar + +solutions + +song + +sony + +soy + +spa + +space + +sport + +spot + +srl + +stada + +staples + +star + +statebank + +statefarm + +stc + +stcgroup + +stockholm + +storage + +store + +stream + +studio + +study + +style + +sucks + +supplies + +supply + +support + +surf + +surgery + +suzuki + +swatch + +swiss + +sydney + +systems + +tab + +taipei + +talk + +taobao + +target + +tatamotors + +tatar + +tattoo + +tax + +taxi + +tci + +tdk + +team + +tech + +technology + +temasek + +tennis + +teva + +thd + +theater + +theatre + +tiaa + +tickets + +tienda + +tips + +tires + +tirol + +tjmaxx + +tjx + +tkmaxx + +tmall + +today + +tokyo + +tools + +top + +toray + +toshiba + +total + +tours + +town + +toyota + +toys + +trade + +trading + +training + +travel + +travelers + +travelersinsurance + +trust + +trv + +tube + +tui + +tunes + +tushu + +tvs + +ubank + +ubs + +unicom + +university + +uno + +uol + +ups + +vacations + +vana + +vanguard + +vegas + +ventures + +verisign + +versicherung + +vet + +viajes + +video + +vig + +viking + +villas + +vin + +vip + +virgin + +visa + +vision + +viva + +vivo + +vlaanderen + +vodka + +volvo + +vote + +voting + +voto + +voyage + +wales + +walmart + +walter + +wang + +wanggou + +watch + +watches + +weather + +weatherchannel + +webcam + +weber + +website + +wed + +wedding + +weibo + +weir + +whoswho + +wien + +wiki + +williamhill + +win + +windows + +wine + +winners + +wme + +wolterskluwer + +woodside + +work + +works + +world + +wow + +wtc + +wtf + +xbox + +xerox + +xihuan + +xin + +कॉम + +セール + +佛山 + +慈善 + +集团 + +在线 + +点看 + +คอม + +八卦 + +موقع + +公益 + +公司 + +香格里拉 + +网站 + +移动 + +我爱你 + +москва + +католик + +онлайн + +сайт + +联通 + +קום + +时尚 + +微博 + +淡马锡 + +ファッション + +орг + +नेट + +ストア + +アマゾン + +삼성 + +商标 + +商店 + +商城 + +дети + +ポイント + +新闻 + +家電 + +كوم + +中文网 + +中信 + +娱乐 + +谷歌 + +電訊盈科 + +购物 + +クラウド + +通販 + +网店 + +संगठन + +餐厅 + +网络 + +ком + +亚马逊 + +食品 + +飞利浦 + +手机 + +ارامكو + +العليان + +بازار + +ابوظبي + +كاثوليك + +همراه + +닷컴 + +政府 + +شبكة + +بيتك + +عرب + +机构 + +组织机构 + +健康 + +招聘 + +рус + +大拿 + +みんな + +グーグル + +世界 + +書籍 + +网址 + +닷넷 + +コム + +天主教 + +游戏 + +vermögensberater + +vermögensberatung + +企业 + +信息 + +嘉里大酒店 + +嘉里 + +广东 + +政务 + +xyz + +yachts + +yahoo + +yamaxun + +yandex + +yodobashi + +yoga + +yokohama + +you + +youtube + +yun + +zappos + +zara + +zero + +zip + +zone + +zuerich + + + + +co.krd +edu.krd + +art.pl +gliwice.pl +krakow.pl +poznan.pl +wroc.pl +zakopane.pl + +lib.de.us + +12chars.dev +12chars.it +12chars.pro + +cc.ua +inf.ua +ltd.ua + +611.to + +a2hosted.com +cpserver.com + +aaa.vodka + +*.on-acorn.io + +activetrail.biz + +adaptable.app + +adobeaemcloud.com +*.dev.adobeaemcloud.com +aem.live +hlx.live +adobeaemcloud.net +aem.page +hlx.page +hlx3.page + +adobeio-static.net +adobeioruntime.net + +africa.com + +beep.pl + +airkitapps.com +airkitapps-au.com +airkitapps.eu + +aivencloud.com + +akadns.net +akamai.net +akamai-staging.net +akamaiedge.net +akamaiedge-staging.net +akamaihd.net +akamaihd-staging.net +akamaiorigin.net +akamaiorigin-staging.net +akamaized.net +akamaized-staging.net +edgekey.net +edgekey-staging.net +edgesuite.net +edgesuite-staging.net + +barsy.ca + +*.compute.estate +*.alces.network + +kasserver.com + +altervista.org + +alwaysdata.net + +myamaze.net + + +execute-api.cn-north-1.amazonaws.com.cn +execute-api.cn-northwest-1.amazonaws.com.cn +execute-api.af-south-1.amazonaws.com +execute-api.ap-east-1.amazonaws.com +execute-api.ap-northeast-1.amazonaws.com +execute-api.ap-northeast-2.amazonaws.com +execute-api.ap-northeast-3.amazonaws.com +execute-api.ap-south-1.amazonaws.com +execute-api.ap-south-2.amazonaws.com +execute-api.ap-southeast-1.amazonaws.com +execute-api.ap-southeast-2.amazonaws.com +execute-api.ap-southeast-3.amazonaws.com +execute-api.ap-southeast-4.amazonaws.com +execute-api.ap-southeast-5.amazonaws.com +execute-api.ca-central-1.amazonaws.com +execute-api.ca-west-1.amazonaws.com +execute-api.eu-central-1.amazonaws.com +execute-api.eu-central-2.amazonaws.com +execute-api.eu-north-1.amazonaws.com +execute-api.eu-south-1.amazonaws.com +execute-api.eu-south-2.amazonaws.com +execute-api.eu-west-1.amazonaws.com +execute-api.eu-west-2.amazonaws.com +execute-api.eu-west-3.amazonaws.com +execute-api.il-central-1.amazonaws.com +execute-api.me-central-1.amazonaws.com +execute-api.me-south-1.amazonaws.com +execute-api.sa-east-1.amazonaws.com +execute-api.us-east-1.amazonaws.com +execute-api.us-east-2.amazonaws.com +execute-api.us-gov-east-1.amazonaws.com +execute-api.us-gov-west-1.amazonaws.com +execute-api.us-west-1.amazonaws.com +execute-api.us-west-2.amazonaws.com + +cloudfront.net + +auth.af-south-1.amazoncognito.com +auth.ap-east-1.amazoncognito.com +auth.ap-northeast-1.amazoncognito.com +auth.ap-northeast-2.amazoncognito.com +auth.ap-northeast-3.amazoncognito.com +auth.ap-south-1.amazoncognito.com +auth.ap-south-2.amazoncognito.com +auth.ap-southeast-1.amazoncognito.com +auth.ap-southeast-2.amazoncognito.com +auth.ap-southeast-3.amazoncognito.com +auth.ap-southeast-4.amazoncognito.com +auth.ca-central-1.amazoncognito.com +auth.ca-west-1.amazoncognito.com +auth.eu-central-1.amazoncognito.com +auth.eu-central-2.amazoncognito.com +auth.eu-north-1.amazoncognito.com +auth.eu-south-1.amazoncognito.com +auth.eu-south-2.amazoncognito.com +auth.eu-west-1.amazoncognito.com +auth.eu-west-2.amazoncognito.com +auth.eu-west-3.amazoncognito.com +auth.il-central-1.amazoncognito.com +auth.me-central-1.amazoncognito.com +auth.me-south-1.amazoncognito.com +auth.sa-east-1.amazoncognito.com +auth.us-east-1.amazoncognito.com +auth-fips.us-east-1.amazoncognito.com +auth.us-east-2.amazoncognito.com +auth-fips.us-east-2.amazoncognito.com +auth-fips.us-gov-west-1.amazoncognito.com +auth.us-west-1.amazoncognito.com +auth-fips.us-west-1.amazoncognito.com +auth.us-west-2.amazoncognito.com +auth-fips.us-west-2.amazoncognito.com + +*.compute.amazonaws.com.cn +*.compute.amazonaws.com +*.compute-1.amazonaws.com +us-east-1.amazonaws.com + +emrappui-prod.cn-north-1.amazonaws.com.cn +emrnotebooks-prod.cn-north-1.amazonaws.com.cn +emrstudio-prod.cn-north-1.amazonaws.com.cn +emrappui-prod.cn-northwest-1.amazonaws.com.cn +emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn +emrstudio-prod.cn-northwest-1.amazonaws.com.cn +emrappui-prod.af-south-1.amazonaws.com +emrnotebooks-prod.af-south-1.amazonaws.com +emrstudio-prod.af-south-1.amazonaws.com +emrappui-prod.ap-east-1.amazonaws.com +emrnotebooks-prod.ap-east-1.amazonaws.com +emrstudio-prod.ap-east-1.amazonaws.com +emrappui-prod.ap-northeast-1.amazonaws.com +emrnotebooks-prod.ap-northeast-1.amazonaws.com +emrstudio-prod.ap-northeast-1.amazonaws.com +emrappui-prod.ap-northeast-2.amazonaws.com +emrnotebooks-prod.ap-northeast-2.amazonaws.com +emrstudio-prod.ap-northeast-2.amazonaws.com +emrappui-prod.ap-northeast-3.amazonaws.com +emrnotebooks-prod.ap-northeast-3.amazonaws.com +emrstudio-prod.ap-northeast-3.amazonaws.com +emrappui-prod.ap-south-1.amazonaws.com +emrnotebooks-prod.ap-south-1.amazonaws.com +emrstudio-prod.ap-south-1.amazonaws.com +emrappui-prod.ap-south-2.amazonaws.com +emrnotebooks-prod.ap-south-2.amazonaws.com +emrstudio-prod.ap-south-2.amazonaws.com +emrappui-prod.ap-southeast-1.amazonaws.com +emrnotebooks-prod.ap-southeast-1.amazonaws.com +emrstudio-prod.ap-southeast-1.amazonaws.com +emrappui-prod.ap-southeast-2.amazonaws.com +emrnotebooks-prod.ap-southeast-2.amazonaws.com +emrstudio-prod.ap-southeast-2.amazonaws.com +emrappui-prod.ap-southeast-3.amazonaws.com +emrnotebooks-prod.ap-southeast-3.amazonaws.com +emrstudio-prod.ap-southeast-3.amazonaws.com +emrappui-prod.ap-southeast-4.amazonaws.com +emrnotebooks-prod.ap-southeast-4.amazonaws.com +emrstudio-prod.ap-southeast-4.amazonaws.com +emrappui-prod.ca-central-1.amazonaws.com +emrnotebooks-prod.ca-central-1.amazonaws.com +emrstudio-prod.ca-central-1.amazonaws.com +emrappui-prod.ca-west-1.amazonaws.com +emrnotebooks-prod.ca-west-1.amazonaws.com +emrstudio-prod.ca-west-1.amazonaws.com +emrappui-prod.eu-central-1.amazonaws.com +emrnotebooks-prod.eu-central-1.amazonaws.com +emrstudio-prod.eu-central-1.amazonaws.com +emrappui-prod.eu-central-2.amazonaws.com +emrnotebooks-prod.eu-central-2.amazonaws.com +emrstudio-prod.eu-central-2.amazonaws.com +emrappui-prod.eu-north-1.amazonaws.com +emrnotebooks-prod.eu-north-1.amazonaws.com +emrstudio-prod.eu-north-1.amazonaws.com +emrappui-prod.eu-south-1.amazonaws.com +emrnotebooks-prod.eu-south-1.amazonaws.com +emrstudio-prod.eu-south-1.amazonaws.com +emrappui-prod.eu-south-2.amazonaws.com +emrnotebooks-prod.eu-south-2.amazonaws.com +emrstudio-prod.eu-south-2.amazonaws.com +emrappui-prod.eu-west-1.amazonaws.com +emrnotebooks-prod.eu-west-1.amazonaws.com +emrstudio-prod.eu-west-1.amazonaws.com +emrappui-prod.eu-west-2.amazonaws.com +emrnotebooks-prod.eu-west-2.amazonaws.com +emrstudio-prod.eu-west-2.amazonaws.com +emrappui-prod.eu-west-3.amazonaws.com +emrnotebooks-prod.eu-west-3.amazonaws.com +emrstudio-prod.eu-west-3.amazonaws.com +emrappui-prod.il-central-1.amazonaws.com +emrnotebooks-prod.il-central-1.amazonaws.com +emrstudio-prod.il-central-1.amazonaws.com +emrappui-prod.me-central-1.amazonaws.com +emrnotebooks-prod.me-central-1.amazonaws.com +emrstudio-prod.me-central-1.amazonaws.com +emrappui-prod.me-south-1.amazonaws.com +emrnotebooks-prod.me-south-1.amazonaws.com +emrstudio-prod.me-south-1.amazonaws.com +emrappui-prod.sa-east-1.amazonaws.com +emrnotebooks-prod.sa-east-1.amazonaws.com +emrstudio-prod.sa-east-1.amazonaws.com +emrappui-prod.us-east-1.amazonaws.com +emrnotebooks-prod.us-east-1.amazonaws.com +emrstudio-prod.us-east-1.amazonaws.com +emrappui-prod.us-east-2.amazonaws.com +emrnotebooks-prod.us-east-2.amazonaws.com +emrstudio-prod.us-east-2.amazonaws.com +emrappui-prod.us-gov-east-1.amazonaws.com +emrnotebooks-prod.us-gov-east-1.amazonaws.com +emrstudio-prod.us-gov-east-1.amazonaws.com +emrappui-prod.us-gov-west-1.amazonaws.com +emrnotebooks-prod.us-gov-west-1.amazonaws.com +emrstudio-prod.us-gov-west-1.amazonaws.com +emrappui-prod.us-west-1.amazonaws.com +emrnotebooks-prod.us-west-1.amazonaws.com +emrstudio-prod.us-west-1.amazonaws.com +emrappui-prod.us-west-2.amazonaws.com +emrnotebooks-prod.us-west-2.amazonaws.com +emrstudio-prod.us-west-2.amazonaws.com + +*.cn-north-1.airflow.amazonaws.com.cn +*.cn-northwest-1.airflow.amazonaws.com.cn +*.af-south-1.airflow.amazonaws.com +*.ap-east-1.airflow.amazonaws.com +*.ap-northeast-1.airflow.amazonaws.com +*.ap-northeast-2.airflow.amazonaws.com +*.ap-northeast-3.airflow.amazonaws.com +*.ap-south-1.airflow.amazonaws.com +*.ap-south-2.airflow.amazonaws.com +*.ap-southeast-1.airflow.amazonaws.com +*.ap-southeast-2.airflow.amazonaws.com +*.ap-southeast-3.airflow.amazonaws.com +*.ap-southeast-4.airflow.amazonaws.com +*.ca-central-1.airflow.amazonaws.com +*.ca-west-1.airflow.amazonaws.com +*.eu-central-1.airflow.amazonaws.com +*.eu-central-2.airflow.amazonaws.com +*.eu-north-1.airflow.amazonaws.com +*.eu-south-1.airflow.amazonaws.com +*.eu-south-2.airflow.amazonaws.com +*.eu-west-1.airflow.amazonaws.com +*.eu-west-2.airflow.amazonaws.com +*.eu-west-3.airflow.amazonaws.com +*.il-central-1.airflow.amazonaws.com +*.me-central-1.airflow.amazonaws.com +*.me-south-1.airflow.amazonaws.com +*.sa-east-1.airflow.amazonaws.com +*.us-east-1.airflow.amazonaws.com +*.us-east-2.airflow.amazonaws.com +*.us-west-1.airflow.amazonaws.com +*.us-west-2.airflow.amazonaws.com + +s3.dualstack.cn-north-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn +s3-website.dualstack.cn-north-1.amazonaws.com.cn +s3.cn-north-1.amazonaws.com.cn +s3-accesspoint.cn-north-1.amazonaws.com.cn +s3-deprecated.cn-north-1.amazonaws.com.cn +s3-object-lambda.cn-north-1.amazonaws.com.cn +s3-website.cn-north-1.amazonaws.com.cn +s3.dualstack.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn +s3.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.cn-northwest-1.amazonaws.com.cn +s3-object-lambda.cn-northwest-1.amazonaws.com.cn +s3-website.cn-northwest-1.amazonaws.com.cn +s3.dualstack.af-south-1.amazonaws.com +s3-accesspoint.dualstack.af-south-1.amazonaws.com +s3-website.dualstack.af-south-1.amazonaws.com +s3.af-south-1.amazonaws.com +s3-accesspoint.af-south-1.amazonaws.com +s3-object-lambda.af-south-1.amazonaws.com +s3-website.af-south-1.amazonaws.com +s3.dualstack.ap-east-1.amazonaws.com +s3-accesspoint.dualstack.ap-east-1.amazonaws.com +s3.ap-east-1.amazonaws.com +s3-accesspoint.ap-east-1.amazonaws.com +s3-object-lambda.ap-east-1.amazonaws.com +s3-website.ap-east-1.amazonaws.com +s3.dualstack.ap-northeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com +s3-website.dualstack.ap-northeast-1.amazonaws.com +s3.ap-northeast-1.amazonaws.com +s3-accesspoint.ap-northeast-1.amazonaws.com +s3-object-lambda.ap-northeast-1.amazonaws.com +s3-website.ap-northeast-1.amazonaws.com +s3.dualstack.ap-northeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com +s3-website.dualstack.ap-northeast-2.amazonaws.com +s3.ap-northeast-2.amazonaws.com +s3-accesspoint.ap-northeast-2.amazonaws.com +s3-object-lambda.ap-northeast-2.amazonaws.com +s3-website.ap-northeast-2.amazonaws.com +s3.dualstack.ap-northeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com +s3-website.dualstack.ap-northeast-3.amazonaws.com +s3.ap-northeast-3.amazonaws.com +s3-accesspoint.ap-northeast-3.amazonaws.com +s3-object-lambda.ap-northeast-3.amazonaws.com +s3-website.ap-northeast-3.amazonaws.com +s3.dualstack.ap-south-1.amazonaws.com +s3-accesspoint.dualstack.ap-south-1.amazonaws.com +s3-website.dualstack.ap-south-1.amazonaws.com +s3.ap-south-1.amazonaws.com +s3-accesspoint.ap-south-1.amazonaws.com +s3-object-lambda.ap-south-1.amazonaws.com +s3-website.ap-south-1.amazonaws.com +s3.dualstack.ap-south-2.amazonaws.com +s3-accesspoint.dualstack.ap-south-2.amazonaws.com +s3-website.dualstack.ap-south-2.amazonaws.com +s3.ap-south-2.amazonaws.com +s3-accesspoint.ap-south-2.amazonaws.com +s3-object-lambda.ap-south-2.amazonaws.com +s3-website.ap-south-2.amazonaws.com +s3.dualstack.ap-southeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com +s3-website.dualstack.ap-southeast-1.amazonaws.com +s3.ap-southeast-1.amazonaws.com +s3-accesspoint.ap-southeast-1.amazonaws.com +s3-object-lambda.ap-southeast-1.amazonaws.com +s3-website.ap-southeast-1.amazonaws.com +s3.dualstack.ap-southeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com +s3-website.dualstack.ap-southeast-2.amazonaws.com +s3.ap-southeast-2.amazonaws.com +s3-accesspoint.ap-southeast-2.amazonaws.com +s3-object-lambda.ap-southeast-2.amazonaws.com +s3-website.ap-southeast-2.amazonaws.com +s3.dualstack.ap-southeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com +s3-website.dualstack.ap-southeast-3.amazonaws.com +s3.ap-southeast-3.amazonaws.com +s3-accesspoint.ap-southeast-3.amazonaws.com +s3-object-lambda.ap-southeast-3.amazonaws.com +s3-website.ap-southeast-3.amazonaws.com +s3.dualstack.ap-southeast-4.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com +s3-website.dualstack.ap-southeast-4.amazonaws.com +s3.ap-southeast-4.amazonaws.com +s3-accesspoint.ap-southeast-4.amazonaws.com +s3-object-lambda.ap-southeast-4.amazonaws.com +s3-website.ap-southeast-4.amazonaws.com +s3.dualstack.ap-southeast-5.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com +s3-website.dualstack.ap-southeast-5.amazonaws.com +s3.ap-southeast-5.amazonaws.com +s3-accesspoint.ap-southeast-5.amazonaws.com +s3-deprecated.ap-southeast-5.amazonaws.com +s3-object-lambda.ap-southeast-5.amazonaws.com +s3-website.ap-southeast-5.amazonaws.com +s3.dualstack.ca-central-1.amazonaws.com +s3-accesspoint.dualstack.ca-central-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com +s3-fips.dualstack.ca-central-1.amazonaws.com +s3-website.dualstack.ca-central-1.amazonaws.com +s3.ca-central-1.amazonaws.com +s3-accesspoint.ca-central-1.amazonaws.com +s3-accesspoint-fips.ca-central-1.amazonaws.com +s3-fips.ca-central-1.amazonaws.com +s3-object-lambda.ca-central-1.amazonaws.com +s3-website.ca-central-1.amazonaws.com +s3.dualstack.ca-west-1.amazonaws.com +s3-accesspoint.dualstack.ca-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com +s3-fips.dualstack.ca-west-1.amazonaws.com +s3-website.dualstack.ca-west-1.amazonaws.com +s3.ca-west-1.amazonaws.com +s3-accesspoint.ca-west-1.amazonaws.com +s3-accesspoint-fips.ca-west-1.amazonaws.com +s3-fips.ca-west-1.amazonaws.com +s3-object-lambda.ca-west-1.amazonaws.com +s3-website.ca-west-1.amazonaws.com +s3.dualstack.eu-central-1.amazonaws.com +s3-accesspoint.dualstack.eu-central-1.amazonaws.com +s3-website.dualstack.eu-central-1.amazonaws.com +s3.eu-central-1.amazonaws.com +s3-accesspoint.eu-central-1.amazonaws.com +s3-object-lambda.eu-central-1.amazonaws.com +s3-website.eu-central-1.amazonaws.com +s3.dualstack.eu-central-2.amazonaws.com +s3-accesspoint.dualstack.eu-central-2.amazonaws.com +s3-website.dualstack.eu-central-2.amazonaws.com +s3.eu-central-2.amazonaws.com +s3-accesspoint.eu-central-2.amazonaws.com +s3-object-lambda.eu-central-2.amazonaws.com +s3-website.eu-central-2.amazonaws.com +s3.dualstack.eu-north-1.amazonaws.com +s3-accesspoint.dualstack.eu-north-1.amazonaws.com +s3.eu-north-1.amazonaws.com +s3-accesspoint.eu-north-1.amazonaws.com +s3-object-lambda.eu-north-1.amazonaws.com +s3-website.eu-north-1.amazonaws.com +s3.dualstack.eu-south-1.amazonaws.com +s3-accesspoint.dualstack.eu-south-1.amazonaws.com +s3-website.dualstack.eu-south-1.amazonaws.com +s3.eu-south-1.amazonaws.com +s3-accesspoint.eu-south-1.amazonaws.com +s3-object-lambda.eu-south-1.amazonaws.com +s3-website.eu-south-1.amazonaws.com +s3.dualstack.eu-south-2.amazonaws.com +s3-accesspoint.dualstack.eu-south-2.amazonaws.com +s3-website.dualstack.eu-south-2.amazonaws.com +s3.eu-south-2.amazonaws.com +s3-accesspoint.eu-south-2.amazonaws.com +s3-object-lambda.eu-south-2.amazonaws.com +s3-website.eu-south-2.amazonaws.com +s3.dualstack.eu-west-1.amazonaws.com +s3-accesspoint.dualstack.eu-west-1.amazonaws.com +s3-website.dualstack.eu-west-1.amazonaws.com +s3.eu-west-1.amazonaws.com +s3-accesspoint.eu-west-1.amazonaws.com +s3-deprecated.eu-west-1.amazonaws.com +s3-object-lambda.eu-west-1.amazonaws.com +s3-website.eu-west-1.amazonaws.com +s3.dualstack.eu-west-2.amazonaws.com +s3-accesspoint.dualstack.eu-west-2.amazonaws.com +s3.eu-west-2.amazonaws.com +s3-accesspoint.eu-west-2.amazonaws.com +s3-object-lambda.eu-west-2.amazonaws.com +s3-website.eu-west-2.amazonaws.com +s3.dualstack.eu-west-3.amazonaws.com +s3-accesspoint.dualstack.eu-west-3.amazonaws.com +s3-website.dualstack.eu-west-3.amazonaws.com +s3.eu-west-3.amazonaws.com +s3-accesspoint.eu-west-3.amazonaws.com +s3-object-lambda.eu-west-3.amazonaws.com +s3-website.eu-west-3.amazonaws.com +s3.dualstack.il-central-1.amazonaws.com +s3-accesspoint.dualstack.il-central-1.amazonaws.com +s3-website.dualstack.il-central-1.amazonaws.com +s3.il-central-1.amazonaws.com +s3-accesspoint.il-central-1.amazonaws.com +s3-object-lambda.il-central-1.amazonaws.com +s3-website.il-central-1.amazonaws.com +s3.dualstack.me-central-1.amazonaws.com +s3-accesspoint.dualstack.me-central-1.amazonaws.com +s3-website.dualstack.me-central-1.amazonaws.com +s3.me-central-1.amazonaws.com +s3-accesspoint.me-central-1.amazonaws.com +s3-object-lambda.me-central-1.amazonaws.com +s3-website.me-central-1.amazonaws.com +s3.dualstack.me-south-1.amazonaws.com +s3-accesspoint.dualstack.me-south-1.amazonaws.com +s3.me-south-1.amazonaws.com +s3-accesspoint.me-south-1.amazonaws.com +s3-object-lambda.me-south-1.amazonaws.com +s3-website.me-south-1.amazonaws.com +s3.amazonaws.com +s3-1.amazonaws.com +s3-ap-east-1.amazonaws.com +s3-ap-northeast-1.amazonaws.com +s3-ap-northeast-2.amazonaws.com +s3-ap-northeast-3.amazonaws.com +s3-ap-south-1.amazonaws.com +s3-ap-southeast-1.amazonaws.com +s3-ap-southeast-2.amazonaws.com +s3-ca-central-1.amazonaws.com +s3-eu-central-1.amazonaws.com +s3-eu-north-1.amazonaws.com +s3-eu-west-1.amazonaws.com +s3-eu-west-2.amazonaws.com +s3-eu-west-3.amazonaws.com +s3-external-1.amazonaws.com +s3-fips-us-gov-east-1.amazonaws.com +s3-fips-us-gov-west-1.amazonaws.com +mrap.accesspoint.s3-global.amazonaws.com +s3-me-south-1.amazonaws.com +s3-sa-east-1.amazonaws.com +s3-us-east-2.amazonaws.com +s3-us-gov-east-1.amazonaws.com +s3-us-gov-west-1.amazonaws.com +s3-us-west-1.amazonaws.com +s3-us-west-2.amazonaws.com +s3-website-ap-northeast-1.amazonaws.com +s3-website-ap-southeast-1.amazonaws.com +s3-website-ap-southeast-2.amazonaws.com +s3-website-eu-west-1.amazonaws.com +s3-website-sa-east-1.amazonaws.com +s3-website-us-east-1.amazonaws.com +s3-website-us-gov-west-1.amazonaws.com +s3-website-us-west-1.amazonaws.com +s3-website-us-west-2.amazonaws.com +s3.dualstack.sa-east-1.amazonaws.com +s3-accesspoint.dualstack.sa-east-1.amazonaws.com +s3-website.dualstack.sa-east-1.amazonaws.com +s3.sa-east-1.amazonaws.com +s3-accesspoint.sa-east-1.amazonaws.com +s3-object-lambda.sa-east-1.amazonaws.com +s3-website.sa-east-1.amazonaws.com +s3.dualstack.us-east-1.amazonaws.com +s3-accesspoint.dualstack.us-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com +s3-fips.dualstack.us-east-1.amazonaws.com +s3-website.dualstack.us-east-1.amazonaws.com +s3.us-east-1.amazonaws.com +s3-accesspoint.us-east-1.amazonaws.com +s3-accesspoint-fips.us-east-1.amazonaws.com +s3-deprecated.us-east-1.amazonaws.com +s3-fips.us-east-1.amazonaws.com +s3-object-lambda.us-east-1.amazonaws.com +s3-website.us-east-1.amazonaws.com +s3.dualstack.us-east-2.amazonaws.com +s3-accesspoint.dualstack.us-east-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com +s3-fips.dualstack.us-east-2.amazonaws.com +s3-website.dualstack.us-east-2.amazonaws.com +s3.us-east-2.amazonaws.com +s3-accesspoint.us-east-2.amazonaws.com +s3-accesspoint-fips.us-east-2.amazonaws.com +s3-deprecated.us-east-2.amazonaws.com +s3-fips.us-east-2.amazonaws.com +s3-object-lambda.us-east-2.amazonaws.com +s3-website.us-east-2.amazonaws.com +s3.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com +s3-fips.dualstack.us-gov-east-1.amazonaws.com +s3.us-gov-east-1.amazonaws.com +s3-accesspoint.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.us-gov-east-1.amazonaws.com +s3-fips.us-gov-east-1.amazonaws.com +s3-object-lambda.us-gov-east-1.amazonaws.com +s3-website.us-gov-east-1.amazonaws.com +s3.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com +s3-fips.dualstack.us-gov-west-1.amazonaws.com +s3.us-gov-west-1.amazonaws.com +s3-accesspoint.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.us-gov-west-1.amazonaws.com +s3-fips.us-gov-west-1.amazonaws.com +s3-object-lambda.us-gov-west-1.amazonaws.com +s3-website.us-gov-west-1.amazonaws.com +s3.dualstack.us-west-1.amazonaws.com +s3-accesspoint.dualstack.us-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com +s3-fips.dualstack.us-west-1.amazonaws.com +s3-website.dualstack.us-west-1.amazonaws.com +s3.us-west-1.amazonaws.com +s3-accesspoint.us-west-1.amazonaws.com +s3-accesspoint-fips.us-west-1.amazonaws.com +s3-fips.us-west-1.amazonaws.com +s3-object-lambda.us-west-1.amazonaws.com +s3-website.us-west-1.amazonaws.com +s3.dualstack.us-west-2.amazonaws.com +s3-accesspoint.dualstack.us-west-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com +s3-fips.dualstack.us-west-2.amazonaws.com +s3-website.dualstack.us-west-2.amazonaws.com +s3.us-west-2.amazonaws.com +s3-accesspoint.us-west-2.amazonaws.com +s3-accesspoint-fips.us-west-2.amazonaws.com +s3-deprecated.us-west-2.amazonaws.com +s3-fips.us-west-2.amazonaws.com +s3-object-lambda.us-west-2.amazonaws.com +s3-website.us-west-2.amazonaws.com + +labeling.ap-northeast-1.sagemaker.aws +labeling.ap-northeast-2.sagemaker.aws +labeling.ap-south-1.sagemaker.aws +labeling.ap-southeast-1.sagemaker.aws +labeling.ap-southeast-2.sagemaker.aws +labeling.ca-central-1.sagemaker.aws +labeling.eu-central-1.sagemaker.aws +labeling.eu-west-1.sagemaker.aws +labeling.eu-west-2.sagemaker.aws +labeling.us-east-1.sagemaker.aws +labeling.us-east-2.sagemaker.aws +labeling.us-west-2.sagemaker.aws + +notebook.af-south-1.sagemaker.aws +notebook.ap-east-1.sagemaker.aws +notebook.ap-northeast-1.sagemaker.aws +notebook.ap-northeast-2.sagemaker.aws +notebook.ap-northeast-3.sagemaker.aws +notebook.ap-south-1.sagemaker.aws +notebook.ap-south-2.sagemaker.aws +notebook.ap-southeast-1.sagemaker.aws +notebook.ap-southeast-2.sagemaker.aws +notebook.ap-southeast-3.sagemaker.aws +notebook.ap-southeast-4.sagemaker.aws +notebook.ca-central-1.sagemaker.aws +notebook-fips.ca-central-1.sagemaker.aws +notebook.ca-west-1.sagemaker.aws +notebook-fips.ca-west-1.sagemaker.aws +notebook.eu-central-1.sagemaker.aws +notebook.eu-central-2.sagemaker.aws +notebook.eu-north-1.sagemaker.aws +notebook.eu-south-1.sagemaker.aws +notebook.eu-south-2.sagemaker.aws +notebook.eu-west-1.sagemaker.aws +notebook.eu-west-2.sagemaker.aws +notebook.eu-west-3.sagemaker.aws +notebook.il-central-1.sagemaker.aws +notebook.me-central-1.sagemaker.aws +notebook.me-south-1.sagemaker.aws +notebook.sa-east-1.sagemaker.aws +notebook.us-east-1.sagemaker.aws +notebook-fips.us-east-1.sagemaker.aws +notebook.us-east-2.sagemaker.aws +notebook-fips.us-east-2.sagemaker.aws +notebook.us-gov-east-1.sagemaker.aws +notebook-fips.us-gov-east-1.sagemaker.aws +notebook.us-gov-west-1.sagemaker.aws +notebook-fips.us-gov-west-1.sagemaker.aws +notebook.us-west-1.sagemaker.aws +notebook-fips.us-west-1.sagemaker.aws +notebook.us-west-2.sagemaker.aws +notebook-fips.us-west-2.sagemaker.aws +notebook.cn-north-1.sagemaker.com.cn +notebook.cn-northwest-1.sagemaker.com.cn + +studio.af-south-1.sagemaker.aws +studio.ap-east-1.sagemaker.aws +studio.ap-northeast-1.sagemaker.aws +studio.ap-northeast-2.sagemaker.aws +studio.ap-northeast-3.sagemaker.aws +studio.ap-south-1.sagemaker.aws +studio.ap-southeast-1.sagemaker.aws +studio.ap-southeast-2.sagemaker.aws +studio.ap-southeast-3.sagemaker.aws +studio.ca-central-1.sagemaker.aws +studio.eu-central-1.sagemaker.aws +studio.eu-north-1.sagemaker.aws +studio.eu-south-1.sagemaker.aws +studio.eu-south-2.sagemaker.aws +studio.eu-west-1.sagemaker.aws +studio.eu-west-2.sagemaker.aws +studio.eu-west-3.sagemaker.aws +studio.il-central-1.sagemaker.aws +studio.me-central-1.sagemaker.aws +studio.me-south-1.sagemaker.aws +studio.sa-east-1.sagemaker.aws +studio.us-east-1.sagemaker.aws +studio.us-east-2.sagemaker.aws +studio.us-gov-east-1.sagemaker.aws +studio-fips.us-gov-east-1.sagemaker.aws +studio.us-gov-west-1.sagemaker.aws +studio-fips.us-gov-west-1.sagemaker.aws +studio.us-west-1.sagemaker.aws +studio.us-west-2.sagemaker.aws +studio.cn-north-1.sagemaker.com.cn +studio.cn-northwest-1.sagemaker.com.cn + +*.experiments.sagemaker.aws + +analytics-gateway.ap-northeast-1.amazonaws.com +analytics-gateway.ap-northeast-2.amazonaws.com +analytics-gateway.ap-south-1.amazonaws.com +analytics-gateway.ap-southeast-1.amazonaws.com +analytics-gateway.ap-southeast-2.amazonaws.com +analytics-gateway.eu-central-1.amazonaws.com +analytics-gateway.eu-west-1.amazonaws.com +analytics-gateway.us-east-1.amazonaws.com +analytics-gateway.us-east-2.amazonaws.com +analytics-gateway.us-west-2.amazonaws.com + +amplifyapp.com + +*.awsapprunner.com + +webview-assets.aws-cloud9.af-south-1.amazonaws.com +vfs.cloud9.af-south-1.amazonaws.com +webview-assets.cloud9.af-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-east-1.amazonaws.com +vfs.cloud9.ap-east-1.amazonaws.com +webview-assets.cloud9.ap-east-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com +vfs.cloud9.ap-northeast-1.amazonaws.com +webview-assets.cloud9.ap-northeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com +vfs.cloud9.ap-northeast-2.amazonaws.com +webview-assets.cloud9.ap-northeast-2.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com +vfs.cloud9.ap-northeast-3.amazonaws.com +webview-assets.cloud9.ap-northeast-3.amazonaws.com +webview-assets.aws-cloud9.ap-south-1.amazonaws.com +vfs.cloud9.ap-south-1.amazonaws.com +webview-assets.cloud9.ap-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com +vfs.cloud9.ap-southeast-1.amazonaws.com +webview-assets.cloud9.ap-southeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com +vfs.cloud9.ap-southeast-2.amazonaws.com +webview-assets.cloud9.ap-southeast-2.amazonaws.com +webview-assets.aws-cloud9.ca-central-1.amazonaws.com +vfs.cloud9.ca-central-1.amazonaws.com +webview-assets.cloud9.ca-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-central-1.amazonaws.com +vfs.cloud9.eu-central-1.amazonaws.com +webview-assets.cloud9.eu-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-north-1.amazonaws.com +vfs.cloud9.eu-north-1.amazonaws.com +webview-assets.cloud9.eu-north-1.amazonaws.com +webview-assets.aws-cloud9.eu-south-1.amazonaws.com +vfs.cloud9.eu-south-1.amazonaws.com +webview-assets.cloud9.eu-south-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-1.amazonaws.com +vfs.cloud9.eu-west-1.amazonaws.com +webview-assets.cloud9.eu-west-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-2.amazonaws.com +vfs.cloud9.eu-west-2.amazonaws.com +webview-assets.cloud9.eu-west-2.amazonaws.com +webview-assets.aws-cloud9.eu-west-3.amazonaws.com +vfs.cloud9.eu-west-3.amazonaws.com +webview-assets.cloud9.eu-west-3.amazonaws.com +webview-assets.aws-cloud9.il-central-1.amazonaws.com +vfs.cloud9.il-central-1.amazonaws.com +webview-assets.aws-cloud9.me-south-1.amazonaws.com +vfs.cloud9.me-south-1.amazonaws.com +webview-assets.cloud9.me-south-1.amazonaws.com +webview-assets.aws-cloud9.sa-east-1.amazonaws.com +vfs.cloud9.sa-east-1.amazonaws.com +webview-assets.cloud9.sa-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-1.amazonaws.com +vfs.cloud9.us-east-1.amazonaws.com +webview-assets.cloud9.us-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-2.amazonaws.com +vfs.cloud9.us-east-2.amazonaws.com +webview-assets.cloud9.us-east-2.amazonaws.com +webview-assets.aws-cloud9.us-west-1.amazonaws.com +vfs.cloud9.us-west-1.amazonaws.com +webview-assets.cloud9.us-west-1.amazonaws.com +webview-assets.aws-cloud9.us-west-2.amazonaws.com +vfs.cloud9.us-west-2.amazonaws.com +webview-assets.cloud9.us-west-2.amazonaws.com + +awsapps.com + +cn-north-1.eb.amazonaws.com.cn +cn-northwest-1.eb.amazonaws.com.cn +elasticbeanstalk.com +af-south-1.elasticbeanstalk.com +ap-east-1.elasticbeanstalk.com +ap-northeast-1.elasticbeanstalk.com +ap-northeast-2.elasticbeanstalk.com +ap-northeast-3.elasticbeanstalk.com +ap-south-1.elasticbeanstalk.com +ap-southeast-1.elasticbeanstalk.com +ap-southeast-2.elasticbeanstalk.com +ap-southeast-3.elasticbeanstalk.com +ca-central-1.elasticbeanstalk.com +eu-central-1.elasticbeanstalk.com +eu-north-1.elasticbeanstalk.com +eu-south-1.elasticbeanstalk.com +eu-west-1.elasticbeanstalk.com +eu-west-2.elasticbeanstalk.com +eu-west-3.elasticbeanstalk.com +il-central-1.elasticbeanstalk.com +me-south-1.elasticbeanstalk.com +sa-east-1.elasticbeanstalk.com +us-east-1.elasticbeanstalk.com +us-east-2.elasticbeanstalk.com +us-gov-east-1.elasticbeanstalk.com +us-gov-west-1.elasticbeanstalk.com +us-west-1.elasticbeanstalk.com +us-west-2.elasticbeanstalk.com + +*.elb.amazonaws.com.cn +*.elb.amazonaws.com + +awsglobalaccelerator.com + +*.private.repost.aws + +eero.online +eero-stage.online + + +apigee.io + +panel.dev + +siiites.com + +appspacehosted.com +appspaceusercontent.com + +appudo.net + +on-aptible.com + +f5.si + +arvanedge.ir + +user.aseinet.ne.jp +gv.vc +d.gv.vc + +user.party.eus + +pimienta.org +poivron.org +potager.org +sweetpepper.org + +myasustor.com + +cdn.prod.atlassian-dev.net + +translated.page + +myfritz.link +myfritz.net + +onavstack.net + +*.awdev.ca +*.advisor.ws + +ecommerce-shop.pl + +b-data.io + +balena-devices.com + +*.banzai.cloud +app.banzaicloud.io +*.backyards.banzaicloud.io + +base.ec +official.ec +buyshop.jp +fashionstore.jp +handcrafted.jp +kawaiishop.jp +supersale.jp +theshop.jp +shopselect.net +base.shop + +beagleboard.io + +*.beget.app + +pages.gay + +betainabox.com + +bnr.la + +bitbucket.io + +blackbaudcdn.net + +of.je + +bluebite.io + +boomla.net + +boutir.com + +boxfuse.io + +square7.ch +bplaced.com +bplaced.de +square7.de +bplaced.net +square7.net + +*.s.brave.io + +shop.brendly.hr +shop.brendly.rs + +browsersafetymark.io + +radio.am +radio.fm + +uk0.bigv.io +dh.bytemark.co.uk +vm.bytemark.co.uk + +cafjs.com + +canva-apps.cn +*.my.canvasite.cn +canva-apps.com +*.my.canva.site + +drr.ac +uwu.ai +carrd.co +crd.co +ju.mp + +api.gov.uk + +cdn77-storage.com +rsc.contentproxy9.cz +r.cdn77.net +cdn77-ssl.net +c.cdn77.org +rsc.cdn77.org +ssl.origin.cdn77-secure.org + +za.bz +br.com +cn.com +de.com +eu.com +jpn.com +mex.com +ru.com +sa.com +uk.com +us.com +za.com +com.de +gb.net +hu.net +jp.net +se.net +uk.net +ae.org +com.se + +cx.ua + +discourse.group +discourse.team + +clerk.app +clerkstage.app +*.lcl.dev +*.lclstage.dev +*.stg.dev +*.stgstage.dev + +cleverapps.cc +*.services.clever-cloud.com +cleverapps.io +cleverapps.tech + +clickrising.net + +cloudns.asia +cloudns.be +cloudns.biz +cloudns.cc +cloudns.ch +cloudns.cl +cloudns.club +dnsabr.com +cloudns.cx +cloudns.eu +cloudns.in +cloudns.info +dns-cloud.net +dns-dynamic.net +cloudns.nz +cloudns.org +cloudns.ph +cloudns.pro +cloudns.pw +cloudns.us + +c66.me +cloud66.ws +cloud66.zone + +jdevcloud.com +wpdevcloud.com +cloudaccess.host +freesite.host +cloudaccess.net + +*.cloudera.site + +cf-ipfs.com +cloudflare-ipfs.com +trycloudflare.com +pages.dev +r2.dev +workers.dev +cloudflare.net +cdn.cloudflare.net +cdn.cloudflareanycast.net +cdn.cloudflarecn.net +cdn.cloudflareglobal.net + +cust.cloudscale.ch +objects.lpg.cloudscale.ch +objects.rma.cloudscale.ch + +wnext.app + +cnpy.gdn + +*.otap.co + +co.ca + +co.com + +codeberg.page + +csb.app +preview.csb.app + +co.nl +co.no + +webhosting.be +hosting-cluster.nl + +ctfcloud.net + +convex.site + +ac.ru +edu.ru +gov.ru +int.ru +mil.ru +test.ru + +dyn.cosidns.de +dnsupdater.de +dynamisches-dns.de +internet-dns.de +l-o-g-i-n.de +dynamic-dns.info +feste-ip.net +knx-server.net +static-access.net + +craft.me + +realm.cz + +on.crisp.email + +*.cryptonomic.net + +curv.dev + +cfolks.pl + +cyon.link +cyon.site + +platform0.app +fnwk.site +folionetwork.site + +biz.dk +co.dk +firm.dk +reg.dk +store.dk + +dyndns.dappnode.io + +builtwithdark.com +darklang.io + +demo.datadetect.com +instance.datadetect.com + +edgestack.me + +dattolocal.com +dattorelay.com +dattoweb.com +mydatto.com +dattolocal.net +mydatto.net + +ddns5.com + +ddnss.de +dyn.ddnss.de +dyndns.ddnss.de +dyn-ip24.de +dyndns1.de +home-webserver.de +dyn.home-webserver.de +myhome-server.de +ddnss.org + +debian.net + +definima.io +definima.net + +deno.dev +deno-staging.dev + +dedyn.io + +deta.app +deta.dev + +dfirma.pl +dkonto.pl +you2.pl + +ondigitalocean.app + +*.digitaloceanspaces.com + +us.kg + +rss.my.id +diher.solutions + +discordsays.com +discordsez.com + +jozi.biz + +dnshome.de + +bci.dnstrace.pro + +online.th +shop.th + +drayddns.com + +shoparena.pl + +dreamhosters.com + +durumis.com + +mydrobo.com + +drud.io +drud.us + +duckdns.org + +dy.fi +tunk.org + +dyndns.biz +for-better.biz +for-more.biz +for-some.biz +for-the.biz +selfip.biz +webhop.biz +ftpaccess.cc +game-server.cc +myphotos.cc +scrapping.cc +blogdns.com +cechire.com +dnsalias.com +dnsdojo.com +doesntexist.com +dontexist.com +doomdns.com +dyn-o-saur.com +dynalias.com +dyndns-at-home.com +dyndns-at-work.com +dyndns-blog.com +dyndns-free.com +dyndns-home.com +dyndns-ip.com +dyndns-mail.com +dyndns-office.com +dyndns-pics.com +dyndns-remote.com +dyndns-server.com +dyndns-web.com +dyndns-wiki.com +dyndns-work.com +est-a-la-maison.com +est-a-la-masion.com +est-le-patron.com +est-mon-blogueur.com +from-ak.com +from-al.com +from-ar.com +from-ca.com +from-ct.com +from-dc.com +from-de.com +from-fl.com +from-ga.com +from-hi.com +from-ia.com +from-id.com +from-il.com +from-in.com +from-ks.com +from-ky.com +from-ma.com +from-md.com +from-mi.com +from-mn.com +from-mo.com +from-ms.com +from-mt.com +from-nc.com +from-nd.com +from-ne.com +from-nh.com +from-nj.com +from-nm.com +from-nv.com +from-oh.com +from-ok.com +from-or.com +from-pa.com +from-pr.com +from-ri.com +from-sc.com +from-sd.com +from-tn.com +from-tx.com +from-ut.com +from-va.com +from-vt.com +from-wa.com +from-wi.com +from-wv.com +from-wy.com +getmyip.com +gotdns.com +hobby-site.com +homelinux.com +homeunix.com +iamallama.com +is-a-anarchist.com +is-a-blogger.com +is-a-bookkeeper.com +is-a-bulls-fan.com +is-a-caterer.com +is-a-chef.com +is-a-conservative.com +is-a-cpa.com +is-a-cubicle-slave.com +is-a-democrat.com +is-a-designer.com +is-a-doctor.com +is-a-financialadvisor.com +is-a-geek.com +is-a-green.com +is-a-guru.com +is-a-hard-worker.com +is-a-hunter.com +is-a-landscaper.com +is-a-lawyer.com +is-a-liberal.com +is-a-libertarian.com +is-a-llama.com +is-a-musician.com +is-a-nascarfan.com +is-a-nurse.com +is-a-painter.com +is-a-personaltrainer.com +is-a-photographer.com +is-a-player.com +is-a-republican.com +is-a-rockstar.com +is-a-socialist.com +is-a-student.com +is-a-teacher.com +is-a-techie.com +is-a-therapist.com +is-an-accountant.com +is-an-actor.com +is-an-actress.com +is-an-anarchist.com +is-an-artist.com +is-an-engineer.com +is-an-entertainer.com +is-certified.com +is-gone.com +is-into-anime.com +is-into-cars.com +is-into-cartoons.com +is-into-games.com +is-leet.com +is-not-certified.com +is-slick.com +is-uberleet.com +is-with-theband.com +isa-geek.com +isa-hockeynut.com +issmarterthanyou.com +likes-pie.com +likescandy.com +neat-url.com +saves-the-whales.com +selfip.com +sells-for-less.com +sells-for-u.com +servebbs.com +simple-url.com +space-to-rent.com +teaches-yoga.com +writesthisblog.com +ath.cx +fuettertdasnetz.de +isteingeek.de +istmein.de +lebtimnetz.de +leitungsen.de +traeumtgerade.de +barrel-of-knowledge.info +barrell-of-knowledge.info +dyndns.info +for-our.info +groks-the.info +groks-this.info +here-for-more.info +knowsitall.info +selfip.info +webhop.info +forgot.her.name +forgot.his.name +at-band-camp.net +blogdns.net +broke-it.net +buyshouses.net +dnsalias.net +dnsdojo.net +does-it.net +dontexist.net +dynalias.net +dynathome.net +endofinternet.net +from-az.net +from-co.net +from-la.net +from-ny.net +gets-it.net +ham-radio-op.net +homeftp.net +homeip.net +homelinux.net +homeunix.net +in-the-band.net +is-a-chef.net +is-a-geek.net +isa-geek.net +kicks-ass.net +office-on-the.net +podzone.net +scrapper-site.net +selfip.net +sells-it.net +servebbs.net +serveftp.net +thruhere.net +webhop.net +merseine.nu +mine.nu +shacknet.nu +blogdns.org +blogsite.org +boldlygoingnowhere.org +dnsalias.org +dnsdojo.org +doesntexist.org +dontexist.org +doomdns.org +dvrdns.org +dynalias.org +dyndns.org +go.dyndns.org +home.dyndns.org +endofinternet.org +endoftheinternet.org +from-me.org +game-host.org +gotdns.org +hobby-site.org +homedns.org +homeftp.org +homelinux.org +homeunix.org +is-a-bruinsfan.org +is-a-candidate.org +is-a-celticsfan.org +is-a-chef.org +is-a-geek.org +is-a-knight.org +is-a-linux-user.org +is-a-patsfan.org +is-a-soxfan.org +is-found.org +is-lost.org +is-saved.org +is-very-bad.org +is-very-evil.org +is-very-good.org +is-very-nice.org +is-very-sweet.org +isa-geek.org +kicks-ass.org +misconfused.org +podzone.org +readmyblog.org +selfip.org +sellsyourhome.org +servebbs.org +serveftp.org +servegame.org +stuff-4-sale.org +webhop.org +better-than.tv +dyndns.tv +on-the-web.tv +worse-than.tv +is-by.us +land-4-sale.us +stuff-4-sale.us +dyndns.ws +mypets.ws + +ddnsfree.com +ddnsgeek.com +giize.com +gleeze.com +kozow.com +loseyourip.com +ooguy.com +theworkpc.com +casacam.net +dynu.net +accesscam.org +camdvr.org +freeddns.org +mywire.org +webredirect.org +myddns.rocks + +dynv6.net + +e4.cz + +easypanel.app +easypanel.host + +*.ewp.live + +onred.one +staging.onred.one + +twmail.cc +twmail.net +twmail.org +mymailer.com.tw +url.tw + +at.emf.camp + +rt.ht + +elementor.cloud +elementor.cool + +en-root.fr + +mytuleap.com +tuleap-partners.com + +encr.app +encoreapi.com + +eu.encoway.cloud + +eu.org +al.eu.org +asso.eu.org +at.eu.org +au.eu.org +be.eu.org +bg.eu.org +ca.eu.org +cd.eu.org +ch.eu.org +cn.eu.org +cy.eu.org +cz.eu.org +de.eu.org +dk.eu.org +edu.eu.org +ee.eu.org +es.eu.org +fi.eu.org +fr.eu.org +gr.eu.org +hr.eu.org +hu.eu.org +ie.eu.org +il.eu.org +in.eu.org +int.eu.org +is.eu.org +it.eu.org +jp.eu.org +kr.eu.org +lt.eu.org +lu.eu.org +lv.eu.org +me.eu.org +mk.eu.org +mt.eu.org +my.eu.org +net.eu.org +ng.eu.org +nl.eu.org +no.eu.org +nz.eu.org +pl.eu.org +pt.eu.org +ro.eu.org +ru.eu.org +se.eu.org +si.eu.org +sk.eu.org +tr.eu.org +uk.eu.org +us.eu.org + +eurodir.ru + +eu-1.evennode.com +eu-2.evennode.com +eu-3.evennode.com +eu-4.evennode.com +us-1.evennode.com +us-2.evennode.com +us-3.evennode.com +us-4.evennode.com + +relay.evervault.app +relay.evervault.dev + +expo.app +staging.expo.app + +onfabrica.com + +ru.net +adygeya.ru +bashkiria.ru +bir.ru +cbg.ru +com.ru +dagestan.ru +grozny.ru +kalmykia.ru +kustanai.ru +marine.ru +mordovia.ru +msk.ru +mytis.ru +nalchik.ru +nov.ru +pyatigorsk.ru +spb.ru +vladikavkaz.ru +vladimir.ru +abkhazia.su +adygeya.su +aktyubinsk.su +arkhangelsk.su +armenia.su +ashgabad.su +azerbaijan.su +balashov.su +bashkiria.su +bryansk.su +bukhara.su +chimkent.su +dagestan.su +east-kazakhstan.su +exnet.su +georgia.su +grozny.su +ivanovo.su +jambyl.su +kalmykia.su +kaluga.su +karacol.su +karaganda.su +karelia.su +khakassia.su +krasnodar.su +kurgan.su +kustanai.su +lenug.su +mangyshlak.su +mordovia.su +msk.su +murmansk.su +nalchik.su +navoi.su +north-kazakhstan.su +nov.su +obninsk.su +penza.su +pokrovsk.su +sochi.su +spb.su +tashkent.su +termez.su +togliatti.su +troitsk.su +tselinograd.su +tula.su +tuva.su +vladikavkaz.su +vladimir.su +vologda.su + +channelsdvr.net +u.channelsdvr.net + +edgecompute.app +fastly-edge.com +fastly-terrarium.com +freetls.fastly.net +map.fastly.net +a.prod.fastly.net +global.prod.fastly.net +a.ssl.fastly.net +b.ssl.fastly.net +global.ssl.fastly.net +fastlylb.net +map.fastlylb.net + +*.user.fm + +fastvps-server.com +fastvps.host +myfast.host +fastvps.site +myfast.space + +conn.uk +copro.uk +hosp.uk + +fedorainfracloud.org +fedorapeople.org +cloud.fedoraproject.org +app.os.fedoraproject.org +app.os.stg.fedoraproject.org + +mydobiss.com + +fh-muenster.io + +filegear.me + +firebaseapp.com + +fldrv.com + +flutterflow.app + +fly.dev +shw.io +edgeapp.net + +forgeblocks.com +id.forgerock.io + +framer.ai +framer.app +framercanvas.com +framer.media +framer.photos +framer.website +framer.wiki + +0e.vc + +freebox-os.com +freeboxos.com +fbx-os.fr +fbxos.fr +freebox-os.fr +freeboxos.fr + +freedesktop.org + +freemyip.com + +*.frusky.de + +wien.funkfeuer.at + +daemon.asia +dix.asia +mydns.bz +0am.jp +0g0.jp +0j0.jp +0t0.jp +mydns.jp +pgw.jp +wjg.jp +keyword-on.net +live-on.net +server-on.net +mydns.tw +mydns.vc + +*.futurecms.at +*.ex.futurecms.at +*.in.futurecms.at +futurehosting.at +futuremailing.at +*.ex.ortsinfo.at +*.kunden.ortsinfo.at +*.statics.cloud + +aliases121.com + +campaign.gov.uk +service.gov.uk +independent-commission.uk +independent-inquest.uk +independent-inquiry.uk +independent-panel.uk +independent-review.uk +public-inquiry.uk +royal-commission.uk + +gehirn.ne.jp +usercontent.jp + +gentapps.com +gentlentapis.com +lab.ms +cdn-edges.net + +localcert.net +localhostcert.net +corpnet.work + +gsj.bz + +githubusercontent.com +githubpreview.dev +github.io + +gitlab.io + +gitapp.si +gitpage.si + +glitch.me + +nog.community + +co.ro +shop.ro + +lolipop.io +angry.jp +babyblue.jp +babymilk.jp +backdrop.jp +bambina.jp +bitter.jp +blush.jp +boo.jp +boy.jp +boyfriend.jp +but.jp +candypop.jp +capoo.jp +catfood.jp +cheap.jp +chicappa.jp +chillout.jp +chips.jp +chowder.jp +chu.jp +ciao.jp +cocotte.jp +coolblog.jp +cranky.jp +cutegirl.jp +daa.jp +deca.jp +deci.jp +digick.jp +egoism.jp +fakefur.jp +fem.jp +flier.jp +floppy.jp +fool.jp +frenchkiss.jp +girlfriend.jp +girly.jp +gloomy.jp +gonna.jp +greater.jp +hacca.jp +heavy.jp +her.jp +hiho.jp +hippy.jp +holy.jp +hungry.jp +icurus.jp +itigo.jp +jellybean.jp +kikirara.jp +kill.jp +kilo.jp +kuron.jp +littlestar.jp +lolipopmc.jp +lolitapunk.jp +lomo.jp +lovepop.jp +lovesick.jp +main.jp +mods.jp +mond.jp +mongolian.jp +moo.jp +namaste.jp +nikita.jp +nobushi.jp +noor.jp +oops.jp +parallel.jp +parasite.jp +pecori.jp +peewee.jp +penne.jp +pepper.jp +perma.jp +pigboat.jp +pinoko.jp +punyu.jp +pupu.jp +pussycat.jp +pya.jp +raindrop.jp +readymade.jp +sadist.jp +schoolbus.jp +secret.jp +staba.jp +stripper.jp +sub.jp +sunnyday.jp +thick.jp +tonkotsu.jp +under.jp +upper.jp +velvet.jp +verse.jp +versus.jp +vivian.jp +watson.jp +weblike.jp +whitesnow.jp +zombie.jp +heteml.net + +graphic.design + +goip.de + +blogspot.ae +blogspot.al +blogspot.am +*.hosted.app +*.run.app +web.app +blogspot.com.ar +blogspot.co.at +blogspot.com.au +blogspot.ba +blogspot.be +blogspot.bg +blogspot.bj +blogspot.com.br +blogspot.com.by +blogspot.ca +blogspot.cf +blogspot.ch +blogspot.cl +blogspot.com.co +*.0emm.com +appspot.com +*.r.appspot.com +blogspot.com +codespot.com +googleapis.com +googlecode.com +pagespeedmobilizer.com +withgoogle.com +withyoutube.com +blogspot.cv +blogspot.com.cy +blogspot.cz +blogspot.de +*.gateway.dev +blogspot.dk +blogspot.com.ee +blogspot.com.eg +blogspot.com.es +blogspot.fi +blogspot.fr +cloud.goog +translate.goog +*.usercontent.goog +blogspot.gr +blogspot.hk +blogspot.hr +blogspot.hu +blogspot.co.id +blogspot.ie +blogspot.co.il +blogspot.in +blogspot.is +blogspot.it +blogspot.jp +blogspot.co.ke +blogspot.kr +blogspot.li +blogspot.lt +blogspot.lu +blogspot.md +blogspot.mk +blogspot.com.mt +blogspot.mx +blogspot.my +cloudfunctions.net +blogspot.com.ng +blogspot.nl +blogspot.no +blogspot.co.nz +blogspot.pe +blogspot.pt +blogspot.qa +blogspot.re +blogspot.ro +blogspot.rs +blogspot.ru +blogspot.se +blogspot.sg +blogspot.si +blogspot.sk +blogspot.sn +blogspot.td +blogspot.com.tr +blogspot.tw +blogspot.ug +blogspot.co.uk +blogspot.com.uy +blogspot.vn +blogspot.co.za + +goupile.fr + +pymnt.uk + +cloudapps.digital +london.cloudapps.digital + +gov.nl + +grayjayleagues.com + +günstigbestellen.de +günstigliefern.de + +fin.ci +free.hr +caa.li +ua.rs +conf.se + +häkkinen.fi + +hs.run +hs.zone + +wdh.app +hrsn.dev + +hashbang.sh + +hasura.app +hasura-app.io + +hatenablog.com +hatenadiary.com +hateblo.jp +hatenablog.jp +hatenadiary.jp +hatenadiary.org + +pages.it.hs-heilbronn.de + +heiyu.space + +helioho.st +heliohost.us + +hepforge.org + +herokuapp.com +herokussl.com + +ravendb.cloud +ravendb.community +development.run +ravendb.run + +homesklep.pl + +*.kin.one +*.id.pub +*.kin.pub + +secaas.hk + +hoplix.shop + +orx.biz +biz.gl +biz.ng +co.biz.ng +dl.biz.ng +go.biz.ng +lg.biz.ng +on.biz.ng +col.ng +firm.ng +gen.ng +ltd.ng +ngo.ng +plc.ng + +ie.ua + +hostyhosting.io + +hf.space +static.hf.space + +hypernode.io + +iobb.net + +co.cz + +*.moonscale.io +moonscale.net + +gr.com + +iki.fi + +ibxos.it +iliadboxos.it + +smushcdn.com +wphostedmail.com +wpmucdn.com +tempurl.host +wpmudev.host + +dyn-berlin.de +in-berlin.de +in-brb.de +in-butter.de +in-dsl.de +in-vpn.de +in-dsl.net +in-vpn.net +in-dsl.org +in-vpn.org + +biz.at +info.at + +info.cx + +ac.leg.br +al.leg.br +am.leg.br +ap.leg.br +ba.leg.br +ce.leg.br +df.leg.br +es.leg.br +go.leg.br +ma.leg.br +mg.leg.br +ms.leg.br +mt.leg.br +pa.leg.br +pb.leg.br +pe.leg.br +pi.leg.br +pr.leg.br +rj.leg.br +rn.leg.br +ro.leg.br +rr.leg.br +rs.leg.br +sc.leg.br +se.leg.br +sp.leg.br +to.leg.br + +pixolino.com + +na4u.ru + +apps-1and1.com +live-website.com +apps-1and1.net +websitebuilder.online +app-ionos.space + +iopsys.se + +*.dweb.link + +ipifony.net + +ir.md + +is-a-good.dev + +is-a.dev + +iservschule.de +mein-iserv.de +schulplattform.de +schulserver.de +test-iserv.de +iserv.dev + +mel.cloudlets.com.au +cloud.interhostsolutions.be +alp1.ae.flow.ch +appengine.flow.ch +es-1.axarnet.cloud +diadem.cloud +vip.jelastic.cloud +jele.cloud +it1.eur.aruba.jenv-aruba.cloud +it1.jenv-aruba.cloud +keliweb.cloud +cs.keliweb.cloud +oxa.cloud +tn.oxa.cloud +uk.oxa.cloud +primetel.cloud +uk.primetel.cloud +ca.reclaim.cloud +uk.reclaim.cloud +us.reclaim.cloud +ch.trendhosting.cloud +de.trendhosting.cloud +jele.club +dopaas.com +paas.hosted-by-previder.com +rag-cloud.hosteur.com +rag-cloud-ch.hosteur.com +jcloud.ik-server.com +jcloud-ver-jpc.ik-server.com +demo.jelastic.com +paas.massivegrid.com +jed.wafaicloud.com +ryd.wafaicloud.com +j.scaleforce.com.cy +jelastic.dogado.eu +fi.cloudplatform.fi +demo.datacenter.fi +paas.datacenter.fi +jele.host +mircloud.host +paas.beebyte.io +sekd1.beebyteapp.io +jele.io +jc.neen.it +jcloud.kz +cloudjiffy.net +fra1-de.cloudjiffy.net +west1-us.cloudjiffy.net +jls-sto1.elastx.net +jls-sto2.elastx.net +jls-sto3.elastx.net +fr-1.paas.massivegrid.net +lon-1.paas.massivegrid.net +lon-2.paas.massivegrid.net +ny-1.paas.massivegrid.net +ny-2.paas.massivegrid.net +sg-1.paas.massivegrid.net +jelastic.saveincloud.net +nordeste-idc.saveincloud.net +j.scaleforce.net +sdscloud.pl +unicloud.pl +mircloud.ru +enscaled.sg +jele.site +jelastic.team +orangecloud.tn +j.layershift.co.uk +phx.enscaled.us +mircloud.us + +myjino.ru +*.hosting.myjino.ru +*.landing.myjino.ru +*.spectrum.myjino.ru +*.vps.myjino.ru + +jotelulu.cloud + +webadorsite.com +jouwweb.site + +*.cns.joyent.com +*.triton.zone + +js.org + +kaas.gg +khplay.nl + +kapsi.fi + +ezproxy.kuleuven.be +kuleuven.cloud + +keymachine.de + +kinghost.net +uni5.net + +knightpoint.systems + +koobin.events + +webthings.io +krellian.net + +oya.to + +git-repos.de +lcube-server.de +svn-repos.de + +leadpages.co +lpages.co +lpusercontent.com + +lelux.site + +libp2p.direct + +runcontainers.dev + +co.business +co.education +co.events +co.financial +co.network +co.place +co.technology + +linkyard-cloud.ch +linkyard.cloud + +members.linode.com +*.nodebalancer.linode.com +*.linodeobjects.com +ip.linodeusercontent.com + +we.bs + +filegear-sg.me +ggff.net + +*.user.localcert.dev + +loginline.app +loginline.dev +loginline.io +loginline.services +loginline.site + +lohmus.me + +servers.run + +krasnik.pl +leczna.pl +lubartow.pl +lublin.pl +poniatowa.pl +swidnik.pl + +glug.org.uk +lug.org.uk +lugs.org.uk + +barsy.bg +barsy.club +barsycenter.com +barsyonline.com +barsy.de +barsy.dev +barsy.eu +barsy.gr +barsy.in +barsy.info +barsy.io +barsy.me +barsy.menu +barsyonline.menu +barsy.mobi +barsy.net +barsy.online +barsy.org +barsy.pro +barsy.pub +barsy.ro +barsy.rs +barsy.shop +barsyonline.shop +barsy.site +barsy.store +barsy.support +barsy.uk +barsy.co.uk +barsyonline.co.uk + +*.magentosite.cloud + +hb.cldmail.ru + +matlab.cloud +modelscape.com +mwcloudnonprod.com +polyspace.com + +mayfirst.info +mayfirst.org + +mazeplay.com + +mcdir.me +mcdir.ru +vps.mcdir.ru +mcpre.ru + +mcpe.me + +mediatech.by +mediatech.dev + +hra.health + +miniserver.com +memset.net + +messerli.app + +atmeta.com +apps.fbsbx.com + +*.cloud.metacentrum.cz +custom.metacentrum.cz +flt.cloud.muni.cz +usr.cloud.muni.cz + +meteorapp.com +eu.meteorapp.com + +co.pl + +*.azurecontainer.io +azure-api.net +azure-mobile.net +azureedge.net +azurefd.net +azurestaticapps.net +1.azurestaticapps.net +2.azurestaticapps.net +3.azurestaticapps.net +4.azurestaticapps.net +5.azurestaticapps.net +6.azurestaticapps.net +7.azurestaticapps.net +centralus.azurestaticapps.net +eastasia.azurestaticapps.net +eastus2.azurestaticapps.net +westeurope.azurestaticapps.net +westus2.azurestaticapps.net +azurewebsites.net +cloudapp.net +trafficmanager.net +blob.core.windows.net +servicebus.windows.net + +routingthecloud.com +sn.mynetname.net +routingthecloud.net +routingthecloud.org + +csx.cc + +mydbserver.com +webspaceconfig.de +mittwald.info +mittwaldserver.info +typo3server.info +project.space + +modx.dev + +bmoattachments.org + +net.ru +org.ru +pp.ru + +hostedpi.com +caracal.mythic-beasts.com +customer.mythic-beasts.com +fentiger.mythic-beasts.com +lynx.mythic-beasts.com +ocelot.mythic-beasts.com +oncilla.mythic-beasts.com +onza.mythic-beasts.com +sphinx.mythic-beasts.com +vs.mythic-beasts.com +x.mythic-beasts.com +yali.mythic-beasts.com +cust.retrosnub.co.uk + +ui.nabu.casa + +cloud.nospamproxy.com + +netfy.app + +netlify.app + +4u.com + +nfshost.com + +ipfs.nftstorage.link + +ngo.us + +ngrok.app +ngrok-free.app +ngrok.dev +ngrok-free.dev +ngrok.io +ap.ngrok.io +au.ngrok.io +eu.ngrok.io +in.ngrok.io +jp.ngrok.io +sa.ngrok.io +us.ngrok.io +ngrok.pizza +ngrok.pro + +torun.pl + +nh-serv.co.uk +nimsite.uk + +mmafan.biz +myftp.biz +no-ip.biz +no-ip.ca +fantasyleague.cc +gotdns.ch +3utilities.com +blogsyte.com +ciscofreak.com +damnserver.com +ddnsking.com +ditchyourip.com +dnsiskinky.com +dynns.com +geekgalaxy.com +health-carereform.com +homesecuritymac.com +homesecuritypc.com +myactivedirectory.com +mysecuritycamera.com +myvnc.com +net-freaks.com +onthewifi.com +point2this.com +quicksytes.com +securitytactics.com +servebeer.com +servecounterstrike.com +serveexchange.com +serveftp.com +servegame.com +servehalflife.com +servehttp.com +servehumour.com +serveirc.com +servemp3.com +servep2p.com +servepics.com +servequake.com +servesarcasm.com +stufftoread.com +unusualperson.com +workisboring.com +dvrcam.info +ilovecollege.info +no-ip.info +brasilia.me +ddns.me +dnsfor.me +hopto.me +loginto.me +noip.me +webhop.me +bounceme.net +ddns.net +eating-organic.net +mydissent.net +myeffect.net +mymediapc.net +mypsx.net +mysecuritycamera.net +nhlfan.net +no-ip.net +pgafan.net +privatizehealthinsurance.net +redirectme.net +serveblog.net +serveminecraft.net +sytes.net +cable-modem.org +collegefan.org +couchpotatofries.org +hopto.org +mlbfan.org +myftp.org +mysecuritycamera.org +nflfan.org +no-ip.org +read-books.org +ufcfan.org +zapto.org +no-ip.co.uk +golffan.us +noip.us +pointto.us + +stage.nodeart.io + +*.developer.app +noop.app + +*.northflank.app +*.build.run +*.code.run +*.database.run +*.migration.run + +noticeable.news + +notion.site + +dnsking.ch +mypi.co +n4t.co +001www.com +myiphost.com +forumz.info +soundcast.me +tcp4.me +dnsup.net +hicam.net +now-dns.net +ownip.net +vpndns.net +dynserv.org +now-dns.org +x443.pw +now-dns.top +ntdll.top +freeddns.us + +nsupdate.info +nerdpol.ovh + +nyc.mn + +prvcy.page + +obl.ong + +observablehq.cloud +static.observableusercontent.com + +omg.lol + +cloudycluster.net + +omniwe.site + +123webseite.at +123website.be +simplesite.com.br +123website.ch +simplesite.com +123webseite.de +123hjemmeside.dk +123miweb.es +123kotisivu.fi +123siteweb.fr +simplesite.gr +123homepage.it +123website.lu +123website.nl +123hjemmeside.no +service.one +simplesite.pl +123paginaweb.pt +123minsida.se + +is-a-fullstack.dev +is-cool.dev +is-not-a.dev +localplayer.dev +is-local.org + +opensocial.site + +opencraft.hosting + +16-b.it +32-b.it +64-b.it + +orsites.com + +operaunite.com + +*.customer-oci.com +*.oci.customer-oci.com +*.ocp.customer-oci.com +*.ocs.customer-oci.com +*.oraclecloudapps.com +*.oraclegovcloudapps.com +*.oraclegovcloudapps.uk + +tech.orange + +can.re + +authgear-staging.com +authgearapps.com +skygearapp.com + +outsystemscloud.com + +*.hosting.ovh.net +*.webpaas.ovh.net + +ownprovider.com +own.pm + +*.owo.codes + +ox.rs + +oy.lc + +pgfog.com + +pagexl.com + +gotpantheon.com +pantheonsite.io + +*.paywhirl.com + +*.xmit.co +xmit.dev +madethis.site +srv.us +gh.srv.us +gl.srv.us + +lk3.ru + +mypep.link + +perspecta.cloud + +on-web.fr + +*.upsun.app +upsunapp.com +ent.platform.sh +eu.platform.sh +us.platform.sh +*.platformsh.site +*.tst.site + +platter-app.com +platter-app.dev +platterp.us + +pley.games + +onporter.run + +co.bn + +postman-echo.com +pstmn.io +mock.pstmn.io +httpbin.org + +prequalifyme.today + +xen.prgmr.com + +priv.at + +protonet.io + +chirurgiens-dentistes-en-france.fr +byen.site + +pubtls.org + +pythonanywhere.com +eu.pythonanywhere.com + +qa2.com + +qcx.io +*.sys.qcx.io + +myqnapcloud.cn +alpha-myqnapcloud.com +dev-myqnapcloud.com +mycloudnas.com +mynascloud.com +myqnapcloud.com + +qoto.io + +qualifioapp.com + +ladesk.com + +qbuser.com + +*.quipelements.com + +vapor.cloud +vaporcloud.io + +rackmaze.com +rackmaze.net + +cloudsite.builders +myradweb.net +servername.us + +web.in +in.net + +myrdbx.io +site.rb-hosting.io + +*.on-rancher.cloud +*.on-k3s.io +*.on-rio.io + +ravpage.co.il + +readthedocs-hosted.com +readthedocs.io + +rhcloud.com + +instances.spawn.cc + +onrender.com +app.render.com + +replit.app +id.replit.app +firewalledreplit.co +id.firewalledreplit.co +repl.co +id.repl.co +replit.dev +archer.replit.dev +bones.replit.dev +canary.replit.dev +global.replit.dev +hacker.replit.dev +id.replit.dev +janeway.replit.dev +kim.replit.dev +kira.replit.dev +kirk.replit.dev +odo.replit.dev +paris.replit.dev +picard.replit.dev +pike.replit.dev +prerelease.replit.dev +reed.replit.dev +riker.replit.dev +sisko.replit.dev +spock.replit.dev +staging.replit.dev +sulu.replit.dev +tarpit.replit.dev +teams.replit.dev +tucker.replit.dev +wesley.replit.dev +worf.replit.dev +repl.run + +resindevice.io +devices.resinstaging.io + +hzc.io + +adimo.co.uk + +itcouldbewor.se + +aus.basketball +nz.basketball + +git-pages.rit.edu + +rocky.page + +биз.рус +ком.рус +крым.рус +мир.рус +мск.рус +орг.рус +самара.рус +сочи.рус +спб.рус +я.рус + +ras.ru + +nyat.app + +180r.com +dojin.com +sakuratan.com +sakuraweb.com +x0.com +2-d.jp +bona.jp +crap.jp +daynight.jp +eek.jp +flop.jp +halfmoon.jp +jeez.jp +matrix.jp +mimoza.jp +ivory.ne.jp +mail-box.ne.jp +mints.ne.jp +mokuren.ne.jp +opal.ne.jp +sakura.ne.jp +sumomo.ne.jp +topaz.ne.jp +netgamers.jp +nyanta.jp +o0o0.jp +rdy.jp +rgr.jp +rulez.jp +s3.isk01.sakurastorage.jp +s3.isk02.sakurastorage.jp +saloon.jp +sblo.jp +skr.jp +tank.jp +uh-oh.jp +undo.jp +rs.webaccel.jp +user.webaccel.jp +websozai.jp +xii.jp +squares.net +jpn.org +kirara.st +x0.to +from.tv +sakura.tv + +*.builder.code.com +*.dev-builder.code.com +*.stg-builder.code.com +*.001.test.code-builder-stg.platform.salesforce.com +*.d.crm.dev +*.w.crm.dev +*.wa.crm.dev +*.wb.crm.dev +*.wc.crm.dev +*.wd.crm.dev +*.we.crm.dev +*.wf.crm.dev + +sandcats.io + +logoip.com +logoip.de + +fr-par-1.baremetal.scw.cloud +fr-par-2.baremetal.scw.cloud +nl-ams-1.baremetal.scw.cloud +cockpit.fr-par.scw.cloud +fnc.fr-par.scw.cloud +functions.fnc.fr-par.scw.cloud +k8s.fr-par.scw.cloud +nodes.k8s.fr-par.scw.cloud +s3.fr-par.scw.cloud +s3-website.fr-par.scw.cloud +whm.fr-par.scw.cloud +priv.instances.scw.cloud +pub.instances.scw.cloud +k8s.scw.cloud +cockpit.nl-ams.scw.cloud +k8s.nl-ams.scw.cloud +nodes.k8s.nl-ams.scw.cloud +s3.nl-ams.scw.cloud +s3-website.nl-ams.scw.cloud +whm.nl-ams.scw.cloud +cockpit.pl-waw.scw.cloud +k8s.pl-waw.scw.cloud +nodes.k8s.pl-waw.scw.cloud +s3.pl-waw.scw.cloud +s3-website.pl-waw.scw.cloud +scalebook.scw.cloud +smartlabeling.scw.cloud +dedibox.fr + +schokokeks.net + +gov.scot +service.gov.scot + +scrysec.com + +client.scrypted.io + +firewall-gateway.com +firewall-gateway.de +my-gateway.de +my-router.de +spdns.de +spdns.eu +firewall-gateway.net +my-firewall.org +myfirewall.org +spdns.org + +seidat.net + +sellfy.store + +minisite.ms + +senseering.net + +servebolt.cloud + +biz.ua +co.ua +pp.ua + +as.sh.cn + +sheezy.games + +shiftedit.io + +myshopblocks.com + +myshopify.com + +shopitsite.com + +shopware.shop +shopware.store + +mo-siemens.io + +1kapp.com +appchizi.com +applinzi.com +sinaapp.com +vipsinaapp.com + +siteleaf.net + +small-web.org + +aeroport.fr +avocat.fr +chambagri.fr +chirurgiens-dentistes.fr +experts-comptables.fr +medecin.fr +notaires.fr +pharmacien.fr +port.fr +veterinaire.fr + +vp4.me + +*.snowflake.app +*.privatelink.snowflake.app +streamlit.app +streamlitapp.com + +try-snowplow.com + +mafelo.net + +playstation-cloud.com + +srht.site + +apps.lair.io +*.stolos.io + +spacekit.io + +ind.mom + +customer.speedpartner.de + +myspreadshop.at +myspreadshop.com.au +myspreadshop.be +myspreadshop.ca +myspreadshop.ch +myspreadshop.com +myspreadshop.de +myspreadshop.dk +myspreadshop.es +myspreadshop.fi +myspreadshop.fr +myspreadshop.ie +myspreadshop.it +myspreadshop.net +myspreadshop.nl +myspreadshop.no +myspreadshop.pl +myspreadshop.se +myspreadshop.co.uk + +w-corp-staticblitz.com +w-credentialless-staticblitz.com +w-staticblitz.com + +stackhero-network.com + +runs.onstackit.cloud +stackit.gg +stackit.rocks +stackit.run +stackit.zone + +musician.io +novecore.site + +api.stdlib.com + +feedback.ac +forms.ac +assessments.cx +calculators.cx +funnels.cx +paynow.cx +quizzes.cx +researched.cx +tests.cx +surveys.so + +storebase.store + +storipress.app + +storj.farm + +strapiapp.com +media.strapiapp.com + +vps-host.net +atl.jelastic.vps-host.net +njs.jelastic.vps-host.net +ric.jelastic.vps-host.net + +streak-link.com +streaklinks.com +streakusercontent.com + +soc.srcf.net +user.srcf.net + +utwente.io + +temp-dns.com + +supabase.co +supabase.in +supabase.net + +syncloud.it + +dscloud.biz +direct.quickconnect.cn +dsmynas.com +familyds.com +diskstation.me +dscloud.me +i234.me +myds.me +synology.me +dscloud.mobi +dsmynas.net +familyds.net +dsmynas.org +familyds.org +direct.quickconnect.to +vpnplus.to + +mytabit.com +mytabit.co.il +tabitorder.co.il + +taifun-dns.de + +beta.tailscale.net +ts.net +*.c.ts.net + +gda.pl +gdansk.pl +gdynia.pl +med.pl +sopot.pl + +p.tawk.email +p.tawkto.email + +site.tb-hosting.com + +edugit.io +s3.teckids.org + +telebit.app +telebit.io +*.telebit.xyz + +*.firenet.ch +*.svc.firenet.ch +reservd.com +thingdustdata.com +cust.dev.thingdust.io +reservd.dev.thingdust.io +cust.disrec.thingdust.io +reservd.disrec.thingdust.io +cust.prod.thingdust.io +cust.testing.thingdust.io +reservd.testing.thingdust.io + +tickets.io + +arvo.network +azimuth.network +tlon.network + +torproject.net +pages.torproject.net + +bloxcms.com +townnews-staging.com + +12hp.at +2ix.at +4lima.at +lima-city.at +12hp.ch +2ix.ch +4lima.ch +lima-city.ch +trafficplex.cloud +de.cool +12hp.de +2ix.de +4lima.de +lima-city.de +1337.pictures +clan.rip +lima-city.rocks +webspace.rocks +lima.zone + +*.transurl.be +*.transurl.eu +site.transip.me +*.transurl.nl + +tuxfamily.org + +dd-dns.de +dray-dns.de +draydns.de +dyn-vpn.de +dynvpn.de +mein-vigor.de +my-vigor.de +my-wan.de +syno-ds.de +synology-diskstation.de +synology-ds.de +diskstation.eu +diskstation.org + +typedream.app + +pro.typeform.com + +*.uberspace.de +uber.space + +hk.com +inc.hk +ltd.hk +hk.org + +it.com + +unison-services.cloud + +virtual-user.de +virtualuser.de + +name.pm +sch.tf +biz.wf +sch.wf +org.yt + +rs.ba + +bielsko.pl + +upli.io + +urown.cloud +dnsupdate.info + +us.org + +v.ua + +express.val.run +web.val.run + +vercel.app +v0.build +vercel.dev +vusercontent.net +now.sh + +2038.io + +router.management + +v-info.info + +voorloper.cloud + +*.vultrobjects.com + +wafflecell.com + +webflow.io +webflowtest.io + +*.webhare.dev + +bookonline.app +hotelwithflight.com +reserve-online.com +reserve-online.net + +cprapid.com +pleskns.com +wp2.host +pdns.page +plesk.page +wpsquared.site + +*.wadl.top + +remotewd.com + +box.ca + +pages.wiardweb.com + +toolforge.org +wmcloud.org +wmflabs.org + +panel.gg +daemon.panel.gg + +wixsite.com +wixstudio.com +editorx.io +wixstudio.io +wix.run + +messwithdns.com + +woltlab-demo.com +myforum.community +community-pro.de +diskussionsbereich.de +community-pro.net +meinforum.net + +affinitylottery.org.uk +raffleentry.org.uk +weeklylottery.org.uk + +wpenginepowered.com +js.wpenginepowered.com + +half.host + +xnbay.com +u2.xnbay.com +u2-local.xnbay.com + +cistron.nl +demon.nl +xs4all.space + +yandexcloud.net +storage.yandexcloud.net +website.yandexcloud.net + +official.academy + +yolasite.com + +ybo.faith +yombo.me +homelink.one +ybo.party +ybo.review +ybo.science +ybo.trade + +ynh.fr +nohost.me +noho.st + +za.net +za.org + +zap.cloud + +zeabur.app + +bss.design + +basicserver.io +virtualserver.io +enterprisecloud.nu diff --git a/DrissionPage/_functions/tools.py b/DrissionPage/_functions/tools.py index 2486718..3ce8970 100644 --- a/DrissionPage/_functions/tools.py +++ b/DrissionPage/_functions/tools.py @@ -2,13 +2,12 @@ """ @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 from shutil import rmtree -from tempfile import gettempdir, TemporaryDirectory +from tempfile import gettempdir from threading import Lock from time import perf_counter, sleep @@ -18,52 +17,48 @@ from ..errors import (ContextLostError, ElementLostError, CDPError, PageDisconne class PortFinder(object): - used_port = {} + used_port = set() + prev_time = 0 lock = Lock() + checked_paths = set() def __init__(self, path=None): - """ - :param path: 临时文件保存路径,为None时使用系统临时文件夹 - """ tmp = Path(path) if path else Path(gettempdir()) / 'DrissionPage' - self.tmp_dir = tmp / 'UserTempFolder' + self.tmp_dir = tmp / 'autoPortData' self.tmp_dir.mkdir(parents=True, exist_ok=True) - if not PortFinder.used_port: - clean_folder(self.tmp_dir) + if str(self.tmp_dir.absolute()) not in PortFinder.checked_paths: + for i in self.tmp_dir.iterdir(): + if i.is_dir() and not port_is_using('127.0.0.1', i.name): + rmtree(i, ignore_errors=True) + PortFinder.checked_paths.add(str(self.tmp_dir.absolute())) def get_port(self, scope=None): - """查找一个可用端口 - :param scope: 指定端口范围,不含最后的数字,为None则使用[9600-19600) - :return: 可以使用的端口和用户文件夹路径组成的元组 - """ + from random import randint with PortFinder.lock: + if PortFinder.prev_time and perf_counter() - PortFinder.prev_time > 60: + PortFinder.used_port.clear() if scope in (True, None): - scope = (9600, 19600) - for i in range(scope[0], scope[1]): - if i in PortFinder.used_port: + scope = (9600, 59600) + max_times = scope[1] - scope[0] + times = 0 + while times < max_times: + times += 1 + port = randint(*scope) + if port in PortFinder.used_port or port_is_using('127.0.0.1', port): continue - elif port_is_using('127.0.0.1', i): - PortFinder.used_port[i] = None - continue - path = TemporaryDirectory(dir=self.tmp_dir).name - PortFinder.used_port[i] = path - return i, path - - for i in range(scope[0], scope[1]): - if port_is_using('127.0.0.1', i): - continue - rmtree(PortFinder.used_port[i], ignore_errors=True) - return i, TemporaryDirectory(dir=self.tmp_dir).name - - raise OSError('未找到可用端口。') + path = self.tmp_dir / str(port) + if path.exists(): + try: + rmtree(path) + except: + continue + PortFinder.used_port.add(port) + PortFinder.prev_time = perf_counter() + return port, str(path) + raise OSError('未找到可用端口。') def port_is_using(ip, port): - """检查端口是否被占用 - :param ip: 浏览器地址 - :param port: 浏览器端口 - :return: bool - """ from socket import socket, AF_INET, SOCK_STREAM s = socket(AF_INET, SOCK_STREAM) s.settimeout(.1) @@ -73,11 +68,6 @@ def port_is_using(ip, port): def clean_folder(folder_path, ignore=None): - """清空一个文件夹,除了ignore里的文件和文件夹 - :param folder_path: 要清空的文件夹路径 - :param ignore: 忽略列表 - :return: None - """ ignore = [] if not ignore else ignore p = Path(folder_path) @@ -89,13 +79,8 @@ def clean_folder(folder_path, ignore=None): rmtree(f, True) -def show_or_hide_browser(page, hide=True): - """执行显示或隐藏浏览器窗口 - :param page: ChromePage对象 - :param hide: 是否隐藏 - :return: None - """ - if not page.address.startswith(('127.0.0.1', 'localhost')): +def show_or_hide_browser(tab, hide=True): + if not tab.browser.address.startswith(('127.0.0.1', 'localhost')): return if system().lower() != 'windows': @@ -107,21 +92,16 @@ def show_or_hide_browser(page, hide=True): except ImportError: raise ImportError('请先安装:pip install pypiwin32') - pid = page._page.process_id + pid = tab.browser.process_id if not pid: return None - hds = get_hwnds_from_pid(pid, page.title) + hds = get_hwnds_from_pid(pid, tab.title) sw = SW_HIDE if hide else SW_SHOW for hd in hds: ShowWindow(hd, sw) def get_browser_progress_id(progress, address): - """获取浏览器进程id - :param progress: 已知的进程对象,没有时传入None - :param address: 浏览器管理地址,含端口 - :return: 进程id或None - """ if progress: return progress.pid @@ -140,11 +120,6 @@ def get_browser_progress_id(progress, address): def get_hwnds_from_pid(pid, title): - """通过PID查询句柄ID - :param pid: 进程id - :param title: 窗口标题 - :return: 进程句柄组成的列表 - """ try: from win32gui import IsWindow, GetWindowText, EnumWindows from win32process import GetWindowThreadProcessId @@ -164,12 +139,6 @@ def get_hwnds_from_pid(pid, title): def wait_until(function, kwargs=None, timeout=10): - """等待传入的方法返回值不为假 - :param function: 要执行的方法 - :param kwargs: 方法参数 - :param timeout: 超时时间(秒) - :return: 执行结果,超时抛出TimeoutError - """ if kwargs is None: kwargs = {} end_time = perf_counter() + timeout @@ -182,21 +151,12 @@ def wait_until(function, kwargs=None, timeout=10): 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 raise_error(result, ignore=None): - """抛出error对应报错 - :param result: 包含error的dict - :param ignore: 要忽略的错误 - :return: None - """ +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'): @@ -217,16 +177,23 @@ def raise_error(result, ignore=None): r = StorageError() elif error == 'Sanitizing cookie failed': r = CookieFormatError(f'cookie格式不正确:{result["args"]}') + elif error == 'Invalid header name': + r = ValueError(f'header名不正确。\n参数:{result["args"]["headers"]}') 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"]}') - elif result['type'] in ('call_method_error', 'timeout'): + 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' \ + f'版本:{__version__}\n超时,可能是浏览器卡了。' + r = TimeoutError(txt) + elif result['type'] == 'call_method_error' and not user: from DrissionPage import __version__ txt = f'\n错误:{result["error"]}\n方法:{result["method"]}\n参数:{result["args"]}\n' \ f'版本:{__version__}\n出现这个错误可能意味着程序有bug,请把错误信息和重现方法' \ '告知作者,谢谢。\n报告网站:https://gitee.com/g1879/DrissionPage/issues' - r = TimeoutError(txt) if result['type'] == 'timeout' else CDPError(txt) + r = CDPError(txt) else: r = RuntimeError(result) diff --git a/DrissionPage/_functions/tools.pyi b/DrissionPage/_functions/tools.pyi index a6fc535..3f32458 100644 --- a/DrissionPage/_functions/tools.pyi +++ b/DrissionPage/_functions/tools.pyi @@ -2,47 +2,108 @@ """ @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 class PortFinder(object): - used_port: dict = ... + used_port: set = ... + prev_time: float = ... lock: Lock = ... tmp_dir: Path = ... + checked_paths: set = ... - def __init__(self, path: Union[str, Path] = None): ... + def __init__(self, path: Union[str, Path] = None): + """ + :param path: 临时文件保存路径,为None时使用系统临时文件夹 + """ + ... @staticmethod - def get_port(scope: Tuple[int, int] = None) -> Tuple[int, str]: ... + def get_port(scope: Tuple[int, int] = None) -> Tuple[int, str]: + """查找一个可用端口 + :param scope: 指定端口范围,不含最后的数字,为None则使用[9600-59600) + :return: 可以使用的端口和用户文件夹路径组成的元组 + """ + ... -def port_is_using(ip: str, port: Union[str, int]) -> bool: ... +def port_is_using(ip: str, port: Union[str, int]) -> bool: + """检查端口是否被占用 + :param ip: 浏览器地址 + :param port: 浏览器端口 + :return: bool + """ + ... -def clean_folder(folder_path: Union[str, Path], ignore: Union[tuple, list] = None) -> None: ... +def clean_folder(folder_path: Union[str, Path], ignore: Union[tuple, list] = None) -> None: + """清空一个文件夹,除了ignore里的文件和文件夹 + :param folder_path: 要清空的文件夹路径 + :param ignore: 忽略列表 + :return: None + """ + ... -def show_or_hide_browser(page: ChromiumBase, hide: bool = True) -> None: ... +def show_or_hide_browser(tab: ChromiumBase, hide: bool = True) -> None: + """执行显示或隐藏浏览器窗口 + :param tab: ChromiumTab对象 + :param hide: 是否隐藏 + :return: None + """ + ... -def get_browser_progress_id(progress: Union[popen, None], address: str) -> Union[str, None]: ... +def get_browser_progress_id(progress: Union[popen, None], address: str) -> Union[str, None]: + """获取浏览器进程id + :param progress: 已知的进程对象,没有时传入None + :param address: 浏览器管理地址,含端口 + :return: 进程id或None + """ + ... -def get_hwnds_from_pid(pid: Union[str, int], title: str) -> list: ... +def get_hwnds_from_pid(pid: Union[str, int], title: str) -> list: + """通过PID查询句柄ID + :param pid: 进程id + :param title: 窗口标题 + :return: 进程句柄组成的列表 + """ + ... -def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): ... +def wait_until(function: callable, kwargs: dict = None, timeout: float = 10): + """等待传入的方法返回值不为假 + :param function: 要执行的方法 + :param kwargs: 方法参数 + :param timeout: 超时时间(秒) + :return: 执行结果,超时抛出TimeoutError + """ + ... -def configs_to_here(file_name: Union[Path, str] = None) -> None: ... +def configs_to_here(save_name: Union[Path, str] = None) -> None: + """把默认ini文件复制到当前目录 + :param save_name: 指定文件名,为None则命名为'dp_configs.ini' + :return: None + """ + ... -def raise_error(result: dict, ignore=None) -> 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 + """ + ... diff --git a/DrissionPage/_functions/web.py b/DrissionPage/_functions/web.py index 461f143..5fcb34c 100644 --- a/DrissionPage/_functions/web.py +++ b/DrissionPage/_functions/web.py @@ -2,26 +2,19 @@ """ @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 html import unescape -from http.cookiejar import Cookie, CookieJar from os.path import sep from pathlib import Path -from re import sub, match +from re import sub from urllib.parse import urlparse, urljoin, urlunparse from DataRecorder.tools import make_valid_name -from tldextract import extract +from requests.structures import CaseInsensitiveDict def get_ele_txt(e): - """获取元素内所有文本 - :param e: 元素对象 - :return: 元素内所有文本 - """ # 前面无须换行的元素 nowrap_list = ('br', 'sub', 'sup', 'em', 'strong', 'a', 'font', 'b', 'span', 's', 'i', 'del', 'ins', 'img', 'td', 'th', 'abbr', 'bdi', 'bdo', 'cite', 'code', 'data', 'dfn', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby', @@ -37,7 +30,7 @@ def get_ele_txt(e): if e.tag in noText_list: return e.raw_text - def get_node_txt(ele, pre: bool = False): + def get_node_txt(ele, pre=False) -> list: tag = ele.tag if tag == 'br': return [True] @@ -59,7 +52,7 @@ def get_ele_txt(e): if sub('[ \n\t\r]', '', el) != '': # 字符除了回车和空格还有其它内容 txt = el if not pre: - txt = txt.replace('\r\n', ' ').replace('\n', ' ').strip(' ') + txt = txt.replace('\r\n', ' ').replace('\n', ' ') txt = sub(r' {2,}', ' ', txt) str_list.append(txt) @@ -80,25 +73,38 @@ def get_ele_txt(e): re_str = get_node_txt(e) if re_str and re_str[-1] == '\n': re_str.pop() - re_str = ''.join([i if i is not True else '\n' for i in re_str]) - return format_html(re_str) + + l = len(re_str) + if l > 1: + r = [] + for i in range(l - 1): + i1 = re_str[i] + i2 = re_str[i + 1] + if i1 is True: + r.append('\n') + continue + elif i2 is True: + r.append(i1) + continue + elif i1.endswith(' ') and i2.startswith(' '): + i1 = i1[:-1] + r.append(i1) + r.append('\n' if re_str[-1] is True else re_str[-1]) + re_str = ''.join(r) + + elif not l: + re_str = '' + else: + re_str = re_str[0] if re_str[0] is not True else '\n' + + return format_html(re_str.strip()) def format_html(text): - """处理html编码字符 - :param text: html文本 - :return: 格式化后的html文本 - """ return unescape(text).replace('\xa0', ' ') if text else text def location_in_viewport(page, loc_x, loc_y): - """判断给定的坐标是否在视口中 |n - :param page: ChromePage对象 - :param loc_x: 页面绝对坐标x - :param loc_y: 页面绝对坐标y - :return: bool - """ js = f'''function(){{let x = {loc_x}; let y = {loc_y}; const scrollLeft = document.documentElement.scrollLeft; const scrollTop = document.documentElement.scrollTop; @@ -106,24 +112,17 @@ def location_in_viewport(page, loc_x, loc_y): const vHeight = document.documentElement.clientHeight; if (x< scrollLeft || y < scrollTop || x > vWidth + scrollLeft || y > vHeight + scrollTop){{return false;}} return true;}}''' - return page.run_js(js) + return page._run_js(js) def offset_scroll(ele, offset_x, offset_y): - """接收元素及偏移坐标,把坐标滚动到页面中间,返回该点在视口中的坐标 - 有偏移量时以元素左上角坐标为基准,没有时以click_point为基准 - :param ele: 元素对象 - :param offset_x: 偏移量x - :param offset_y: 偏移量y - :return: 视口中的坐标 - """ loc_x, loc_y = ele.rect.location cp_x, cp_y = ele.rect.click_point lx = loc_x + offset_x if offset_x else cp_x ly = loc_y + offset_y if offset_y else cp_y if not location_in_viewport(ele.owner, lx, ly): - clientWidth = ele.owner.run_js('return document.body.clientWidth;') - clientHeight = ele.owner.run_js('return document.body.clientHeight;') + clientWidth = ele.owner._run_js('return document.body.clientWidth;') + clientHeight = ele.owner._run_js('return document.body.clientHeight;') ele.owner.scroll.to_location(lx - clientWidth // 2, ly - clientHeight // 2) cl_x, cl_y = ele.rect.viewport_location ccp_x, ccp_y = ele.rect.viewport_click_point @@ -133,19 +132,13 @@ def offset_scroll(ele, offset_x, offset_y): def make_absolute_link(link, baseURI=None): - """获取绝对url - :param link: 超链接 - :param baseURI: 页面或iframe的url - :return: 绝对链接 - """ if not link: return link link = link.strip().replace('\\', '/') parsed = urlparse(link)._asdict() if baseURI: - p = urlparse(baseURI)._asdict() - baseURI = f'{p["scheme"]}://{p["netloc"]}' + baseURI = baseURI.rstrip('/\\') # 是相对路径,与页面url拼接并返回 if not parsed['netloc']: @@ -162,7 +155,6 @@ def make_absolute_link(link, baseURI=None): def is_js_func(func): - """检查文本是否js函数""" func = func.strip() if (func.startswith('function') or func.startswith('async ')) and func.endswith('}'): return True @@ -171,195 +163,7 @@ def is_js_func(func): return False -def cookie_to_dict(cookie): - """把Cookie对象转为dict格式 - :param cookie: Cookie对象、字符串或字典 - :return: cookie字典 - """ - if isinstance(cookie, Cookie): - cookie_dict = cookie.__dict__.copy() - cookie_dict.pop('rfc2109', None) - cookie_dict.pop('_rest', None) - return cookie_dict - - elif isinstance(cookie, dict): - cookie_dict = cookie - - elif isinstance(cookie, str): - cookie_dict = {} - for attr in cookie.strip().rstrip(';,').split(',' if ',' in cookie else ';'): - attr_val = attr.strip().split('=', 1) - if attr_val[0] in ('domain', 'path', 'expires', 'max-age', 'HttpOnly', 'secure', 'expiry', 'name', 'value'): - cookie_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else '' - else: - cookie_dict['name'] = attr_val[0] - cookie_dict['value'] = attr_val[1] if len(attr_val) == 2 else '' - - return cookie_dict - - else: - raise TypeError('cookie参数必须为Cookie、str或dict类型。') - - return cookie_dict - - -def cookies_to_tuple(cookies): - """把cookies转为tuple格式 - :param cookies: cookies信息,可为CookieJar, list, tuple, str, dict - :return: 返回tuple形式的cookies - """ - if isinstance(cookies, (list, tuple, CookieJar)): - cookies = tuple(cookie_to_dict(cookie) for cookie in cookies) - - elif isinstance(cookies, str): - c_dict = {} - cookies = cookies.rstrip('; ') - cookies = cookies.split(';') - # r = match(r'.*?=([^=]+)=', cookies) - # if not r: # 只有一个 - # cookies = [cookies.rstrip(',;')] - # else: - # s = match(r'.*([,;]).*', r.group(1)).group(1) - # cookies = cookies.rstrip(s).split(s) - - for attr in cookies: - attr_val = attr.strip().split('=', 1) - c_dict[attr_val[0]] = attr_val[1] if len(attr_val) == 2 else True - cookies = _dict_cookies_to_tuple(c_dict) - - elif isinstance(cookies, dict): - cookies = _dict_cookies_to_tuple(cookies) - - elif isinstance(cookies, Cookie): - cookies = (cookie_to_dict(cookies),) - - else: - raise TypeError('cookies参数必须为Cookie、CookieJar、list、tuple、str或dict类型。') - - return cookies - - -def set_session_cookies(session, cookies): - """设置Session对象的cookies - :param session: Session对象 - :param cookies: cookies信息 - :return: None - """ - for cookie in cookies_to_tuple(cookies): - if cookie['value'] is None: - cookie['value'] = '' - - kwargs = {x: cookie[x] for x in cookie - if x.lower() in ('version', 'port', 'domain', 'path', 'secure', - 'expires', 'discard', 'comment', 'comment_url', 'rest')} - - if 'expiry' in cookie: - kwargs['expires'] = cookie['expiry'] - - session.cookies.set(cookie['name'], cookie['value'], **kwargs) - - -def set_browser_cookies(page, cookies): - """设置cookies值 - :param page: 页面对象 - :param cookies: cookies信息 - :return: None - """ - for cookie in cookies_to_tuple(cookies): - if 'expiry' in cookie: - cookie['expires'] = int(cookie['expiry']) - cookie.pop('expiry') - - if 'expires' in cookie: - if not cookie['expires']: - cookie.pop('expires') - - elif isinstance(cookie['expires'], str): - if cookie['expires'].isdigit(): - cookie['expires'] = int(cookie['expires']) - - elif cookie['expires'].replace('.', '').isdigit(): - cookie['expires'] = float(cookie['expires']) - - else: - try: - cookie['expires'] = datetime.strptime(cookie['expires'], - '%a, %d %b %Y %H:%M:%S GMT').timestamp() - except ValueError: - cookie['expires'] = datetime.strptime(cookie['expires'], - '%a, %d %b %y %H:%M:%S GMT').timestamp() - - if cookie['value'] is None: - cookie['value'] = '' - elif not isinstance(cookie['value'], str): - cookie['value'] = str(cookie['value']) - - if cookie['name'].startswith('__Host-'): - cookie['path'] = '/' - cookie['secure'] = True - if not page.url.startswith('http'): - cookie['name'] = cookie['name'].replace('__Host-', '__Secure-', 1) - else: - cookie['url'] = page.url - page.run_cdp_loaded('Network.setCookie', **cookie) - continue # 不用设置域名,可退出 - - if cookie['name'].startswith('__Secure-'): - cookie['secure'] = True - - if cookie.get('domain', None): - try: - page.run_cdp_loaded('Network.setCookie', **cookie) - if is_cookie_in_driver(page, cookie): - continue - except Exception: - pass - - url = page._browser_url - if not url.startswith('http'): - raise RuntimeError(f'未设置域名,请设置cookie的domain参数或先访问一个网站。{cookie}') - ex_url = extract(url) - d_list = ex_url.subdomain.split('.') - d_list.append(f'{ex_url.domain}.{ex_url.suffix}' if ex_url.suffix else ex_url.domain) - - tmp = [d_list[0]] - if len(d_list) > 1: - for i in d_list[1:]: - tmp.append('.') - tmp.append(i) - - for i in range(len(tmp)): - cookie['domain'] = ''.join(tmp[i:]) - page.run_cdp_loaded('Network.setCookie', **cookie) - if is_cookie_in_driver(page, cookie): - break - - -def is_cookie_in_driver(page, cookie): - """查询cookie是否在浏览器内 - :param page: BasePage对象 - :param cookie: dict格式cookie - :return: bool - """ - if 'domain' in cookie: - for c in page.cookies(all_domains=True): - if cookie['name'] == c['name'] and cookie['value'] == c['value'] and cookie['domain'] == c.get('domain', - None): - return True - else: - for c in page.cookies(all_domains=True): - if cookie['name'] == c['name'] and cookie['value'] == c['value']: - return True - return False - - def get_blob(page, url, as_bytes=True): - """获取知道blob资源 - :param page: 资源所在页面对象 - :param url: 资源url - :param as_bytes: 是否以字节形式返回 - :return: 资源内容 - """ if not url.startswith('blob'): raise TypeError('该链接非blob类型。') js = """ @@ -378,7 +182,7 @@ def get_blob(page, url, as_bytes=True): } """ try: - result = page.run_js(js, url) + result = page._run_js(js, url) except: raise RuntimeError('无法获取该资源。') if as_bytes: @@ -389,14 +193,6 @@ def get_blob(page, url, as_bytes=True): def save_page(tab, path=None, name=None, as_pdf=False, kwargs=None): - """把当前页面保存为文件,如果path和name参数都为None,只返回文本 - :param tab: Tab或Page对象 - :param path: 保存路径,为None且name不为None时保存在当前路径 - :param name: 文件名,为None且path不为None时用title属性值 - :param as_pdf: 为Ture保存为pdf,否则为mhtml且忽略kwargs参数 - :param kwargs: pdf生成参数 - :return: as_pdf为True时返回bytes,否则返回文件文本 - """ if name: if name.endswith('.pdf'): name = name[:-4] @@ -420,13 +216,7 @@ def save_page(tab, path=None, name=None, as_pdf=False, kwargs=None): def get_mhtml(page, path=None, name=None): - """把当前页面保存为mhtml文件,如果path和name参数都为None,只返回mhtml文本 - :param page: 要保存的页面对象 - :param path: 保存路径,为None且name不为None时保存在当前路径 - :param name: 文件名,为None且path不为None时用title属性值 - :return: mhtml文本 - """ - r = page.run_cdp('Page.captureSnapshot')['data'] + r = page._run_cdp('Page.captureSnapshot')['data'] if path is None and name is None: return r @@ -439,20 +229,13 @@ def get_mhtml(page, path=None, name=None): def get_pdf(page, path=None, name=None, kwargs=None): - """把当前页面保存为pdf文件,如果path和name参数都为None,只返回字节 - :param page: 要保存的页面对象 - :param path: 保存路径,为None且name不为None时保存在当前路径 - :param name: 文件名,为None且path不为None时用title属性值 - :param kwargs: pdf生成参数 - :return: pdf文本 - """ if not kwargs: kwargs = {} kwargs['transferMode'] = 'ReturnAsBase64' if 'printBackground' not in kwargs: kwargs['printBackground'] = True try: - r = page.run_cdp('Page.printToPDF', **kwargs)['data'] + r = page._run_cdp('Page.printToPDF', **kwargs)['data'] except: raise RuntimeError('保存失败,可能浏览器版本不支持。') from base64 import b64decode @@ -469,14 +252,6 @@ def get_pdf(page, path=None, name=None, kwargs=None): def tree(ele_or_page, text=False, show_js=False, show_css=False): - """把页面或元素对象DOM结构打印出来 - :param ele_or_page: 页面或元素对象 - :param text: 是否打印文本,输入数字可指定打印文本长度上线 - :param show_js: 打印文本时是否包含 - - - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 63f0080..1e8ab6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ requests lxml cssselect -DownloadKit>=2.0.0 +DownloadKit>=2.0.7 websocket-client click -tldextract +tldextract>=3.4.4 psutil \ No newline at end of file diff --git a/setup.py b/setup.py index 2ad5dcb..61bf28b 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,9 @@ 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://gitee.com/g1879/DrissionPage", + url="https://DrissionPage.cn", include_package_data=True, packages=find_packages(), zip_safe=False, @@ -23,17 +23,17 @@ setup( 'lxml', 'requests', 'cssselect', - 'DownloadKit>=2.0.0', + '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={