写爬虫这几年我的代码库里有一大半精力不是花在采集逻辑本身而是在跟“登录态”和“渲染环境”较劲。requests 拿不到的页面换 Selenium 能拿到但 Selenium 开浏览器又慢又重想两全其美只能维护两套代码还得来回搬运 cookie。直到我按项目需要系统接触了 DrissionPage才真正体会到“一套代码通吃静态页面和动态页面”是什么感觉。这篇文章不讲虚的直接用我实际踩过的坑和使用经验把 DrissionPage 的 Session、Chromium、Mix 三种模式讲透分别解决什么问题、怎么写、有哪些坑新手也能照着用。1. 先从痛点看 DrissionPage为什么需要它1.1 静态爬虫的天然短板传统 Python 爬虫最舒服的姿势是 requests 一把梭。写起来非常直接性能也高一个线程几百毫秒就能跑完一个请求。但问题在于requests 拿到的是服务器返回的原始 HTML浏览器里的动态渲染、异步加载、点击事件触发的内容它一概看不到。实际开发中我经常遇到一种场景用 requests 发请求返回的 HTML 里明明有数据节点但内容却是空壳或者干脆是“请开启 JavaScript”的提示。这时候你千万别怀疑是请求参数写错了九成是目标页面用了前端渲染数据是后续接口返回到浏览器再呈现的。这种页面用 requests 硬啃效率极低还得自己逆向分析 XHR 接口一旦对方接口参数带加密逻辑工作量直接翻倍。1.2 浏览器自动化的“重武器”困境于是很多人想到 Selenium 这类浏览器自动化工具。Selenium 确实是通用方案能模拟真实浏览器行为点击、输入、滚动、等待都能做。但它的问题也显而易见启动一个完整浏览器实例内存随便几百 MB 起步每个操作之间还要处理隐式等待、显式等待、各种 ugly 的定位器如果元素加载慢一点脚本就飘了。用 Selenium 做爬虫还有个隐性成本它本质是“模拟人在操作浏览器”很多页面的反爬策略就是专门盯这种自动化特征的。比如检测navigator.webdriver、检测鼠标轨迹、检测窗口大小异常等。你写一套脚本今天能跑明天对方加一个特征检测就废了。而且 Selenium 的“慢”不只是浏览器启动慢还包括它每次查找元素都走远端的 WebDriver 协议网络开销和本地开销都在整个采集链路被拖得很笨重。1.3 DrissionPage 的平衡之道DrissionPage 的设计理念正好卡在 requests 和 Selenium 之间的灰色地带。它本质上是一个封装库手上同时握着两套能力底层基于 requests 的静态请求能力以及基于 DevTools 协议的浏览器控制能力。你可以把它理解成一个“双持工具人”遇到普通接口就用轻装的静态请求模式速度快、资源省遇到需要渲染、需要登录状态、需要解混淆的页面就切到浏览器模式让页面自己跑完 JavaScript 再把结果给你。更重要的是这套库的设计者把大多数底层细节都收起来了API 风格非常统一定位元素、切换标签页、读取响应都像写普通 Python 代码一样直接。用下来最直观的感受是写同一个采集任务Selenium 可能要 80 行DrissionPage 可能 30 到 40 行就够了而且稳定性明显好一截。这也是我后来在多个项目里把 Selenium 逐步替换掉的根本原因。2. 三种模式的底层逻辑与适用场景2.1 Session 模式轻量级请求的“快枪手”DrissionPage 的 Session 模式对应对象是SessionPage。它更像是在 requests 之上包了一层更顺手的 API但没有丢掉 requests 的优点请求快、占用低、适合大量翻页。用SessionPage的时候你依然只是做着 HTTP 请求拿到的数据是服务器返回的最原始内容。但因为 DrissionPage 统一了元素定位语法代码写起来和操作浏览器没区别这给我最大的便利是不管以后页面改成动态渲染还是保持静态我的定位代码都可以沿用不用从头改。Scene 上它的最佳场景是后端接口直接返回 JSON 或结构良好的 HTML数据不需要经过浏览器环境就能拿到你需要快速遍历大量 URL并且不想为每个请求都开个浏览器。我在采集一条数据量很大的信息流时基本都是用 Session 模式做主力。它比 requests 好在哪好在它的请求对象还自动帮你管理了 headers、cookie、连接复用等琐事代码更少出错概率也更低。2.2 Chromium 模式让真实浏览器替你跑页面Chromium 模式对应的对象是ChromiumPage。它直接控制一个 Chromium 内核浏览器实例能做的就远不止“发请求拿 HTML”了。在这个模式下页面会真实加载JavaScript 会真实执行点击、输入、滚动、切换 iframe、处理弹窗都可以自动化操作。它底层走的是 DevTools 协议而不是 WebDriver 协议所以启动速度和操作响应速度通常比 Selenium 快对自动化特征的暴露也更少一些。我最喜欢用它做两件事登录态敏感的采集先手动或自动登录一次让浏览器里持有真实 cookie之后再用这些 cookie 做后续请求动态数据过重的页面比如瀑布流加载、滚动触发接口、canvas 绘制数据这些用纯 requests 要逆向接口用浏览器模式则简单很多——直接像人一样等页面自己加载完再取页面里的文本或元素即可。需要强调的是Chromium 模式并不是让你每次都从无到有地启动一个浏览器。DrissionPage 支持连接你已经打开的浏览器实例也就是说我平时有一个常驻的浏览器进程脚本随时把某个标签页接管过来这样省掉反复启动浏览器的开销体验非常接近“人类操作浏览器”。2.3 Mix 模式两种能力之间的“任意门”第三种模式也是标题里说的 Mix 模式在新版本里对应的对象是WebPage。以前老版本里叫MixPage后来改名为WebPage但核心思路没变。Mix 模式最大的价值是同一个页面对象可以在“静态请求”和“浏览器操作”之间来回切换。比如采集流程的第一步需要登录登录按钮点击、滑块拖动这种东西只能用浏览器模式做一旦登录成功cookie 已经落在浏览器环境里后续几十上百个详情页数据的获取完全可以切回 Session 模式用快速请求去跑速度立刻提升一个档次。为什么需要这种切换因为反爬系统往往盯着浏览器的操作频率。你如果用浏览器模式连续刷新 100 个页面频繁的自动化特征很容易被识别但切到 Session 模式后它每次只是发一个普通 HTTP 请求压力小很多对你本地资源消耗也小很多。Mix 模式的巧妙之处就是让你能在一套代码里把这两种优势都发挥出来。不过这里有个关键点需要严格理解Session 模式和浏览器模式各自维护着独立的 cookie 容器。切到 Session 模式它不会自动拥有浏览器模式里的 cookie反之也一样。这是非常多初学者翻车的重灾区我在后面单独用一节来讲。3. 环境准备与最小可运行示例3.1 安装与版本差异安装 DrissionPage 非常简单pip install drissionpage但我在项目里见过不少同学出问题大多是没注意版本差异。老版本里叫MixPage新版本 4.x 里已经改成了WebPage对象名也有调整。写代码之前务必确认一下你的版本import DrissionPage print(DrissionPage.__version__)如果你看的是互联网上的老教程代码里还在用MixPage那就需要根据版本做迁移或者直接安装固定旧版本。我的建议是用新版本因为新版本 API 更统一很多 bug 也修了。以下示例我都按 4.x 的写法来展示。另外Chromium 模式依赖你电脑上有可用的 Chrome/Chromium 浏览器。DrissionPage 会自动查找系统里的 Chrome但如果你的浏览器安装路径比较特殊它可能找不到。这时候可以手动传入浏览器路径具体配置后文会讲。3.2 Session 模式最小示例直接看代码这是用SessionPage请求一个页面并提取标题from DrissionPage import SessionPage page SessionPage() page.get(https://example.com) print(page.title) print(page.html[:100]) print(page.ele(tag:h1).text)如果你用过 requests会发现这里省掉了很多样板代码不用单独建 Session不用处理编码不用手动定位标题节点。page.ele()就是统一的元素定位方法接受 CSS 选择器、xpath、文本等多种方式。有一点我特别提醒新手SessionPage.get()返回的是响应对象但page.title、page.html已经帮你解析好了。HTTP 状态码、响应头这些信息仍然可以从返回对象里拿到。比如resp page.get(https://example.com) print(resp.status_code) print(resp.headers.get(Content-Type))3.3 Chromium 模式最小示例使用ChromiumPage控制浏览器也一样直接我随便写一个打开页面并操作搜索框的例子from DrissionPage import ChromiumPage page ChromiumPage() page.get(https://example.com) # 找元素并输入 search_box page.ele(#search-input) search_box.clear() search_box.input(Python 爬虫) # 查找按钮并点击 page.ele(#search-button).click() # 等待新内容出现 page.wait.ele_displayed(#result-list) # 读取所有结果文本 results [el.text for el in page.eles(.result-item)] print(results)这里page.wait.ele_displayed()是显式等待非常实用。真实网页的数据加载都是异步的你点完按钮立刻去取元素极大概率取不到。加一个等待脚本稳定性能好非常多。Chromium 模式里还能做很多“人”才会做的事比如滚动到底部触发加载page.scroll.to_bottom() page.wait(1)或者切到指定 iframe 里定位元素iframe page.get_frame(#main-iframe) iframe.ele(.data-cell).text这些操作是 Session 模式完全做不到的。所以判断清楚场景再选模式能省掉很多无用功。3.4 Mix 模式在一套代码里切换状态用WebPage做混合流程代码结构大概是这样from DrissionPage import WebPage page WebPage() # 当前是浏览器模式先打开登录页 page.get(https://example.com/login) page.ele(#username).input(my_account) page.ele(#password).input(my_password) page.ele(#login-btn).click() page.wait.ele_displayed(#user-info) # 登录成功后切到 Session 模式快速请求数据接口 page.change_mode(session) data_page page.get(https://example.com/api/data?page1) print(data_page.text)这里change_mode()就是那扇“任意门”。当你切回 Session 模式后还可以再切回来page.change_mode(chromium) page.get(https://example.com/some-dynamic-page)多切几次都没问题不会因为你反复切换就弄丢浏览器上下文。但记住前面埋的雷两种模式下的 cookie 是隔离的。用change_mode()只切换了请求通道不会自动把你浏览器里的 cookie 塞给 Session 模式反过来也一样。没有处理 cookie 同步你在 Session 模式下请求登录后的接口依然会得到未登录的响应。4. 一个完整的实战案例登录后动态数据采集4.1 场景设计为了把三种模式串起来我分享一个真实做过的模型。假设有一个数据管理平台页面数据是前端渲染的列表数据接口是异步请求的而且必须登录后才能查看完整数据。如果直接用 requests 模拟你得先分析登录接口、处理加密参数、还要维护会话成本很高如果全程用浏览器模式点 200 页数据又慢又不稳。最佳解法就是 Mix 模式浏览器登录一次然后切 Session 模式做批量数据拉取。4.2 第一步用 Chromium 模式完成登录我先把目标站点的登录流程拆成最小步骤。假设登录需要账号、密码、验证码验证码不复杂的情况下可以先暂停脚本让我手动输一次也可以接打码平台。这里以最省事的“手动介入一次”为例from DrissionPage import WebPage page WebPage() page.get(https://example.com/login) # 自动填账号密码验证码环节暂停手动处理 page.ele(#loginname).input(your_account) page.ele(#loginpwd).input(your_password) page.ele(#captcha_input).click() # 停在这里人工处理验证码/滑块然后回车继续 input(请完成页面上的验证码操作完成后按回车...) page.ele(#login-btn).click() page.wait.ele_displayed(#welcome)这种方式在开发调试阶段非常好用因为一次性写对验证码识别逻辑的成本很高先手动过一遍保证后续流程跑通。等确认登录流程稳了再考虑接入自动识别。登录成功后浏览器里已经持有登录态 cookie。这是后续所有数据请求的基础。4.3 第二步切换 Session 模式批量拉取接口登录态稳妥了我把模式切到 Session循环拉取多页接口import json page.change_mode(session) all_data [] for page_num in range(1, 101): res page.get(fhttps://example.com/api/list?page{page_num}size50) if res.status_code ! 200: print(f第 {page_num} 页请求失败: {res.status_code}) break data json.loads(res.text) rows data.get(rows, []) if not rows: break all_data.extend(rows) print(f第 {page_num} 页拿到 {len(rows)} 条)这里注意我直接用page.get()去请求静态接口而不是去操作浏览器打开这个 URL。Session 模式下它就是一个带上了登录 cookie 的普通 HTTP 请求速度快得多。4.4 第三步数据清洗与保存拿到原始数据之后数据清洗就是常规操作了。一般来说我习惯先抽出需要的字段再做简单过滤和去重最后落库。import csv clean_data [] seen set() for row in all_data: uid row.get(id) if uid in seen: continue seen.add(uid) clean_data.append({ id: uid, title: row.get(title), status: row.get(status), created_at: row.get(created_at), }) with open(data.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[id, title, status, created_at]) writer.writeheader() writer.writerows(clean_data)utf-8-sig是写给 Excel 用户看的如果自己程序读就不需要这个 BOM 头。这一步属于通用数据处理和 DrissionPage 本身关系不大但完整流程里缺不了。4.5 关键环节回顾cookie 同步问题如果你按照上面的代码跑实际结果很可能是“切换模式后请求接口返回 401”。原因我在前面埋过线切换模式不等于共享 cookie。DrissionPage 的 WebPage 虽然帮你把两个通道放在一个对象里但每个通道内部还是各自持有一份 cookie 容器。要解决这个问题你得在切换模式后把浏览器模式的 cookie 同步给 Session 模式。我常用的写法是把 cookie 遍历出来再重新设置# 切到 session 模式后同步 cookie cookies page.cookies() for c in cookies: page.cookies.add(c)更稳妥的做法是在切换模式之前就把 cookie 手动赋给两个通道。不同版本的 API 可能略有差异务必以你当前版本的官方文档为准。我在这里只强调一个片面的教训不清理 cookie 同步Mix 模式的实际威力发挥不出来这也是大家最容易骂“混合模式没用”的核心原因。5. 高频故障与排查实录5.1 元素定位不到第一反应不应该是换选择器在实际使用中最常见的错误是ele返回空结果然后报错。很多人第一反应是“选择器写错了”其实大部分情况是“元素还没加载出来”。我的排查顺序是先看是不是动态加载在浏览器模式下手动打开页面刷新几次看目标元素是首屏就有还是滚动后才出现。如果是异步加载那就加上等待条件再确认是单数还是复数ele_()在早期版本里是返回所有元素新版本是eles()如果混淆了就会一头雾水。你想要多个结果却用了单个元素方法往往会返回第一个元素或报错最后检查是否在 iframe 里iframe 内的元素不能直接从主文档中定位必须先获取 frame 对象。新手经常在这里卡很久页面源码里明明有元素但脚本就是找不到。还有一个经验尽量别用一份元素定位在所有页面里复用。有些页面结构会随版本变化你在环境 A 写死的 xpath换个页面就失效。用 DrissionPage 的时候我会优先用 CSS 选择器它相对稳定可读性也好。5.2 Cookie 相关问题版本不同API 也不同Cookie 这块除了前面说的两种模式隔离问题还有几个常见坑一是过期问题。浏览器模式下的 cookie 有有效期Session 模式的请求如果比你预期晚了很多天cookie 可能早失效了。所以比较稳的采集任务都会做“登录态检测”也就是在批量请求第一页时判断返回内容里是否出现未登录特征如果出现就直接重新登录。二是域名范围问题。在浏览器模式里登录成功通常会种下多个域下的 cookie尤其是 SSO 单点登录体系。你手动同步 cookie 的时候只同步当前页面的可能还不够得把整个域相关的 cookie 都带过去。有些站点还会用 localStorage 或 IndexedDB 存登录态那 DrissionPage 的普通 cookie 同步逻辑就无能为力了只能用浏览器模式继续操作。三是某些平台会在请求头里校验更多额外字段光有 cookie 还不够可能还校验 user-agent、sec-ch-ua、referer 等。遇到这种你需要给 Session 模式的请求加上合适的 headers或者干脆继续用浏览器模式。5.3 浏览器模式资源占用高与稳定性问题Chromium 模式虽然比 Selenium 轻一些但毕竟跑了一个真实浏览器内存占用不可能和纯 requests 一样低。我的做法是“只在必要的时候进入浏览器模式”。比如我采集一个大列表第一页用浏览器模式渲染拿到真实接口地址和参数后后面几十页直接切换成 Session 模式用接口参数循环请求。这样浏览器只工作一小会儿内存压力很小采集速度也没有拖垮。还有一个稳定性问题长时间跑浏览器模式页面可能会越积越多内存慢慢涨上去。如果脚本要跑几小时最好定时清理多余标签页或者干脆定期重启浏览器进程。DrissionPage 支持直接接管已有浏览器所以在超长任务里我会用“独立浏览器实例 定时重启”的策略来保活。5.4 反爬与风控低调是最好的策略这个话题回避不了。爬虫做久了一定会面对反爬策略。DrissionPage 在浏览器模式下有一定的自动化特征只是比 Selenium 隐蔽一些但不代表绝对安全。我总结几个亲测有效的低调策略控制请求频率接口拉取时每页之间加一个随机延时比如time.sleep(random.uniform(0.5, 1.5))比固定延时效果好很多尽量不改变浏览器窗口大小和设备指纹频繁改窗口尺寸会触发站点端的异常行为检测Session 模式下除了 cookie把 user-agent、accept-language 等常见请求头也都带上看起来更像浏览器发的请求别对同一接口做分钟级密集抓取。数据需要长期更新时拉长采集周期比一次性疯狂拉取要安全得多。当然最重要的一点是合规做爬虫必须尊重目标网站的服务条款和 robots 协议只抓自己有权抓取的公开数据不要碰个人隐私、非公开数据也不要用爬虫干扰对方服务的正常运行。这些底线一旦突破技术上再漂亮也没有意义。6. 我在实战中的几条心得与模式选择建议6.1 模式选择的决策树经过这几个项目的反复磨炼我给自己总结了一套模式选择方法遇到新目标页面时按下面逻辑判断如果目标页面几乎不用登录且数据直接写在 HTML 里直接用SessionPage这是最快最省资源的选择如果页面需要登录但登录后的数据接口是普通 JSON用WebPage做登录然后切 Session 模式批量拉接口如果页面里有复杂的交互流程比如从下拉框选条件、点击按钮触发计算、等待图表渲染就用ChromiumPage全程操作浏览器如果页面里部分内容要浏览器渲染部分内容可以直接从接口拿依然优先考虑WebPage切模式完成不要两个对象各写一段代码。这套方法的核心思路是让每一行代码都跑在最合适的环境里。浏览器模式能干很多事但它的“慢”和“重”是客观存在的Session 模式很快但拿不到渲染后的内容。根据任务阶段灵活切模式才是 DrissionPage 真正值钱的地方。6.2 错误处理与重试机制再稳的脚本也会遇到网络抖动、接口限流、页面结构短暂变化等情况。我写 DrissionPage 脚本时一定会给关键请求包一层重试逻辑import time def safe_get(page, url, retries3): for i in range(retries): try: resp page.get(url) if resp and resp.status_code 200: return resp except Exception as e: print(f第 {i1} 次请求失败: {e}) time.sleep(0.5 * (i 1)) return None对 Session 模式这是最基本的防崩溃手段。对 Chromium 模式某些页面加载超时也会抛异常同样可以用这种方式兜底。另外浏览器模式里遇到网络弹窗、alert 等原生弹窗脚本容易被卡住。DrissionPage 有弹窗处理机制但我在实战中更倾向于提前把触发弹窗的路径优化掉比如用接口请求替代需要点击才能触发的流程从源头减少不确定因素。6.3 页面结构变化时的维护思路爬虫脚本最烦的是“今天能跑明天跑不了”。页面结构一变所有写死的选择器全部失效。我现在的习惯是把页面对应的关键选择器集中到一个配置区或者单独模块里页面变化时只改一处尽量用稳定的属性做定位比如>
阅读完成 · 觉得有帮助?