Qt QWebSocket实战:从零构建实时双向通信服务端与客户端
1. 项目概述为什么选择QWebSocket在开发需要实时双向通信的桌面应用时我们常常面临选择。传统的HTTP轮询效率低下长轮询又不够优雅。而WebSocket协议作为HTML5标准的一部分为我们提供了全双工、低延迟的通信能力。对于Qt开发者而言QWebSocket模块的出现让我们能够以非常Qt的方式在服务端和客户端轻松集成WebSocket功能无需引入复杂的第三方库。这个项目就是一次基于QWebSocket的完整实践。我将带你从零开始构建一个可运行的服务端和一个客户端并附上可以直接复制粘贴使用的详细代码。无论你是想为你的Qt应用添加一个实时聊天模块还是需要实现一个轻量级的实时数据监控面板这套代码都能提供一个坚实的起点。我们将重点关注连接建立、消息收发、错误处理以及连接生命周期管理这些核心环节这些都是实际项目中必然会遇到的。2. 环境准备与项目配置在开始敲代码之前确保你的开发环境已经就绪。你需要一个安装了Qt的开发环境版本建议在Qt 5.3或更高因为QWebSocket模块是在Qt 5.3中引入的。Qt Creator是一个不错的选择。2.1 在.pro文件中引入模块这是第一步也是最关键的一步。无论你的项目是新建的还是已有的都需要在.pro项目文件中添加WebSockets模块的依赖。打开你的.pro文件添加如下一行QT core gui websockets如果你的项目是纯控制台应用没有GUI那么gui可以不加但core和websockets是必须的。保存.pro文件后Qt Creator会重新解析项目这时你就可以在代码中#include QWebSocket和#include QWebSocketServer了。注意有时候添加模块后代码中#include仍然报错这可能是因为Qt Creator的代码模型没有及时更新。尝试执行“构建”-“执行qmake”或者直接关闭再重新打开项目通常可以解决。2.2 服务端与客户端的设计思路在动手之前我们先理清思路。一个典型的WebSocket应用包含两个角色服务端 (QWebSocketServer)它像一个电话总机监听特定的网络端口等待客户端的连接请求。一旦有客户端拨号进来发起连接它就创建一个专属的QWebSocket对象来代表这个具体的连接负责与此客户端进行后续的所有通信。客户端 (QWebSocket)它像一部电话主动向服务端的地址和端口发起连接。连接成功后它就可以通过这个QWebSocket对象向服务端发送消息并接收服务端的回复或推送。我们的目标是构建一个简单的“回声”服务端和一个测试客户端。服务端会将收到的任何文本消息原样发回给发送它的客户端这能最直观地验证双向通信是否畅通。3. 服务端实现详解服务端是整个通信的核心我们创建一个继承自QObject的类来实现它这样能方便地使用Qt的信号槽机制。3.1 创建WebSocket服务器首先我们需要实例化一个QWebSocketServer对象。它的构造函数需要三个参数服务器名称可以任意字符串用于标识服务器。运行模式通常是QWebSocketServer::NonSecureMode非安全模式即ws://或SslMode安全模式即wss://。我们这里先用NonSecureMode。父对象用于Qt对象树的内存管理。// websocketserver.h #ifndef WEBSOCKETSERVER_H #define WEBSOCKETSERVER_H #include QObject #include QWebSocketServer #include QWebSocket #include QList class WebSocketServer : public QObject { Q_OBJECT public: explicit WebSocketServer(quint16 port, QObject *parent nullptr); ~WebSocketServer(); private slots: void onNewConnection(); void onTextMessageReceived(const QString message); void onSocketDisconnected(); private: QWebSocketServer *m_pWebSocketServer; QListQWebSocket * m_clients; // 用于管理所有连接的客户端 }; #endif // WEBSOCKETSERVER_H// websocketserver.cpp #include “websocketserver.h” #include QDebug WebSocketServer::WebSocketServer(quint16 port, QObject *parent) : QObject(parent) , m_pWebSocketServer(new QWebSocketServer(QStringLiteral(“Echo Server”), QWebSocketServer::NonSecureMode, this)) { // 尝试监听指定端口 if (m_pWebSocketServer-listen(QHostAddress::Any, port)) { qDebug() “Echo Server listening on port” port; // 连接新连接信号 connect(m_pWebSocketServer, QWebSocketServer::newConnection, this, WebSocketServer::onNewConnection); } else { qDebug() “Failed to start server on port” port “:” m_pWebSocketServer-errorString(); } }这里有几个关键点QHostAddress::Any表示服务器将监听所有可用的网络接口如本地回环127.0.0.1和局域网IP。如果你只想本地测试可以换成QHostAddress::LocalHost。listen()方法的返回值很重要必须检查。失败的原因可能是端口已被占用或者没有权限如Linux/Mac下监听1024以下端口需要root权限。3.2 处理新客户端连接当有新的客户端连接时QWebSocketServer会发出newConnection()信号。我们在对应的槽函数中需要获取代表这个新连接的QWebSocket对象。void WebSocketServer::onNewConnection() { // 获取等待连接的客户端套接字 QWebSocket *pSocket m_pWebSocketServer-nextPendingConnection(); if (!pSocket) { return; } qDebug() “Client connected from:” pSocket-peerAddress().toString() “:” pSocket-peerPort(); // 将新客户端加入管理列表 m_clients.append(pSocket); // 连接该客户端套接字的相关信号 connect(pSocket, QWebSocket::textMessageReceived, this, WebSocketServer::onTextMessageReceived); connect(pSocket, QWebSocket::disconnected, this, WebSocketServer::onSocketDisconnected); // 可选向新连接的客户端发送欢迎消息 pSocket-sendTextMessage(QStringLiteral(“Welcome to Echo Server!”)); }nextPendingConnection()这个方法至关重要它从服务器的待处理连接队列中取出下一个连接并返回一个QWebSocket对象。这个对象就是后续与该特定客户端通信的句柄。连接管理我们将每个新的QWebSocket指针存入一个QList中。这在广播消息向所有客户端发送消息或进行连接统计时非常有用。但务必注意内存管理确保在客户端断开时将其从列表中移除并删除。信号连接我们连接了两个关键信号textMessageReceived(const QString )当客户端发送文本消息时触发。disconnected()当客户端连接断开时触发。3.3 处理消息与连接断开消息处理和连接清理是服务端稳定运行的关键。void WebSocketServer::onTextMessageReceived(const QString message) { // 获取发送消息的客户端对象 QWebSocket *pSender qobject_castQWebSocket *(sender()); if (!pSender) { return; } qDebug() “Message from client [” pSender-peerAddress().toString() “]:” message; // 实现“回声”功能将消息发回给发送者 pSender-sendTextMessage(QStringLiteral(“Echo: %1”).arg(message)); // 示例广播消息给所有客户端非回声模式 // for (QWebSocket *client : qAsConst(m_clients)) { // if (client ! pSender client-isValid()) { // 不发给发送者自己 // client-sendTextMessage(QString(“Broadcast: %1”).arg(message)); // } // } } void WebSocketServer::onSocketDisconnected() { QWebSocket *pClient qobject_castQWebSocket *(sender()); qDebug() “Client disconnected:” (pClient ? pClient-peerAddress().toString() : “Unknown”); if (pClient) { // 从管理列表中移除 m_clients.removeAll(pClient); // 断开所有信号连接 pClient-deleteLater(); // 安排对象在事件循环中安全删除 } }sender()函数在槽函数中sender()返回发出信号的对象的指针。我们通过qobject_cast将其转换为QWebSocket*以确定是哪个客户端发送了消息或断开了连接。这是一个非常Qt风格的用法。消息发送使用sendTextMessage()发送文本消息。QWebSocket也支持发送二进制数据sendBinaryMessage。连接清理在disconnected槽中必须将客户端从管理列表中移除并调用deleteLater()来安全删除对象。直接delete pClient在特定时序下可能导致程序崩溃deleteLater是更安全的做法。实操心得在实际项目中m_clients列表的访问如在广播循环中可能会遇到多线程问题。如果你的服务端逻辑复杂或连接数极高需要考虑使用QMutex等机制保护共享数据或者将网络事件分发到单独的线程中处理。对于中小规模应用在主线程即对象所在线程中处理通常是安全的因为所有信号槽调用默认在同一线程。4. 客户端实现详解客户端相对更简单它的主要任务是连接服务器并处理收发消息。4.1 建立WebSocket连接我们同样创建一个类来封装客户端功能。// websocketclient.h #ifndef WEBSOCKETCLIENT_H #define WEBSOCKETCLIENT_H #include QObject #include QWebSocket class WebSocketClient : public QObject { Q_OBJECT public: explicit WebSocketClient(const QUrl url, QObject *parent nullptr); void sendMessage(const QString message); signals: void connected(); void messageReceived(const QString message); void disconnected(); private slots: void onConnected(); void onTextMessageReceived(const QString message); void onDisconnected(); private: QWebSocket m_webSocket; QUrl m_url; }; #endif // WEBSOCKETCLIENT_H// websocketclient.cpp #include “websocketclient.h” #include QDebug WebSocketClient::WebSocketClient(const QUrl url, QObject *parent) : QObject(parent) , m_url(url) { // 连接WebSocket对象的信号 connect(m_webSocket, QWebSocket::connected, this, WebSocketClient::onConnected); connect(m_webSocket, QWebSocket::textMessageReceived, this, WebSocketClient::onTextMessageReceived); connect(m_webSocket, QWebSocket::disconnected, this, WebSocketClient::onDisconnected); // 发起连接 qDebug() “Connecting to server...” url.toString(); m_webSocket.open(url); }连接URL格式为ws://host:port或wss://host:port加密。例如连接本地服务端QUrl(“ws://127.0.0.1:12345”)。自动重连上面的代码只在构造函数中连接一次。一个健壮的客户端应该实现断线重连逻辑可以在onDisconnected槽中启动一个QTimer延时后再次调用open()。4.2 处理连接事件与收发消息客户端的槽函数实现与服务端对应。void WebSocketClient::onConnected() { qDebug() “Connected to server!”; emit connected(); // 通知外部连接成功 // 连接成功后可以立即发送一条测试消息 // sendMessage(“Hello Server!”); } void WebSocketClient::onTextMessageReceived(const QString message) { qDebug() “Message from server:” message; emit messageReceived(message); // 将消息转发给UI或其他业务模块 } void WebSocketClient::onDisconnected() { qDebug() “Disconnected from server.”; emit disconnected(); } void WebSocketClient::sendMessage(const QString message) { if (m_webSocket.state() QAbstractSocket::ConnectedState) { m_webSocket.sendTextMessage(message); qDebug() “Sent message:” message; } else { qDebug() “Cannot send message, socket is not connected.”; } }状态检查在sendMessage中我们检查了socket的状态。这是一个好习惯避免在连接不可用时尝试发送数据。信号转发我们定义了几个信号connected,messageReceived,disconnected。这样设计可以将网络层与UI层或业务逻辑层解耦。例如你的主窗口可以连接这些信号来更新界面状态。5. 主程序与测试运行有了服务端和客户端类我们需要一个main函数来启动它们。5.1 服务端主程序// server_main.cpp #include QCoreApplication #include “websocketserver.h” int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 在12345端口启动服务端 WebSocketServer server(12345); return a.exec(); }这是一个控制台应用使用QCoreApplication。编译运行后你会看到“Echo Server listening on port 12345”的输出。5.2 客户端主程序带简单UI为了直观测试我们给客户端加一个最简单的命令行交互界面。// client_main.cpp #include QCoreApplication #include QTextStream #include QTimer #include “websocketclient.h” int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QTextStream cin(stdin); QTextStream cout(stdout); WebSocketClient client(QUrl(“ws://127.0.0.1:12345”)); QObject::connect(client, WebSocketClient::connected, [cout](){ cout “*** Connected to server. Type your message (type ‘exit’ to quit):” endl; cout.flush(); }); QObject::connect(client, WebSocketClient::messageReceived, [cout](const QString msg){ cout “ ” msg endl; cout.flush(); }); QObject::connect(client, WebSocketClient::disconnected, [a](){ qDebug() “Connection closed. Exiting.”; a.quit(); }); // 简单的事件循环读取命令行输入 QTimer::singleShot(0, [client, cin, cout, a](){ QString line; while (true) { cout “ “; cout.flush(); line cin.readLine().trimmed(); if (line.isEmpty()) { continue; } if (line.compare(“exit”, Qt::CaseInsensitive) 0) { qDebug() “Exiting...”; a.quit(); break; } client.sendMessage(line); } }); return a.exec(); }这个客户端会连接本地服务端连接成功后你可以在命令行输入任何文本客户端会将其发送给服务端并立即收到服务端的“回声”回复显示在屏幕上。5.3 编译与运行分别创建两个Qt项目或在一个项目的两个子目录中正确配置.pro文件QT core websockets对于服务端和命令行客户端可能就够了如果客户端有GUI则需要加gui和widgets。先编译运行服务端程序确保它成功监听端口。再编译运行客户端程序。观察两者的控制台输出并在客户端输入消息进行测试。6. 进阶话题与性能调优一个基础的“回声”服务器跑起来后我们可以考虑更多实际场景下的问题。6.1 心跳机制与连接保活在网络不稳定的环境下TCP连接可能“半死不活”即系统未及时感知断开。为了检测这种死连接需要实现心跳机制。服务端实现思路为每个连接的QWebSocket关联一个QTimer。定时如每30秒向客户端发送一个特定的“心跳”消息如字符串“PING”。客户端收到后需立即回复“PONG”。服务端在另一个定时器里检查如果某个连接在超时时间内如60秒未收到任何消息包括PONG和业务消息则判定其失效主动断开连接。客户端实现思路类似可以主动向服务端发送心跳并期待回复。6.2 二进制数据传输除了文本QWebSocket也完美支持二进制数据适用于传输图片、文件或自定义协议包。// 发送二进制数据 QByteArray data ...; // 你的图片或文件数据 pWebSocket-sendBinaryMessage(data); // 接收二进制数据 connect(pWebSocket, QWebSocket::binaryMessageReceived, this, [](const QByteArray message){ // 处理二进制消息 });注意事项传输大文件时不宜一次性调用sendBinaryMessage这可能导致内存压力和网络阻塞。应考虑分片传输定义自己的协议头包含文件信息、分片序号等。6.3 多线程与连接管理当需要处理成千上万的并发连接时单线程模型会成为瓶颈。Qt提供了几种方案每连接一线程传统但资源消耗大不推荐用于高并发。线程池使用QThreadPool和QRunnable将收到消息后的业务逻辑处理任务丢到线程池中网络IO仍在主线程。这是比较常见的折中方案。基于QAbstractSocket的非阻塞IO与多线程QWebSocket底层基于QTcpSocket你可以深入底层使用QThread创建多个工作线程每个线程运行一个QEventLoop并使用moveToThread将一批socket移到工作线程中进行读写。这需要更精细的控制。对于绝大多数应用主线程处理网络IO配合线程池处理业务逻辑已经足够。关键在于不要在textMessageReceived或binaryMessageReceived的槽函数中执行耗时操作如复杂的数据库查询、图像处理应尽快返回将任务派发出去。6.4 错误处理与日志完善的错误处理是服务稳定的基石。连接QWebSocket::error信号处理网络错误。检查sendTextMessage返回值虽然它是异步的但可以检查state()。记录日志使用qDebug(),qWarning(),qCritical()等分级记录日志。生产环境应考虑使用更专业的日志库并记录连接、断开、消息流量等关键信息便于故障排查。7. 常见问题排查与调试技巧在实际开发中你肯定会遇到各种问题。这里记录一些典型场景和排查思路。问题1服务端启动失败提示“Address already in use”原因端口被占用。解决换一个端口号。在命令行Linux/Mac用lsof -i :端口号Windows用netstat -ano | findstr :端口号查找占用进程并结束它。确保你的程序没有重复启动。问题2客户端连接失败提示“Connection refused”原因服务端没有运行。服务端监听的IP地址和客户端连接的地址不匹配例如服务端监听127.0.0.1客户端连接192.168.1.x。防火墙阻止了连接。解决确认服务端程序已运行并成功监听。服务端使用QHostAddress::Any监听所有IP。客户端检查连接URL是否正确。临时关闭防火墙或添加规则放行对应端口。问题3能连接但收不到消息或消息延迟原因代码逻辑错误信号槽没有正确连接。事件循环阻塞在槽函数中执行了耗时操作导致Qt事件循环被卡住无法及时处理网络事件。排查在connected(),textMessageReceived()等槽函数开始处添加qDebug()输出确认信号是否触发。检查是否在主线程UI线程中执行了sleep()或长时间循环。任何可能阻塞主线程的操作都应移到工作线程。问题4内存泄漏连接数增多后内存持续增长原因QWebSocket对象没有正确删除。检查确保在disconnected()信号对应的槽中调用了deleteLater()。确保从管理列表如m_clients中移除了该对象指针。可以使用Qt Creator的调试模式或工具如heob、valgrind检测内存泄漏。调试技巧使用qDebug()进行跟踪在关键步骤连接、断开、收/发消息打印信息这是最直接的调试手段。利用Qt Creator的调试器可以设置断点查看QWebSocket对象的状态state()、错误error()等属性。网络抓包工具对于复杂的协议问题使用Wireshark等工具抓取localhost或本机网卡上的WebSocket流量可以清晰看到握手过程和数据帧是排查网络层问题的终极武器。这套基于QWebSocket的服务端和客户端实现代码结构清晰功能完整可以直接作为你项目中的通信模块使用。从简单的回声测试到复杂的实时数据交互其核心框架是相通的。理解并处理好连接管理、消息分发、错误处理和资源释放就能构建出稳定可靠的实时通信功能。

相关新闻

参与开源项目的收获与反思:代码写得再好,文档不好没人用

参与开源项目的收获与反思:代码写得再好,文档不好没人用

参与开源项目的收获与反思:代码写得再好,文档不好没人用 一、一份 2000 行的 Pull Request,被 Reviewer 打了回来 PR 描述写了 300 字,代码通过了所有单元测试,性能提升了 12%。但 Reviewer 的第一条评论只有五个字&…

2026/10/14 23:51:51 阅读更多 →
天气丹料体拿货内行话:源头工厂拆解韩系贵妇霜的利润与防坑术

天气丹料体拿货内行话:源头工厂拆解韩系贵妇霜的利润与防坑术

做韩系贵妇霜白牌定制的老板,别上来就问“能不能做某品牌同款”,先算清楚料体成本占比和乳化粒径公差。我在这行十几年,见过太多拿着大牌图片来询价,一听正规料体38元/kg就说贵,转头去拿18元/kg的“矿物油香精”垃圾料…

2026/10/14 10:57:54 阅读更多 →
品牌口红小样进货门道:别被低价包材骗了,工艺底牌全拆解

品牌口红小样进货门道:别被低价包材骗了,工艺底牌全拆解

拿着大牌口红小样图片来问代工价,开口就要一毛五一支,还要求灌装正装同款料体——这种单子我直接劝退,因为做出来就是给自己埋雷。▼ 源头车间质检备案与合作授权说明 ▼小样灌装的精度硬伤:料体成本只是冰山一角很多人以为小样就…

2026/10/15 4:08:33 阅读更多 →

最新新闻

2025北森测评题库刷题攻略:言语理解、图形推理与资料分析备考方法

2025北森测评题库刷题攻略:言语理解、图形推理与资料分析备考方法

简介:2025北森测评题库是一份面向校招、社招求职者备考北森在线测评的练习资料,聚焦言语理解、资料分析、图形推理三大高频模块,适合用来熟悉题型、训练逻辑思维与提升做题速度。资源包为单个docx文档,大小约6.18MB,文…

2026/10/15 23:21:41 阅读更多 →
TOGAF企业架构实战指南:十个阶段、落地工具与常见误区

TOGAF企业架构实战指南:十个阶段、落地工具与常见误区

我在参与企业信息化规划时,见过最多的场面是:业务部门拉出一张组织架构图讲需求,IT部门打开一堆系统清单讲现状,两边聊了几个小时,最后没有一张图能把“业务为什么需要、系统怎么支持、数据怎么流转”串起来。后来引入…

2026/10/15 23:21:41 阅读更多 →
UEditor公式插件集成指南:从LaTeX录入到回显的完整实践

UEditor公式插件集成指南:从LaTeX录入到回显的完整实践

简介:一份整合了 kityformula-plugin 数学公式插件的 UEditor 编辑器资源包,面向需要在网页或博客后台录入数学公式的开发者、教育类站点运维者及前端工程师。插件与编辑器深度结合后,可在富文本中直接插入与编辑 LaTeX 风格的公式&#xff0…

2026/10/15 23:21:41 阅读更多 →
手把手教你用 Codex 打造 AI 编程工作流:从需求拆解到代码验收(TaoToken 统一 Key 接入版)

手把手教你用 Codex 打造 AI 编程工作流:从需求拆解到代码验收(TaoToken 统一 Key 接入版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 23:21:41 阅读更多 →
C#版PACS源码拆解:DICOM协议与.NET影像链路全解析

C#版PACS源码拆解:DICOM协议与.NET影像链路全解析

简介:这套资源是一套基于C#与.NET框架开发的全套PACS(医学影像存档与通信系统)源码,适合医疗信息化开发者、HIS/LIS系统集成人员以及想了解医学影像技术的软件工程师。源码围绕图像采集、DICOM通信、SQL数据库存储、工作站显示等核…

2026/10/15 23:21:41 阅读更多 →
Python函数全解析:从基础语法到工程实践

Python函数全解析:从基础语法到工程实践

函数是每个 Python 开发者都绕不开的核心话题,无论你写的是几十行的脚本,还是几千文件的大型系统,本质上都是在定义函数、调用函数、组织函数。很多人学完基础语法后写代码总感觉“差点意思”,代码又长又乱,改一个逻辑…

2026/10/15 23:20:40 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →