修复页面跳转后状态丢失的问题,待进一步测试

This commit is contained in:
g1879 2022-11-14 00:24:17 +08:00
parent 3be47a80ca
commit e1f3b729e6
11 changed files with 127 additions and 118 deletions

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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路径

View File

@ -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:

View File

@ -42,6 +42,8 @@ DrissionPage即 driver 和 session 组合而成的 page。
- 可以对整个网页截图包括视口外的部分90以上版本浏览器支持
- 对 Linux 提供良好支持
新版是自己实现的功能,开发不会受太多限制,以后将主要对`WebPage`进行更新。
3.0 版已经发布,目前正在测试,欢迎试用并提出意见,让我做得更好。

View File

@ -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使用体验一致。
- 人性化设计,集成众多实用功能,大大降低开发工作量。

View File

@ -1,8 +1,9 @@
# DrissionPage
以页面为单位整合 selenium 和 requests封装了常用操作。
DrissionPage 是一个 Web 自动化框架。
以页面为单位整合浏览器和 requests封装了常用操作。
极大地简化了代码,易于使用,并可实现两种模式的无缝切换。
可兼顾 selenium 的易用性和 requests 的高性能。
可兼顾浏览器的易用性和 requests 的高性能。
如果本库对你有所帮助,请给予支持!持续维护中。

View File

@ -1,5 +1,5 @@
本节讲解 DrissionPage 的一些基本概念。了解它大概的构成和运作原理。
默认读者对网络协议、html、selenium、requests 等有基本认识。
默认读者对网络协议、html、网页自动化、requests 等有基本认识。
# 网页自动化
@ -12,8 +12,10 @@
后者使用浏览器模拟人的行为,如 selenium。写起来简单得多免去复杂的分析过程但速度非常慢占用资源巨大。
鉴于此DrissionPage 以页面为单位将两者整合,对 selenium 和 requests 进行了重新封装,实现两种模式的互通,并加入常用的页面和元素控制功能,可大幅降低开发难度和代码量。
selenium 用于操作浏览器的对象叫 Driverrequests 用于管理连接的对象叫 SessionDrission 就是它们两者的合体。
鉴于此DrissionPage 以页面为单位将两者整合,对 chromium 协议 和 requests 进行了重新封装,实现两种模式的互通,并加入常用的页面和元素控制功能,可大幅降低开发难度和代码量。
用于操作浏览器的对象叫 Driverrequests 用于管理连接的对象叫 SessionDrission 就是它们两者的合体。
在旧版本,本库是通过对 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** <br>当须要打包程序时,必须把配置写到代码里,否则会报错。
?> **Tips** <br>当须要打包程序时,必须把配置写到代码里,或打包后手动复制配置文件到运行路径,否则会报错。

View File

@ -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 文件(后面的章节介绍)
!> **注意:** <br>这里介绍的方法只适用于 Windows 系统和 Chrome 浏览器,使用其它系统或浏览器请查看 [使用其它系统或浏览器](使用方法\使用其它系统或浏览器.md) 章节。<br>如果您有已打开的 Chrome
浏览器,请先关闭,否则会造成冲突。后面在 [创建页面对象](使用方法\使创建页面对象.md) 章节再介绍多 Chrome 浏览器共存的方法。
默认情况下,如果系统内安装了 Chrome 浏览器,或在系统路径中指定了 chrome 执行文件DrissionPage 就可以使用了。
## 自动配置方式
当然也可指定其它浏览器或启动路径:
身为自动化工具DrissionPage 拥有自动识别电脑安装的 Chrome 版本并自动下载对应 chromedriver 功能。这时只要直接创建页面对象即可正常使用。
第一次运行后,程序会记录 Chrome 和 driver 路径到配置文件,以后直接调用。
!> **注意:**<br>这段代码只用于设置配置文件中的路径信息,**运行一次即可**,勿写到正式程序里 。
```python
page = MixPage()
page.get('https://www.baidu.com')
```
## 手动配置方式
!> **注意:** <br>这段代码只用于设置配置文件中的路径信息,**运行一次即可**,勿写到正式程序里 。
有些版本的 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 的工作方式。
## 爬取新冠排行榜
!> **注意:**<br>如果您有已打开的 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 模式爬取和解析。
该网址为外网网址,连接可能稍慢。
![](https://gitee.com/g1879/DrissionPage-demos/raw/master/pics/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20211231225026.jpg)
```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 文件,就不能使用默认配置文件记录配置,具体方法请查看“使用方法->打包程序”章节。

View File

@ -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",