From 545ce2e2aaa7888077be00a187ccc09c2df43e08 Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 16 Dec 2022 00:12:03 +0800 Subject: [PATCH] =?UTF-8?q?s=E6=A8=A1=E5=BC=8Fget()=E4=BD=BF=E7=94=A8page?= =?UTF-8?q?=5Fload=E4=BD=9C=E4=B8=BAtimeout=EF=BC=9Bset=5Fpage=5Fload=5Fst?= =?UTF-8?q?rategy=E6=94=B9=E7=94=A8=E7=B1=BB=E6=9D=A5=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/chromium_base.py | 47 +- DrissionPage/chromium_base.pyi | 16 +- DrissionPage/web_page.py | 10 +- .../Drission对象.md | 0 .../DriverPage和SessionPage.md | 0 .../cookies的使用.md | 0 .../{使用方法 => MixPage使用方法}/下载文件.md | 0 .../使用其它系统或浏览器.md | 0 .../{使用方法 => MixPage使用方法}/元素操作.md | 0 .../创建页面对象.md | 3 +- .../启动配置/Chrome启动配置.md | 0 .../启动配置/Session启动配置.md | 0 .../启动配置/使用配置文件.md | 0 .../启动配置/概述.md | 0 .../对接selenium及requests代码.md | 0 .../{使用方法 => MixPage使用方法}/打包程序.md | 0 .../查找页面元素.md | 0 .../监听浏览器网络数据.md | 4 +- .../获取元素信息.md | 3 +- .../获取网页信息.md | 0 .../{使用方法 => MixPage使用方法}/访问网页.md | 0 .../{使用方法 => MixPage使用方法}/页面操作.md | 0 docs/WebPage使用方法/3.1创建页面对象.md | 207 +++++ docs/WebPage使用方法/3.2访问网页.md | 164 ++++ docs/WebPage使用方法/3.3查找元素.md | 708 ++++++++++++++++++ docs/_sidebar.md | 88 ++- setup.py | 2 +- 27 files changed, 1188 insertions(+), 64 deletions(-) rename docs/{使用方法 => MixPage使用方法}/Drission对象.md (100%) rename docs/{使用方法 => MixPage使用方法}/DriverPage和SessionPage.md (100%) rename docs/{使用方法 => MixPage使用方法}/cookies的使用.md (100%) rename docs/{使用方法 => MixPage使用方法}/下载文件.md (100%) rename docs/{使用方法 => MixPage使用方法}/使用其它系统或浏览器.md (100%) rename docs/{使用方法 => MixPage使用方法}/元素操作.md (100%) rename docs/{使用方法 => MixPage使用方法}/创建页面对象.md (99%) rename docs/{使用方法 => MixPage使用方法}/启动配置/Chrome启动配置.md (100%) rename docs/{使用方法 => MixPage使用方法}/启动配置/Session启动配置.md (100%) rename docs/{使用方法 => MixPage使用方法}/启动配置/使用配置文件.md (100%) rename docs/{使用方法 => MixPage使用方法}/启动配置/概述.md (100%) rename docs/{使用方法 => MixPage使用方法}/对接selenium及requests代码.md (100%) rename docs/{使用方法 => MixPage使用方法}/打包程序.md (100%) rename docs/{使用方法 => MixPage使用方法}/查找页面元素.md (100%) rename docs/{使用方法 => MixPage使用方法}/监听浏览器网络数据.md (99%) rename docs/{使用方法 => MixPage使用方法}/获取元素信息.md (99%) rename docs/{使用方法 => MixPage使用方法}/获取网页信息.md (100%) rename docs/{使用方法 => MixPage使用方法}/访问网页.md (100%) rename docs/{使用方法 => MixPage使用方法}/页面操作.md (100%) create mode 100644 docs/WebPage使用方法/3.1创建页面对象.md create mode 100644 docs/WebPage使用方法/3.2访问网页.md create mode 100644 docs/WebPage使用方法/3.3查找元素.md diff --git a/DrissionPage/chromium_base.py b/DrissionPage/chromium_base.py index 55c85b8..a5682c5 100644 --- a/DrissionPage/chromium_base.py +++ b/DrissionPage/chromium_base.py @@ -28,6 +28,7 @@ class ChromiumBase(BasePage): self._root_id = None self._debug = False self._debug_recorder = None + self.timeouts = Timeout(self) self._connect_browser(address, tab_id) def _connect_browser(self, addr_tab_opts=None, tab_id=None): @@ -37,7 +38,6 @@ class ChromiumBase(BasePage): :return: None """ self._root_id = None - self.timeouts = Timeout(self) self._control_session = Session() self._control_session.keep_alive = False self._first_run = True @@ -256,14 +256,10 @@ class ChromiumBase(BasePage): self._scroll = ChromeScroll(self) return self._scroll - def set_page_load_strategy(self, value): - """设置页面加载策略 \n - :param value: 可选'normal', 'eager', 'none' - :return: None - """ - if value not in ('normal', 'eager', 'none'): - raise ValueError("只能选择'normal', 'eager', 'none'。") - self._page_load_strategy = value + @property + def set_page_load_strategy(self): + """返回用于设置页面加载策略的对象""" + return pageLoadStrategy(self) def set_timeouts(self, implicit=None, page_load=None, script=None): """设置超时时间,单位为秒 \n @@ -307,7 +303,7 @@ class ChromiumBase(BasePage): :param retry: 重试次数 :param interval: 重试间隔(秒) :param timeout: 连接超时时间 - :return: 目标url是否可用,返回None表示不确定 + :return: 目标url是否可用 """ self._url_available = self._get(url, show_errmsg, retry, interval, timeout) return self._url_available @@ -810,3 +806,34 @@ class Timeout(object): @property def implicit(self): return self.page.timeout + + +class pageLoadStrategy(object): + """用于设置页面加载策略的类""" + + def __init__(self, page): + """ + :param page: ChromiumBase对象 + """ + self.page = page + + def __call__(self, value): + """设置加载策略 \n + :param value: 可选 'normal', 'eager', 'none' + :return: None + """ + if value.lower() not in ('normal', 'eager', 'none'): + raise ValueError("只能选择 'normal', 'eager', 'none'。") + self.page._page_load_strategy = value + + def set_normal(self): + """设置页面加载策略为normal""" + self.page._page_load_strategy = 'normal' + + def set_eager(self): + """设置页面加载策略为eager""" + self.page._page_load_strategy = 'eager' + + def set_none(self): + """设置页面加载策略为none""" + self.page._page_load_strategy = 'none' diff --git a/DrissionPage/chromium_base.pyi b/DrissionPage/chromium_base.pyi index c39576e..c5129b0 100644 --- a/DrissionPage/chromium_base.pyi +++ b/DrissionPage/chromium_base.pyi @@ -101,7 +101,8 @@ class ChromiumBase(BasePage): @property def scroll(self) -> 'ChromeScroll': ... - def set_page_load_strategy(self, value: str) -> None: ... + @property + def set_page_load_strategy(self) -> pageLoadStrategy: ... def set_timeouts(self, implicit: float = ..., page_load: float = ..., script: float = ...) -> None: ... @@ -279,3 +280,16 @@ class Timeout(object): @property def implicit(self) -> float: ... + + +class pageLoadStrategy(object): + def __init__(self, page: ChromiumBase): + self.page: ChromiumBase = ... + + def __call__(self, value: str) -> None: ... + + def set_normal(self) -> None: ... + + def set_eager(self) -> None: ... + + def set_none(self) -> None: ... diff --git a/DrissionPage/web_page.py b/DrissionPage/web_page.py index fbc7076..6aeb95f 100644 --- a/DrissionPage/web_page.py +++ b/DrissionPage/web_page.py @@ -5,7 +5,7 @@ from requests import Session from tldextract import extract from .base import BasePage -from .chromium_base import ChromiumBase +from .chromium_base import ChromiumBase, Timeout from .chromium_page import ChromiumPage from .config import DriverOptions, SessionOptions, cookies_to_tuple from .session_page import SessionPage @@ -32,6 +32,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage): self._session = None self._tab_obj = None self._is_loading = False + self.timeouts = Timeout(self) self._set_session_options(session_or_options) self._set_driver_options(driver_or_options) self._setting_tab_id = tab_id @@ -102,9 +103,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage): if self._session is None: self._set_session(self._session_options) - # if self._proxy: - # self._session.proxies = self._proxy - return self._session @property @@ -117,7 +115,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage): """返回用于控制浏览器的Tab对象,会先等待页面加载完毕""" while self._is_loading: sleep(.1) - # self._wait_loading() return self._driver @property @@ -150,6 +147,8 @@ class WebPage(SessionPage, ChromiumPage, BasePage): if self._mode == 'd': return super(SessionPage, self).get(url, show_errmsg, retry, interval, timeout) elif self._mode == 's': + if timeout is None: + timeout = self.timeouts.page_load if self._has_driver else self.timeout return super().get(url, show_errmsg, retry, interval, timeout, **kwargs) def ele(self, loc_or_ele, timeout=None): @@ -291,7 +290,6 @@ class WebPage(SessionPage, ChromiumPage, BasePage): 'name': cookie['name'], 'domain': cookie['domain']} result_cookies.append(c) - # super(WebPage, self)._wait_driver.Network.setCookies(cookies=result_cookies) self._tab_obj.Network.setCookies(cookies=result_cookies) # 添加cookie到session diff --git a/docs/使用方法/Drission对象.md b/docs/MixPage使用方法/Drission对象.md similarity index 100% rename from docs/使用方法/Drission对象.md rename to docs/MixPage使用方法/Drission对象.md diff --git a/docs/使用方法/DriverPage和SessionPage.md b/docs/MixPage使用方法/DriverPage和SessionPage.md similarity index 100% rename from docs/使用方法/DriverPage和SessionPage.md rename to docs/MixPage使用方法/DriverPage和SessionPage.md diff --git a/docs/使用方法/cookies的使用.md b/docs/MixPage使用方法/cookies的使用.md similarity index 100% rename from docs/使用方法/cookies的使用.md rename to docs/MixPage使用方法/cookies的使用.md diff --git a/docs/使用方法/下载文件.md b/docs/MixPage使用方法/下载文件.md similarity index 100% rename from docs/使用方法/下载文件.md rename to docs/MixPage使用方法/下载文件.md diff --git a/docs/使用方法/使用其它系统或浏览器.md b/docs/MixPage使用方法/使用其它系统或浏览器.md similarity index 100% rename from docs/使用方法/使用其它系统或浏览器.md rename to docs/MixPage使用方法/使用其它系统或浏览器.md diff --git a/docs/使用方法/元素操作.md b/docs/MixPage使用方法/元素操作.md similarity index 100% rename from docs/使用方法/元素操作.md rename to docs/MixPage使用方法/元素操作.md diff --git a/docs/使用方法/创建页面对象.md b/docs/MixPage使用方法/创建页面对象.md similarity index 99% rename from docs/使用方法/创建页面对象.md rename to docs/MixPage使用方法/创建页面对象.md index c91de8a..6023035 100644 --- a/docs/使用方法/创建页面对象.md +++ b/docs/MixPage使用方法/创建页面对象.md @@ -89,7 +89,8 @@ page = MixPage(mode='s', session_options=so, driver_options=do) # 传入`Drission`对象创建 -在入门指南的基本概念一节里,我们讲过`Drission`对象相当于驱动器的角色。事实上,上述两种方式,`MixPage`都会自动创建一个`Drission`对象用于管理与网站或浏览器的连接,我们当然也可以手动创建并传入`MixPage`。 +在入门指南的基本概念一节里,我们讲过`Drission`对象相当于驱动器的角色。事实上,上述两种方式,`MixPage`都会自动创建一个`Drission`对象用于管理与网站或浏览器的连接,我们当然也可以手动创建并传入`MixPage` +。 `Drission`一般是不用手动创建的,要手动创建的时候,一般是用于i以下几种情况: - 指定使用某个配置文件 diff --git a/docs/使用方法/启动配置/Chrome启动配置.md b/docs/MixPage使用方法/启动配置/Chrome启动配置.md similarity index 100% rename from docs/使用方法/启动配置/Chrome启动配置.md rename to docs/MixPage使用方法/启动配置/Chrome启动配置.md diff --git a/docs/使用方法/启动配置/Session启动配置.md b/docs/MixPage使用方法/启动配置/Session启动配置.md similarity index 100% rename from docs/使用方法/启动配置/Session启动配置.md rename to docs/MixPage使用方法/启动配置/Session启动配置.md diff --git a/docs/使用方法/启动配置/使用配置文件.md b/docs/MixPage使用方法/启动配置/使用配置文件.md similarity index 100% rename from docs/使用方法/启动配置/使用配置文件.md rename to docs/MixPage使用方法/启动配置/使用配置文件.md diff --git a/docs/使用方法/启动配置/概述.md b/docs/MixPage使用方法/启动配置/概述.md similarity index 100% rename from docs/使用方法/启动配置/概述.md rename to docs/MixPage使用方法/启动配置/概述.md diff --git a/docs/使用方法/对接selenium及requests代码.md b/docs/MixPage使用方法/对接selenium及requests代码.md similarity index 100% rename from docs/使用方法/对接selenium及requests代码.md rename to docs/MixPage使用方法/对接selenium及requests代码.md diff --git a/docs/使用方法/打包程序.md b/docs/MixPage使用方法/打包程序.md similarity index 100% rename from docs/使用方法/打包程序.md rename to docs/MixPage使用方法/打包程序.md diff --git a/docs/使用方法/查找页面元素.md b/docs/MixPage使用方法/查找页面元素.md similarity index 100% rename from docs/使用方法/查找页面元素.md rename to docs/MixPage使用方法/查找页面元素.md diff --git a/docs/使用方法/监听浏览器网络数据.md b/docs/MixPage使用方法/监听浏览器网络数据.md similarity index 99% rename from docs/使用方法/监听浏览器网络数据.md rename to docs/MixPage使用方法/监听浏览器网络数据.md index 4e418e4..5ae4838 100644 --- a/docs/使用方法/监听浏览器网络数据.md +++ b/docs/MixPage使用方法/监听浏览器网络数据.md @@ -10,11 +10,9 @@ 由于该工具不依赖 DrissionPage,现已独立发布为一个库,但仍然可以在 DrissionPage 中导入。 - - !> 为了便于维护,该工具用法请异步 [FlowViewer](https://gitee.com/g1879/FlowViewer) 查看。 -# +# # 简单示例 diff --git a/docs/使用方法/获取元素信息.md b/docs/MixPage使用方法/获取元素信息.md similarity index 99% rename from docs/使用方法/获取元素信息.md rename to docs/MixPage使用方法/获取元素信息.md index fcbaf5b..193a3df 100644 --- a/docs/使用方法/获取元素信息.md +++ b/docs/MixPage使用方法/获取元素信息.md @@ -1,6 +1,7 @@ 获取到须要的页面元素后,可以使用元素对象获取元素的信息。 -本库有三种元素对象,分别是`DriverElement`、`ShadowRootElement`、`SessionElement`,前两者是 d 模式下通过浏览器页面元素生成,后者由静态文本生成。`DriverElement`和`SessionElement`的基本属性一致。 +本库有三种元素对象,分别是`DriverElement`、`ShadowRootElement`、`SessionElement`,前两者是 d 模式下通过浏览器页面元素生成,后者由静态文本生成。`DriverElement` +和`SessionElement`的基本属性一致。 `ShadowRootElement`由于是 shadow dom 元素,属性比较其余两种少,下文单独介绍。 diff --git a/docs/使用方法/获取网页信息.md b/docs/MixPage使用方法/获取网页信息.md similarity index 100% rename from docs/使用方法/获取网页信息.md rename to docs/MixPage使用方法/获取网页信息.md diff --git a/docs/使用方法/访问网页.md b/docs/MixPage使用方法/访问网页.md similarity index 100% rename from docs/使用方法/访问网页.md rename to docs/MixPage使用方法/访问网页.md diff --git a/docs/使用方法/页面操作.md b/docs/MixPage使用方法/页面操作.md similarity index 100% rename from docs/使用方法/页面操作.md rename to docs/MixPage使用方法/页面操作.md diff --git a/docs/WebPage使用方法/3.1创建页面对象.md b/docs/WebPage使用方法/3.1创建页面对象.md new file mode 100644 index 0000000..de04108 --- /dev/null +++ b/docs/WebPage使用方法/3.1创建页面对象.md @@ -0,0 +1,207 @@ +在入门指南的快速上手一节,我们已经初步了解如何创建页面对象,本节进一步介绍更多功能。 + +# ✔️ `WebPage`类 + +`WebPage`对象封装了常用的网页操作,并实现在浏览器和 requests 两种模式之间的切换。 + +初始化参数: + +- mode:初始化时模式,`'d'`或`'s'`,默认为`'d'` +- timeout:超时时间,s 模式时为连接时间,d 模式时为查找元素、处理弹出框、输入文本等超时时间 +- driver_or_options:`Tab`对象或`DriverOptions`对象,为`None`时使用 ini 文件配置,为`False`时不读取 ini 文件 +- session_or_options:`Session`对象或`SessionOptions`对象,为`None`时使用 ini 文件配置,为`False`时不读取 ini 文件 + +# ✔️ 直接创建 + +这种方式代码最简洁,程序会从配置文件中读取配置,自动生成页面对象。可以保持代码简洁。 +在基本概念一节我们提到过,本库使用配置文件记录常用配置信息,也可以直接把配置写在代码里。 +?>**Tips:**
默认配置文件中,程序使用 9222 端口启动浏览器,浏览器路径为 Chrome。如路径中没找到浏览器执行文件,Windows 系统下程序会在注册表中查找路径,如果还是没找到,则要用下一种方式手动配置路径。 + +```python +# 默认以 d 模式创建页面对象 +page = WebPage('d') + +# 指定以 s 模式创建页面对象 +page = WebPage('s') +``` + +# ✔️ 通过配置信息创建 + +本库有两种管理配置信息的对象,`DriverOptions`和`SessionOptions`,分别对应 d 模式和 s 模式的配置。须要时,可以创建相应的配置对象进行设置。 + +## 📍 `DriverOptions`类 + +`DriverOptions`用于管理创建浏览器时的配置,内置了常用的配置,并能实现链式操作。详细使用方法见“启动配置”一节。 + +初始化参数: + +- read_file:是否从 ini 文件中读取配置信息 +- ini_path:ini 文件路径,为`None`则读取默认 ini 文件 + +!>**注意:**
浏览器创建后再修改这个配置是没有效果的。 + +```python +# 导入 DriverOptions +from DrissionPage import WebPage, DriverOptions + +# 创建浏览器配置对象,指定浏览器路径和运行端口 +do = DriverOptions().set_paths(chrome_path=r'D:\chrome.exe', local_port=9333) +# 用该配置创建页面对象 +page = WebPage(driver_or_options=do) +``` + +## 📍 `SessionOptions`类 + +`SessionOptions`用于管理创建`Session`对象时的配置,内置了常用的配置,并能实现链式操作。详细使用方法见“启动配置”一节。 + +初始化参数: + +- read_file:是否从 ini 文件中读取配置信息 +- ini_path:ini 文件路径,为`None`则读取默认 ini 文件 + +!>**注意:**
`Session`对象创建后再修改这个配置是没有效果的。 + +```python +# 导入 SessionOptions +from DrissionPage import WebPage, SessionOptions + +proxies = {'http': 'http://127.0.0.1:1080', + 'https': 'https://127.0.0.1:1080'} + +# 创建配置对象,不从 ini 文件读取,并设置代理信息 +so = SessionOptions(read_file=False).set_proxies(proxies) +# 用该配置创建页面对象(s 模式) +page = WebPage(mode='s', session_options=so) +``` + +d 模式的配置和 s 模式的配置是可以同时使用的,不会互相影响。 + +```python +page = WebPage(mode='s', session_options=so, driver_options=do) +``` + +## 📍 使用其它 ini 文件创建 + +以上方法是使用默认 ini 文件中保存的配置信息创建对象,你可以保存一个 ini 文件到别的地方,并在创建对象时指定使用它。 + +```python +from DrissionPage import WebPage, DriverOptinos + +do = DriverOptinos(ini_path=r'./config1.ini') +page = WebPage(driver_or_options=do) +``` + +# ✔️ 传递驱动器 + +当须要使用多个页面对象共同操作一个页面时,可在对象间传递驱动器。 + +```python +from DrissionPage import WebPage + +page1 = WebPage() +driver = page1.driver +session = page1.session +page2 = WebPage(driver_or_options=driver, session_or_options=session) +``` + +# ✔️ 接管手动打开的浏览器 + +如果须要手动打开浏览器再接管,可以这样做: + +- 右键点击浏览器图标,选择属性 + +- 在“目标”路径后面加上` --remote-debugging-port=端口号`(注意最前面有个空格) + +- 点击确定 + +- 在程序中的浏览器配置中指定接管该端口浏览器,如下: + +``` +# 文件快捷方式的目标路径设置 +D:\chrome.exe --remote-debugging-port=9222 +``` + +```python +from DrissionPage import WebPage, DriverOptions + +do = DriverOptions().set_paths(local_port=9222) +page = WebPage(driver_options=do) +``` + +?>**Tips:**
接管使用 bat 文件打开的浏览器也是一样做法做法。
接管浏览器时只有`local_port`、`debugger_address`参数是有效的。 + +# ✔️ 多 Chrome 浏览器共存 + +如果想要同时操作多个 Chrome 浏览器,或者自己在使用 Chrome 上网,同时控制另外几个跑自动化,就须要给这些被程序控制的浏览器设置单独的端口和用户文件夹,否则会造成冲突。具体用`DriverOptions`对象进行设置,示例如下: + +```python +from DrissionPage import DriverOptions + +do1 = DriverOptions().set_paths(local_port=9111, user_data_path=r'D:\data1') +do2 = DriverOptions().set_paths(local_port=9222, user_data_path=r'D:\data2') + +page1 = WebPage(driver_or_options=do1) +page2 = WebPage(driver_or_options=do2) + +page1.get('https://www.baidu.com') +page2.get('http://www.163.com') +``` + +如果要接管多个手动打开的浏览器,每个浏览器后面的参数都要添加`--remote-debugging-port`和`--user-data-dir`参数。示例如下: + +``` +# 浏览器1目标设置 +D:\chrome.exe --remote-debugging-port=9111 --user-data-dir=D:\data1 + +# 浏览器2目标设置 +D:\chrome.exe --remote-debugging-port=9222 --user-data-dir=D:\data2 +``` + +```python +from DrissionPage import DriverOptions + +do1 = DriverOptions().set_paths(local_port=9111) +do2 = DriverOptions().set_paths(local_port=9222) + +page1 = WebPage(driver_or_options=do1) +page2 = WebPage(driver_or_options=do2) +``` + +?> **Tips:**
使用 bat 文件打开浏览器再接管操作是一样的。 + +# ✔️ 一些技巧 + +事实上,本库默认启动浏览器的方式是先通过程序在 9222(或用户指定的)端口运行一个浏览器进程,然后通过程序接管。这种做法有很多好处: + +## 📍 可重复使用的浏览器对象 + +当程序运行完毕,浏览器不会主动关闭,下次再运行的时候可以直接在当前状态下继续运行。于是无须每次打开新的浏览器对象,无须从最开始步骤重新运行整个程序,一些前置条件(如登录)也无须每次运行,大大提高开发效率。 + +## 📍 简便的调试方式 + +通过重复使用浏览器对象,用户可以把浏览器页面调整到某个状态再用程序接管,对调试某个特定问题效率极高。比如有些通过很多步骤才能到达的页面,如果每次重新运行会花费大量时间,而将页面固定再由程序接管,测试各种细节非常方便快捷。 + +## 📍 一行代码传递登录状态到 requests + +本库的一个特点是打通了浏览器和`requests`之间的登录状态,我们可以手动登录浏览器,再用程序接管,然后切换到 s 模式,这时 s 模式的`Session`对象即已活动浏览器的登录信息,无须用`requests` +处理登录过程,极大地简化了开发复杂程度。示例: + +```python +# 假设已在 9222 端口打开了一个浏览器,并已登录某网站 +from DrissionPage import WebPage + +page = WebPage() # 用 d 模式创建页面对象,默认接管 9222 端口 +page.change_mode() # 切换到 s 模式,即使用 requests 进行操作 +page.get('某url...') # 即可已登录状态访问 +``` + +使用其它端口号: + +```python +from DrissionPage import WebPage, DriverOptions + +do = DriverOptions().set_paths(local_port=9333) +page = WebPage(driver_option=do) +page.change_mode() +page.get('某url...') +``` diff --git a/docs/WebPage使用方法/3.2访问网页.md b/docs/WebPage使用方法/3.2访问网页.md new file mode 100644 index 0000000..1ec4bfb --- /dev/null +++ b/docs/WebPage使用方法/3.2访问网页.md @@ -0,0 +1,164 @@ +本节介绍如果访问网页。 + +# ✔️ d 模式 + +d 模式下使用`get()`方法访问网页。 + +## 📍 `get()`方法 + +该方法用于跳转到一个网址。 +当连接失败时,程序默认重试 3 次,每次间隔 2 秒,可以通过参数设置重试次数和间隔。 +d 模式下根据浏览器的状态,还可以通过重写`check_page()`方法实现自定义检查方式。 + +参数: + +- url:目标 url +- show_errmsg:是否显示和抛出异常。默认不抛出,连接错误会返回`None` +- retry:重试次数,与页面对象的设置一致,默认 3 次 +- interval:重试间隔(秒),与页面对象的设置一致,默认 2 秒 +- timeout:连接超时时间(秒) + +返回:`bool`类型,表示是否连接成功 + +```python +from DrissionPage import WebPage + +page = WebPage() +page.get('https://www.baidu.com') +``` + +## 📍 设置超时时间和重试参数 + +网络不稳定的时候访问页面不一定成功,`get()`方法内置了超时和重试功能。通过`retry`、`interval`、`timeout`三个参数进行设置。 +其中,如不指定`timeout`参数,该参数会使用`WebPage`的`timeouts`属性的`page_load`参数中的值。 + +```python +from DrissionPage import WebPage + +page = WebPage() +page.get('https://www.163.com', retry=1, interval=1, timeout=1.5) +``` + +## 📍 设置加载策略 + +通过设置`WebPage`对象的`page_load_strategy`属性,可设置页面停止加载的时机。页面加载时,在到达超时时间,或达到设定的状态,就会停止,可有效节省采集时间。有以下三种模式: + +- `'normal'`:常规模式,会等待页面加载完毕。 + +- `'eager'`:加载完 DOM 即停止加载。 + +- `'none'`:完成连接即停止加载。 + +默认设置为`'normal'`。 + +```python +from DrissionPage import WebPage + +page = WebPage() +page.set_page_load_strategy.set_eager() +``` + +# ✔️ s 模式 + +s 模式基于 requests,因此可使用 requests 内置的所有请求方式,包括`get()`、`post()`、`head()`、`options()`、`put()`、`patch()`、`delete()` +。不过本库目前只对`get()`和`post()`做了封装和优化,其余方式可通过调用`WebPage`内置的`Session`对象使用。 + +## 📍 `get()`方法 + +`get()`方法使用语法与 requests 的`get()`方法一致,在此基础上增加了重试设置参数。 + +参数: + +- url:目标 url +- show_errmsg:是否显示和抛出异常,默认不抛出,连接错误会返回 None +- retry:重试次数,与页面对象的设置一致,默认 3 次 +- interval:重试间隔(秒),与页面对象的设置一致,默认 2 秒 +- timeout:连接超时时间(秒) +- **kwargs:连接参数,具体见 requests用法 + +返回:`bool`类型,表示是否连接成功,根据`Response`对象的`status_code`参数决定 + +s 模式的`**kwargs`参数与 requests 中该参数使用方法一致,但有一个特点,如果该参数中设置了某一项(如`headers`),该项中的每个项会覆盖从配置中读取的同名项,而不会整个覆盖。 +就是说,如果想继续使用配置中的`headers`信息,而只想修改其中一项,只需要传入该项的值即可。这样可以简化代码逻辑。 +与 requests 不一样,`get()`方法不返回`Response`对象以获取结果,处理返回结果的方式详见后面章节。 + +实用功能: + +- 程序会根据要访问的网址自动在`headers`中加入`Host`和`Referer`项 +- 程序会自动从返回内容中确定编码,一般情况无须手动设置 + +```python +from DrissionPage import WebPage + +page = WebPage('s') + +url = 'https://www.baidu.com' +headers = {'referer': 'gitee.com'} +cookies = {'name': 'value'} +proxies = {'http': '127.0.0.1:1080', 'https': '127.0.0.1:1080'} +page.get(url, headers=headers, cookies=cookies, proxies=proxies) +``` + +## 📍 `post()`方法 + +此方法是用 post 方式请求页面。用法与`get()`一致。调用时,`WebPage`对象会自动切换到 s 模式。 + +参数: + +- url:目标 url +- data:提交的数据,可以是`dict`或`str`类型 +- json:提交的数据,可以是`dict`或`str`类型 +- show_errmsg:是否显示和抛出异常,默认不抛出,连接错误会返回 None +- retry:重试次数,与页面对象的设置一致,默认 3 次 +- interval:重试间隔(秒),与页面对象的设置一致,默认 2 秒 +- **kwargs:连接参数,s 模式专用 + +```python +from DrissionPage import WebPage + +page = WebPage('s') +data = {'username': 'xxxxx', 'pwd': 'xxxxx'} + +page.post('http://example.com', data=data) +# 或 +page.post('http://example.com', json=data) +``` + +`data`参数和`json`参数都可接收`str`和`dict`格式数据,即有以下 4 种传递数据的方式: + +```python +# 向 data 参数传入字符串 +page.post(url, data='abc=123') + +# 向 data 参数传入字典 +page.post(url, data={'abc': '123'}) + +# 向 json 参数传入字符串 +page.post(url, json='abc=123') + +# 向 json 参数传入字典 +page.post(url, json={'abc': '123'}) +``` + +具体使用哪种,按服务器要求而定。 + +## 📍 其它请求方式 + +本库只针对常用的 get 和 post 方式作了优化,但也可以通过提取页面对象内的`Session`对象以原生 requests 代码方式执行其它请求方式。当然,它们工作在 s 模式。 + +```python +from DrissionPage import WebPage + +page = WebPage('s') +# 获取内置的 Session 对象 +session = page.session +# 以 head 方式发送请求 +response = session.head('https://www.baidu.com') +print(response.headers) +``` + +输出: + +```shell +{'Accept-Ranges': 'bytes', 'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'keep-alive', 'Content-Length': '277', 'Content-Type': 'text/html', 'Date': 'Tue, 04 Jan 2022 06:49:18 GMT', 'Etag': '"575e1f72-115"', 'Last-Modified': 'Mon, 13 Jun 2016 02:50:26 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18'} +``` diff --git a/docs/WebPage使用方法/3.3查找元素.md b/docs/WebPage使用方法/3.3查找元素.md new file mode 100644 index 0000000..204fd76 --- /dev/null +++ b/docs/WebPage使用方法/3.3查找元素.md @@ -0,0 +1,708 @@ +本节介绍如何获取元素对象。 + +无论是数据采集还是页面自动化,定位元素都是重中之重的的技能,浏览器开发者工具虽然可以直接复制绝对 xpath 或 css 路径,但这样做一来代码繁琐,可读性低,二来难以应付动态变化的页面。 +本库提供一套简洁易用的语法,用于快速定位元素,并且内置等待功能、支持链式查找,减少了代码的复杂性。 + +定位元素大致分为三种方法: + +- 在页面或元素内查找子元素 +- 根据 DOM 结构相对定位 +- 根据页面布局位置相对定位 + +d 模式的元素还有专门用于处理 shadow dom 的`shadow_root`属性。获取到的元素可继续用这些方法获取后代元素,使用方法和普通元素一致。 + +# ✔️ 示例 + +## 📍 简单示例 + +```html + + +
+

第一行

+

第二行

+

第三行

+
+
+ 第二个div +
+ + +``` + +假设 page 为以上页面的`WebPage`对象,我们可以用这些方法获取其中的元素: + +```python +# 获取 id 为 one 的元素 +div1 = page.ele('#one') + +# 获取 div1 元素内所有 p 元素组成的列表 +p_eles = div1.eles('tag:p') + +# 获取 name 属性为 row1 的元素 +p1 = page.ele('@name=row1') + +# 获取 name 属性为 row2 且包含“第二”文本的 p 元素 +p_ele = page.ele('tag:p@@text():第二@@name=row2') + +# 获取包含“第二个div”文本的元素 +div2 = page.ele('第二个div') + +# 用 xpath 查找 +div2 = page.ele('xpath://div[@id="tow"]') + +# 从第一行元素用相对定位获取第三行元素 +p3 = p1.next(2) + +# 获取 p1 元素的父元素 +parent = p1.parent() + +# 获取 p1 后面的第一个 div 元素 +div2 = p1.after(1, 'tag:div') +``` + +## 📍 实际示例 + +复制此代码可直接运行查看结果。 + +```python +from DrissionPage import WebPage + +page = WebPage('s') +page.get('https://gitee.com/explore') + +# 获取包含“全部推荐项目”文本的 ul 元素 +ul_ele = page.ele('tag:ul@@text():全部推荐项目') + +# 获取该 ul 元素下所有 a 元素 +titles = ul_ele.eles('tag:a') + +# 遍历列表,打印每个 a 元素的文本 +for i in titles: + print(i.text) + +"""输出: +全部推荐项目 +前沿技术 +智能硬件 +IOT/物联网/边缘计算 +车载应用 +以下省略…… +""" +``` + +# ✔️ 查找元素方法 + +以下方法,既可用于在页面中查找元素,也可用于在元素中查找子元素。 + +## ele() + +此方法用于查找并返回第一个匹配的元素,d 模式下返回`ChromiumElement`,s 模式下返回`SessionElement`,用 xpath 获取元素属性时,直接返回属性文本。查找不到结果则返回`None`。 + +参数: + +- loc_or_str(元素对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串 +- loc_or_ele(页面对象拥有):元素的定位信息,可以是元素对象,loc 元组,或查询字符串 +- timeout:查找元素超时时间,默认与元素所在页面等待时间一致,s 模式下无效 + +返回:s 模式下返回`SessionElement`,d 模式下返回`DriverElement`,或用 xpath 获取到的属性值 + +```python +# 在页面内查找元素 +ele1 = page.ele('search text') + +# 在元素内查找后代元素 +ele2 = ele1.ele('search text') + + +# 使用 xpath 获取后代中第一个 div 元素的 class 属性 +class = ele1.ele('xpath://div/@class') +``` + +## eles() + +此方法与`ele()`相似,但返回的是匹配到的所有元素组成的列表,用 xpath 获取元素属性时,返回属性文本组成的列表。 + +参数: + +- loc_or_str:元素的定位信息,可以是 loc 元组,或查询字符串 +- timeout:查找元素超时时间,默认与元素所在页面等待时间一致,s 模式下无效 + +返回:s 模式下返回`SessionElement`组成的列表,d 模式下返回`DriverElement`组成的列表,或用 xpath 获取到的属性值组成的列表 + +```python +# 获取 ele 元素内的所有 p 元素 +p_eles = ele.eles('tag:p') +# 打印第一个 p 元素 +print(p_eles[0]) +``` + +## s_ele() + +此方法用于在一个元素下查找后代元素,以`SessionElement`形式返回结果(xpath 获取属性值时依然是返回`str`),也可以直接将一个元素或页面转换为`SessionElement`版本。 + +这是为了 d 模式处理速度的提升,当爬取复杂页面信息而且不须要和元素进行交互时,生成整个页面或者主要容器元素的`SessionElement`,再在其中获取信息,可以将速度提升几个数量级。 + +s 模式下这个方法和`ele()`是一样的。 + +参数: + +- loc_or_str(元素对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前元素的`SessionElemnet`版本 +- loc_or_ele(页面对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前页面的 `SessionElemnet`版本 + +返回:`SessionElement`,或用 xpath 获取到的属性值 + +```python +# 获取元素或页面的的 SessionElement 版本 +ele2 = ele1.s_ele() +ele2 = page.s_ele() + +# 在 ele1 元素下查找元素,并以 SessionElemnet 返回 +ele2 = ele1.s_ele('search text') + +# 在页面下查找元素,并以 SessionElemnet 返回 +ele = page.s_ele('search text') +``` + +## s_eles() + +此方法与`s_ele()`相似,但返回的是匹配到的所有元素组成的列表,或属性值组成的列表。 + +参数: + +- loc_or_str:元素的定位信息,可以是 loc 元组,或查询字符串(必填) + +返回:`SessionElement`组成的列表,或用 xpath 获取到的属性值组成的列表 + +## active_ele + +该属性返回当前页面焦点所在元素。d 模式独有。 + +```python +ele = page.active_ele +``` + +## shadow_root + +`DriverElement`元素除了以上方法和属性外,还有`shadow_root`属性,用于获取其内部的 shadow_root 元素。 +该属性返回的是一个`ShadowRootElement`,类似于`DriverElement`,功能比`DriverElement`少。但也有`ele()`和`eles()`方法,可直接搜索其下的元素,返回 `DriverElement` +元素。返回的`DriverElement`和普通的没有区别。 + +```python +# 获取一个元素下是 shadow root +shadow_ele = ele.shadow_root +# 获取该 shadow root 下的一个元素 +ele1 = shadow_ele.ele('search text') +# 点击获取到的元素 +ele1.click() +``` + +# ✔️ 查找语法 + +我们使用一套简洁高效的语法去定位元素,大大简化了定位元素的代码量,增强了功能,也兼容 css selector、xpath、selenium 原生的 loc 元组(s 模式也能用)。d 模式和 s +模式定位元素的语法是完全一样的,便于模式切换时平滑过渡。 + +**匹配模式** 指字符串是否完全匹配,有以下两种: + +## 📍 精确匹配符`=` + +表示精确匹配,匹配完全符合的文本或属性。 + +## 📍 模糊匹配符`:` + +表示模糊匹配,匹配含有某个字符串的文本或属性。 + +**关键字** 是出现在定位语句最左边,用于指明该语句以哪种方式去查找元素,有以下这些: + +## 📍 id 匹配符`#` + +表示`id`属性,只在语句最前面且单独使用时生效,可配合`=`或`:`。 + +```python +# 在页面中查找 id 属性为 ele_id 的元素 +ele1 = page.ele('#ele_id') + +# 在 ele1 元素内查找 id 属性包含 ele_id 文本的元素 +ele2 = ele1.ele('#:ele_id') +``` + +## 📍 class 匹配符`.` + +表示`class`属性,只在语句最前面且单独使用时生效,可配合`=`或`:`。 + +```python +# 查找 class 属性为 ele_class 的元素 +ele2 = ele1.ele('.ele_class') + +# 查找 class 属性包含 ele_class 文本的元素 +ele2 = ele1.ele('.:ele_class') +``` + +## 📍 单属性匹配符 `@` + +表示某个属性,只匹配一个属性。 +`@`关键字只有一个简单功能,就是匹配`@`后面的内容,不再对后面的字符串进行解析。因此即使后面的字符串也存在`@`或`@@`,也作为要匹配的内容对待。 + +!> **注意:** +如果属性中包含特殊字符,如包含`@`,用这个方式不能正确匹配到,须使用 css selector 方式查找。且特殊字符要用`\`转义。 + +```python +# 查找 name 属性为 ele_name 的元素 +ele2 = ele1.ele('@name=ele_name') + +# 查找 name 属性包含 ele_name 文本的元素 +ele2 = ele1.ele('@name:ele_name') + +# 查找有 name 属性的元素 +ele2 = ele1.ele('@name') + +# 查找没有任何属性的元素 +ele2 = ele1.ele('@') + +# 查找 emaile 属性为 abc@def.com 的元素,有多个 @ 也不会重复处理 +ele2 = ele1.ele('@email=abc@def.com') + +# 属性中有特殊字符的情形,匹配abc@def属性等于v的元素 +ele2 = ele1.ele('css:div[abc\@def="v"]') +``` + +## 📍 多属性匹配符`@@` + +表示某个属性,多属性匹配时使用,个数不限。还能匹配要忽略的元素,匹配文本时也和`@`不一样。 +`@@`后跟 - 时,表示 not。如: + +- `@@-name`表示匹配没有`name`属性的元素 + +- `@@-name=ele_name`表示匹配`name`属性不为`ele_name`的元素 + +如有以下情况,不能使用此方式,须改用 xpath 的方式: + +- 匹配文本或属性中出现`@@` + +- 属性名本身以`-`开头 + +!> **注意:** +如果属性中包含特殊字符,如包含`@`,用这个方式不能正确匹配到,须使用 css selector 方式查找。且特殊字符要用`\`转义。 + +```python +# 查找 name 属性为 name 且 class 属性包含 cls 文本的元素 +ele2 = ele1.ele('@@name=name@@class:cls') + +# 查找没有 class 属性的元素 +ele2 = ele1.ele('@@-class') + +# 查找 name 属性不包含 ele_name 的元素 +ele2 = ele1.ele('@@-name:ele_name') +``` + +## 📍 文本匹配符`text` + +要匹配的文本,查询字符串如开头没有任何关键字,也表示根据传入的文本作模糊查找。 +如果元素内有多个直接的文本节点,精确查找时可匹配所有文本节点拼成的字符串,模糊查找时可匹配每个文本节点。 + +```python +# 查找文本为 some text 的元素 +ele2 = ele1.ele('text=some text') + +# 查找文本包含 some text 的元素 +ele2 = ele1.ele('text:some text') + +# 与上一行一致 +ele2 = ele1.ele('some text') +``` + +?> **Tips:**
若要查找的文本包含`text:` ,可下面这样写,即第一个`text:` 为关键字,第二个是要查找的内容: + +```python +ele2 = page.ele('text:text:') +``` + +## 📍 文本匹配符`text()` + +作为查找属性时使用的文本关键字,必须与`@`或`@@`配合使用。 +与`@`配合和与`@@`配合使用时,`text()`的意义是有差别的。 + +`@text()`表示在元素的直接子文本节点中匹配,且多个节点不能合并匹配。 `@@text()`表示在元素内部所有文本中匹配,且会把元素内部所有文本拼成一个总字符串再进行匹配,因此可以模糊匹配元素里面任意文本。 + +```python +# 查找文本为 some text 的元素 +ele2 = ele1.ele('@text()=some text') + +# 查找文本包含 some text 的元素 +ele2 = ele1.ele('@text():some text') + +# 查找文本为 some text 且 class 属性为 cls 的元素 +ele2 = ele1.ele('@@text()=some text@@class=cls') + +# 查找文本为 some text 且没有任何属性的元素(因第一个 @@ 后为空) +ele2 = ele1.ele('@@@@text():some text') + +# 查找直接子文本包含 some text 字符串的元素,和 text 关键字没有区别 +ele = page.ele('@text():some text') +ele = page.ele('text:some text') + +# 查找元素内部包含 some text 字符串的元素 +ele = page.ele('@@text():some text') +``` + +## 📍 类型匹配符`tag` + +表示元素的标签,只在语句最前面且单独使用时生效,可与`@`或`@@`配合使用。`tag:`与`tag=`效果一致。 + +```python +# 定位 div 元素 +ele2 = ele1.ele('tag:div') + +# 定位 class 属性为 cls 的 div 元素 +ele2 = ele1.ele('tag:div@class=cls') + +# 定位文本为 text 的 div 元素 +ele2 = ele1.ele('tag:div@text()=text') + +# 定位 class 属性为 cls 且文本为 text 的 div 元素 +ele2 = ele1.ele('tag:div@@class=cls@@text()=text') + +# 查找直接文本节点包含 text 字符串的 div 元素 +ele2 = ele1.ele('tag:div@text():text') + +# 查找内部文本节点包含 text 字符串的 div 元素 +ele2 = ele1.ele('tag:div@@text():text') +``` + +?> **Tips:**
+注意, `tag:div@text():text` 和 `tag:div@@text():text` 是有区别的,前者只在`div`的直接文本节点搜索,后者搜索`div`的整个内部。 + +## 📍 css selector 匹配符`css` + +表示用 css selector 方式查找元素。`css:`与`css=`效果一致。 + +```python +# 查找 div 元素 +ele2 = ele1.ele('css:.div') + +# 查找 div 子元素元素,这个写法是本库特有,原生不支持 +ele2 = ele1.ele('css:>div') +``` + +## 📍 xpath 匹配符`xpath` + +表示用 xpath 方式查找元素。`xpath:`与`xpath=`效果一致。 +该方法支持完整的 xpath 语法,能使用 xpath 直接获取元素属性,selenium 不支持这种用法。 + +```python +# 查找 div 元素 +ele2 = ele1.ele('xpath:.//div') + +# 和上面一行一样,查找元素的后代时,// 前面的 . 可以省略 +ele2 = ele1.ele('xpath://div') + +# 获取 div 元素的 class 属性,返回字符串 +txt = ele1.ele('xpath://div/@class') +``` + +?> **Tips:**
+查找元素的后代时,selenium 原生代码要求 xpath 前面必须加`.`,否则会变成在全个页面中查找。笔者觉得这个设计是画蛇添足,既然已经通过元素查找了,自然应该只查找这个元素内部的元素。所以,用 xpath +在元素下查找时,最前面`//`或`/`前面的`.`可以省略。 + +## 📍 selenium 的 loc 元组 + +查找方法能直接接收 selenium 原生定位元组进行查找,s 模式下也支持这种写法。 + +```python +from selenium.webdriver.common.by import By + +# 查找 id 为 ele_id 的元素 +loc1 = (By.ID, 'ele_id') +ele = page.ele(loc1) + +# 按 xpath 查找 +loc2 = (By.XPATH, '//div[@class="ele_class"]' +ele = page.ele(loc2) +``` + +# ✔️ 等待 + +d 模式下所有查找元素操作都自带等待,默认为跟随元素所在页面`timeout`属性(默认 10 秒),也可以在每次查找时单独设置,单独设置的等待时间不会改变页面原来设置。 + +```python +# 页面初始化时设置查找元素超时时间为 15 秒 +page = MixPage(timeout=15) +# 设置查找元素超时时间为 5 秒 +page.timeout = 5 + +# 使用页面超时时间来查找元素(5 秒) +ele1 = page.ele('some text') +# 为这次查找页面独立设置等待时间(1 秒) +ele1 = page.ele('some text', timeout=1) +# 查找后代元素,使用页面超时时间(5 秒) +ele2 = ele1.ele('some text') +# 查找后代元素,使用单独设置的超时时间(1 秒) +ele2 = ele1.ele('some text', timeout=1) +``` + +# ✔️ 相对定位 + +以下方法可以以某元素为基准,在 DOM 中按照条件获取其兄弟元素、祖先元素、文档前后元素。 +除获取元素外,还能通过 xpath 获取任意节点内容,如文本节点、注释节点。这在处理元素和文本节点混排的时候非常有用。 + +## parent() + +此方法获取当前元素某一级父元素,可指定筛选条件或层数。 + +参数: + +- level_or_loc:第几级父元素,或定位符 + +返回: + +```python +# 获取 ele1 的第二层父元素 +ele2 = ele1.parent(2) + +# 获取 ele1 父元素中 id 为 id1 的元素 +ele2 = ele1.parent('#id1') +``` + +## next() + +此方法返回当前元素后面的某一个兄弟元素,可指定筛选条件和第几个。 + +参数: + +- index:查询结果中的第几个 +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素后面某个兄弟元素或节点文本 + +```python +# 获取 ele1 后面第一个兄弟元素 +ele2 = ele1.next() + +# 获取 ele1 后面第 3 个兄弟元素 +ele2 = ele1.next(3) + +# 获取 ele1 后面第 3 个 div 兄弟元素 +ele2 = ele1.next(3, 'tag:div') + +# 获取 ele1 后面第一个文本节点的文本 +txt = ele1.next(1, 'xpath:text()') +``` + +## nexts() + +此方法返回后面全部符合条件的兄弟元素或节点组成的列表,可用查询语法筛选。 + +参数: + +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素前面符合条件的兄弟元素或节点文本组成的列表 + +```python +# 获取 ele1 后面所有兄弟元素 +eles = ele1.nexts() + +# 获取 ele1 后面所有 div 兄弟元素 +divs = ele1.nexts('tag:div') + +# 获取 ele1 后面的所有文本节点 +txts = ele1.nexts('xpath:text()') +``` + +## prev() + +此方法返回当前元素前面的某一个兄弟元素,可指定筛选条件和第几个。 + +参数: + +- index:查询结果中的第几个 +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素前面某个兄弟元素或节点文本 + +```python +# 获取 ele1 前面第一个兄弟元素 +ele2 = ele1.prev() + +# 获取 ele1 前面第 3 个兄弟元素 +ele2 = ele1.prev(3) + +# 获取 ele1 前面第 3 个 div 兄弟元素 +ele2 = ele1.prev(3, 'tag:div') + +# 获取 ele1 前面第一个文本节点的文本 +txt = ele1.prev(1, 'xpath:text()') +``` + +## prevs() + +此方法返回前面全部符合条件的兄弟元素或节点组成的列表,可用查询语法筛选。 + +参数: + +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素前面符合条件的兄弟元素或节点文本组成的列表 + +```python +# 获取 ele1 前面所有兄弟元素 +eles = ele1.prevs() + +# 获取 ele1 前面所有 div 兄弟元素 +divs = ele1.prevs('tag:div') +``` + +## after() + +此方法返回当前元素后面的某一个元素,可指定筛选条件和第几个。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 + +参数: + +- index:查询结果中的第几个 +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素后面某个元素或节点 + +```python +# 获取 ele1 后面第 3 个元素 +ele2 = ele1.after(3) + +# 获取 ele1 后面第 3 个 div 元素 +ele2 = ele1.after(3, 'tag:div') + +# 获取 ele1 后面第一个文本节点的文本 +txt = ele1.after(1, 'xpath:text()') +``` + +## afters() + +此方法返回后面符合条件的全部元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 + +参数: + +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素后面符合条件的元素或节点组成的列表 + +```python +# 获取 ele1 后所有元素 +eles = ele1.afters() + +# 获取 ele1 前面所有 div 元素 +divs = ele1.afters('tag:div') +``` + +## before() + +此方法返回当前元素前面的某一个元素,可指定筛选条件和第几个。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 + +参数: + +- index:查询结果中的第几个 +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素前面某个元素或节点 + +```python +# 获取 ele1 前面第 3 个元素 +ele2 = ele1.before(3) + +# 获取 ele1 前面第 3 个 div 元素 +ele2 = ele1.before(3, 'tag:div') + +# 获取 ele1 前面第一个文本节点的文本 +txt = ele1.before(1, 'xpath:text()') +``` + +## befores() + +此方法返回前面全部符合条件的元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 + +参数: + +- filter_loc:用于筛选元素的查询语法 +- timeout:查找元素的超时时间 + +返回:本元素前面符合条件的元素或节点组成的列表 + +```python +# 获取 ele1 前面所有元素 +eles = ele1.befores() + +# 获取 ele1 前面所有 div 元素 +divs = ele1.befores('tag:div') +``` + +# ✔️ `ShadowRootElement`相关查找 + +本库把 shadow-root 也作为元素对象看待,是为`ShadowRootElement`对象。对`ShadowRootElement`对象可与普通元素一样查找下级元素和 DOM 内相对定位,但不能用页面布局相对定位。 +对`ShadowRootElement`对象进行相对定位时,把它看作其父对象内部的第一个对象,其余定位逻辑与普通对象一致。 + +!> **注意:**
+如果`ShadowRootElement`元素的下级元素中有其它`ShadowRootElement`元素,那这些下级`ShadowRootElement` +元素内部是无法直接通过定位语句查找到的,只能先定位到其父元素,再用`shadow-root`属性获取。 + +```python +# 获取一个 shadow-root 元素 +sr_ele = page.ele('#app').shadow_root + +# 在该元素下查找下级元素 +ele1 = sr_ele.ele('tag:div') + +# 用相对定位获取其它元素 +ele1 = sr_ele.parent(2) +ele1 = sr_ele.next(1, 'tag:div') +ele1 = sr_ele.after(1, 'tag:div') +eles = sr_ele.nexts('tag:div') + +# 定位下级元素中的 shadow+-root 元素 +sr_ele2 = sr_ele.ele('tag:div').shadow_root +``` + +# ✔️ 简化写法 + +为进一步精简代码,对语法进行了精简 + +- 定位语法都有其简化形式。 +- 页面和元素对象都实现了`__call__()`方法,可直接调用。 +- 所有查找方法都支持链式操作 + +示例: + +```python +# 定位到页面中 id 为 table_id 的元素,然后获取它的所有 tr 元素 +eles = page('#table_id').eles('t:tr') + +# 定位到 class 为 cls 的元素,然后在它里面查找文本为 text 的元素 +ele2 = ele1('.cls1')('tx=text') + +# 获取 ele1 的 shadow_root 元素,再在里面查找 class 属性为 cls 的元素 +ele2 = ele1.sr('.cls') + +# 按xpath 查找元素 +ele2 = ele1('x://div[@class="ele_class"]') +``` + +简化写法对应列表 + +| 原写法 | 简化写法 | +|:-----------:|:----:| +| text | tx | +| text() | tx() | +| tag | t | +| xpath | x | +| css | c | +| shadow_root | sr | + +# Tips + +- 从一个`DriverElement`元素获取到的`SessionElement`版本,依然能够使用相对定位方法定位祖先或兄弟元素。 +- `SessionElement`和`SessionPage`的`ele()`和`eles()`方法也有`timeout`参数,但它是不生效的,仅用于保持与 d 模式元素书写一致,便于无差别的调用。 +- 定位语句内容与关键字重复时,请使用 xpath 或 css selector 代替。 diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 81fc1e8..e94bec1 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,48 +1,54 @@ -* [⭐️ 简介](README.md) +* [⭐️ 1 简介](README.md) -* [🧭 入门指南](#) - - * [🔥 基本概念](入门指南\基本概念.md) - * [👍 快速上手](入门指南\快速上手.md) - * [🍀 特性演示](#) - * [🌿 与 requests 对比](入门指南\特性演示\与requests代码对比.md) - * [🌿 与 selenium 对比](入门指南\特性演示\与selenium代码对比.md) - * [🌿 模式切换](入门指南\特性演示\模式切换.md) - * [🌿 获取并打印元素属性](入门指南\特性演示\获取并打印元素属性.md) - * [🌿 下载文件](入门指南\特性演示\下载文件.md) +* [🧭 2 入门指南](#) -* [🛠 使用方法](#) - - * [🔨 创建页面对象](使用方法\创建页面对象.md) - * [🔨 访问网页](使用方法\访问网页.md) - * [🔨 查找页面元素](使用方法\查找页面元素.md) - * [🔨 获取元素信息](使用方法\获取元素信息.md) - * [🔨 元素操作](使用方法\元素操作.md) - * [🔨 获取网页信息](使用方法\获取网页信息.md) - * [🔨 页面操作](使用方法\页面操作.md) - * [🔨 启动配置](#) - * [🔧 概述](使用方法\启动配置\概述.md) - * [🔧 Chrome 启动配置](使用方法\启动配置\Chrome启动配置.md) - * [🔧 Session 启动配置](使用方法\启动配置\Session启动配置.md) - * [🔧 使用配置文件](使用方法\启动配置\使用配置文件.md) - * [🔨 下载文件](使用方法\下载文件.md) - * [🔨 监听浏览器网络数据](使用方法\监听浏览器网络数据.md) - * [🔨 cookies 的使用](使用方法\cookies的使用.md) - * [🔨 Drission 对象](使用方法\Drission对象.md) - * [🔨 对接 selenium 及 requests 代码](使用方法\对接selenium及requests代码.md) - * [🔨 使用其它系统或浏览器](使用方法\使用其它系统或浏览器.md) - * [🔨 DriverPage 和 SessionPage](使用方法\DriverPage和SessionPage.md) - * [🔨 打包程序](使用方法\打包程序.md) + * [🔥 2.1 基本概念](入门指南\基本概念.md) + * [👍 2.2 快速上手](入门指南\快速上手.md) + * [🍀 2.3 特性演示](#) + * [🌿 与 requests 对比](入门指南\特性演示\与requests代码对比.md) + * [🌿 与 selenium 对比](入门指南\特性演示\与selenium代码对比.md) + * [🌿 模式切换](入门指南\特性演示\模式切换.md) + * [🌿 获取并打印元素属性](入门指南\特性演示\获取并打印元素属性.md) + * [🌿 下载文件](入门指南\特性演示\下载文件.md) -* [💖 实用示例](#) - - * [🧡 自动登录码云](实用示例\自动登录码云.md) - * [🧡 获取各国疫情排名](实用示例\获取各国疫情排名.md) - * [🧡 下载星巴克产品图片](实用示例\下载星巴克产品图片.md) - * [🧡 同时操作多个浏览器](实用示例\同时操作多个浏览器.md) +* [🛠 3 WebPage 使用方法](#) -* [⚡️ Tips大集合](Tips大集合.md) + * [🔨 3.1 创建页面对象](WebPage使用方法\3.1创建页面对象.md) + * [🔨 3.2 访问网页](WebPage使用方法\3.2访问网页.md) + * [🔨 3.3 查找元素](WebPage使用方法\3.3查找元素.md) -* [🎯️ 版本历史](版本历史.md) +* [🛠 4 MixPage 使用方法](#) + + * [🔨 4.1 创建页面对象](MixPage使用方法\创建页面对象.md) + * [🔨 4.2 访问网页](MixPage使用方法\访问网页.md) + * [🔨 4.3 查找页面元素](MixPage使用方法\查找页面元素.md) + * [🔨 4.4 获取元素信息](MixPage使用方法\获取元素信息.md) + * [🔨 4.5 元素操作](MixPage使用方法\元素操作.md) + * [🔨 4.6 获取网页信息](MixPage使用方法\获取网页信息.md) + * [🔨 4.7 页面操作](MixPage使用方法\页面操作.md) + * [🔨 4.8 启动配置](#) + * [🔧 概述](MixPage使用方法\启动配置\概述.md) + * [🔧 Chrome 启动配置](MixPage使用方法\启动配置\Chrome启动配置.md) + * [🔧 Session 启动配置](MixPage使用方法\启动配置\Session启动配置.md) + * [🔧 使用配置文件](MixPage使用方法\启动配置\使用配置文件.md) + * [🔨 4.9 下载文件](MixPage使用方法\下载文件.md) + * [🔨 4.10 监听浏览器网络数据](MixPage使用方法\监听浏览器网络数据.md) + * [🔨 4.11 cookies 的使用](MixPage使用方法\cookies的使用.md) + * [🔨 4.12 Drission 对象](MixPage使用方法\Drission对象.md) + * [🔨 4.13 对接 selenium 及 requests 代码](MixPage使用方法\对接selenium及requests代码.md) + * [🔨 4.14 使用其它系统或浏览器](MixPage使用方法\使用其它系统或浏览器.md) + * [🔨 4.15 DriverPage 和 SessionPage](MixPage使用方法\DriverPage和SessionPage.md) + * [🔨 4.16 打包程序](MixPage使用方法\打包程序.md) + +* [💖 5 实用示例](#) + + * [🧡 自动登录码云](实用示例\自动登录码云.md) + * [🧡 获取各国疫情排名](实用示例\获取各国疫情排名.md) + * [🧡 下载星巴克产品图片](实用示例\下载星巴克产品图片.md) + * [🧡 同时操作多个浏览器](实用示例\同时操作多个浏览器.md) + +* [⚡️ 6 Tips大集合](Tips大集合.md) + +* [🎯️ 7 版本历史](版本历史.md) * [💐 鸣谢](鸣谢.md) diff --git a/setup.py b/setup.py index 63e1845..aca9951 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md", "r", encoding='utf-8') as fh: setup( name="DrissionPage", - version="3.0.22", + version="3.0.23", author="g1879", author_email="g1879@qq.com", description="A module that integrates selenium and requests session, encapsulates common page operations.",