From 6b9f0c42df6b9b824f7ed685a554d0c1e5fcaff7 Mon Sep 17 00:00:00 2001 From: g1879 Date: Wed, 1 Dec 2021 17:43:11 +0800 Subject: [PATCH] =?UTF-8?q?2.0.0=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= =?UTF-8?q?=EF=BC=9Bs=E6=A8=A1=E5=BC=8F=E7=9A=84ele()=E5=92=8Celes()?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=B7=BB=E5=8A=A0timeout=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=9B=E4=B8=80=E4=BA=9B=E5=B0=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/base.py | 16 +--- DrissionPage/common.py | 2 +- DrissionPage/driver_element.py | 11 +-- DrissionPage/mix_page.py | 4 +- DrissionPage/session_element.py | 6 +- DrissionPage/session_page.py | 14 ++-- DrissionPage/shadow_root_element.py | 6 -- README.md | 112 ++++++++++++++-------------- 8 files changed, 79 insertions(+), 92 deletions(-) diff --git a/DrissionPage/base.py b/DrissionPage/base.py index fccfa45..0c9b5d5 100644 --- a/DrissionPage/base.py +++ b/DrissionPage/base.py @@ -20,11 +20,11 @@ class BaseParser(object): def __call__(self, loc_or_str): return self.ele(loc_or_str) - def ele(self, loc_or_ele): - return self._ele(loc_or_ele, True) + def ele(self, loc_or_ele, timeout=None): + return self._ele(loc_or_ele, timeout, True) - def eles(self, loc_or_str: Union[Tuple[str, str], str]): - return self._ele(loc_or_str, False) + def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None): + return self._ele(loc_or_str, timeout, False) @property def html(self) -> str: @@ -70,10 +70,6 @@ class BaseElement(BaseParser): def tag(self): return - # @property - # def html(self): - # return - @property def parent(self): return @@ -265,10 +261,6 @@ class BasePage(BaseParser): def url(self): return - # @property - # def html(self): - # return - @property def json(self): return diff --git a/DrissionPage/common.py b/DrissionPage/common.py index 43bc481..bf8188b 100644 --- a/DrissionPage/common.py +++ b/DrissionPage/common.py @@ -51,7 +51,7 @@ def get_ele_txt(e) -> str: str_list.append(el) else: - if sub('[ \n]', '', el) != '': # 字符除了回车和空格还有其它内容 + if sub('[ \n\t]', '', el) != '': # 字符除了回车和空格还有其它内容 txt = el if not pre: txt = txt.replace('\n', ' ').strip(' ') diff --git a/DrissionPage/driver_element.py b/DrissionPage/driver_element.py index 97c9001..bd56fc6 100644 --- a/DrissionPage/driver_element.py +++ b/DrissionPage/driver_element.py @@ -81,7 +81,6 @@ class DriverElement(DrissionElement): def text(self) -> str: """返回元素内所有文本""" return get_ele_txt(make_session_ele(self.raw_html)) - # return get_ele_txt(self) @property def raw_text(self) -> str: @@ -106,7 +105,6 @@ class DriverElement(DrissionElement): :param attr: 属性名 :return: 属性值文本 """ - # attr = 'innerText' if attr == 'text' else attr if attr in ('text', 'innerText'): return self.text @@ -343,7 +341,7 @@ class DriverElement(DrissionElement): """ if not insure_input: # 普通输入 if clear: - self.clear() + self.inner_ele.clear() self.inner_ele.send_keys(*vals) @@ -357,8 +355,7 @@ class DriverElement(DrissionElement): t1 = perf_counter() while self.is_valid() and self.attr('value') != full_txt and perf_counter() - t1 <= self.page.timeout: if clear: - self.clear() - + self.inner_ele.clear() self.inner_ele.send_keys(vals) if enter: @@ -378,7 +375,7 @@ class DriverElement(DrissionElement): def clear(self) -> None: """清空元素文本""" - self.inner_ele.clear() + self.input('') def is_selected(self) -> bool: """是否选中""" @@ -861,7 +858,7 @@ def _wait_ele(page_or_ele, if mode.lower() not in ('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 ele_or_driver = page_or_ele.inner_ele else: diff --git a/DrissionPage/mix_page.py b/DrissionPage/mix_page.py index 169c6eb..6674400 100644 --- a/DrissionPage/mix_page.py +++ b/DrissionPage/mix_page.py @@ -132,7 +132,7 @@ class MixPage(SessionPage, DriverPage, BasePage): -> Union[DriverElement, SessionElement, str, List[SessionElement], List[DriverElement]]: """返回第一个符合条件的元素、属性或节点文本 \n :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 - :param timeout: 查找元素超时时间,d模式专用 + :param timeout: 查找元素超时时间 :return: 元素对象或属性、文本节点文本 """ if self._mode == 's': @@ -145,7 +145,7 @@ class MixPage(SessionPage, DriverPage, BasePage): timeout: float = None) -> Union[List[DriverElement], List[SessionElement], List[str]]: """返回页面中所有符合条件的元素、属性或节点文本 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 - :param timeout: 查找元素超时时间,d模式专用 + :param timeout: 查找元素超时时间 :return: 元素对象或属性、文本组成的列表 """ if self._mode == 's': diff --git a/DrissionPage/session_element.py b/DrissionPage/session_element.py index 25385d8..e80c981 100644 --- a/DrissionPage/session_element.py +++ b/DrissionPage/session_element.py @@ -109,16 +109,18 @@ class SessionElement(DrissionElement): else: 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 :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用 :return: SessionElement对象或属性、文本 """ 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 :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用 :return: SessionElement对象或属性、文本组成的列表 """ return self._ele(loc_or_str, single=False) diff --git a/DrissionPage/session_page.py b/DrissionPage/session_page.py index 38a9065..adefd7c 100644 --- a/DrissionPage/session_page.py +++ b/DrissionPage/session_page.py @@ -16,7 +16,7 @@ from requests import Session, Response from tldextract import extract 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 .session_element import SessionElement, make_session_ele @@ -96,17 +96,19 @@ class SessionPage(BasePage): 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]: - """返回页面中符合条件的第一个元素、属性或节点文本 \n + """返回页面中符合条件的第一个元素、属性或节点文本 \n :param loc_or_ele: 元素的定位信息,可以是元素对象,loc元组,或查询字符串 + :param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用 :return: SessionElement对象或属性、文本 """ return self._ele(loc_or_ele) - def eles(self, loc_or_str: Union[Tuple[str, str], str]) -> List[SessionElement]: - """返回页面中所有符合条件的元素、属性或节点文本 \n + def eles(self, loc_or_str: Union[Tuple[str, str], str], timeout=None) -> List[SessionElement]: + """返回页面中所有符合条件的元素、属性或节点文本 \n :param loc_or_str: 元素的定位信息,可以是loc元组,或查询字符串 + :param timeout: 不起实际作用,用于和DriverElement对应,便于无差别调用 :return: SessionElement对象或属性、文本组成的列表 """ return self._ele(loc_or_str, single=False) @@ -300,7 +302,7 @@ class SessionPage(BasePage): 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: diff --git a/DrissionPage/shadow_root_element.py b/DrissionPage/shadow_root_element.py index 476af05..431f12d 100644 --- a/DrissionPage/shadow_root_element.py +++ b/DrissionPage/shadow_root_element.py @@ -10,7 +10,6 @@ from typing import Union, Any, Tuple, List from selenium.webdriver.remote.webelement import WebElement from .base import BaseElement -from .common import format_html from .driver_element import make_driver_ele, DriverElement from .session_element import make_session_ele @@ -41,11 +40,6 @@ class ShadowRootElement(BaseElement): """元素标签名""" return 'shadow-root' - # @property - # def html(self) -> str: - # """内部已转码的html文本""" - # return format_html(self.inner_ele.get_attribute('innerHTML')) - @property def raw_html(self) -> str: """内部没有转码的html文本""" diff --git a/README.md b/README.md index 662a398..bea23e7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# 简洁!易用 !方便! + # 简介 *** @@ -10,65 +12,58 @@ DrissionPage,即 driver 和 session 组合而成的 page。 它用 POM 模式封装了页面元素常用的方法,适合自动化操作功能扩展。 更棒的是,它的使用方式非常简洁和人性化,代码量少,对新手友好。 -**项目地址:** - -- https://github.com/g1879/DrissionPage -- https://gitee.com/g1879/DrissionPage - **示例地址:** [使用DrissionPage的网页自动化及爬虫示例](https://gitee.com/g1879/DrissionPage-demos) -**联系邮箱:** g1879@qq.com +**交流QQ群:** 897838127 **联系邮箱:** g1879@qq.com -**交流QQ群:** 897838127 - -**理念** - -**简洁、易用 、可扩展** - -**背景** +## 背景 requests 爬虫面对要登录的网站时,要分析数据包、JS 源码,构造复杂的请求,往往还要应付验证码、JS 混淆、签名参数等反爬手段,门槛较高。若数据是由 JS 计算生成的,还须重现计算过程,体验不好,开发效率不高。 使用 selenium,可以很大程度上绕过这些坑,但 selenium 效率不高。因此,这个库将 selenium 和 requests 合而为一,不同须要时切换相应模式,并提供一种人性化的使用方法,提高开发和运行效率。 除了合并两者,本库还以网页为单位封装了常用功能,简化了 selenium 的操作和语句,在用于网页自动化操作时,减少考虑细节,专注功能实现,使用更方便。 一切从简,尽量提供简单直接的使用方法,对新手更友好。 -# 特性 +# 特性和亮点 *** -- 以简洁的代码为第一追求。 -- 允许在 selenium 和 requests 间无缝切换,共享 session。 +作者有多年自动化和爬虫经验,踩过无数坑,总结出的经验全写到这个库里了。内置了N多实用功能,对常用功能作了整合和优化。 + +## 特性 + +- 代码高度集成,以简洁的代码为第一追求。 + +- 页面对象可在 selenium 和 requests 模式间任意切换,保留登录状态。 + - 两种模式提供一致的 API,使用体验一致。 -- 人性化的页面元素操作方式,减轻页面分析工作量和编码量。 -- 对常用功能作了整合和优化,更符合实际使用需要。 -- 兼容 selenium 代码,便于项目迁移。 -- 使用 POM 模式封装,便于扩展。 -- 统一的文件下载方法,弥补浏览器下载的不足。 -- 简易的配置方法,摆脱繁琐的浏览器配置。 -# 项目结构 +- 人性化设计,集成众多实用功能,大大降低开发工作量。 -*** +## 亮点 -**结构图** - -![](https://gitee.com/g1879/DrissionPage-demos/raw/master/pics/20201118170751.jpg) - -**Drission 类** - -管理负责与网页通讯的 WebDriver 对象和 Session 对象,相当于驱动器的角色。 - -**MixPage 类** - -MixPage 封装了页面操作的常用功能,它调用 Drission 类中管理的驱动器,对页面进行访问、操作。可在 driver 和 session 模式间切换。切换的时候会自动同步登录状态。 - -**DriverElement 类** - -driver 模式下的页面元素类,可对元素进行点击、输入文本、修改属性、运行 js 等操作,也可在其下级搜索后代元素。 - -**SessionElement 类** - -session 模式下的页面元素类,可获取元素属性值,也可在其下级搜索后代元素。 +- 每次运行程序可以反复使用已经打开的浏览器。如手动设置网页到某个状态,再用程序接管,或手动处理登录,再用程序爬内容。无须每次运行从头启动浏览器,超级方便。 +- 极简单但强大的元素查找功能,支持链式操作,代码极其简洁。 +- 使用 ini 文件保存常用配置,自动调用,也提供便捷的设置api,远离繁杂的配置项。 +- 强大的下载工具,操作浏览器时也能享受快捷可靠的下载功能。 +- 下载工具支持多种方式处理文件名冲突、自动创建目标路径、断链重试等。 +- 访问网址带自动重试功能,可设置间隔和超时时间。 +- 访问网页能自动识别编码,无须手动设置。 +- 链接参数默认自动生成 Host 和 Referer 属性。 +- 可随时直接隐藏或显示浏览器进程窗口,非 headless 或最小化。 +- 可自动下载合适版本的 chromedriver,免去麻烦的配置。 +- d 模式查找元素内置等待,可任意设置全局等待时间或单次查找等待时间。 +- 点击元素集成 js 点击方式,一个参数即可切换点击方式。 +- 点击支持失败重试,可用于保证点击成功、判读网页遮罩层是否消失等。 +- 输入文本能自动判断是否成功并重试,避免某些情况下输入或清空失效的情况。 +- d 模式下支持全功能的 xpath,可直接获取元素的某个属性,selenium 原生无此功能。 +- 支持直接获取 shadow-root,和普通元素一样操作其下的元素。 +- 支持直接获取 after 和 before 伪元素的内容。 +- 可以在元素下直接使用 > 以 css selector 方式获取当前元素直接子元素。原生不支持这种写法。 +- 可简单地使用 lxml 来解析 d 模式的页面或元素,爬取复杂页面数据时速度大幅提高。 +- 输出的数据均已转码及处理基本排版,减少重复劳动。 +- 可方便地与 selenium 或 requests 原生代码对接,便于项目迁移。 +- 使用 POM 模式封装,可直接用于测试,便于扩展。 +- 还有很多这里不一一列举………… # 简单演示 @@ -158,9 +153,10 @@ text = element.after shadow_element = webdriver.execute_script('return arguments[0].shadowRoot', element) # 使用 DrissionPage: -shadow_element = element.shadow_root -# 或 shadow_element = element.sr + +# 在 shadow_root 下可继续执行查找,获取普通元素 +ele = shadow_element.ele('tag:div') ``` - 用 xpath 直接获取属性或文本节点(返回文本) @@ -174,6 +170,18 @@ class_name = element('xpath://div[@id="div_id"]/@class') text = element('xpath://div[@id="div_id"]/text()[2]') ``` +- 随时让浏览器窗口消失和显示 + +```python +# selenium无此功能 + +# 使用 DrissionPage +page.hide_browser() # 让浏览器窗口消失 +page.show_browser() # 重新显示浏览器窗口 +``` + +注:本功能只支持 Windows,且须设置了 debugger_address 参数时才能生效 + **与 requests 代码对比** 以下代码实现一模一样的功能,对比两者的代码量: @@ -185,7 +193,7 @@ url = 'https://baike.baidu.com/item/python' # 使用 requests: 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) html = etree.HTML(response.text) element = html.xpath('//h1')[0] @@ -212,7 +220,7 @@ with open(f'{save_path}\\img.png', 'wb') as fd: fd.write(chunk) # 使用 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 ``` -**获取并打印元素属性** +**获取并显示元素属性** ```python # 接上段代码 @@ -262,14 +270,6 @@ Git 命令学习 https://oschina.gitee.io/learn-git-branching/ Git 命令学习 ``` -**下载文件** - -```python -url = 'https://www.baidu.com/img/flexible/logo/pc/result.png' -save_path = r'C:\download' -page.download(url, save_path) -``` - # 使用方法 ***