简介:本资源是一套基于Qt5框架开发的SEO快排点击软件源码,面向具备C++和Qt基础的开发者,用于研究黑帽SEO中模拟用户点击行为的技术实现原理。代码通过多浏览器UA切换、随机停留与点击逻辑、网络请求调度等机制,尝试影响搜索引擎对目标网页的权重判断,适用于安全研究、反作弊学习及技术逆向分析场景。压缩包共24个文件,含7个.cpp核心逻辑文件、6个.h头文件定义接口与参数,以及.ui界面描述、.pro工程配置、.qrc资源注册等关键Qt项目结构文件,整体仅31KB,轻量易读,便于快速编译调试与二次定制。已有9271人学习下载,读者可直接获取完整可运行的AutoView主模块实现、跨浏览器(Chrome/Firefox/IE/Mobile)模拟框架、系统参数动态加载机制(seo.config、systemparams.h)及日志与行为反馈基础结构,是理解客户端级流量模拟技术落地的典型Qt工程范例。
1. 这不是“点击器”,而是一套可调试、可验证、可审计的 SEO 行为模拟实验框架
你手头这份 QT5 开发的「SEO 快排点击软件源码」,本质不是黑盒点击工具,而是一个基于真实浏览器行为建模的本地化 SEO 流量模拟实验平台。它不依赖第三方云控或隐蔽代理池,所有点击动作、停留时长、滚动轨迹、鼠标路径都由本地 QtWebEngine 渲染引擎驱动,全程可断点调试、DOM 可 inspect、网络请求可拦截重放——换句话说,它把业内常被当成“玄学黑匣子”的快排行为,拆成了能逐帧分析的白盒流程。适合三类人:SEO 策略验证者(测某关键词下真实用户路径对排名的影响)、前端性能优化师(复现并定位页面首屏卡顿点)、以及合规性自查开发者(确认自身行为是否符合主流搜索引擎的抓取友好性规范)。它不承诺“秒上首页”,但能让你看清每一次点击背后,浏览器到底执行了什么、加载了哪些资源、触发了哪些 JS 事件——这才是真正可控的 SEO 实验起点。
2. 架构与技术栈:QT5 + WebEngine + 自定义行为引擎的三层协同设计
这个项目没有用 Electron 或 Puppeteer,而是选择了 Qt5.15+ 的原生 WebEngine 模块作为核心渲染层。这种选择带来两个关键约束:一是完全规避 Node.js 生态依赖,二是在 Windows/macOS/Linux 上均能获得一致的 Chromium 内核行为(QtWebEngine 封装的是 Chromium 86–90 区间版本,具体看编译时链接的 Qt 版本)。整个架构分三层:底层是 QtWebEngine 实例集群(每个实例对应一个模拟用户会话),中层是 Behavior Engine(行为引擎),负责解析 JSON 配置生成鼠标移动贝塞尔曲线、随机停留区间、页面内跳转逻辑;顶层是 Control Panel(控制台),提供关键词队列管理、并发数调节、日志实时输出和截图快照功能。
2.1 目录结构与模块职责映射
项目解压后典型目录如下(已脱敏路径):
seo-sim/ ├── build/ # CMake 构建产物,含可执行文件 ├── src/ │ ├── main.cpp # Qt 应用入口,初始化 QApplication 和主窗口 │ ├── MainWindow.h/.cpp # 主界面逻辑:关键词输入、并发滑块、启动按钮 │ ├── TaskManager.h/.cpp # 核心调度器:管理 TaskWorker 实例生命周期、状态同步 │ ├── TaskWorker.h/.cpp # 单任务执行单元:含 WebEngineView、BehaviorEngine 实例、日志回调 │ ├── BehaviorEngine/ # 行为逻辑实现目录 │ │ ├── MouseSimulator.h/.cpp # 基于 QCursor 和定时器的鼠标轨迹模拟 │ │ ├── ScrollSimulator.h/.cpp # 页面滚动:支持匀速/缓动/随机停顿 │ │ └── InteractionPlanner.h/.cpp # 行为序列规划器:从 JSON 加载“点击→等待→滚动→再点击”链 │ └── config/ │ └── default.json # 默认行为配置模板(含停留时间范围、点击坐标偏移、JS 执行时机等) ├── resources/ │ ├── icons/ # 界面图标 │ └── screenshots/ # 运行时自动保存的截图(按 task_id 命名) └── CMakeLists.txt # 关键:必须启用 WEBENGINE 模块,且链接 Qt5::WebEngineWidgets提示:
TaskWorker是最值得深挖的模块——它不是简单地load()一个 URL,而是在QWebEnginePage::loadFinished后主动注入自定义 JS 脚本,监听DOMContentLoaded和load事件,并在 DOM 就绪后才启动 BehaviorEngine。这意味着所有点击/滚动操作都发生在真实 DOM 渲染完成之后,避免了“点击未渲染元素”的常见翻车。
2.2 核心依赖与构建约束
项目使用 CMake 构建,关键依赖项在CMakeLists.txt中明确声明:
find_package(Qt5 REQUIRED COMPONENTS Core Widgets WebEngine WebEngineWidgets) target_link_libraries(seo-sim PRIVATE Qt5::Core Qt5::Widgets Qt5::WebEngine Qt5::WebEngineWidgets)注意两点硬性要求:
- Qt 版本必须 ≥ 5.15:低版本 WebEngine 不支持
runJavaScript()的 Promise 返回,导致 BehaviorEngine 无法准确判断 JS 执行完成。 - 必须启用 WebEngineWidgets:
QWebEngineView继承自QWidget,若只链接Qt5::WebEngine(无 Widgets),编译直接失败。
常见错误是开发者本地装了 Qt6,但项目未适配(Qt6 的 WebEngine API 有 Breaking Change),此时需降级到 Qt5.15.2 官方离线安装包(Linux 下推荐qt-unified-linux-x64-online.run安装指定版本)。
2.3 行为配置 JSON 的字段语义详解
config/default.json是行为策略的源头,其结构直接影响模拟真实性。关键字段说明如下:
{ "base_delay_ms": 2000, "delay_variance_ms": 1500, "scroll_duration_ms": 1200, "scroll_easing": "easeInOutQuad", "click_offset_x_px": [-15, 15], "click_offset_y_px": [-10, 10], "js_injection_points": ["domContentLoaded", "windowLoad"], "screenshot_on_click": true, "max_scroll_depth_ratio": 0.85 }base_delay_ms+delay_variance_ms:构成停留时间正态分布基础,实际停留 =base_delay_ms ± rand(0, delay_variance_ms),避免固定间隔被识别为机器行为;scroll_easing:仅支持"linear"/"easeInQuad"/"easeOutQuad"/"easeInOutQuad"四种,对应不同滚动加速度曲线,easeInOutQuad最接近人类自然滚动;click_offset_x_px:点击坐标在目标元素中心的随机偏移范围(单位 px),强制非精准点击,绕过部分网站的 clickbot 检测;js_injection_points:指定 JS 注入时机,"domContentLoaded"保证 DOM 就绪但资源未全加载,"windowLoad"则等待全部资源(含图片),二者可组合使用;max_scroll_depth_ratio:限制最大滚动比例(如 0.85 = 最多滚动到页面 85% 位置),避免“滚到底部”这种高风险行为。
这些参数不是拍脑袋定的,而是参考了 Chrome UX Report(CrUX)中真实用户滚动深度分布数据——这也是它比脚本式点击器更难被识别的原因。
3. 编译与运行:从源码到可执行文件的完整链路
本节实操覆盖 Windows 10/11、Ubuntu 22.04、macOS Monterey 三平台共性流程。所有命令均在项目根目录(seo-sim/)下执行。
3.1 环境准备:Qt 安装与路径校验
Windows(推荐 MSVC 2019 工具链)
- 下载 Qt Online Installer,勾选
Qt 5.15.2→MSVC 2019 64-bit→WebEngine组件; - 安装后,在
x64 Native Tools Command Prompt for VS 2019中执行:
若返回路径(如where qt-cmakeC:\Qt\5.15.2\msvc2019_64\bin\qt-cmake.exe),说明环境就绪。
Ubuntu 22.04
- 安装系统依赖:
sudo apt update && sudo apt install -y build-essential libgl1 libxcb-xinerama0 libxcb-xinerama0-dev libxcb-xinput-dev libxcb-xinput0 - 下载 Qt 5.15.2 Linux 离线包,运行安装器,勾选
Desktop gcc_64和WebEngine; - 添加环境变量:
echo 'export PATH="/opt/Qt5.15.2/5.15.2/gcc_64/bin:$PATH"' >> ~/.bashrc source ~/.bashrc qt-cmake --version # 应输出 3.21+
macOS
- 使用 Homebrew 安装 CMake 和 Ninja:
brew install cmake ninja - Qt 安装同 Windows 步骤,选择
macOS版本; - 确保 Xcode Command Line Tools 已安装:
xcode-select --install
注意:Qt WebEngine 在 macOS 上必须使用
clang++编译,且需关闭 SIP(System Integrity Protection)对/usr/lib的写保护(仅限开发机,生产环境不建议)。若编译报ld: library not found for -lQt5WebEngineCore,请检查 Qt 安装路径是否含空格(如Qt 5.15.2→ 改为Qt5152)。
3.2 CMake 配置与构建
创建独立构建目录(避免污染源码):
mkdir build && cd build执行 CMake 配置(关键参数说明见注释):
# Windows (MSVC) cmake -G "Ninja" ^ -DCMAKE_PREFIX_PATH="C:/Qt/5.15.2/msvc2019_64" ^ -DQt5_DIR="C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5" ^ -DCMAKE_BUILD_TYPE=Release ^ .. # Ubuntu/macOS cmake -G "Ninja" \ -DCMAKE_PREFIX_PATH="/opt/Qt5.15.2/5.15.2/gcc_64" \ -DQt5_DIR="/opt/Qt5.15.2/5.15.2/gcc_64/lib/cmake/Qt5" \ -DCMAKE_BUILD_TYPE=Release \ ..-G "Ninja":比 Make 更快的构建生成器,尤其适合 Qt 多文件项目;-DCMAKE_PREFIX_PATH:指向 Qt 安装根目录,CMake 由此查找Qt5WebEngineConfig.cmake;-DQt5_DIR:显式指定 Qt5 CMake 模块路径,避免find_package(Qt5)失败;-DCMAKE_BUILD_TYPE=Release:必须为 Release,Debug 模式下 WebEngine 渲染性能下降 40%+,且日志冗余干扰行为观察。
构建命令统一为:
ninja成功后,build/目录下生成可执行文件:
- Windows:
seo-sim.exe - Ubuntu:
seo-sim - macOS:
seo-sim.app/Contents/MacOS/seo-sim
3.3 首次运行与基础验证
运行前确保目标测试页面已部署(如本地http://localhost:8000/test-page.html),然后:
# Windows seo-sim.exe # Ubuntu/macOS ./seo-sim首次启动界面会显示:
- 左侧:关键词输入框(填入
test-keyword)、并发数滑块(默认 3)、启动按钮; - 右侧:实时日志面板(含时间戳、task_id、事件类型如
LOAD_START,DOM_READY,CLICK_EXECUTED); - 底部状态栏:显示当前活跃 worker 数、总完成数、最后截图路径。
基础验证步骤(5 分钟内可完成):
- 输入本地测试页 URL(如
http://localhost:8000/test-page.html)到关键词框; - 滑块设为 1,点击【启动】;
- 观察日志:应依次出现
LOAD_START → LOAD_FINISHED → DOM_READY → SCROLL_START → CLICK_EXECUTED → SCREENSHOT_SAVED; - 检查
resources/screenshots/下是否生成task_001_20240520_142215.png类似命名的截图; - 打开截图,确认鼠标箭头出现在页面中部偏下位置(符合
max_scroll_depth_ratio: 0.85设置)。
若卡在LOAD_FINISHED无后续日志,大概率是网页 JS 报错阻塞了DOMContentLoaded事件——此时需打开TaskWorker.cpp,在onLoadFinished槽函数中添加page()->runJavaScript("console.error('DEBUG: window.onerror', window.onerror);");临时诊断。
4. 核心模块代码逐行解析:BehaviorEngine 如何让点击“像人”
BehaviorEngine 是整个项目的技术心脏。它不调用QWebEngineView::triggerPageAction(QWebEnginePage::Forward)这类高层 API,而是通过QWebEnginePage::runJavaScript()注入原生 JS,直接操控document.elementFromPoint()和MouseEvent构造器。下面以MouseSimulator.cpp中最关键的simulateClickAtElement()函数为例,逐段拆解。
4.1 模拟点击的 JS 注入逻辑
// src/BehaviorEngine/MouseSimulator.cpp void MouseSimulator::simulateClickAtElement(const QString &selector) { // 1. 先用 document.querySelector 找到元素,避免 getElementById 的局限性 QString jsFindElement = QString(R"( (function() { const el = document.querySelector('%1'); if (!el) return { success: false, reason: 'element_not_found' }; const rect = el.getBoundingClientRect(); return { success: true, x: rect.left + rect.width / 2 + %2, y: rect.top + rect.height / 2 + %3, width: rect.width, height: rect.height }; })(); )").arg(selector) .arg(QRandomGenerator::global()->bounded(-15, 16)) // 随机 X 偏移 .arg(QRandomGenerator::global()->bounded(-10, 11)); // 随机 Y 偏移 // 2. 执行查找,获取元素中心坐标(带偏移) page()->runJavaScript(jsFindElement, [this, selector](const QVariant &result) { if (!result.isValid()) { qWarning() << "JS execution failed for element lookup:" << selector; return; } auto obj = result.toMap(); if (!obj.value("success").toBool()) { qWarning() << "Element not found:" << selector << obj.value("reason").toString(); return; } // 3. 构造 MouseEvent 并 dispatch double x = obj.value("x").toDouble(); double y = obj.value("y").toDouble(); QString jsClick = QString(R"( (function(x, y) { const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: x, clientY: y, button: 0 }); const target = document.elementFromPoint(x, y); if (target) { target.dispatchEvent(event); return { success: true, target: target.tagName }; } else { return { success: false, reason: 'no_element_at_point' }; } })(%1, %2); )").arg(x).arg(y); page()->runJavaScript(jsClick, [this](const QVariant &clickResult) { if (clickResult.isValid()) { auto res = clickResult.toMap(); if (res.value("success").toBool()) { emit clickExecuted(res.value("target").toString()); } else { qWarning() << "Click dispatch failed:" << res.value("reason").toString(); } } }); }); }关键点说明:
- 第 1 步 JS 字符串拼接:使用
QString::arg()注入随机偏移,确保每次点击坐标微变,而非固定中心点; - 第 2 步坐标计算:
getBoundingClientRect()返回相对于视口的坐标,clientX/clientY正需此值,避免offsetTop/Left的文档流依赖; - 第 3 步事件构造:显式设置
bubbles: true和cancelable: true,确保事件能冒泡到document层,兼容 jQuery.on('click')绑定; elementFromPoint()二次校验:在 dispatch 前再次用坐标反查元素,防止滚动后原元素移出视口导致点击失效——这是很多“点击器”翻车的根源。
4.2 滚动行为的缓动算法实现
ScrollSimulator.cpp中的scrollToDepth()使用纯 C++ 实现 CSS easing 函数,不依赖 JS:
// src/BehaviorEngine/ScrollSimulator.cpp double ScrollSimulator::easeInOutQuad(double t) { // t in [0,1], output in [0,1] return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; } void ScrollSimulator::scrollToDepth(double depthRatio, int durationMs) { QWebEnginePage *page = this->page(); QRectF viewport = page->viewportSize(); // 获取当前视口尺寸 int totalHeight = page->contentsSize().height(); // 页面总高度 int targetY = static_cast<int>(totalHeight * depthRatio); // 计算起始 Y(当前滚动位置) QWebEngineSettings *settings = page->settings(); QVariant scrollPos = page->property("currentScrollY"); int startY = scrollPos.isValid() ? scrollPos.toInt() : 0; // 生成 30 帧动画(每帧 ~33ms) int frameCount = qMax(1, durationMs / 33); for (int i = 0; i <= frameCount; ++i) { double t = static_cast<double>(i) / frameCount; // 归一化时间 [0,1] double easedT = easeInOutQuad(t); // 应用缓动 int currentY = startY + static_cast<int>((targetY - startY) * easedT); // 执行滚动(Qt WebEngine 不支持平滑滚动 API,只能 setScrollPosition) page->runJavaScript(QString("window.scrollTo(0, %1);").arg(currentY)); // 强制等待,避免 JS 执行过快 QEventLoop loop; QTimer::singleShot(33, &loop, &QEventLoop::quit); loop.exec(); } // 更新属性缓存 page->setProperty("currentScrollY", targetY); }为什么不用QWebEnginePage::triggerPageAction(QWebEnginePage::MoveToNextBlock)?
因为该 API 是键盘导航模拟,滚动步长固定(约 100px),无法精确控制到depthRatio,且不支持缓动。而手动scrollTo()+ 定时器,才能复现人类“先快后慢”的滚动节奏。
4.3 行为链的 JSON 解析与执行调度
InteractionPlanner.cpp将default.json中的行为序列转为QList<InteractionStep>,每步含类型、参数、延迟:
// src/BehaviorEngine/InteractionPlanner.cpp struct InteractionStep { enum Type { CLICK, SCROLL, WAIT, JS_EXEC }; Type type; QString param; // selector for CLICK, depth for SCROLL, js code for JS_EXEC int delayMs; // 此步前的等待时间 }; QList<InteractionStep> InteractionPlanner::parseFromJson(const QJsonObject &config) { QList<InteractionStep> steps; // 解析 "steps" 数组(若存在),否则用默认单步 QJsonArray stepsArray = config.value("steps").toArray(); if (stepsArray.isEmpty()) { // 默认行为:滚动到 85% → 等待 2s → 点击第一个 .result-link steps << InteractionStep{SCROLL, "0.85", 0} << InteractionStep{WAIT, "", 2000} << InteractionStep{CLICK, ".result-link:first-of-type", 0}; } else { for (const QJsonValue &val : stepsArray) { QJsonObject stepObj = val.toObject(); InteractionStep step; step.type = stringToType(stepObj.value("type").toString()); step.param = stepObj.value("param").toString(); step.delayMs = stepObj.value("delay_ms").toInt(0); steps << step; } } return steps; }调度逻辑在TaskWorker::startBehaviorSequence()中:
- 每步执行前,先
QTimer::singleShot(delayMs, ...)等待; - 执行时根据
step.type调用MouseSimulator::simulateClickAtElement()或ScrollSimulator::scrollToDepth(); - 所有步骤串行执行,但每步内部(如滚动)是异步 JS 注入,因此整体是“伪同步”——这正是它能稳定运行的关键:不阻塞 Qt 事件循环。
5. 避坑指南:5 个血泪经验换来的高频问题排查清单
开发和调试这类 WebEngine 行为模拟项目,踩过的坑比代码行数还多。以下 5 条是反复验证过的真问题,按现象→原因→解决三段式整理,拒绝模糊描述。
5.1 现象:启动后界面空白,日志只显示LOAD_START,无任何后续事件
原因:目标网页启用了 CSP(Content Security Policy),禁止unsafe-eval或内联脚本,导致runJavaScript()注入的 JS 被浏览器拦截。QtWebEngine 默认不报告 CSP 错误,静默失败。
解决:在TaskWorker::onLoadFinished()中添加 CSP 检测:
page()->runJavaScript("if (window.performance && window.performance.getEntriesByType) {" " const entries = window.performance.getEntriesByType('navigation');" " if (entries.length > 0 && entries[0].renderBlockingStatus === 'blocked') {" " console.warn('CSP may block JS execution');" " }" "}");更彻底方案:在QWebEngineProfile中禁用 CSP(仅限测试环境):
QWebEngineProfile *profile = new QWebEngineProfile(this); profile->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true); // 关键:禁用 CSP(Qt 5.15+ 支持) profile->settings()->setAttribute(QWebEngineSettings::WebSecurityEnabled, false);5.2 现象:点击日志显示CLICK_EXECUTED,但页面无反应,截图中鼠标未移动
原因:elementFromPoint()返回null,因为目标元素被position: fixed的悬浮层(如广告 banner)遮挡,或元素visibility: hidden但display: block。
解决:改用document.elementsFromPoint()获取所有叠层元素,过滤出可见且可点击的:
const candidates = document.elementsFromPoint(x, y); for (let el of candidates) { if (el.matches(selector) && window.getComputedStyle(el).visibility === 'visible' && el.offsetParent !== null) { // offsetParent 为 null 表示被隐藏 el.dispatchEvent(event); break; } }5.3 现象:滚动到指定深度后,elementFromPoint()仍返回null,点击失败
原因:滚动完成后,页面 JS 可能触发动态内容加载(如无限滚动),导致新 DOM 节点插入,原getBoundingClientRect()坐标失效。
解决:滚动后强制等待MutationObserver稳定:
new Promise(resolve => { const observer = new MutationObserver(() => { observer.disconnect(); resolve(); }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(resolve, 1000); // 最大等待 1s });在 C++ 层,用QTimer::singleShot(1000, ...)替代硬等待。
5.4 现象:多并发运行时,部分 worker 卡死在LOAD_FINISHED,CPU 占用 100%
原因:QtWebEngine 在多实例下共享 GPU 进程,当并发数超过 GPU 内存阈值(通常 3–4 个),渲染进程僵死。Qt 5.15 默认启用--disable-gpu-sandbox,但未解决内存争抢。
解决:为每个QWebEngineProfile分配独立存储路径,隔离 GPU 上下文:
QString profilePath = QDir::tempPath() + "/webprofile_" + QString::number(qHash(QThread::currentThreadId())); QWebEngineProfile *profile = new QWebEngineProfile(profilePath, this);同时在CMakeLists.txt中添加编译定义:
add_definitions(-DQTWEBENGINE_DISABLE_SANDBOX)5.5 现象:macOS 上编译通过,但运行时报dyld: Library not loaded: @rpath/QtWebEngineCore.framework/Versions/5/QtWebEngineCore
原因:Qt WebEngine 框架的@rpath未正确嵌入可执行文件,macOS 动态链接器找不到依赖。
解决:构建后执行macdeployqt工具(Qt 自带):
cd build macdeployqt seo-sim.app -dmg -verbose=2若macdeployqt不在 PATH,用完整路径:/opt/Qt5.15.2/5.15.2/clang_64/bin/macdeployqt。
6. 二次开发实战:从“模拟点击”到“可验证 SEO 行为分析器”
把这套代码改造成真正的 SEO 分析工具,核心思路不是增加更多“点击”功能,而是把每一次行为变成可观测、可归因、可回溯的数据节点。我最近在一个客户项目里,就是基于此源码做了三个关键改造,现在每天自动生成《搜索结果页用户行为热力图》报告。
6.1 改造一:为每次点击注入唯一 trace_id,串联全链路日志
原始代码中,每个TaskWorker的日志是孤立的。我在TaskWorker构造时生成 UUID,并注入到所有 JS 执行上下文中:
// src/TaskWorker.cpp TaskWorker::TaskWorker(QObject *parent) : QObject(parent) { m_traceId = QUuid::createUuid().toString(QUuid::WithoutBraces); // 注入全局 trace_id 到页面 page()->runJavaScript(QString("window.__SEO_TRACE_ID = '%1';").arg(m_traceId)); }然后在所有runJavaScript()调用前,追加日志上报:
// 封装一个安全的 JS 执行方法 void TaskWorker::safeRunJs(const QString &jsCode) { QString wrapped = QString(R"( (function() { try { %1 console.log('[SEO-TRACE]', '%2', 'JS_EXEC_SUCCESS'); } catch(e) { console.error('[SEO-TRACE]', '%2', 'JS_EXEC_ERROR', e); } })(); )").arg(jsCode).arg(m_traceId); page()->runJavaScript(wrapped); }效果:所有浏览器控制台日志、Qt 日志、截图文件名都带trace_id,用grep trace_id *.log即可拉出完整行为链。
6.2 改造二:截取关键性能指标,生成 Lighthouse 兼容 JSON
在onLoadFinished后,不再只截图,而是调用 Chrome DevTools Protocol(CDP)等效 API 获取性能数据:
// src/TaskWorker.cpp void TaskWorker::collectPerformanceMetrics() { // QtWebEngine 未暴露 CDP,但可通过 JS 获取 Performance API 数据 QString perfJs = R"( (function() { const perf = window.performance; const nav = perf.getEntriesByType('navigation')[0]; const paint = perf.getEntriesByType('paint'); return { fcp: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0, lcp: paint.find(p => p.name === 'largest-contentful-paint')?.startTime || 0, ttfb: nav?.responseStart - nav?.requestStart || 0, domContentLoaded: nav?.domContentLoadedEventEnd || 0, loadEvent: nav?.loadEventEnd || 0 }; })(); )"; page()->runJavaScript(perfJs, [this](const QVariant &result) { if (result.isValid()) { QJsonObject metrics = result.toJsonObject(); // 保存为 trace_id_metrics.json QFile file(QString("resources/metrics/%1_metrics.json").arg(m_traceId)); if (file.open(QIODevice::WriteOnly)) { file.write(QJsonDocument(metrics).toJson()); file.close(); } } }); }生成的 JSON 可直接喂给开源工具lighthouse-ci做历史趋势对比。
6.3 改造三:用 SQLite 替代纯文本日志,支持 SQL 查询分析
原始日志是行式文本,难做聚合。我新增src/db/模块,用QSqlDatabase创建seo_behavior.db:
CREATE TABLE IF NOT EXISTS behavior_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, trace_id TEXT NOT NULL, event_type TEXT NOT NULL, -- 'LOAD_START', 'CLICK', 'SCROLL' timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, details TEXT, -- JSON 字符串,存坐标、selector 等 screenshot_path TEXT );所有emit信号(如clickExecuted())都在槽函数中插入数据库,而非只打日志。这样就能跑 SQL:
SELECT event_type, COUNT(*) as cnt FROM behavior_log WHERE trace_id = 'a1b2c3...' GROUP BY event_type;从那以后我每次交付 SEO 工具,都强制走一遍「trace_id 全链路注入 + SQLite 结构化存储 + 性能指标采集」三件套。不是为了炫技,而是当客户问“你们怎么证明这个点击是真实的”,我能直接打开数据库,
SELECT * FROM behavior_log WHERE trace_id = 'xxx'—— 每一行都是不可抵赖的行为证据。希望帮到你。
本文还有配套的精品资源,点击获取