2.0.0基本完成;s模式的ele()和eles()重新添加timeout参数;一些小调整

This commit is contained in:
g1879 2021-12-01 17:43:11 +08:00
parent af69ca22c9
commit 6b9f0c42df
8 changed files with 79 additions and 92 deletions

View File

@ -20,11 +20,11 @@ class BaseParser(object):
def __call__(self, loc_or_str): def __call__(self, loc_or_str):
return self.ele(loc_or_str) return self.ele(loc_or_str)
def ele(self, loc_or_ele): def ele(self, loc_or_ele, timeout=None):
return self._ele(loc_or_ele, True) return self._ele(loc_or_ele, timeout, True)
def eles(self, loc_or_str: Union[Tuple[str, str], str]): def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None):
return self._ele(loc_or_str, False) return self._ele(loc_or_str, timeout, False)
@property @property
def html(self) -> str: def html(self) -> str:
@ -70,10 +70,6 @@ class BaseElement(BaseParser):
def tag(self): def tag(self):
return return
# @property
# def html(self):
# return
@property @property
def parent(self): def parent(self):
return return
@ -265,10 +261,6 @@ class BasePage(BaseParser):
def url(self): def url(self):
return return
# @property
# def html(self):
# return
@property @property
def json(self): def json(self):
return return

View File

@ -51,7 +51,7 @@ def get_ele_txt(e) -> str:
str_list.append(el) str_list.append(el)
else: else:
if sub('[ \n]', '', el) != '': # 字符除了回车和空格还有其它内容 if sub('[ \n\t]', '', el) != '': # 字符除了回车和空格还有其它内容
txt = el txt = el
if not pre: if not pre:
txt = txt.replace('\n', ' ').strip(' ') txt = txt.replace('\n', ' ').strip(' ')

View File

@ -81,7 +81,6 @@ class DriverElement(DrissionElement):
def text(self) -> str: def text(self) -> str:
"""返回元素内所有文本""" """返回元素内所有文本"""
return get_ele_txt(make_session_ele(self.raw_html)) return get_ele_txt(make_session_ele(self.raw_html))
# return get_ele_txt(self)
@property @property
def raw_text(self) -> str: def raw_text(self) -> str:
@ -106,7 +105,6 @@ class DriverElement(DrissionElement):
:param attr: 属性名 :param attr: 属性名
:return: 属性值文本 :return: 属性值文本
""" """
# attr = 'innerText' if attr == 'text' else attr
if attr in ('text', 'innerText'): if attr in ('text', 'innerText'):
return self.text return self.text
@ -343,7 +341,7 @@ class DriverElement(DrissionElement):
""" """
if not insure_input: # 普通输入 if not insure_input: # 普通输入
if clear: if clear:
self.clear() self.inner_ele.clear()
self.inner_ele.send_keys(*vals) self.inner_ele.send_keys(*vals)
@ -357,8 +355,7 @@ class DriverElement(DrissionElement):
t1 = perf_counter() t1 = perf_counter()
while self.is_valid() and self.attr('value') != full_txt and perf_counter() - t1 <= self.page.timeout: while self.is_valid() and self.attr('value') != full_txt and perf_counter() - t1 <= self.page.timeout:
if clear: if clear:
self.clear() self.inner_ele.clear()
self.inner_ele.send_keys(vals) self.inner_ele.send_keys(vals)
if enter: if enter:
@ -378,7 +375,7 @@ class DriverElement(DrissionElement):
def clear(self) -> None: def clear(self) -> None:
"""清空元素文本""" """清空元素文本"""
self.inner_ele.clear() self.input('')
def is_selected(self) -> bool: def is_selected(self) -> bool:
"""是否选中""" """是否选中"""
@ -861,7 +858,7 @@ def _wait_ele(page_or_ele,
if mode.lower() not in ('del', 'display', 'hidden'): if mode.lower() not in ('del', 'display', 'hidden'):
raise ValueError('mode参数只能是"del""display""hidden"') raise ValueError('mode参数只能是"del""display""hidden"')
if isinstance(page_or_ele, DrissionElement): # TODO: 是否要改为 BaseElement if isinstance(page_or_ele, BaseElement):
page = page_or_ele.page page = page_or_ele.page
ele_or_driver = page_or_ele.inner_ele ele_or_driver = page_or_ele.inner_ele
else: else:

View File

@ -132,7 +132,7 @@ class MixPage(SessionPage, DriverPage, BasePage):
-> Union[DriverElement, SessionElement, str, List[SessionElement], List[DriverElement]]: -> Union[DriverElement, SessionElement, str, List[SessionElement], List[DriverElement]]:
"""返回第一个符合条件的元素、属性或节点文本 \n """返回第一个符合条件的元素、属性或节点文本 \n
:param loc_or_ele: 元素的定位信息可以是元素对象loc元组或查询字符串 :param loc_or_ele: 元素的定位信息可以是元素对象loc元组或查询字符串
:param timeout: 查找元素超时时间d模式专用 :param timeout: 查找元素超时时间
:return: 元素对象或属性文本节点文本 :return: 元素对象或属性文本节点文本
""" """
if self._mode == 's': if self._mode == 's':
@ -145,7 +145,7 @@ class MixPage(SessionPage, DriverPage, BasePage):
timeout: float = None) -> Union[List[DriverElement], List[SessionElement], List[str]]: timeout: float = None) -> Union[List[DriverElement], List[SessionElement], List[str]]:
"""返回页面中所有符合条件的元素、属性或节点文本 \n """返回页面中所有符合条件的元素、属性或节点文本 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串 :param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param timeout: 查找元素超时时间d模式专用 :param timeout: 查找元素超时时间
:return: 元素对象或属性文本组成的列表 :return: 元素对象或属性文本组成的列表
""" """
if self._mode == 's': if self._mode == 's':

View File

@ -109,16 +109,18 @@ class SessionElement(DrissionElement):
else: else:
return self.inner_ele.get(attr) return self.inner_ele.get(attr)
def ele(self, loc_or_str: Union[Tuple[str, str], str]): def ele(self, loc_or_str: Union[Tuple[str, str], str], timeout=None):
"""返回当前元素下级符合条件的第一个元素、属性或节点文本 \n """返回当前元素下级符合条件的第一个元素、属性或节点文本 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串 :param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param timeout: 不起实际作用用于和DriverElement对应便于无差别调用
:return: SessionElement对象或属性文本 :return: SessionElement对象或属性文本
""" """
return self._ele(loc_or_str) return self._ele(loc_or_str)
def eles(self, loc_or_str: Union[Tuple[str, str], str]): def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None):
"""返回当前元素下级所有符合条件的子元素、属性或节点文本 \n """返回当前元素下级所有符合条件的子元素、属性或节点文本 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串 :param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param timeout: 不起实际作用用于和DriverElement对应便于无差别调用
:return: SessionElement对象或属性文本组成的列表 :return: SessionElement对象或属性文本组成的列表
""" """
return self._ele(loc_or_str, single=False) return self._ele(loc_or_str, single=False)

View File

@ -16,7 +16,7 @@ from requests import Session, Response
from tldextract import extract from tldextract import extract
from .base import BasePage from .base import BasePage
from .common import get_usable_path, format_html, make_valid_name from .common import get_usable_path, make_valid_name
from .config import _cookie_to_dict from .config import _cookie_to_dict
from .session_element import SessionElement, make_session_ele from .session_element import SessionElement, make_session_ele
@ -96,17 +96,19 @@ class SessionPage(BasePage):
return self._url_available return self._url_available
def ele(self, loc_or_ele: Union[Tuple[str, str], str, SessionElement]) \ def ele(self, loc_or_ele: Union[Tuple[str, str], str, SessionElement], timeout=None) \
-> Union[SessionElement, List[SessionElement], str, None]: -> Union[SessionElement, List[SessionElement], str, None]:
"""返回页面中符合条件的第一个元素、属性或节点文本 \n """返回页面中符合条件的第一个元素、属性或节点文本 \n
:param loc_or_ele: 元素的定位信息可以是元素对象loc元组或查询字符串 :param loc_or_ele: 元素的定位信息可以是元素对象loc元组或查询字符串
:param timeout: 不起实际作用用于和DriverElement对应便于无差别调用
:return: SessionElement对象或属性文本 :return: SessionElement对象或属性文本
""" """
return self._ele(loc_or_ele) return self._ele(loc_or_ele)
def eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[SessionElement]: def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None) -> List[SessionElement]:
"""返回页面中所有符合条件的元素、属性或节点文本 \n """返回页面中所有符合条件的元素、属性或节点文本 \n
:param loc_or_str: 元素的定位信息可以是loc元组或查询字符串 :param loc_or_str: 元素的定位信息可以是loc元组或查询字符串
:param timeout: 不起实际作用用于和DriverElement对应便于无差别调用
:return: SessionElement对象或属性文本组成的列表 :return: SessionElement对象或属性文本组成的列表
""" """
return self._ele(loc_or_str, single=False) return self._ele(loc_or_str, single=False)
@ -300,7 +302,7 @@ class SessionPage(BasePage):
return False, f'Status code: {r.status_code}.' return False, f'Status code: {r.status_code}.'
# -------------------获取文件名------------------- # -------------------获取文件名-------------------
file_name = _get_download_file_name(file_url, r.headers) file_name = _get_download_file_name(file_url, r)
# -------------------重命名,不改变扩展名------------------- # -------------------重命名,不改变扩展名-------------------
if rename: if rename:

View File

@ -10,7 +10,6 @@ from typing import Union, Any, Tuple, List
from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.remote.webelement import WebElement
from .base import BaseElement from .base import BaseElement
from .common import format_html
from .driver_element import make_driver_ele, DriverElement from .driver_element import make_driver_ele, DriverElement
from .session_element import make_session_ele from .session_element import make_session_ele
@ -41,11 +40,6 @@ class ShadowRootElement(BaseElement):
"""元素标签名""" """元素标签名"""
return 'shadow-root' return 'shadow-root'
# @property
# def html(self) -> str:
# """内部已转码的html文本"""
# return format_html(self.inner_ele.get_attribute('innerHTML'))
@property @property
def raw_html(self) -> str: def raw_html(self) -> str:
"""内部没有转码的html文本""" """内部没有转码的html文本"""

112
README.md
View File

@ -1,3 +1,5 @@
# 简洁!易用 !方便!
# 简介 # 简介
*** ***
@ -10,65 +12,58 @@ DrissionPage即 driver 和 session 组合而成的 page。
它用 POM 模式封装了页面元素常用的方法,适合自动化操作功能扩展。 它用 POM 模式封装了页面元素常用的方法,适合自动化操作功能扩展。
更棒的是,它的使用方式非常简洁和人性化,代码量少,对新手友好。 更棒的是,它的使用方式非常简洁和人性化,代码量少,对新手友好。
**项目地址:**
- https://github.com/g1879/DrissionPage
- https://gitee.com/g1879/DrissionPage
**示例地址:** [使用DrissionPage的网页自动化及爬虫示例](https://gitee.com/g1879/DrissionPage-demos) **示例地址:** [使用DrissionPage的网页自动化及爬虫示例](https://gitee.com/g1879/DrissionPage-demos)
**联系邮箱:** g1879@qq.com **交流QQ群** 897838127 **联系邮箱:** g1879@qq.com
**交流QQ群** 897838127 ## 背景
**理念**
**简洁、易用 、可扩展**
**背景**
requests 爬虫面对要登录的网站时要分析数据包、JS 源码构造复杂的请求往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高。若数据是由 JS 计算生成的,还须重现计算过程,体验不好,开发效率不高。 requests 爬虫面对要登录的网站时要分析数据包、JS 源码构造复杂的请求往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高。若数据是由 JS 计算生成的,还须重现计算过程,体验不好,开发效率不高。
使用 selenium可以很大程度上绕过这些坑但 selenium 效率不高。因此,这个库将 selenium 和 requests 合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 使用 selenium可以很大程度上绕过这些坑但 selenium 效率不高。因此,这个库将 selenium 和 requests 合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。
除了合并两者,本库还以网页为单位封装了常用功能,简化了 selenium 的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 除了合并两者,本库还以网页为单位封装了常用功能,简化了 selenium 的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。
一切从简,尽量提供简单直接的使用方法,对新手更友好。 一切从简,尽量提供简单直接的使用方法,对新手更友好。
# 特性 # 特性和亮点
*** ***
- 以简洁的代码为第一追求。 作者有多年自动化和爬虫经验踩过无数坑总结出的经验全写到这个库里了。内置了N多实用功能对常用功能作了整合和优化。
- 允许在 selenium 和 requests 间无缝切换,共享 session。
## 特性
- 代码高度集成,以简洁的代码为第一追求。
- 页面对象可在 selenium 和 requests 模式间任意切换,保留登录状态。
- 两种模式提供一致的 API使用体验一致。 - 两种模式提供一致的 API使用体验一致。
- 人性化的页面元素操作方式,减轻页面分析工作量和编码量。
- 对常用功能作了整合和优化,更符合实际使用需要。
- 兼容 selenium 代码,便于项目迁移。
- 使用 POM 模式封装,便于扩展。
- 统一的文件下载方法,弥补浏览器下载的不足。
- 简易的配置方法,摆脱繁琐的浏览器配置。
# 项目结构 - 人性化设计,集成众多实用功能,大大降低开发工作量。
*** ## 亮点
**结构图** - 每次运行程序可以反复使用已经打开的浏览器。如手动设置网页到某个状态,再用程序接管,或手动处理登录,再用程序爬内容。无须每次运行从头启动浏览器,超级方便。
- 极简单但强大的元素查找功能,支持链式操作,代码极其简洁。
![](https://gitee.com/g1879/DrissionPage-demos/raw/master/pics/20201118170751.jpg) - 使用 ini 文件保存常用配置自动调用也提供便捷的设置api远离繁杂的配置项。
- 强大的下载工具,操作浏览器时也能享受快捷可靠的下载功能。
**Drission 类** - 下载工具支持多种方式处理文件名冲突、自动创建目标路径、断链重试等。
- 访问网址带自动重试功能,可设置间隔和超时时间。
管理负责与网页通讯的 WebDriver 对象和 Session 对象,相当于驱动器的角色。 - 访问网页能自动识别编码,无须手动设置。
- 链接参数默认自动生成 Host 和 Referer 属性。
**MixPage 类** - 可随时直接隐藏或显示浏览器进程窗口,非 headless 或最小化。
- 可自动下载合适版本的 chromedriver免去麻烦的配置。
MixPage 封装了页面操作的常用功能,它调用 Drission 类中管理的驱动器,对页面进行访问、操作。可在 driver 和 session 模式间切换。切换的时候会自动同步登录状态。 - d 模式查找元素内置等待,可任意设置全局等待时间或单次查找等待时间。
- 点击元素集成 js 点击方式,一个参数即可切换点击方式。
**DriverElement 类** - 点击支持失败重试,可用于保证点击成功、判读网页遮罩层是否消失等。
- 输入文本能自动判断是否成功并重试,避免某些情况下输入或清空失效的情况。
driver 模式下的页面元素类,可对元素进行点击、输入文本、修改属性、运行 js 等操作,也可在其下级搜索后代元素。 - d 模式下支持全功能的 xpath可直接获取元素的某个属性selenium 原生无此功能。
- 支持直接获取 shadow-root和普通元素一样操作其下的元素。
**SessionElement 类** - 支持直接获取 after 和 before 伪元素的内容。
- 可以在元素下直接使用 > 以 css selector 方式获取当前元素直接子元素。原生不支持这种写法。
session 模式下的页面元素类,可获取元素属性值,也可在其下级搜索后代元素。 - 可简单地使用 lxml 来解析 d 模式的页面或元素,爬取复杂页面数据时速度大幅提高。
- 输出的数据均已转码及处理基本排版,减少重复劳动。
- 可方便地与 selenium 或 requests 原生代码对接,便于项目迁移。
- 使用 POM 模式封装,可直接用于测试,便于扩展。
- 还有很多这里不一一列举…………
# 简单演示 # 简单演示
@ -158,9 +153,10 @@ text = element.after
shadow_element = webdriver.execute_script('return arguments[0].shadowRoot', element) shadow_element = webdriver.execute_script('return arguments[0].shadowRoot', element)
# 使用 DrissionPage # 使用 DrissionPage
shadow_element = element.shadow_root
# 或
shadow_element = element.sr shadow_element = element.sr
# 在 shadow_root 下可继续执行查找,获取普通元素
ele = shadow_element.ele('tag:div')
``` ```
- 用 xpath 直接获取属性或文本节点(返回文本) - 用 xpath 直接获取属性或文本节点(返回文本)
@ -174,6 +170,18 @@ class_name = element('xpath://div[@id="div_id"]/@class')
text = element('xpath://div[@id="div_id"]/text()[2]') text = element('xpath://div[@id="div_id"]/text()[2]')
``` ```
- 随时让浏览器窗口消失和显示
```python
# selenium无此功能
# 使用 DrissionPage
page.hide_browser() # 让浏览器窗口消失
page.show_browser() # 重新显示浏览器窗口
```
注:本功能只支持 Windows且须设置了 debugger_address 参数时才能生效
**与 requests 代码对比** **与 requests 代码对比**
以下代码实现一模一样的功能,对比两者的代码量: 以下代码实现一模一样的功能,对比两者的代码量:
@ -185,7 +193,7 @@ url = 'https://baike.baidu.com/item/python'
# 使用 requests # 使用 requests
from lxml import etree from lxml import etree
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'} headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'}
response = requests.get(url, headers = headers) response = requests.get(url, headers = headers)
html = etree.HTML(response.text) html = etree.HTML(response.text)
element = html.xpath('//h1')[0] element = html.xpath('//h1')[0]
@ -212,7 +220,7 @@ with open(f'{save_path}\\img.png', 'wb') as fd:
fd.write(chunk) fd.write(chunk)
# 使用 DrissionPage # 使用 DrissionPage
page.download(url, save_path, 'img') # 支持重命名,处理文件名冲突 page.download(url, save_path, 'img') # 支持重命名,处理文件名冲突,自动创建目标文件夹
``` ```
**模式切换** **模式切换**
@ -237,7 +245,7 @@ print('登录后title', page.title, '\n') # 登录后 session 模式的输
登录后title 个人资料 - 码云 Gitee.com 登录后title 个人资料 - 码云 Gitee.com
``` ```
**获取并打印元素属性** **获取并显示元素属性**
```python ```python
# 接上段代码 # 接上段代码
@ -262,14 +270,6 @@ Git 命令学习 https://oschina.gitee.io/learn-git-branching/
Git 命令学习 Git 命令学习
``` ```
**下载文件**
```python
url = 'https://www.baidu.com/img/flexible/logo/pc/result.png'
save_path = r'C:\download'
page.download(url, save_path)
```
# 使用方法 # 使用方法
*** ***