diff --git a/DrissionPage/chromium_element.py b/DrissionPage/chromium_element.py
index 2a01094..3757abe 100644
--- a/DrissionPage/chromium_element.py
+++ b/DrissionPage/chromium_element.py
@@ -1234,7 +1234,7 @@ def _run_script(page_or_ele, script: str, as_expr: bool = False, timeout: float
obj_id = page_or_ele.obj_id
else:
page = page_or_ele
- obj_id = page_or_ele.root.obj_id
+ obj_id = page_or_ele._root_id
if as_expr:
res = page.run_cdp('Runtime.evaluate',
diff --git a/DrissionPage/chromium_page.py b/DrissionPage/chromium_page.py
index a5d4652..65984ae 100644
--- a/DrissionPage/chromium_page.py
+++ b/DrissionPage/chromium_page.py
@@ -31,6 +31,8 @@ class ChromiumPage(BasePage):
:param timeout: 超时时间
"""
super().__init__(timeout)
+ self._is_loading = None
+ self._root_id = None
self._connect_browser(Tab_or_Options, tab_id)
def _connect_browser(self, Tab_or_Options: Union[Tab, DriverOptions] = None, tab_id: str = None) -> None:
@@ -39,6 +41,8 @@ class ChromiumPage(BasePage):
:param tab_id: 要控制的标签页id,不指定默认为激活的
:return: None
"""
+ self._is_loading = False
+ self._root_id = None
self.timeouts = Timeout(self)
self._page_load_strategy = 'normal'
if isinstance(Tab_or_Options, Tab):
@@ -66,12 +70,28 @@ class ChromiumPage(BasePage):
self._driver.start()
self._driver.DOM.enable()
self._driver.Page.enable()
- root = self._driver.DOM.getDocument()
- self.root = ChromiumElement(self, node_id=root['root']['nodeId'])
+ root_id = self._driver.DOM.getDocument()['root']['nodeId']
+ self._root_id = self._driver.DOM.resolveNode(nodeId=root_id)['object']['objectId']
self._alert = Alert()
- self.driver.Page.javascriptDialogOpening = self._on_alert_open
- self.driver.Page.javascriptDialogClosed = self._on_alert_close
+ self._driver.Page.javascriptDialogOpening = self._on_alert_open
+ self._driver.Page.javascriptDialogClosed = self._on_alert_close
+
+ self._driver.Page.frameNavigated = self.onFrameNavigated
+ self._driver.Page.loadEventFired = self.onLoadEventFired
+
+ def onLoadEventFired(self, **kwargs):
+ """在页面刷新、变化后重新读取页面内容"""
+ self._is_loading = True
+ self._driver.DOM.enable()
+ self._driver.Page.enable()
+ root_id = self._driver.DOM.getDocument()['root']['nodeId']
+ self._root_id = self._driver.DOM.resolveNode(nodeId=root_id)['object']['objectId']
+ self._is_loading = False
+
+ def onFrameNavigated(self, **kwargs):
+ if not kwargs['frame'].get('parentId', None):
+ self._is_loading = True
def __call__(self, loc_or_str: Union[Tuple[str, str], str, 'ChromiumElement'],
timeout: float = None) -> Union['ChromiumElement', None]:
@@ -86,6 +106,8 @@ class ChromiumPage(BasePage):
@property
def driver(self) -> Tab:
"""返回用于控制浏览器的Tab对象"""
+ while self._is_loading:
+ sleep(.1)
return self._driver
@property
@@ -236,7 +258,6 @@ class ChromiumPage(BasePage):
interval=interval,
show_errmsg=show_errmsg,
timeout=timeout)
- self.driver.DOM.getDocument()
return self._url_available
def get_cookies(self, as_dict: bool = False) -> Union[list, dict]:
@@ -412,7 +433,10 @@ class ChromiumPage(BasePage):
:return: None
"""
node_id = self.ele(loc_or_ele).node_id
- self.driver.DOM.scrollIntoViewIfNeeded(nodeId=node_id)
+ try:
+ self.driver.DOM.scrollIntoViewIfNeeded(nodeId=node_id)
+ except Exception:
+ self.ele(loc_or_ele).run_script("this.scrollIntoView();")
def refresh(self, ignore_cache: bool = False) -> None:
"""刷新当前页面 \n
@@ -590,6 +614,7 @@ class ChromiumPage(BasePage):
timeout = timeout or self.timeout
end_time = perf_counter() + timeout
while not self._alert.activated and perf_counter() < end_time:
+ print('vvv')
sleep(.1)
if not self._alert.activated:
return None
diff --git a/DrissionPage/common.py b/DrissionPage/common.py
index bede592..4350da3 100644
--- a/DrissionPage/common.py
+++ b/DrissionPage/common.py
@@ -618,7 +618,7 @@ def _get_running_args(opt: DriverOptions) -> list:
return result
from json import load, dump
- with open(prefs_file, "r") as f:
+ with open(prefs_file, "r", encoding='utf-8') as f:
j = load(f)
for pref in prefs:
@@ -627,7 +627,7 @@ def _get_running_args(opt: DriverOptions) -> list:
_make_leave_in_dict(j, pref, 0, len(pref))
_set_value_to_dict(j, pref, value)
- with open(prefs_file, 'w') as f:
+ with open(prefs_file, 'w', encoding='utf-8') as f:
dump(j, f)
return result
@@ -668,8 +668,7 @@ def _location_in_viewport(page, loc_x: int, loc_y: int) -> bool:
:param loc_y: 页面绝对坐标y
:return:
"""
- js = f'''
- function(){{var x = {loc_x};var y = {loc_y};
+ js = f'''function(){{var x = {loc_x};var y = {loc_y};
const vWidth = window.innerWidth || document.documentElement.clientWidth
const vHeight = window.innerHeight || document.documentElement.clientHeight
if (x< document.documentElement.scrollLeft || y < document.documentElement.scrollTop
diff --git a/DrissionPage/easy_set.py b/DrissionPage/easy_set.py
index 92244a8..b4a421b 100644
--- a/DrissionPage/easy_set.py
+++ b/DrissionPage/easy_set.py
@@ -38,7 +38,7 @@ def set_paths(driver_path: str = None,
user_data_path: str = None,
cache_path: str = None,
ini_path: str = None,
- check_version: bool = True) -> None:
+ check_version: bool = False) -> None:
"""快捷的路径设置函数 \n
:param driver_path: chromedriver.exe路径
:param chrome_path: chrome.exe路径
diff --git a/DrissionPage/web_page.py b/DrissionPage/web_page.py
index 847321b..47eaa9b 100644
--- a/DrissionPage/web_page.py
+++ b/DrissionPage/web_page.py
@@ -123,7 +123,7 @@ class WebPage(SessionPage, ChromiumPage, BasePage):
if self._driver is None:
self._connect_browser(self._driver_options, self._setting_tab_id)
- return self._driver
+ return super().driver
@property
def _session_url(self) -> str:
diff --git a/README.md b/README.md
index 7ff0dae..3f34256 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,8 @@ DrissionPage,即 driver 和 session 组合而成的 page。
- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持)
+- 对 Linux 提供良好支持
+
新版是自己实现的功能,开发不会受太多限制,以后将主要对`WebPage`进行更新。
3.0 版已经发布,目前正在测试,欢迎试用并提出意见,让我做得更好。
diff --git a/docs/README.md b/docs/README.md
index da00c16..ebc8355 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -40,10 +40,14 @@ DrissionPage,即 driver 和 session 组合而成的 page。
- 可以对整个网页截图,包括视口外的部分(90以上版本浏览器支持)
+- 对 Linux 提供良好支持
+
新版是自己实现的功能,开发不会受太多限制,以后将主要对`WebPage`进行更新。
3.0 版已经发布,目前正在测试,欢迎试用并提出意见,让我做得更好。
+文档正在更新,目前还是旧版,以后实例主要使用`WebPage`,`MixPage`与其不一致的地方才会说明。
+
# 💡 特性和亮点
作者踩过无数坑,总结出的经验全写到这个库里了。内置了 N 多实用功能,对常用功能作了整合和优化。
@@ -51,7 +55,7 @@ DrissionPage,即 driver 和 session 组合而成的 page。
## 🎉 特性
- 代码高度集成,以简洁的代码为第一追求。
-- 页面对象可在 selenium 和 requests 模式间任意切换,保留登录状态。
+- 页面对象可在浏览器和 requests 模式间任意切换,保留登录状态。
- 极简单但强大的元素定位语法,支持链式操作,代码极其简洁。
- 两种模式提供一致的 API,使用体验一致。
- 人性化设计,集成众多实用功能,大大降低开发工作量。
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
index 6d9625c..ebb14d7 100644
--- a/docs/_coverpage.md
+++ b/docs/_coverpage.md
@@ -1,8 +1,9 @@
# DrissionPage
-以页面为单位整合 selenium 和 requests,封装了常用操作。
+DrissionPage 是一个 Web 自动化框架。
+以页面为单位整合浏览器和 requests,封装了常用操作。
极大地简化了代码,易于使用,并可实现两种模式的无缝切换。
-可兼顾 selenium 的易用性和 requests 的高性能。
+可兼顾浏览器的易用性和 requests 的高性能。
如果本库对你有所帮助,请给予支持!持续维护中。
diff --git a/docs/入门指南/基本概念.md b/docs/入门指南/基本概念.md
index d6fbbf3..5f9d9e0 100644
--- a/docs/入门指南/基本概念.md
+++ b/docs/入门指南/基本概念.md
@@ -1,5 +1,5 @@
本节讲解 DrissionPage 的一些基本概念。了解它大概的构成和运作原理。
-默认读者对网络协议、html、selenium、requests 等有基本认识。
+默认读者对网络协议、html、网页自动化、requests 等有基本认识。
# 网页自动化
@@ -12,8 +12,10 @@
后者使用浏览器模拟人的行为,如 selenium。写起来简单得多,免去复杂的分析过程,但速度非常慢,占用资源巨大。
-鉴于此,DrissionPage 以页面为单位将两者整合,对 selenium 和 requests 进行了重新封装,实现两种模式的互通,并加入常用的页面和元素控制功能,可大幅降低开发难度和代码量。
-selenium 用于操作浏览器的对象叫 Driver,requests 用于管理连接的对象叫 Session,Drission 就是它们两者的合体。
+鉴于此,DrissionPage 以页面为单位将两者整合,对 chromium 协议 和 requests 进行了重新封装,实现两种模式的互通,并加入常用的页面和元素控制功能,可大幅降低开发难度和代码量。
+用于操作浏览器的对象叫 Driver,requests 用于管理连接的对象叫 Session,Drission 就是它们两者的合体。
+在旧版本,本库是通过对 selenium 和 requests 的重新封装实现的。
+从 3.0 版开始,作者另起炉灶,用 chromium 协议自行实现了 selenium 全部功能,从而摆脱了对 selenium 的依赖,功能更多更强,运行效率更高,开发更灵活。
# 工作模式
@@ -58,13 +60,14 @@ rows = ele.eles('tag:tr')
# 主要对象
-## MixPage
+## WebPage
-顾名思义,`MixPage`对象是整合了两种模式的页面对象,所有页面的跳转、读取、操作、标签页控制都由该对象进行。
+是从 3.0 版开始推出的页面页面控制对象,整合了两种模式,d 模式所有页面的跳转、读取、操作、标签页控,s 模式发送和解析数据都由该对象进行。
+该对象是作者用 chromium devtools protocol 开发而成,不依赖 selenium 对浏览器进行控制。
```python
# 创建页面对象
-page = MixPage('d')
+page = WebPage('d')
# 访问百度
page.get('http://www.baidu.com')
# 定位输入框并输入关键字
@@ -75,9 +78,9 @@ page.ele('@value=百度一下').click()
详细使用方法见“创建页面对象”和“操作页面”章节。
-## DriverElement
+## ChromiumElement
-`DriverElemnet`对象是 d 模式所产生的页面元素对象,用于可对其进行点击、文本输入、拖拽、运行 js 脚本等操作,也可以基于这个元素查找其下级或周围的元素。
+`ChromiumElemnet`对象是 `WebPage`在 d 模式所产生的页面元素对象,用于可对其进行点击、文本输入、拖拽、运行 js 脚本等操作,也可以基于这个元素查找其下级或周围的元素。
```python
# 点击一个元素
@@ -97,7 +100,7 @@ links = ele1.eles('tag:a')
## SessionElement
`SessionElement`对象是 s 模式所产生的页面元素对象,可以读取元素信息,或基于它进行下级元素查找、相对定位其它元素,但不能执行点击等操作。
-这种对象解析效率很高,当 d 模式页面太复杂时,可把 d 模式的元素转换为`SessionElement`进行解析,提高速度。转换的同时可以执行下级元素的查找。
+这种对象解析效率非常高,当 d 模式页面太复杂时,可把 d 模式的元素转换为`SessionElement`进行解析,提高速度。转换的同时可以执行下级元素的查找。
```python
# 获取元素 tag 属性
@@ -105,13 +108,51 @@ tag = ele1.tag
# 在元素下查找第一个 name 为 name1 的子元素
ele2 = ele1.ele('@name=name1')
-# 假设 d_ele 是一个 DriverElement,使用其 SessionElement 版本进行子元素查找
+# 假设 d_ele 是一个 ChromiumElement,使用其 SessionElement 版本进行子元素查找
ele2 = d_ele.s_ele('@name=name1')
```
+## MixPage
+
+`MixPage`对象使用方法与`WebPage`大致相同,不同的是它控制浏览器方面的功能是通过封装 selenium 实现的。作者以后主要对`WebPage`进行更新,`MixPage`会一直维持当前状态。
+
+```python
+# 创建页面对象
+page = MixPage('d')
+# 访问百度
+page.get('http://www.baidu.com')
+# 定位输入框并输入关键字
+page.ele('#kw').input('python')
+# 点击“百度一下”按钮
+page.ele('@value=百度一下').click()
+```
+
+详细使用方法见“创建页面对象”和“操作页面”章节。
+
+## DriverElement
+
+`DriverElemnet`对象与`ChromiumElement`大致相同。区别是它是由`MiPage`所产生。
+
+```python
+# 点击一个元素
+ele1.click()
+# 输入文本
+ele1.input('some text')
+# 获取 class 属性
+attr = ele1.attr('class')
+# 设置 style 属性
+ele1.set_attr('style', 'display:none;')
+# 获取其子元素中所有 a 元素
+links = ele1.eles('tag:a')
+```
+
+详细使用方法见“获取页面元素”、“获取元素信息”和“操作页面元素”章节。
+
## Drission
-`Drission`对象用于管理与网页通讯的`WebDriver`对象和`Session`对象,相当于驱动器的角色。能实现这两个对象间的登录状态传递等。但它提供的功能往往通过`MixPage`调用,所以出场几率并不高。
+`Drission`对象用于管理`MixPage`中与网页通讯的`WebDriver`对象和`Session`对象,相当于驱动器的角色。能实现这两个对象间的登录状态传递等。但它提供的功能往往通过`MixPage`
+调用,所以出场几率并不高。
+`WebPage`自行实现了这些功能,所以`WebPage`不需要`Drission`对象。
```python
from DrissionPage import Drission
@@ -133,6 +174,12 @@ page = MixPage()
# 结构图
+## `WebPage`结构图
+
+待更新。
+
+## `MixPage`结构图
+
如图所示,`Drission`对象负责链接的创建、共享登录状态等工作,类似 selenium 中 driver 的概念。
`MixPage`对象负责对获取到的页面进行解析、操作。
`DriverElement`和`SessionElement`则是从页面对象中获取到的元素对象。负责对元素进行解析和操作。
@@ -141,9 +188,9 @@ page = MixPage()
# 配置管理
-无论 requests 还是 selenium,都通常须要一些配置信息才能正常工作,如长长的`user_agent`、driver 路径、浏览器配置等。这些代码往往是繁琐而重复的,不利于代码的简洁。
+无论 requests 还是浏览器,都通常须要一些配置信息才能正常工作,如长长的`user_agent`、driver 路径、浏览器配置等。这些代码往往是繁琐而重复的,不利于代码的简洁。
因此,DrissionPage 使用配置文件记录常用配置信息,程序会自动读取默认配置文件里的内容。所以,在示例中,通常看不见配置信息的代码。
这个功能支持用户保存不同的配置文件,按情况调研,也可以支持直接把配置写在代码里面,屏蔽读取配置文件。
-?> **Tips:**
当须要打包程序时,必须把配置写到代码里,否则会报错。
\ No newline at end of file
+?> **Tips:**
当须要打包程序时,必须把配置写到代码里,或打包后手动复制配置文件到运行路径,否则会报错。
\ No newline at end of file
diff --git a/docs/入门指南/快速上手.md b/docs/入门指南/快速上手.md
index e10b716..cee2138 100644
--- a/docs/入门指南/快速上手.md
+++ b/docs/入门指南/快速上手.md
@@ -13,32 +13,20 @@ pip install DrissionPage --upgrade
# 导入
```python
-from DrissionPage import MixPage
+from DrissionPage import WebPage
```
# 初始化
-s 模式是无须初始化的,导入后就可以直接使用。
-d 模式因为使用浏览器,须要配置浏览器和对应的 driver。
+如果只使用 s 模式是无须初始化的,导入后就可以直接使用。
+如果使用 d 模式,`WebPage`须指定浏览器执行文件路径。`MixPage`还须下载与浏览器版本匹配的 driver 文件(后面的章节介绍)。
-!> **注意:**
这里介绍的方法只适用于 Windows 系统和 Chrome 浏览器,使用其它系统或浏览器请查看 [使用其它系统或浏览器](使用方法\使用其它系统或浏览器.md) 章节。
如果您有已打开的 Chrome
-浏览器,请先关闭,否则会造成冲突。后面在 [创建页面对象](使用方法\使创建页面对象.md) 章节再介绍多 Chrome 浏览器共存的方法。
+默认情况下,如果系统内安装了 Chrome 浏览器,或在系统路径中指定了 chrome 执行文件,DrissionPage 就可以使用了。
-## 自动配置方式
+当然也可指定其它浏览器或启动路径:
-身为自动化工具,DrissionPage 拥有自动识别电脑安装的 Chrome 版本并自动下载对应 chromedriver 功能。这时只要直接创建页面对象即可正常使用。
-第一次运行后,程序会记录 Chrome 和 driver 路径到配置文件,以后直接调用。
+!> **注意:**
这段代码只用于设置配置文件中的路径信息,**运行一次即可**,勿写到正式程序里 。
-```python
-page = MixPage()
-page.get('https://www.baidu.com')
-```
-
-## 手动配置方式
-
-!> **注意:**
这段代码只用于设置配置文件中的路径信息,**运行一次即可**,勿写到正式程序里 。
-
-有些版本的 Chrome 程序无法获取正确的 driver,就须要手动配置路径。
新建一个**临时文件** ,修改并运行以下代码,可手动指定 Chrome 和 driver 路径,记录到配置文件,以后程序会自动读取其中的配置,无须再写。
如果想把配置信息写在代码里,请查阅“使用方法”里相关章节。
@@ -46,78 +34,21 @@ page.get('https://www.baidu.com')
```python
from DrissionPage.easy_set import set_paths
-# 请将以下路径修改为本机实际路径,chrome_path多数情况下可省略
-set_paths(driver_path=r"D:\chrome\chromedriver.exe",
- chrome_path=r"D:\chrome\chrome.exe")
+# 请将以下路径修改为本机实际路径
+set_paths(chrome_path=r"D:\chrome\chrome.exe", # 浏览器执行文件路径
+ user_data_path=r"D:\chrome\userData", # 用户数据保存路径
+ local_port=9999) # 设置浏览器端口,默认9222
```
-当出现以下提示,说明设置成功:
-
-```shell
-正在检测可用性...
-版本匹配,可正常使用。
-```
-
-各版本 driver 下载地址:
-
-- 国外:https://chromedriver.chromium.org/downloads
-- 国内:http://npm.taobao.org/mirrors/chromedriver/
-
# 上手示例
现在,我们通过一些例子,来直观感受一下 DrissionPage 的工作方式。
-## 爬取新冠排行榜
+!> **注意:**
如果您有已打开的 Chrome
+浏览器,请先关闭,否则会造成冲突。后面在 [创建页面对象](%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%5C%E4%BD%BF%E5%88%9B%E5%BB%BA%E9%A1%B5%E9%9D%A2%E5%AF%B9%E8%B1%A1.md)
+章节再介绍多 Chrome 浏览器共存的方法。
-网址:https://www.outbreak.my/zh/world
-此示例爬取全球新冠情况排行榜。该网站是纯 html 页面,特别适合 s 模式爬取和解析。
-
-该网址为外网网址,连接可能稍慢。
-
-
-
-```python
-from DrissionPage import MixPage
-
-# 用 s 模式创建页面对象
-page = MixPage('s')
-# 访问数据网页
-page.get('https://www.outbreak.my/zh/world')
-
-# 获取表头元素
-thead = page('tag:thead')
-# 获取表头列,跳过其中的隐藏的列
-title = thead.eles('tag:th@@-style:display: none;')
-data = [th.text for th in title]
-
-print(data) # 打印表头
-
-# 获取内容表格元素
-tbody = page('tag:tbody')
-# 获取表格所有行
-rows = tbody.eles('tag:tr')
-
-for row in rows:
- # 获取当前行所有列
- cols = row.eles('tag:td')
- # 生成当前行数据列表(跳过其中没用的几列)
- data = [td.text for k, td in enumerate(cols) if k not in (2, 4, 6)]
-
- print(data) # 打印行数据
-```
-
-输出:
-
-```shell
-['总 (205)', '累积确诊', '死亡', '治愈', '现有确诊', '死亡率', '恢复率']
-['美国', '55252823', '845745', '41467660', '12,939,418', '1.53%', '75.05%']
-['印度', '34838804', '481080', '34266363', '91,361', '1.38%', '98.36%']
-['巴西', '22277239', '619024', '21567845', '90,370', '2.78%', '96.82%']
-['英国', '12748050', '148421', '10271706', '2,327,923', '1.16%', '80.57%']
-['俄罗斯', '10499982', '308860', '9463919', '727,203', '2.94%', '90.13%']
-['法国', '9740600', '123552', '8037752', '1,579,296', '1.27%', '82.52%']
-......
-```
+##
## 登录 gitee 网站
@@ -125,10 +56,10 @@ for row in rows:
此示例演示使用控制浏览器的方式自动登录 gitee 网站。
```python
-from DrissionPage import MixPage
+from DrissionPage import WebPage
# 用 d 模式创建页面对象(默认模式)
-page = MixPage()
+page = WebPage()
# 跳转到登录页面
page.get('https://gitee.com/login')
@@ -142,7 +73,6 @@ page.ele('@value=登 录').click()
# 说明
-无论电脑安装的 Chrome 能否正常使用,我们都建议使用绿色版 Chrome。因为 driver 的更新速度往往跟不上浏览器的更新速度,一旦浏览器自动升级而 driver
-未出新版,就会出现无法启动的情况。所以建议用关闭升级功能的绿色版浏览器,使程序运行更稳定。
+无论电脑安装的 Chrome 能否正常使用,都建议使用绿色版 Chrome,并且设置`user_data_path`。
如果计划程序打包成 exe 文件,就不能使用默认配置文件记录配置,具体方法请查看“使用方法->打包程序”章节。
diff --git a/setup.py b/setup.py
index 27e4693..b385362 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.0",
+ version="3.0.2",
author="g1879",
author_email="g1879@qq.com",
description="A module that integrates selenium and requests session, encapsulates common page operations.",
@@ -23,7 +23,8 @@ setup(
"tldextract",
"requests",
"DownloadKit",
- "FlowViewer"
+ "FlowViewer",
+ "pychrome"
],
classifiers=[
"Programming Language :: Python :: 3.6",