尧图精选

DrissionPage v4.0.2:统一DOM与接口的Web自动化API

🕒 发布时间:2026/10/2 14:49:35 📁 来源:尧图网络
简介网页自动化与数据抓取是Python工程中的常见需求传统方案常需在Selenium浏览器操控与requests接口请求间反复切换不仅代码冗余还常遇到登录态难以共享的难题。DrissionPage作为一款Web自动化集成工具将DOM操作与HTTP接口请求统一在Python API之下通过ChromiumPage、SessionPage和WebPage三种页面对象让开发者根据场景自由切换浏览器模式与请求模式并自动同步Cookie、Header等会话信息。本文以v4.0.2为例介绍安装初始化、元素定位语法、等待策略、混合模式抓取接口数据等核心操作并结合真实踩坑记录帮助自动化测试、数据采集、页面巡检等场景快速落地。1. DrissionPage v4.0.2Web 自动化的 API 统一方案从 DOM 操作到接口请求DrissionPage 是一款把浏览器 DOM 操作、HTTP 接口请求和会话管理整合进统一 Python API 的 Web 自动化集成工具v4.0.2 在元素定位、页面等待和配置项上比之前版本稳定了不少。它解决的核心问题是以前做网页自动化要么用 Selenium 耗浏览器资源、要么用 requests 模拟请求却拿不到浏览器渲染后的内容而 DrissionPage 让你在 ChromiumPage浏览器模式和 SessionPage请求模式之间无缝切换登录态、Cookie、Header 可以一套对象带走。适合自动化测试、数据采集、Web 页面巡检、毕设二次开发这几类用量。这份资源里带完整源码包和说明文档直接读源码比看二手教程更快。2. 安装与初始化从源码包读懂模块划分跑通第一个脚本2.1 源码目录结构先知道每个包是干什么的拿到压缩包解压后第一眼看到的是DrissionPage主包和一堆配置文件。很多初学者上来就去翻README.md我建议你先看目录结构因为 v4.0.2 的模块划分已经相当清晰知道每个目录的职责后面排查问题能省大量时间。DrissionPage/ ├── _pages/ # 页面对象ChromiumPage、SessionPage、WebPage ├── _elements/ # 元素对象ChromiumElement、SessionElement ├── _configs/ # 配置对象ChromiumOptions、SessionOptions ├── _base/ # 浏览器与请求的基础封装 ├── _functions/ # 工具函数格式化、文件监听等 ├── _units/ # 底层单元日志、设置 ├── common.py ├── errors.py ├── setup.py └── requirements.txt_pages是你日常导入的页面对象所在处_elements负责元素操作_configs管浏览器参数和请求参数。errors.py里定义了所有异常类型遇到报错先查这里能快速定位是配置问题还是运行时问题。先装依赖再装包v4.0.2 依赖requests、lxml、click、tldextract这几个库。用 pip 一条命令解决pip install -r requirements.txt python setup.py install如果你没有解压源码只想当普通库用也可以直接pip install DrissionPage但既然是拿到了源码包我建议用setup.py install方式装这样后续要改源码时直接改本地文件就能生效。装完验证一下导入是否正常python -c from DrissionPage import ChromiumPage, SessionPage, WebPage; print(import ok)注意装包时如果提示lxml编译失败在 Windows 上常见直接换成pip install lxml --preferencebinary或从非官方预编译轮子装这属于老生常谈的坑。装好后别急着写脚本先看下一节三个页面对象怎么选。2.2 三个核心页面对象ChromiumPage、SessionPage、WebPagev4.0.2 把所有操作收敛到了三个页面类上页面对象底层实现适用场景ChromiumPage控制真实 Chromium 浏览器需要 JS 渲染、点击、表单填写、验证码人工过SessionPagerequests lxml纯接口请求、静态 HTML 抓取WebPage混合模式可切换先浏览器登录再切 requests 抓接口新手最容易犯的错是完全用 ChromiumPage 抓所有页面慢且费内存。反过来遇到数据是 JS 动态渲染的非要用 SessionPage 去抓抓回来一堆空标签。我一般这样判断能直接看到接口返回 JSON 的用 SessionPage必须操作页面元素的用 ChromiumPage两者都要的用 WebPage。WebPage 的切换模式是它最值钱的能力先记下这个 APIfrom DrissionPage import WebPage page WebPage() page.get(https://example.com/login) # 在浏览器模式完成登录操作 page.ele(#username).input(admin) page.ele(#password).input(123456) page.ele(#login-btn).click() # 切换到请求模式复用登录态 page.change_mode() data page.get(https://example.com/api/user/info) print(data.json)change_mode()切换时默认把当前页面的 Cookie 和 Header 同步给新的 Session所以登录态不会丢。这个特性做需登录的接口抓取非常爽省去了手动 copy Cookie 的麻烦。要注意的是切换模式后page的属性从浏览器模式变成请求模式原本的.ele()逻辑仍然可用但背后不再是真实的浏览器。2.3 第一个实操脚本打开页面、定位、输入、点击这里用一个最简单的搜索场景来跑通全流程。以百度搜索为例目标是用 DrissionPage 打开首页、在输入框填入关键词、点击搜索按钮、等待结果加载、取回页面文本。from DrissionPage import ChromiumPage page ChromiumPage() page.get(https://www.baidu.com) # 定位输入框填入关键词 search_box page.ele(#kw) search_box.input(DrissionPage) # 点击搜索按钮 page.ele(#su).click() # 等结果加载 page.wait(2) # 取回整个页面的文本确认是否进入结果页 print(page.html[:200]) page.quit()这段代码有三个关键点。第一ChromiumPage()不带任何参数时会自动去找系统默认的 Chrome / Edge 浏览器找不到会报错后面避坑章节细说。第二.ele(#kw)的#kw是 CSS 选择器语法v4.0.2 默认支持 id、class、属性、文本等多种方式下一章展开。第三.click()执行的是标准点击如果元素被遮挡或者不可用需要换.click(by_jsTrue)强制用 JS 点击。.input()方法在 v4.0.2 里做了增强它会自动聚焦元素、清空原有内容再输入比 Selenium 的send_keys好用。.wait(2)是固定等待 2 秒真实项目中不要全用固定等待配合显式等待条件更可靠。第一个脚本跑通后你已经掌握了 70% 的日常用法剩下的就是各种定位和页面交互细节。3. 元素定位与动态页面交互选择器语法、等待策略和 iframe 切换3.1 元素定位的通用语法ele() 和 eles() 的匹配规则DrissionPage 的定位 API 相比 Selenium 最大的差异在于一个ele()搞定大部分场景不需要find_element_by_id、find_element_by_xpath这种冗长方法。v4.0.2 的ele()内部按规则自动识别传入的字符串类型常见用法如下page.ele(#login-btn) # id page.ele(.search-box) # class page.ele(nameusername) # 属性 page.ele(tag:div) # 标签 page.ele(text:立即登录) # 文本 page.ele(xpath://div[classbox]/span) # XPath page.ele(css:.main .title) # CSSeles()是复数版返回匹配同规则的多个元素组成的列表。文本匹配是 DrissionPage 的特色对于没有 id、没有 class 的按钮非常管用比如page.ele(text:确认)就能定位到文本为确认的那个节点。匹配规则有个细节text:默认是精确匹配如果你需要部分匹配用text^:前缀即page.ele(text^:确认)能匹配确认收货确认订单等。还有一个容易被忽略的定位参数是timeoutpage.ele(#dynamic-element, timeout10)会在 10 秒内轮询查找元素不需要手动写等循环。这个参数在页面元素是异步加载时特别有用省去先 sleep 再定位的两步操作。元素对象本身也支持继续向下定位比如拿到一个容器元素后在它内部再找子元素container page.ele(.product-list) item container.ele(tag:li, timeout5) print(item.text)这种链式定位比从根节点写一个长 XPath 好维护得多。当页面结构调整时只需要改容器选择器内层定位逻辑不用动。这也是 DrissionPage 的设计思路把定位粒度拆散到尽量小的上下文。3.2 等待策略显式等待、隐式等待和重试机制做网页自动化的人都知道页面加载是个玄学网络波动、图片加载、JS 延迟都能让脚本时好时坏。DrissionPage 提供了几种等待机制按优先级排列# 1. 隐式等待页面 get 时内部已带默认超时 page.get(url, timeout15) # 2. 固定等待 page.wait(2) # 3. 元素出现等待 page.wait.ele_displayed(#result, timeout10) # 4. 元素消失等待 page.wait.ele_hidden(.loading, timeout10).wait.ele_displayed(#result, timeout10)是等某个元素出现在页面且可见.wait.ele_hidden()是等某个元素消失这两个组合能覆盖绝大多数加载场景。使用原则是能用条件等待就不用固定等待固定等待只会让脚本变慢且不稳定。还有一个细节page.get()默认超时是 30 秒但如果页面部分加载成功、关键元素还没出来get不会报错所以最好在get后主动加一个关键元素的显式等待确保操作前提成立。重试机制 v4.0.2 没有内置需要自己包一层循环from DrissionPage.errors import ElementNotFoundError def retry_get(page, url, retries3): for i in range(retries): try: page.get(url, timeout15) page.wait.ele_displayed(#main, timeout10) return True except ElementNotFoundError: page.quit() time.sleep(2) page ChromiumPage() return False注意每次重试要重新创建页面对象因为浏览器实例挂掉后不能复用。这个重试函数我实际用了很久对付不稳定的测试环境比任何等待参数都好用。3.3 多标签页与 iframe 切换动态页面的隐藏坑多标签页是 Web 自动化里绕不开的场景。DrissionPage 的设计和 Selenium 不同不需要switch_to.window那一套而是通过get_tab()获取# 点击一个在新标签页打开页面的链接 page.ele(text:新窗口打开).click() # 等待新标签出现 tab page.get_tab(page.tab_ids[-1]) # 在新标签页里操作 tab.ele(#new-content).click()page.tab_ids返回所有标签页的 id 列表get_tab()可以按 id 获取指定标签页的页面对象也可以不传参数获取最新操作的标签页。这里有个常见误用直接用page.ele()去操作新标签页的内容但当前焦点还在旧标签页导致元素找不到。记住一句话每个标签页是独立对象用哪个操作哪个。iframe 是另一类经典问题。页面里的 iframe 是一个独立文档你从主页面直接ele()是找不到里面元素的。DrissionPage 的解法是先用get_frame()拿到 iframe 对象再在它里面定位frame page.get_frame(#main-iframe) inner_ele frame.ele(#inner-input) inner_ele.input(hello)get_frame()接收 iframe 的 id、name 或元素对象。要注意的是iframe 元素和普通元素一样有加载时机问题如果 iframe 是异步创建的先wait.ele_displayed(#main-iframe)等它出现。我踩过最深的坑是页面里有两层嵌套 iframe必须先取外层、再取内层outer_frame page.get_frame(.outer-iframe) inner_frame outer_frame.get_frame(.inner-iframe) inner_frame.ele(#target).click()嵌套 iframe 的链式获取很简单但很容易写成page.get_frame(.inner-iframe)去直接定位内层结果返回空。记住 iframe 本身也是文档定位永远从它所在的父文档开始。4. 数据抓取与接口请求模式从浏览器登录态到 requests 会话4.1 SessionPage 的数据抓取把请求模式用到极致很多网页的最终数据来自一个 JSON 接口浏览器只是把 JSON 渲染成了表格或列表。对这种场景用 ChromiumPage 去打开页面再解析 DOM 是绕远路直接用 SessionPage 请求接口解析 JSON效率高一个数量级。from DrissionPage import SessionPage page SessionPage() # 直接请求接口 res page.get(https://httpbin.org/json) if res: data res.json print(data[slideshow][title])res.json是 v4.0.2 内置的 JSON 解析属性底层调用了响应对象对应的方法不需要额外json.loads()。res本身是一个封装后的响应对象支持.status_code、.json、.html、.text等属性。SessionPage 也支持.ele()定位但只适用于后端渲染的 HTML对纯接口返回的数据不适用。判断逻辑很简单如果目标页面查看源代码能看到数据用 SessionPage如果源代码里是空壳、数据靠 JS 加载用 ChromiumPage。SessionPage的请求参数可以在初始化时传入from DrissionPage import SessionPage, SessionOptions so SessionOptions() so.set_headers({User-Agent: Mozilla/5.0}) so.set_timeout(10) so.set_proxies({http: http://127.0.0.1:8080}) # 代理配置按实际环境填不是必须的 page SessionPage(so)4.2 混合模式WebPage 登录后抓接口数据最经典的实战场景是目标网站需要登录登录后才能调用接口拿数据。传统做法是手动从浏览器的 DevTools 里复制 Cookie 到 requests 的 Header 里Cookie 过期就要重来。WebPage 把这个过程压缩成了切换一行from DrissionPage import WebPage page WebPage() page.get(https://example.com/login) page.ele(#username).input(demo_user) page.ele(#password).input(demo_pass) page.ele(#submit).click() # 等待登录成功跳转 page.wait.ele_displayed(#user-info, timeout10) # 切换到请求模式 page.change_mode() # 带着登录态请求接口 data page.get(https://example.com/api/orders?page1) orders data.json print(orders)change_mode()执行时WebPage 会把当前浏览器的 Cookie、UA、Header 同步到内部 Session所以切换后请求接口就跟浏览器里的状态完全一致。这个能力是 DrissionPage 对比 Selenium requests 组合拳的最大优势。需要注意的一点切换模式后原来的 ChromiumPage 元素操作全部不可用因为底层已经不是浏览器了。如果后续又需要点击操作要再change_mode()切回去。模式切换成本很低但别写成频繁来回切每次切换都会重新同步数据高频切换浪费性能。4.3 数据验证与落盘断言、去重和导出抓下来的数据不能直接进数据库先做一层验证和落盘。v4.0.2 的页面对象提供了一个方便属性.html配合 Python 标准库做基础清洗import json from DrissionPage import SessionPage page SessionPage() page.get(https://httpbin.org/anything?namedemo) # 取出 json 数据 raw page.json # 关键字段校验 assert raw[args][name] demo, 参数回显不一致 # 写文件 with open(output.json, w, encodingutf-8) as f: json.dump(raw, f, ensure_asciiFalse, indent2)assert在自动化里扮演守门员角色字段不符时立刻抛错避免脏数据进入后续流程。我在巡检脚本里把断言结果统一收集到一个列表最后批量写入文件这样既能控制每条数据的质量还能留审计记录。落盘时注意两点一是用encodingutf-8不然中文全变乱码二是用ensure_asciiFalse否则中文会被转成\uXXXX序列直接看没法读。对于批量抓取的场景去重逻辑建议用集合或字典做键seen set() results [] for page_num in range(1, 6): json_data page.get(fhttps://httpbin.org/anything?page{page_num}).json key json_data[args].get(page) if key in seen: continue seen.add(key) results.append(json_data)这种去重方式比在列表里in判断快得多数据量大时不至于卡死。5. 避坑与常见问题排查六个真实踩坑记录5.1 元素点击无效元素明明定位到了click() 不报错但页面无反应现象page.ele(#submit).click()执行后没有任何报错结果页面没有跳转、没有提交静悄悄的。原因元素可能被其他节点覆盖或者该元素处于disabled/readonly状态标准的鼠标点击事件被拦截了。还有一个常见原因是按钮上绑定的是mousedown事件而非click事件标准.click()只触发 click 事件。解决换成 JS 强制点击ele.click(by_jsTrue)。如果还不行先ele.scroll.to_see()把元素滚进可视区再点。对绑定mousedown的元素直接用 JS 触发事件ele.run_js(this.dispatchEvent(new MouseEvent(mousedown, {bubbles: true})))我一般会先用by_jsTrue快速验证是不是事件绑定问题通常这一招能解决 80% 的点击失效。5.2 启动报错找不到浏览器可执行文件现象ChromiumPage()直接抛错提示类似于找不到 Chrome 或浏览器路径未配置。原因DrissionPage 默认从系统常用路径和注册表找浏览器如果装的浏览器是绿色版、路径自定义或服务器环境根本没装完整版 Chrome就会找不到。解决显式指定浏览器路径在初始化配置里写死from DrissionPage import ChromiumPage, ChromiumOptions co ChromiumOptions() co.set_browser_path(rC:\Program Files\Google\Chrome\Application\chrome.exe) page ChromiumPage(co)如果连浏览器都装不了还可以尝试用系统的 EdgeWindows 自带DrissionPage 也支持 Edge。另外提一句set_browser_path的路径最好写成配置文件从外部读取方便部署到不同机器时改路径。5.3 页面加载超时get() 卡在某个环节一直转圈现象page.get(url)有时会在约 30 秒后抛超时异常有时干脆卡住脚本停滞不动。原因目标页面里有外链资源加载慢比如统计脚本、广告 SDK、字体文件阻塞了页面加载事件。DrissionPage 的get()等待的是页面加载完成事件如果某个资源一直 pending它就一直等。解决给get()设置一个合理的timeout并在关键元素上再补一个条件等待page.get(url, timeout15) page.wait.ele_displayed(#main, timeout10)还可以通过配置项禁止加载图片和部分资源co ChromiumOptions() co.set_argument(--blink-settingsimagesEnabledfalse) page ChromiumPage(co)禁图片对纯文字页面或接口调试非常有效加载速度能快一大截。注意这种方式对需要验证图片验证码的页面不适用。5.4 iframe 里找不到元素ele() 返回 None现象在主页面用page.ele(#iframe_text)定位 iframe 里的输入框结果返回 None等多久都找不到。原因iframe 是独立文档主页面对象默认不能直接访问 iframe 里的 DOM“页面”边界在这里是硬隔离的。解决先拿到 iframe 再定位前面 3.3 节写过这里补一个实战例子page.wait.ele_displayed(.iframe-container, timeout10) frame page.get_frame(.iframe-container) if frame: frame.ele(#iframe_text).input(hello) else: print(iframe 未加载)这里.iframe-container是 iframe 的外层容器wait 它出现不能保证 iframe 内容加载完所以get_frame()后面最好也加一个元素等待再用ele()定向。5.5 多线程执行时数据串了Session 对象跨线程共享现象用ThreadPoolExecutor同时跑 10 个 SessionPage 任务结果 A 任务拿到 B 任务的数据响应内容张冠李戴。原因SessionPage 底层的requests.Session不是线程安全的。如果多个线程共用一个 SessionPage 实例它们共享同一个保存在 session 里的 Cookie、Header还有可能互相覆盖当前响应对象导致取回的数据错位。解决每个线程创建独立的 SessionPage 实例绝不共享。如果不想每个线程都重新配置写一个工厂函数def make_page(): so SessionOptions() so.set_headers({User-Agent: Mozilla/5.0}) so.set_timeout(10) return SessionPage(so) with ThreadPoolExecutor(max_workers5) as executor: pages [make_page() for _ in range(5)] # 每个线程只操作自己的 pages[i]如果是 WebPage 混用浏览器模式更要注意每个线程的浏览器路径、缓存目录、用户数据目录也要隔离否则会报“Chromium 进程已存在”之类的错。我一般在多线程场景干脆用 SessionPage轻量又安全。5.6 PyInstaller 打包后资源路径缺失开发环境能跑打包 exe 就挂现象开发环境脚本跑得好好的用 PyInstaller 打包成 exe 后提示缺少文件或路径错误浏览器驱动也定位不到。原因PyInstaller 打包后程序运行在临时解压目录sys._MEIPASS相对路径和资源查找路径都变了。DrissionPage 相关配置文件如用户数据目录如果写的是相对路径在 exe 环境一定找不到。解决路径全部用os.path.join基于sys._MEIPASS拼接配置项中的路径不要写死相对路径import sys import os base_dir getattr(sys, _MEIPASS, os.path.dirname(os.path.abspath(__file__))) co ChromiumOptions() co.set_user_data_path(os.path.join(base_dir, user_data))另外打包时加--collect-all DrissionPage确保把包内资源打进去。我自己的习惯是凡是打包脚本先在本机强制跑一遍解压后的临时目录模式也就是设一个_MEIPASS环境变量模拟提前暴露路径问题比上线了再发现强太多。6. 进阶把 DrissionPage 用于 Web 页面自动化巡检6.1 巡检任务的最小封装巡检类场景和普通抓取不太一样它要求每次执行是幂等的而且结果要能对比。我习惯把巡检逻辑封装成一个函数绑定一个唯一的检查表达式def check_site(name, url, selector, expected_text): co ChromiumOptions() co.set_argument(--headless) page ChromiumPage(co) page.get(url, timeout15) try: page.wait.ele_displayed(selector, timeout10) ele page.ele(selector) return {name: name, status: ok, text: ele.text, url: url} except Exception: return {name: name, status: error, text: page.html[:200], url: url} finally: page.quit()无头模式--headless是巡检脚本的标配不弹窗口、资源占用少可以同时跑多个站点。每个巡检项独立创建和销毁页面避免状态污染。异常分支里我返回了page.html[:200]这样报错的时候能快速看到页面当时的内容而不是干巴巴的一个错误标记。6.2 失败重试与结果记录巡检任务一旦失败先别急着报错给一次重试机会。很多临时故障是网络抖动引起的重试一次的成本远比人工介入低import time def run_with_retry(check_item, retries2): last_result None for attempt in range(retries): last_result check_site(**check_item) if last_result[status] ok: return last_result time.sleep(3 * (attempt 1)) return last_result # 多站点巡检 sites [ {name: 首页, url: https://example.com, selector: #home-banner, expected_text: Welcome}, {name: 登录页, url: https://example.com/login, selector: #login-form, expected_text: }, ] report [run_with_retry(item) for item in sites] # 输出简洁结果表 import json with open(check_report.json, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2)巡检结果落 JSON 的好处是可以直接交给 CI/CD 系统去解析也可以配一个简单的定时任务每天跑一遍。真正上线的时候我还会在报告里加上执行时长这个字段对判断页面性能劣化很有用。做过一次巡检脚本的人都有经验第二次扩展巡检项的时候一定会后悔当初没把检查项列表设计成可配置的所以sites列表我直接用一个可扩展的数据结构后续加新页面只需要追加一项不用改巡检循环。封装巡检这套东西的时候我栽过最大的跟头是不设timeout就让脚本跑到天荒地老。从那以后我每次写巡检都会强制走一遍“显式超时 条件等待 重试”三件套缺一不可。希望这些经验能帮到你少走这些弯路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →