1. base 文件代码

1. base 文件代码

des 这篇是来自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