1. base 文件代码
这篇是来自DrissionPage/DrissionPage/_base/base.py,当然各位也可以看见还有一个同名但是不同后缀的文件,DrissionPage/DrissionPage/_base/base.pyi
pyi文件核心作用是:为 Python 代码提供静态类型信息,但不包含任何运行时逻辑
简单说就是pyi是对py源码的描述,接口的意思,两个文件类内容是一样的,都是在形容一个东西
我们先从开头引入的东西进行分析,我就直接写在代码段里面了,没必要再单独拿出来写
from abc import abstractmethod # 抽象方法装饰器,用于定义抽象类
from copy import copy # 对象浅拷贝
from pathlib import Path # 面向对象的文件路径操作
from re import sub # 正则表达式替换
from urllib.parse import quote # URL编码
from DownloadKit import DownloadKit # 下载管理器,支持多线程/断点续传
from requests import Session # HTTP会话,保持cookies和连接
from .._configs.session_options import SessionOptions # 会话配置选项
from .._elements.none_element import NoneElement # 空元素占位符
from .._functions.elements import get_frame, get_eles # 获取框架和元素
from .._functions.locator import get_loc # 获取定位器
from .._functions.settings import Settings as _S # 设置管理
from .._functions.web import format_html # HTML格式化
from ..errors import ElementNotFoundError, LocatorError然后就开始我们代码了,首先这个文件里面有4个大类
class BaseParser(object):
pass
class BaseElement(BaseParser):
pass
class DrissionElement(BaseElement):
pass
class BasePage(BaseParser):
pass可以看见BaseParser是最基本的大类,下面的类都基于它进行逻辑编写,那么我们就先开始分析它,代码解析还是写在代码段里面
class BaseParser(object):
"""
网页元素解析器基类
定义统一的元素查找接口,具体实现由子类完成
支持多种查找方式和链式调用
"""
def __call__(self, locator):
"""
使解析器对象可以像函数一样被调用
parser = BaseParser()
element = parser('#element_id')
"""
return self.ele(locator)
def ele(self, locator, index=1, timeout=None):
"""
查找单个元素
Args:
locator: 定位器(XPath、CSS选择器、文本等)
index: 返回第几个匹配的元素(从1开始)
timeout: 超时时间,None表示使用默认超时
Returns:
找到的元素对象或NoneElement
"""
return self._ele(locator, timeout, index=index, method="ele()")
def eles(self, locator, timeout=None):
return self._ele(locator, timeout, index=None)
def find(self, locators, any_one=True, first_ele=True, timeout=None):
"""
使用多个定位器查找元素
Args:
locators: 定位器列表,如['#id1', '//div[@class="test"]']
any_one: 是否只要找到任意一个即可
first_ele: 当any_one=True时,是否只返回第一个匹配的元素
timeout: 超时时间
Returns:
根据参数不同返回不同的结果:
- any_one=True: 返回 (定位器, 元素) 元组 或 (None, None)
- any_one=False: 返回字典 {定位器: [元素列表]}
"""
if "Session" in self._type:
timeout = 0
if timeout is None:
timeout = self.timeout
r = get_eles(locators, self, any_one, first_ele, timeout)
if any_one:
for ele in r:
if r[ele]:
return ele, r[ele]
return None, None
return r
# ----------------以下属性或方法待后代实现----------------
"""
下面这个到实现的地方具体描述
"""
@property
def html(self):
return ""
def s_ele(self, locator=None):
pass
def s_eles(self, locator):
pass
def _ele(self, locator, timeout=None, index=1, raise_err=None, method=None):
pass
def _find_elements(self, locator, timeout, index=1, relative=False, raise_err=None):
pass接下来就是BaseElement各元素类的基类
class BaseElement(BaseParser):
def __init__(self, owner=None):
"""
初始化元素对象
Args:
owner: 元素的拥有者(通常是父元素或页面对象)
"""
self.owner = owner # 如果元素有 owner,就使用 owner 的超时设置;如果没有,就用默认值10秒。这符合 "子元素继承父元素配置" 的设计模式
self._type = "BaseElement" # 标识元素类型
# 这个方法可以根据网站文档上来看
# 此方法用于获取页面中一个<frame>或<iframe>对象。
def get_frame(self, loc_or_ind, timeout=None):
"""
定位并切换到iframe/frame框架
Args:
loc_or_ind: 框架的定位器或索引
- int: 索引号(从0开始)
- str: CSS选择器、XPath等
- tuple: (by, value) 元组形式
timeout: 超时时间
Returns:
Frame对象或Page对象
Raises:
ValueError: 当参数类型不正确时
"""
if not isinstance(loc_or_ind, (int, str, tuple)):
raise ValueError(
_S._lang.join(
_S._lang.INCORRECT_TYPE_,
"loc_or_ind",
ALLOW_TYPE=_S._lang.LOC_OR_IND,
CURR_VAL=loc_or_ind,
)
)
# 调用外部函数处理iframe,这个函数我们在下面具体讲解
return get_frame(self, loc_ind_ele=loc_or_ind, timeout=timeout)
def _ele(
self,
locator,
timeout=None,
index=1,
relative=False,
raise_err=None,
method=None,
):
"""
查找元素的统一实现
Args:
locator: 定位器
timeout: 超时时间
index: 元素索引
relative: 是否相对查找(相对于当前元素)
raise_err: 是否抛出异常
method: 调用方法名
Returns:
元素对象、元素列表、数值或NoneElement
"""
# 如果locator已经是元素对象,如果这个元素已经有了就说明已经被处理过了,直接就可以返回了
if hasattr(locator, "_type"):
return locator
if timeout is None:
timeout = self.timeout
# 调用具体实现查找元素
r = self._find_elements(
locator,
timeout=timeout,
index=index,
relative=relative,
raise_err=raise_err,
)
# 处理查找结果
if r or isinstance(r, (list, float, int)):
return r
# 未找到元素的处理
if raise_err is True or (_S.raise_when_ele_not_found and raise_err is None):
raise ElementNotFoundError(
METHOD=method,
ARGS={"locator": locator, "index": index, "timeout": timeout},
)
# 返回NoneElement并记录查找信息
r.method = method
r.args = {"locator": locator, "index": index, "timeout": timeout}
return r
@property
def timeout(self):
return self.owner.timeout if self.owner else 10
@property
def child_count(self):
"""
获取子元素数量
使用XPath的count函数计算
返回整数类型
"""
return int(self._ele("xpath:count(./*)"))
# ----------------以下属性或方法由后代实现----------------
# 下面的再等具体实现时候描述
@property
def tag(self):
return
def parent(self, level_or_loc=1):
pass
def next(self, index=1):
pass
def nexts(self):
pass这里我们插入一个函数
def get_frame(owner, loc_ind_ele, timeout=None):
"""
定位页面中的iframe/frame框架
Args:
owner: 拥有者对象(页面或元素),用于查找框架,执行查找的上下文
loc_ind_ele: 框架的定位标识,支持多种格式,xpath,css,re
timeout: 超时时间
Returns:
ChromiumFrame对象或NoneElement
"""
if isinstance(loc_ind_ele, str):
if is_str_loc(loc_ind_ele): # 这个可以在下面看到
xpath = loc_ind_ele
else:
# 转换为XPath:查找name或id匹配的iframe/frame
xpath = f'xpath://*[(name()="iframe" or name()="frame") and (@name="{loc_ind_ele}" or @id="{loc_ind_ele}")]'
ele = owner._ele(xpath, timeout=timeout)
# 页面上有一个div的id是"contentFrame",但不是iframe
if ele and ele._type != 'ChromiumFrame': # 这个后面会定义,在DrissionPage/DrissionPage/_pages/chromium_frame.py
raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)
r = ele
# 直接使用元组形式定位器
elif isinstance(loc_ind_ele, tuple):
ele = owner._ele(loc_ind_ele, timeout=timeout)
if ele and ele._type != 'ChromiumFrame':
raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)
r = ele
# 查找所有iframe和frame,取第n个
elif isinstance(loc_ind_ele, int):
"""
@|tag():iframe:查找所有 iframe 标签
@|tag():frame:查找所有 frame 标签
@|:框架自定义的"或"运算符,合并两个查找结果
"""
ele = owner._ele('@|tag():iframe@|tag():frame', timeout=timeout, index=loc_ind_ele)
if ele and ele._type != 'ChromiumFrame':
raise LocatorError(_S._lang.LOC_NOT_FOR_FRAME, LOCATOR=loc_ind_ele)
r = ele
# 如果已经是ChromiumFrame对象,直接返回
elif getattr(loc_ind_ele, '_type', None) == 'ChromiumFrame':
r = loc_ind_ele
else:
raise ValueError(
_S._lang.join(
_S._lang.INCORRECT_VAL_,
"loc_ind_ele",
ALLOW_VAL=_S._lang.FRAME_LOC_FORMAT,
CURR_VAL=loc_ind_ele,
)
)
# 找不到时返回NoneElement
if isinstance(r, NoneElement):
r.method = 'get_frame()'
r.args = {'loc_ind_ele': loc_ind_ele}
return r我们再插入一个函数is_str_loc
def is_str_loc(text):
"""
判断字符串是否为定位表达式
Args:
text: 要判断的字符串
Returns:
bool: 如果是定位表达式返回True,否则返回False
"""
return text.startswith(
(
".",
"#",
"@",
"t:",
"t=",
"tag:",
"tag=",
"tx:",
"tx=",
"tx^",
"tx$",
"text:",
"text=",
"text^",
"text$",
"xpath:",
"xpath=",
"x:",
"x=",
"css:",
"css=",
"c:",
"c=",
)
)wc了,给我写成傻逼了,太jb多了,过几天把另外两个类写成1_2