资讯动态

SeleniumBase网络日志采集实战:从CDP事件到接口分析

发布时间:2026/10/21 18:36:07 来源:尧图企业网站定制
用SeleniumBase做自动化测试的时候我一直觉得浏览器本身就是一个天然的数据采集器每一个请求、每一段响应、每一次跳转背后其实都藏着完整的网络日志。问题在于大多数人不清楚怎么把这些日志从浏览器里“导”出来更不知道怎么变成能拿去分析的干净数据。这篇博文就写我实际跑过的一套流程用三步把SeleniumBase里的网络日志拿到手从环境配置、事件采集到数据清洗和分析顺带把踩过的坑都列出来。内容覆盖接口调试、页面性能统计、前后端问题定位这些常见场景适合正在用SeleniumBase做UI自动化、又想让脚本顺便输出请求级情报的同学参考。一开始接触这个需求很多人第一反应是“抓包不就行了”。我承认抓包工具在某些场景下确实好用但放进自动化脚本里它和浏览器内部的事件顺序、页面首次渲染时机、以及测试断言之间的联动都很别扭。浏览器开发者工具里那个Network面板才是和页面执行路径严格对齐的。SeleniumBase底层就是Selenium天然支持通过DevTools日志拿到这些信息所以没必要在外面再套一层代理。下面就把这套配置、采集、分析一整套流程讲透。1. 为什么要抓SeleniumBase网络日志1.1 网络日志能帮你解决什么问题网络日志并不是只有“看某个接口返回了什么”这一个作用。我实践下来拿它能做四件很实际的事情。第一接口联调和前端问题定位。页面报错了、按钮点了没反应看控制台报错往往不够你得知道请求有没有发出去、发到了哪里、返回的状态码是什么、响应体长什么样。网络日志能把这些字段完整拉出来有了它再也不用靠肉眼去F12里翻。第二性能基线分析。页面加载到底慢在哪个环节是首屏的某个静态资源太大还是某个XHR接口卡了把请求耗时和资源大小统计出来就能一眼看出瓶颈。对做性能监控和优化的人来说这是一手数据。第三自动化的智能断言。以前我们断言页面元素有没有出现那是“结果”。有了网络日志你在测试脚本里还能断言“某个接口请求过”或者“请求失败了”等“过程”。这种基于链路而非元素的断言在监控场景里尤其好使。第四爬虫模拟和数据采集。想摸清一个站点在登录、翻页、搜索这些动作后到底发起了哪些请求用SeleniumBase真实操作一遍并记录日志比自己造请求头省心得多。1.2 为什么选SeleniumBase而不是自己造轮子如果不用SeleniumBase裸写Selenium也能拿到一部分日志但你会被各种琐碎问题拖住浏览器二进制目录要自己配、日志开关要自己设置、事件解析要自己处理一不小心get_log的返回格式变了还得去翻源码。SeleniumBase把浏览器自动化和日志采集的底层细节封装好了你用BaseCase写用例时driver已经是增强过的省掉了非常多初始化工作。另外SeleniumBase内置了许多测试辅助能力比如自动等待、失败截图、日志归档。你把网络日志采集挂载在它之上跑完一轮测试日志会自动落在本地目录再配合它的报告体系整个数据闭环就跑通了。这套组合比我之前只用纯Selenium时省了一半代码量。1.3 底层机制CDP事件和SeleniumBase的封装讲原理可能听着绕但理解了就会用得更放心。浏览器里的网络日志可靠来源是Chrome DevTools Protocol也就是常说的CDP。浏览器会把所有与页面生命周期相关的事件统统广播出来包括网络请求发起、响应接收、DOM改动、控制台输出等等。Selenium的日志接口get_log其实就是在帮你把这些CDP事件搬运到测试脚本里。SeleniumBase正是在这层做了一层增强封装。它的driver实例在满足条件时会保留performance类型的日志你调用get_log(performance)就能拿到按时间顺序排列的网络事件列表。每个事件都是一条JSON里面有方法名和参数例如Network.requestWillBeSent表示请求发出Network.responseReceived表示收到响应。我们后面要做的“日志采集”本质上就是遍历这些JSON事件把需要的字段抽出来重组为用户可读的结构化数据。2. 第一步环境准备与初始化配置2.1 安装SeleniumBase和浏览器驱动环境这步其实很常规但偶尔会有人卡住。直接用pip装就行pip install seleniumbase装完SeleniumBase会带一个命令行工具它会自动识别你本机的Chrome/Edge等浏览器版本并下载匹配的驱动。如果第一次跑的时候驱动没就位可以手动执行下面这个命令seleniumbase install chromedriverSeleniumBase还支持用--driver-version指定版本但我个人建议让工具自己处理除非你确实遇到了驱动版本和浏览器版本不匹配的报错。总之确认seleniumbase命令能正常响应Python里import seleniumbase不抛异常基础环境就算到位了。2.2 开启浏览器的日志开关核心就在这里。要让浏览器记录performance日志浏览器启动时的ChromeOptions里必须开启对应的日志偏好否则你后面拿到的是一个空列表。在裸Selenium里是这样设置的from selenium.webdriver.chrome.options import Options options Options() options.set_capability(goog:loggingPrefs, { performance: ALL, browser: ALL, })在SeleniumBase里如果直接用驱动示例启动浏览器其实默认就会带上一部分日志能力。万一你的版本没开最简单的方式是在启动命令行里加参数让浏览器用上这些偏好或者自己重新构造driver。我跑过的项目里大部分情况下self.driver.get_log(performance)是能直接返回内容的但如果返回为空优先检查的地方就是loggingPrefs有没有生效。2.3 写一个最简的验证脚本环境配置对不对跑一下就知道。下面的脚本是纯SeleniumBase写法打开一个页面后把日志拉出来打印条数from seleniumbase import BaseCase class NetworkLogSmokeTest(BaseCase): def test_fetch_network_log(self): self.open(https://example.com) self.sleep(3) logs self.driver.get_log(performance) print(fcollected {len(logs)} log entries) for entry in logs[:3]: print(entry[message][:200])如果在控制台看到collected N log entries说明日志开关已经生效。看到这里很多人的疑问就来了entry[message]又长又难读怎么解析别急第三步的解析代码才是主力这里只要证明链路是通的就行。3. 第二步开启采集与关键参数详解3.1 从get_log拉取事件日志真正的采集脚本要做的不仅仅是get_log一次。页面加载过程中网络事件是持续产生的事件列表也是动态累积的。最稳妥的方式是在关键动作之后主动拉取一次比如页面open之后、点击某个按钮之后、等待某个接口返回之后。logs self.driver.get_log(performance) for entry in logs: message entry.get(message, ) # 这串message是一段JSON字符串需要二次解析很多人卡住是因为entry[message]像是一段被转义的JSON字符串直接看都看不出来。这一步的解析逻辑得先把message字段取出来再用json.loads加载最后拿到事件方法名和参数。这个环节是整个采集流程的技术核心。3.2 解析CDP事件requestWillBeSent和responseReceivedCDP里和网络请求最相关的两个事件一个是Network.requestWillBeSent代表请求开始发出一个是Network.responseReceived代表接到了响应。两者都有requestId字段可以用来把请求和响应配对。这里写一个我实际在用的解析函数把两个事件的关键字段都抽出来import json def extract_network_records(logs): records [] for entry in logs: if not entry.get(message): continue try: message json.loads(entry[message]) except json.JSONDecodeError: continue method message.get(method, ) params message.get(params, {}) if method Network.requestWillBeSent: req params.get(request, {}) records.append({ type: request, requestId: params.get(requestId), url: req.get(url), method: req.get(method), headers: req.get(headers, {}), timestamp: params.get(timestamp), }) elif method Network.responseReceived: resp params.get(response, {}) records.append({ type: response, requestId: params.get(requestId), url: resp.get(url), status: resp.get(status), mimeType: resp.get(mimeType), headers: resp.get(headers, {}), timestamp: params.get(timestamp), }) return records这个函数把日志列表传进去返回一个全部由普通字典组成的列表后面不管是转DataFrame还是存JSON都非常顺手。3.3 采集过程中的两个关键取舍实际采集时我遇到过两个需要权衡的点。第一日志要不要全量采集。performance日志里除了网络事件还有页面加载、性能指标、渲染信息等一堆内容。如果只关心接口数据解析时直接跳过非Network.*事件就行。想全量保留也可以但要注意日志体积会明显变大。第二要不要顺路拿到响应体。responseReceived事件里包含响应头但不包含响应体。想拿响应体得在拿到requestId之后再调用一个CDP命令比如通过SeleniumBase里的execute_cdp_cmdtry: body_response self.driver.execute_cdp_cmd(Network.getResponseBody, {requestId: request_id}) response_body body_response.get(body, ) except Exception: response_body 这条命令有明确的限制条件必须是还在页面生命周期内的请求如果页面已经跳转到下一个地址再取上一个页面的响应体会报错。所以我的做法是在动作发生后的短时间内尽快去取并且做上try/except兜底。永远不要在线上跑一个因为“某个响应体取不到”而崩溃的采集脚本。4. 第三步日志导出与核心字段解读4.1 把结构化数据落盘解析函数写完之后剩下的就是导出了。日志采集完我喜欢同时存两种格式原始事件存一份清洗后的结构化数据存一份。原始事件用于事后排查结构化数据用于分析。import json records extract_network_records(logs) with open(network_records.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2)或者你更习惯用Excel/CSV做后续分析那就得先转成pandas的DataFrame再写文件。这一步我通常在分析阶段做而不是在采集阶段做因为CSV丢失了嵌套结构在数据还没完全确定要哪些字段时先存JSON更稳。4.2 核心字段逐项拆解一张表讲清楚清洗后记录里的关键字段是什么意思字段来源事件含义requestId两者都有请求的全局唯一ID用来配对请求和响应url两者都有请求或响应对应的URL地址methodrequestWillBeSentHTTP方法如GET、POSTstatusresponseReceivedHTTP状态码如200、404、500mimeTyperesponseReceived响应类型如text/html、application/jsontimestamp两者都有事件时间戳单位为秒是相对会话开始的时间headers两者都有请求头或响应头是字典结构理解这些字段后面做任何分析都有底气。特别是requestId它就像一个链条上的编号没有它流量日志就只是一堆孤立的记录。4.3 日志量评估与存储建议日志量有多大我跑一个中型页面的完整加载流程每次测试大约会产生几百到上千条原始事件。如果解析后只保留请求和响应记录数量会少一半左右。按每条记录约1KB计算一次测试产生的结构化数据也就几百KB磁盘压力真不大。真正需要担心的是频繁采集场景。比如每5分钟跑一次监控任务一天下来数据量就是几百MB。这种情况我建议直接把清洗后的JSON按天归档再挂一个简单的定时清理任务保留最近七天即可。如果上了时序数据库那就另说但对多数自动化团队来说本地JSON文件夹已经够用。5. 数据分析实战从日志到决策5.1 合并请求与响应计算请求耗时分析和采集分开的好处是你可以在同一份日志上反复折腾不用每次改动分析逻辑都重跑浏览器。我把清洗后的network_records.json加载进pandas然后按requestId把请求和响应合并成一张宽表这样每条记录就有了完整的请求耗时。import pandas as pd df pd.DataFrame(records) req_df df[df[type] request].rename( columns{timestamp: req_time} ) resp_df df[df[type] response].rename( columns{timestamp: resp_time, status: status_code} ) merged req_df[[requestId, url, method, req_time]].merge( resp_df[[requestId, status_code, resp_time]], onrequestId, howinner ) merged[duration_ms] (merged[resp_time] - merged[req_time]) * 1000这里有个细节CDP的timestamp单位是秒两个时间戳相减得到的是秒乘以1000才转成毫秒。如果你看到某个请求的耗时是负数多半是把前面某个事件的timestamp当成了同一时刻这种数据需要直接丢弃。合并之后想看最慢的10个接口非常简单slowest merged.sort_values(duration_ms, ascendingFalse).head(10) print(slowest[[url, method, status_code, duration_ms]])配合URL过滤还能只看API接口的耗时分布。这个表一旦生成页面性能优化该从哪里下手就很好判断了。5.2 状态码分布与失败率统计状态码是最容易出报表的数据。直接用value_counts就能看整体分布status_counts merged[status_code].value_counts() print(status_counts)还可以算4xx和5xx的比例进一步定位系统性问题。我实际用它抓出过一次很隐蔽的“图片懒加载导致部分资源404”问题之前光靠肉眼翻图片链接根本看不出来。回到断言场景如果你希望用例在发现某个接口失败时自动报警可以把这个统计写进测试的末尾assert 500 not in merged[status_code].values, 检测到500状态码这样就把日志从“事后分析”升级成了“实时防线”。5.3 还原页面初始化时的请求链路我比较喜欢做的一类分析是还原某个操作触发的请求链路。比如点击登录按钮后浏览器到底依次发出了哪些请求把记录按时间戳排序再按操作发生的时间点切分就能看到链路图。这种分析不需要额外工具只要排序加过滤operation_logs records[records[timestamp] start_time] operation_requests [r for r in operation_logs if r[type] request]然后把URL清单打印出来。对接口联调或者排查“哪个请求导致下一步失败”这种方法比我曾经用的任何抓包工具都快。特别是配合SeleniumBase的网页操作步骤你能清楚地知道每一个用户动作背后对应的所有HTTP请求。6. 常见问题与避坑指南6.1 日志返回为空怎么排查这个问题排在所有问题里的第一位。出现空日志先按顺序检查三个地方。一是浏览器日志开关没生效。回到第2步确认ChromeOptions里的goog:loggingPrefs有没有设置成功尤其在看self.driver.get_log(performance)之前可以用一段代码先打印浏览器日志类型print(self.driver.log_types)如果输出里没有performance说明开关确实没开别急着往下排查。二是日志被其他浏览器自动化方式绕过了。SeleniumBase有时会用特殊方式启动浏览器比如一些反检测模式会重设浏览器参数导致原来配置的日志偏好被覆盖。这种情况我建议在测试方法里用最普通的模式打开页面先验证。三是拉日志的时机太晚。页面跳转或刷新后之前的日志可能会被清理。要拿关键请求的日志请在操作后的短时间窗口内主动拉取而不是等到整个测试结束再去翻。6.2 开启日志后脚本明显变慢日志采集会增加开销这是正常的但慢到影响用例执行就需要干预。我总结出一个控制节奏的思路。首先是减少拉取次数。不要在每个动作后都调用get_log而是在关键动作之后比如一次页面跳转、一次点击、一次表单提交后拉一次然后立刻把本地已有的事件缓存起来。其次是按需解析。解析时只保留Network.requestWillBeSent和Network.responseReceived其余事件全部跳过。很多性能事件体积大又没用解析它是纯浪费。最后是避免同步获取响应体。Network.getResponseBody每次调用都有不小的开销而且设计上就是“按需获取”。如果不需要响应体千万不要在每条请求上都来一次。6.3 重复记录和并发请求的处理同一个URL出现多条日志并不奇怪。CDP会记录重定向之前的请求和之后的请求每个阶段的requestId可能不同URL也可能相同。另外页面并发发出多个相同URL的请求也是正常现象。处理方式很简单分析时要么保留全部记录看“请求次数”要么用requestId去重看“独立请求”之间的差异。别急着删数据在真正理解业务场景之前数据先保留原始版本分析版本再做去重。6.4 常见问题速查表问题现象可能原因解决办法get_log返回空列表loggingPrefs未开启检查goog:loggingPrefs配置message字段解析失败JSON格式转义异常确认先用json.loads解析message请求和响应配不上页面跳转导致日志被清理缩短拉取间隔或改用performancelo纰漏事件缓存耗时数据是负数时间戳基准不一致丢弃异常数据检查事件来源脚本整体变慢日志拉取过于频繁减少拉取次数按需解析响应体取不到请求已经不在当前页面生命周期try/except兜底必要时提前取就我个人的体会来说等这套日志采集和分析流程跑顺之后最爽的地方在于它可以反过来优化你的自动化用例本身。以前遇到偶发性失败只能靠截图和页面源码猜现在日志里清清楚楚写着是哪个请求超时了、哪个接口返回了异常状态码。我甚至会把网络日志采集设置成所有用例的公共前置逻辑让每个用例都默认带上这个数据源。这样无论是做覆盖率分析还是稳定性监控手里的素材都多了很多。如果你现在的项目里也卡在“日志拿不到”或者“拿到了不知道怎么用”这个阶段不妨照上面这套流程搭一遍应该会比我当初摸索时省下不少时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑