现代C++:Easylogging++和spdlog:两个好用的日志库
上一章正文我介绍了两个可以在 C 中进行单元测试的库。今天类似的我介绍两个实用的日志库分别是 Easylogging [1] 和 spdlog [2]。Easylogging事实事实上我本来想只介绍 Easylogging 的。但在检查其 GitHub 页面时我发现了一个问题它在 2019 年基本没有更新且目前上报的问题也没有人处理。这是个潜在问题除非你觉得这个库好到愿意自己动手修问题话说回来这个库还是不错的我在这个项目贡献了 8 个被合并的 pull request。不管怎样原先说了要介绍这个库所以我也还是介绍一下。概述Easylogging 一共只有两个文件一个是头文件一个是普通 C 源文件。事实上它的一个较早版本只有一个文件。正如 Catch2 里一旦定义了 CATCH_CONFIG_MAIN 编译速度会大大减慢一样把什么东西都放一起最终证明对编译速度还是相当不利的因此有人提交了一个补丁把代码拆成了两个文件。使用 Easylogging 也只需要这两个文件——除此之外就只有对标准和系统头文件的依赖了。要使用 Easylogging推荐直接把这两个文件放到你的项目里。Easylogging 有很多的配置项会影响编译结果我们先大致查看一下常用的可配置项ELPP_UNICODE启用 Unicode 支持为在 Windows 上输出混合语言所必需ELPP_THREAD_SAFE启用多线程支持ELPP_DISABLE_LOGS全局禁用日志输出ELPP_DEFAULT_LOG_FILE定义缺省日志文件名称ELPP_NO_DEFAULT_LOG_FILE不使用缺省的日志输出文件ELPP_UTC_DATETIME在日志里使用协调世界时而非本地时间ELPP_FEATURE_PERFORMANCE_TRACKING开启性能跟踪功能ELPP_FEATURE_CRASH_LOG启用 GCC 专有的崩溃日志功能ELPP_SYSLOG允许使用系统日志Unix 世界的 syslog来记录日志ELPP_STL_LOGGING允许在日志里输出常用的标准容器对象std::vector 等ELPP_QT_LOGGING允许在日志里输出 Qt 的核心对象QVector 等ELPP_BOOST_LOGGING允许在日志里输出某些 Boost 的容器boost::container::vector 等ELPP_WXWIDGETS_LOGGING允许在日志里输出某些 wxWidgets 的模板对象wxVector 等可以看到Easylogging 的功能还是很丰富很全面的。开始使用 Easylogging虽说 Easylogging 的功能非常多但开始使用它毫不困难。我们从一个简单的例子开始看一下#include easylogging.h INITIALIZE_EASYLOGGINGPP int main() { LOG(INFO) My first info log; }编译链接的时候要把 easylogging.cc 放进去。比如使用 GCC 的话命令行会像g -stdc17 test.cpp easylogging.cc运行生成的可执行程序你就可以看到结果输出到终端和 myeasylog.log 文件里包含了日期、时间、级别、日志名称和日志信息形如2020-01-25 20:47:50,990 INFO [default] My first info log如果你对上面用到的宏感到好奇的话 INITIALIZE_EASYLOGGINGPP 展开后可以用编译器的 -E 参数查看宏展开后的结果是定义了 Easylogging 使用到的全局对象而 LOG(INFO) 则是 Info 级别的日志记录器同时传递了文件名、行号、函数名等日志需要的信息。使用 Unicode如果你在 Windows 上那有一个复杂性就是是否使用“Unicode”的问题[第 11 讲] 中讨论了。就我们日志输出而言启用 Unicode 支持的好处是可以使用宽字符来输出日志文件的格式是 UTF-8而不是传统的字符集只能支持一种文字要启用 Unicode 支持你需要定义宏 ELPP_UNICODE并确保程序中有对标准输出进行区域或格式设置如[第 11 讲] 中所述需要进行设置才能输出含非 ASCII 字符的宽字符串。下面的程序给出了一个简单的示例#ifdef _WIN32 #include fcntl.h #include io.h #else #include locale #endif #include easylogging.h INITIALIZE_EASYLOGGINGPP int main() { #ifdef _WIN32 _setmode(_fileno(stdout), _O_WTEXT); #else using namespace std; locale::global(locale()); wcout.imbue(locale()); #endif LOG(INFO) L测试 test; LOG(INFO) Narrow ASCII always OK; }编译使用的命令行是cl /EHsc /DELPP_UNICODE test.cpp easylogging.cc改变输出文件名Easylogging 的缺省输出日志名为 myeasylog.log这在大部分情况下都是不适用的。我们可以直接在命令行上使用宏定义来修改当然稍大点的项目就应该放在项目的编译配置文件里了如 Makefile。比如要把输出文件名改成 test.log我们只需要在命令行上加入下面的选项就可以-DELPP_DEFAULT_LOG_FILE\test.log\使用配置文件设置日志选项不过对于日志文件名称这样的设置使用配置文件是一个更好的办法。Easylogging 库自己支持配置文件我也推荐使用一个专门的配置文件并让 Easylogging 自己来加载配置文件。我自己使用的配置文件是这个样子的* GLOBAL: FORMAT %datetime{%Y-%M-%d %H:%m:%s.%g} %levshort %msg FILENAME test.log ENABLED true TO_FILE true ## 输出到文件 TO_STANDARD_OUTPUT true ## 输出到标准输出 SUBSECOND_PRECISION 6 ## 秒后面保留 6 位 MAX_LOG_FILE_SIZE 2097152 ## 最大日志文件大小设为 2MB LOG_FLUSH_THRESHOLD 10 ## 写 10 条日志刷新一次缓存 * DEBUG: FORMAT %datetime{%Y-%M-%d %H:%m:%s.%g} %levshort [%fbase:%line] %msg TO_FILE true TO_STANDARD_OUTPUT false ## 调试日志不输出到标准输出这个配置文件里有两节第一节是全局global配置配置了适用于所有级别的日志选项第二节是专门用于调试debug级别的配置你当然也可以自己配置 fatal、error、warning 等其他级别。假设这个配置文件的名字是 log.conf我们在代码中可以这样使用#include easylogging.h INITIALIZE_EASYLOGGINGPP int main() { el::Configurations conf{ log.conf}; el::Loggers:: reconfigureAllLoggers(conf); LOG(DEBUG) A debug message; LOG(INFO) An info message; }注意编译命令行上应当加上 -DELPP_NO_DEFAULT_LOG_FILE否则 Easylogging 仍然会生成缺省的日志文件。运行生成的可执行程序我们会在终端上看到一条信息但在日志文件里则可以看到两条信息。如下所示2020-01-26 12:54:58.986739 D [test.cpp:11] A debug message2020-01-26 12:54:58.987444 I An info message我们也可以明确看到我们在配置文件中定义的日志格式生效了包括日期时间的格式使用“.”分隔秒的整数和小数部分并且小数部分使用 6 位日志级别使用单个大写字母对于普通的日志后面直接跟日志的信息对于调试日志则会输出文件名和行号我们现在只需要修改配置文件就能调整日志格式、决定输出和不输出哪些日志了。此外我也推荐在编译时定义宏 ELPP_DEBUG_ASSERT_FAILURE这样能在找不到配置文件时直接终止程序而不是继续往下执行、在终端上以缺省的方式输出日志了。性能跟踪Easylogging 可以用来在日志中记录程序执行的性能数据。这个功能还是很方便的。下面的代码展示了用于性能跟踪的三个宏的用法#include chrono #include thread #include easylogging.h INITIALIZE_EASYLOGGINGPP void foo() { TIMED_FUNC(timer); LOG(WARNING) A warning message; } void bar() { using namespace std::literals; TIMED_SCOPE(timer1, void bar()); foo(); foo(); TIMED_BLOCK(timer2, a block) { foo(); std::this_thread::sleep_for(100us); } } int main() { el::Configurations conf{ log.conf}; el::Loggers:: reconfigureAllLoggers(conf); bar(); }简单说明一下TIMED_FUNC 接受一个参数是用于性能跟踪的对象的名字。它能自动产生函数的名称。示例中的 TIMED_FUNC 和 TIMED_SCOPE 的作用是完全相同的。TIMED_SCOPE 接受两个参数分别是用于性能跟踪的对象的名字以及用于记录的名字。如果你不喜欢 TIMED_FUNC 生成的函数名字可以用 TIMED_SCOPE 来代替。TIMED_BLOCK 用于对下面的代码块进行性能跟踪参数形式和 TIMED_SCOPE 相同。在编译含有上面三个宏的代码时需要定义宏 ELPP_FEATURE_PERFORMANCE_TRACKING。你一般也应该定义 ELPP_PERFORMANCE_MICROSECONDS来获取微秒级的精度。下面是定义了上面两个宏编译的程序的某次执行的结果不过需要注意由于 Easylogging 本身有一定开销且开销有一定的不确定性这种方式只适合颗粒度要求比较粗的性能跟踪。性能跟踪产生的日志级别固定为 Info。性能跟踪本身可以在配置文件里的 GLOBAL 节下用 PERFORMANCE_TRACKING false 来关闭。当然关闭所有 Info 级别的输出也能达到关闭性能跟踪的效果。记录崩溃日志在 GCC 和 Clang 下通过定义宏 ELPP_FEATURE_CRASH_LOG 我们可以启用崩溃日志。此时当程序崩溃时Easylogging 会自动在日志中记录程序的调用栈信息。通过记录下的信息再利用 addr2line 这样的工具我们就能知道是程序的哪一行引发了崩溃。下面的代码可以演示这一行为#include easylogging.h INITIALIZE_EASYLOGGINGPP void boom() { char* ptr nullptr; *ptr \0; } int main() { el::Configurations conf{ log.conf}; el::Loggers:: reconfigureAllLoggers(conf); boom(); }你可以自己尝试编译运行一下就会在终端和日志文件中看到崩溃的信息了。使用 macOS 的需要特别注意一下由于缺省方式产生的可执行文件是位置独立的系统每次加载程序会在不同的地址导致无法通过地址定位到程序行。在编译命令行尾部加上 -Wl,-no_pie 可以解决这个问题。其他Easylogging 还有很多其他功能我就不再一一讲解了。有些你简单试一下就可以用起来的。对于 ELPP_STL_LOGGING你也可以在包含 easylogging.h 之前包含我的 output_container.h可以达到类似的效果。此外Easylogging 的 samples 目录下有不少例子可以用作参考。比如常见的日志文件切换功能在 Easylogging 里实现是需要稍微写一点代码的Easylogging 会在文件满的时候调用你之前注册的回调函数而你需要在回调函数里对老的日志文件进行重命名、备份之类的工作samples/STL/roll-out.cpp 则提供了最简单的实现参考。注意我使用的都是全局的日志记录器但 Easylogging 允许你使用多个不同的日志记录器用于比如不同的模块或功能。你如果需要这样的功能的话也请你自行查阅文档了。spdlog跟 Easylogging 比起来spdlog 要新得多了前者是 2012 年开始的项目而后者是 2014 年开始的。我在 2016 年末开始在项目中使用 Easylogging 时Easylogging 的版本是 9.85 左右而 spdlog 大概是 0.11成熟度和热度都不那么高。整体上spdlog 也确实感觉要新很多。项目自己提到的功能点是开始使用 spdlog跟 Easylogging 的例子相对应我们以最简单的日志输出开头#include spdlog/spdlog.h int main() { spdlog::info(My first info log); }代码里看不到的是输出结果中的“info”字样是彩色的方便快速识别日志的级别。这个功能在 Windows、Linux 和 macOS 上都能正常工作对用户还是相当友好的。不过和 Easylogging 缺省就会输出到文件中不同spdlog 缺省只是输出到终端而已。你也许从代码中已经注意到spdlog 不是使用 IO 流风格的输出了。它采用跟 Python 里的 str.format 一样的方式使用大括号——可选使用序号和格式化要求——来对参数进行格式化。下面是一个很简单的例子spdlog::warn( Message with arg {}, 42); spdlog::error( {0:d}, {0:x}, {0:o}, {0:b}, 42);输出会像下面这样[2020-01-26 17:20:08.355] [warning] Message with arg 42[2020-01-26 17:20:08.355] [error] 42, 2a, 52, 101010事实上这就是 C20 的 format 的风格了——spdlog 就是使用了一个 format 的库实现 fmt [3]。设置输出文件在 spdlog 里要输出文件得打开专门的文件日志记录器下面的例子展示了最简单的用法#include spdlog/spdlog.h #include spdlog/sinks/basic_file_sink.h int main() { auto file_logger spdlog::basic_logger_mt( basic_logger, test.log); spdlog::set_default_logger( file_logger); spdlog::info(Into file: {1} {0}, world, hello); }执行之后终端上没有任何输出但 test.log 文件里就会增加如下的内容[2020-01-26 17:47:37.864] [basic_logger] [info] Intofile: hello world估计你立即会想问那我想同时输出到终端和文件该怎么办呢答案是你可以设立一个日志记录器让它有两个或更多个日志槽sink即可。示例代码如下#include memory #include spdlog/spdlog.h #include spdlog/sinks/basic_file_sink.h #include spdlog/sinks/stdout_color_sinks.h using namespace std; using namespace spdlog::sinks; void set_multi_sink() { auto console_sink make_shared stdout_color_sink_mt(); console_sink-set_level( spdlog::level::warn); console_sink-set_pattern( %H:%M:%S.%e %^%L%$ %v); auto file_sink make_sharedbasic_file_sink_mt( test.log); file_sink-set_level( spdlog::level::trace); file_sink-set_pattern( %Y-%m-%d %H:%M:%S.%f %L %v); auto logger shared_ptrspdlog::logger( new spdlog::logger( multi_sink, {console_sink, file_sink})); logger-set_level( spdlog::level::debug); spdlog::set_default_logger( logger); } int main() { set_multi_sink(); spdlog::warn( this should appear in both console and file); spdlog::info( this message should not appear in the console, only in the file); }大致说明一下console_sink 是一个指向 stdout_color_sink_mt 的智能指针我们设定让它只显示警告级别及以上的日志信息并把输出式样调整成带毫秒的时间、有颜色的短级别以及信息本身。file_sink 是一个指向 basic_file_sink_mt 的智能指针我们设定让它显示跟踪级别及以上也就是所有级别了的日志信息并把输出式样调整成带微秒的日期时间、短级别以及信息本身。然后我们创建了日志记录器让它具有上面的两个日志槽。注意这儿的两个细节1. 这儿的接口普遍使用 shared_ptr2. 由于 make_shared 在处理 initializer_list 上的缺陷对 spdlog::logger 的构造只能直接调用 shared_ptr 的构造函数而不能使用 make_shared否则编译会出错。最后我们调用了 spdlog::set_default_logger 把缺省的日志记录器设置成刚创建的对象。这样之后的日志缺省就会记录到这个新的日志记录器了我们当然也可以手工调用这个日志记录器的 critical、error、warn 等日志记录方法。在某次运行之后我的终端上出现了20:44:45.086 W this should appear in both console and file而 test.log 文件中则增加了2020-01-26 20:44:45.086524 W this should appear in both console and file2020-01-26 20:44:45.087174 I this message should not appear in the console, only in the file跟 Easylogging 相比我们现在看到了 spdlog 也有复杂的一面。两者在输出式样的灵活性上也有不同的选择Easylogging 对不同级别的日志可采用不同的式样而 spdlog 对不同的日志槽可采用不同的式样。日志文件切换在 Easylogging 里实现日志文件切换是需要写代码的而且完善的多文件切换代码需要写上几十行代码才能实现。这项工作在 spdlog 则是超级简单的因为 spdlog 直接提供了一个实现该功能的日志槽。把上面的例子改造成带日志文件切换我们只需要修改两处#include spdlog/sinks/rotating_file_sink.h // 替换 basic_file_sink.h … auto file_sink make_shared rotating_file_sink_mt( test.log, 1048576 * 5, 3); // 替换 basic_file_sink_mt文件大 // 小为 5MB一共保留 3 个日志文件这就非常简单好用了。适配用户定义的流输出虽然 spdlog 缺省不支持容器的输出但是它是可以和用户提供的流 运算符协同工作的。如果我们要输出普通容器的话我们只需要在代码开头加入#include output_container.h #include spdlog/fmt/ostr.h前一行包含了我们用于容器输出的代码后一行包含了 spdlog 使用 ostream 来输出对象的能力。注意此处包含的顺序是重要的spdlog 必须能看到用户的 的定义。在有了这两行之后我们就可以像下面这样写代码了vectorint v; // … spdlog::info( Content of vector: {}, v);只用头文件吗使用 spdlog 可以使用只用头文件的方式也可以使用预编译的方式。只用头文件的编译速度较慢我的机器上使用预编译方式构建第一个例子需要一秒多而只用头文件的方式需要五秒多Clang 的情况GCC 耗时要更长。因此正式使用的话我还是推荐你使用预编译、安装的方式。在安装了库后编译时需额外定义一个宏在命令行上要添加库名。以 GCC 为例命令行会像下面这个样子g -stdc17 -DSPDLOG_COMPILED_LIB test.cpp -lspdlog其他刚才介绍的还只是 spdlog 的部分功能。你如果对使用这个库感兴趣的话应该查阅文档来获得进一步的信息。我这儿觉得下面这些功能点值得提一下可以使用多个不同的日志记录器用于不同的模块或功能。可以使用异步日志减少记日志时阻塞的可能性。通过 spdlog::to_hex 可以方便地在日志里输出二进制信息。可用的日志槽还有 syslog、systemd、Android、Windows 调试输出等扩展新的日志槽较为容易。参考资料[1] Amrayn Web Services, easyloggingpp. https://github.com/amrayn/easyloggingpp[2] Gabi Melman, spdlog. https://github.com/gabime/spdlog[3] Victor Zverovich, fmt. https://github.com/fmtlib/fmt[4] Andrey Semashev, Boost.Log v2. https://www.boost.org/doc/libs/release/libs/log/doc/html/index.html[5] Kjell Hedström, g3log. https://github.com/KjellKod/g3log[6] Stanford University, NanoLog. https://github.com/PlatformLab/NanoLog

相关新闻

闲鱼超级管家部署教程:本地搭建与Docker容器化方案对比

闲鱼超级管家部署教程:本地搭建与Docker容器化方案对比

闲鱼超级管家部署教程:本地搭建与Docker容器化方案对比 【免费下载链接】xianyu-super-butler 闲鱼超级管家是在 xianyu-auto-reply 基础上的二次开发版本,保留了原项目的所有核心功能,并对前端 UI 进行了全面重构,带来更加现代化…

2026/10/10 16:56:21 阅读更多 →
量化分批交易设计:触发间距、仓位与停止条件

量化分批交易设计:触发间距、仓位与停止条件

面对波动行情,很多人会说“跌了分批买、涨了分批卖”。这句话听起来简单,真正执行时却容易变形:第一笔买多少?每跌多少再买?反弹后先卖哪一笔?连续触发时是否还要继续?如果没有事先写清楚&#…

2026/10/14 15:54:31 阅读更多 →
用一句话需求,做完一张 9:16 的 Skill 发布海报:我把设计生图交给 Agent 试了一次

用一句话需求,做完一张 9:16 的 Skill 发布海报:我把设计生图交给 Agent 试了一次

大家好,我是吾鳴。专注于分享提升工作与生活效率的工具,持续关注AI的前沿动向。这阵子,我做了一个设计图片的Skill,它的名字叫做gen-ai-image。它的作用可以让Agent根据不同的需求,去使用像Nano Banana Pro、Nano Bana…

2026/10/14 15:55:18 阅读更多 →

最新新闻

IP地址与子网划分:网络运维核心技能详解

IP地址与子网划分:网络运维核心技能详解

1. IP地址基础概念解析IP地址就像互联网世界的门牌号码,每台联网设备都需要一个独特的标识。我在网络运维岗位上摸爬滚打十年,见过太多因为IP基础知识不扎实导致的网络故障。这次系统梳理IP地址的核心概念,希望能帮助刚入行的朋友少走弯路。I…

2026/10/15 14:58:42 阅读更多 →
OpenShell 智能体运行时沙箱与策略执行框架深度解析

OpenShell 智能体运行时沙箱与策略执行框架深度解析

1. 从零认识 OpenShell:它到底解决什么问题第一次听到 OpenShell 这个名字,很多人会下意识把它和某种远程登录工具或者命令行增强插件联系起来。我当初也是这么想的,直到真正把它跑起来、翻完它的源码结构,才发现这个判断偏得有点…

2026/10/15 14:58:42 阅读更多 →
IP地址详解:从基础概念到子网划分实战

IP地址详解:从基础概念到子网划分实战

1. IP地址基础概念解析IP地址就像互联网世界的"门牌号码",每台联网设备都需要一个独特的标识才能相互通信。我在网络运维岗位工作多年,处理过无数IP地址相关的问题,今天就来系统梳理下这个看似简单实则内涵丰富的技术概念。IP地址的…

2026/10/15 14:58:42 阅读更多 →
NX二次开发必会:UF_VEC3_distance两点距离计算详解

NX二次开发必会:UF_VEC3_distance两点距离计算详解

简介:面向NX二次开发初学者的工程源码包,围绕UFUN库中的UF_VEC3_distance接口,演示如何在UG/NX环境里用C计算三维空间中两点距离。包内提供完整工程源代码、VS工程配置与编译产物,从源文件编写、库依赖配置到动态库生成的完整脉络…

2026/10/15 14:58:42 阅读更多 →
自动驾驶系统分层架构与工程落地核心逻辑

自动驾驶系统分层架构与工程落地核心逻辑

1. 这不是科普讲座,是工程师视角下的自动驾驶第一课“讲一点点自动驾驶技术(1)概论”——这个标题乍看轻描淡写,像随手记下的笔记,但恰恰是这种克制的表达,最接近一线从业者的真实状态。我带过不少刚入行的…

2026/10/15 14:58:42 阅读更多 →
MobaXterm 2026版:一站式远程管理工具全解析

MobaXterm 2026版:一站式远程管理工具全解析

1. MobaXterm工具定位与核心优势解析作为Windows平台下最强大的全能终端工具,MobaXterm在2026年版本中延续了"一站式远程管理解决方案"的设计理念。相比传统终端工具,其核心价值在于集成了SSH客户端、X11服务器、SFTP文件传输、多标签管理、宏…

2026/10/15 14:57: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/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

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

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

2026/10/14 16:30:29 阅读更多 →
影刀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 阅读更多 →