做爬虫这件事我从requests入坑后来被Selenium折磨过再后来遇到DrissionPage才算是把“采集网页数据”这件事真正理顺了。先说结论如果你要对付的是需要登录、数据由JS动态渲染、或者带着各种反爬策略的网站那么DrissionPage可能是近些年最值得上手的库之一。它最猛的地方在于把HTTP请求requests和浏览器自动化类似于Selenium统一成同一套API你可以在浏览器模式和requests模式之间无缝切换登录态、cookie、localStorage全都不用自己手动去抠。我上一份工作里有个数据看板必须在登录之后点半天筛选条件才能看到汇总表格。早期用requests模拟Ajax接口被前端的一个签名参数折腾到怀疑人生换用Selenium倒是能点击了可网站有webdriver检测登录状态也总是莫名其妙地丢。后来换成DrissionPage整个采集脚本从两百多行缩到八十多行效率还翻了一倍。这篇文章就把我在这几次实战里的一些经验和踩过的坑整理出来给正在纠结爬虫方案的朋友一个参考。1. 为什么我把requests和Selenium都先放到一边1.1 requests请求库的“天真”之处requests本身是一个非常优秀的HTTP库轻量、快速、语义清晰我在很多简单场景下依旧用它。可一旦目标网站复杂起来它的问题就暴露得很明显页面数据如果是JS异步加载的requests拿到的只是空壳HTML抓不到实际内容。登录后的接口往往带签名、加密参数、时间戳甚至每次请求的header都不同靠手工抓包去逆向成本高还不稳定。cookie一旦过期就得重新模拟一遍登录流程中间只要漏掉一个header字段接口就给你返回401。我举个例子之前想抓一个平台的后台报表数据浏览器Network里明明能看到一个返回JSON的接口但用requests一访问对方直接返回“签名错误”。我把所有header都复刻过去了还是不行后来才发现它在前端页面里用一段加密JS生成了动态token而这个token只存在于浏览器环境里。requests再厉害也没办法替你执行那段混淆后的JS。1.2 Selenium的笨重与暴露感Selenium的价值在于它是“真浏览器”用户看到什么它就拿到什么。但它身上有几个让我不太舒服的点环境配置繁琐。Chrome版本一升级selenium还要重新下载匹配的driverCI环境里经常出现“session not created”这类报错。特征暴露太明显。navigator.webdriver这个属性一测一个准很多网站就靠这个特征拦住自动化脚本。代码写起来相对啰嗦。查找元素要写find_element(By.ID, xxx)各种显式等待、隐式等待混在一起维护起来很痛苦。资源占用大。每个Selenium会话本质都是拉起一个完整浏览器如果同时跑几十个任务服务器内存直接告急。而DrissionPage给我的感觉是它把requests和Selenium各自的长处都留下来了又把各自的短板基本补齐了。它既可以直接驱动本机的Chromium浏览器完成“真人操作”也可以切换到requests模式去高频请求接口两者之间还能互相转手。1.3 DrissionPage的设计哲学这个库的核心设计很简单统一。它把“元素查找”这件事从底层重做了一套语法让requests模式的SessionPage和浏览器模式的ChromiumPage共用同一套API。你学了ele()怎么用就同时会在两种模式下找元素了。另一个贴合实际的设计是ChromiumPage在启动时可以直接接管你本机已有的Chrome浏览器配置文件也就是说你平时登录过的网站、保持的登录态它都能一口气继承过来。这一点在需要登录的采集场景里太实用了——不需要自己在代码里走一遍复杂登录直接复用浏览器里已有的session就行。2. 核心概念与整体设计思路拆解2.1 SessionPage与ChromiumPageDrissionPage里有两大核心类。我先用最简单的方式解释它们SessionPage底层基于requests实现本质是HTTP请求封装。它速度快、占用资源小适合抓取接口、静态页面、纯数据读取。ChromiumPage底层通过DevTools协议控制Chromium内核浏览器本质是浏览器自动化。它能渲染JS、点击按钮、填写表单适合对付动态网页和需要真实交互的场景。这两个类不是孤立存在的。DrissionPage通过一个跨模式的元素语法让SessionPage和ChromiumPage之间的代码风格保持高度一致。更关键的是ChromiumPage对象可以调用change_mode()方法直接转换成SessionPage模式同时把浏览器的cookie、user-agent等身份信息一并带过去。这样一来你完全可以让浏览器先完成登录、滑块验证、点击操作这些“高难度动作”然后立刻切到requests模式去批量拉数据减少浏览器进程的压力。from DrissionPage import ChromiumPage # 浏览器模式完成需要真实环境的操作 page ChromiumPage() page.get(https://example.com/login) page.ele(nameusername).input(demo_user) page.ele(namepassword).input(DemoPass123) page.ele(typesubmit).click() # 切换到requests模式继续抓接口数据 session_page page.change_mode() data session_page.get(https://example.com/api/user/info).json print(data)上面的代码里登录操作发生在浏览器环境切换后发起HTTP请求时登录cookie已经自动附带上了。放在以前你得手动从浏览器DevTools里把cookie字符串复制出来再拼到requests的header里操作繁琐且易错。2.2 一套好记的元素查找语法Selenium里查找元素要写一长串find_element而DrissionPage把查找动作浓缩成了ele()和eles()方法配合字符串表达式。常见的定位写法有定位方式表达式示例说明按标签tag:h1找第一个h1标签按idid:username找id为username的元素按classclass:title找class含title的元素按文本text:登录找文本为“登录”的元素按属性nameusername找name属性为username的元素CSS选择器css:#main .data用CSS语法定位XPathxpath://div[idmain]用XPath定位多个条件还可以组合ele(idusertext:张三)表示同时满足两个条件ele(classa||classb)表示满足其中任意一个即可属于and和or的语义。这种写法比Selenium的条件链简洁很多在日常抓取中真的能节省不少代码量。2.3 浏览器自动化的细节能力ChromiumPage除了可以像Selenium那样点击、输入、滚动之外还支持不少有意思的特性多标签页管理可以打开多个标签页按标签对象切换相当于操作一个真正的浏览器窗口。iframe直接进入用get_frame()方法切入frame内部不用像selenium那样来回switch_to.frame。监听网络请求可以用listen方法捕获页面发起的接口请求用来分析数据来源。文件下载处理可以直接接管浏览器下载保存到指定目录。执行JS脚本需要绕开某些限制时可以调用run_js()直接注入代码。这些能力让DrissionPage不只是爬虫库做Web自动化测试也很顺手。3. 实操过程一个需要登录的报表采集项目3.1 场景设定与环境准备假设我要采集一个后台系统的销售报表数据。流程是这样的访问登录页输入账号密码点击登录进入报表页面筛选日期读取表格最后把数据保存到Excel。这个场景里页面是动态渲染的接口也加了签名校验用requests直接抓接口是行不通的。先安装DrissionPagepip install DrissionPage这个库不要求你额外下载driver前提是电脑上装了Chrome或者Edge浏览器。我第一次用的时候还挺惊讶的安装完就能启动浏览器完全没有Selenium那种“版本不匹配”的烦恼。如果你的Chrome安装在非默认位置或者有特殊参数需求可以用ChromiumOptions来做配置。3.2 第一步启动浏览器并打开登录页from DrissionPage import ChromiumPage page ChromiumPage() page.get(https://example.com/login)这一步会弹出一个真实的Chrome窗口而且默认不带自动化特征标识。和我以前用Selenium时那种“一启动就被网站认出来”的体验完全不同。有一点需要注意ChromiumPage()直接启动时会自动使用临时用户目录登录状态不会保存到本地。要做到“登录一次后续复用”可以指定一个固定的用户数据目录from DrissionPage import ChromiumOptions, ChromiumPage co ChromiumOptions() co.set_user_data_path(./my_user_data) # 固定用户目录 page ChromiumPage(co) page.get(https://example.com/login)第一次运行到这里时你手动在弹出来的浏览器窗口里登录一次之后cookie和登录状态都会保存在./my_user_data目录下。下次运行脚本打开就是已登录状态连代码里的登录逻辑都可以省掉。3.3 第二步表单填写与登录触发大多数网站的登录表单无非是用户名、密码、登录按钮DrissionPage的写法很直观page.ele(nameusername).clear().input(demo_user) page.ele(namepassword).clear().input(DemoPass123) page.ele(typesubmit).click()input()方法会先清空原有内容再输入免得表单里有默认值导致输入错误。点击登录按钮之后我习惯加一句等待加载完成的逻辑page.wait.load_start()这条语句会阻塞到页面跳转完成避免下一步操作时元素还没渲染出来。3.4 第三步进入报表页并抓取表格数据登录成功后通过URL直接跳到报表页面page.get(https://example.com/report) page.wait.ele_displayed(css:table.data-table)这里用了一个显式等待等到table.data-table出现且可见再往下执行。相比固定的sleep(5)这种等待方式既高效又稳妥。读取表格所有行rows page.eles(css:table.data-table tbody tr) for row in rows[:5]: cells row.eles(tag:td) values [cell.text.strip() for cell in cells] print(values)row.eles(tag:td)是查找当前行内所有td单元格。注意这里用的查找方法是在元素对象上调用的而不是在page上调用的DrissionPage的元素对象自带查找能力这个设计让层级遍历变得非常自然。3.5 第四步数据落盘成Excel抓到的数据可以直接转成DataFrame再保存为Excel。我在本地测试时用的pandas版本是2.x导出Excel需要openpyxl库一并装好就行。import pandas as pd all_rows [] for row in rows: cells row.eles(tag:td) all_rows.append([cell.text.strip() for cell in cells]) df pd.DataFrame(all_rows) df.to_excel(report.xlsx, indexFalse, engineopenpyxl) print(f共导出 {len(df)} 行数据)整个采集脚本跑下来从启动浏览器到Excel生成也就几十秒。放在以前用requests逆向接口加签名的方案里这个过程可能要排查一整天。3.6 进阶隐藏数据接口的监听与直采很多报表页面的表格数据其实是Ajax接口返回的页面只是把JSON渲染成了表格。如果接口数据结构非常规整我更推荐直接监听接口请求然后解析JSON比每次读表格再清洗HTML效率高得多。DrissionPage可以用listen方法捕获网络请求page.listen.start(api/report) # 开始监听url中包含api/report的请求 page.get(https://example.com/report) # 刷新页面触发接口请求 resp page.listen.wait() # 等待捕获 json_data resp.response.body这里的思路是先开始监听再触发页面操作拿到接口返回的JSON直接按字段提取。我在实际项目中经常用这种方式跳过HTML解析步骤让数据采集精准命中数据源头。不过要注意接口请求的URL或参数有时候会带随机化前缀监听条件需要写得宽一点拿到响应后再在代码里做过滤。4. 常见问题与排查技巧实录4.1 一个问题和对应排查思路的速查表现象可能原因解决思路启动报错找不到浏览器本机没有Chrome/Edge或浏览器路径非常规用ChromiumOptions指定browser_path登录后重启脚本又掉登录态使用了临时用户数据目录设置固定的user_data_path元素一直定位不到页面还没加载完或元素在iframe里用wait.ele_displayed或get_frame()进入iframe再查找点击按钮没反应元素被遮挡或滚动条没到位先scroll_to_see到元素再执行点击页面提示检测到自动化脚本网站做了爬虫特征检测使用真实用户目录不要用headless模式避免并发过高加随机延迟切换到requests模式后接口仍报错cookie带过去了但某些header缺失检查接口必须的X-CSRF-Token、Referer等字段手动补充4.2 定位不到元素时不要盲目加延时新手最容易踩的坑是一旦元素找不到就在代码里疯狂加sleep(3)结果不是慢就是还报错。DrissionPage提供了一套wait方法来处理这类问题我实测下来最常用的是两个page.wait.ele_displayed(idresult) # 等待某个元素可见 page.wait.load_start() # 等待页面加载启动如果用了wait还是找不到优先考虑两个方向一是元素在iframe里需要先切入frame二是页面发生了弹窗遮挡需要先关闭或绕过。定位问题前先用page.html把当前页面源码输出到本地文件确认元素到底在不在DOM里。这一步能帮你筛掉大部分错觉。4.3 浏览器被识别的问题尽管DrissionPage比Selenium低调很多但大型网站的反爬策略一直在升级。如果你的脚本被识别了第一个反应不应该是找更隐蔽的工具而是反思自己的行为模式是不是请求频率太猛是不是启动参数暴露了特征是不是用户目录太干净、没有任何历史记录我常用的方法是给请求之间加随机小延迟行为尽量模拟真人import random import time for page_num in range(1, 20): page.get(fhttps://example.com/report?page{page_num}) # 解析数据... time.sleep(random.uniform(1.5, 3.5))这样虽然慢一点但稳定性高很多。爬虫采集的本质是和数据源博弈速度和安全是一对矛盾你得根据项目规模做取舍。4.4 我对这套方案的整体感受DrissionPage真正打动我的不是某个单独功能而是它把“浏览器身份”和“HTTP速度”缝合在了一起。遇到需要登录的网站我现在的标准操作是先用浏览器模式登录并把cookie持久化到用户目录然后切到requests模式进行批量请求必要时用listen监听关键的接口响应。这套组合拳下来几乎能覆盖我工作中遇到的大部分采集需求。如果你手头正好有一个需要登录才能看到数据、又不想逆向繁琐加密逻辑的项目我建议你从本文的登录示例开始跑一遍哪怕只是抓一个无关紧要的标题元素把环境跑通的感觉也很有价值。做爬虫这件事工具永远不是最难的难的是你愿意花一个下午把流程彻底摸透。DrissionPage至少帮你把最脏最累的那部分活给省了大半。