Flink Operator State 深度解析:从原理到批量 Sink 实战
1. 为什么 Operator State 值得单独拎出来讲Flink 的状态体系里Keyed State 出镜率极高几乎每篇讲状态的资料都会先拿 keyBy 之后的 ValueState、ListState 举例。但真正落到生产环境有一类状态是绕不开的它不依赖 key作用范围直接绑定在算子的并行实例上这就是Operator State非键控状态。我第一次被 Operator State 教育是在做一个从消息队列往外部存储做批量写入的作业。当时想当然地用 Keyed State 缓存一批数据再统一 flush结果发现 keyBy 之后数据被打散到不同 key group每个 key 各自维护一份缓冲批次根本凑不齐写入放大严重。后来换成 Operator State让每个并行子任务自己维护一个缓冲区凑够条数或到时间就刷一次问题才解决。这个坑让我意识到Keyed State 解决的是“按 key 隔离”的问题Operator State 解决的是“按并行实例隔离”的问题两者不是替代关系而是互补关系。这篇内容我会把 Operator State 从概念、结构、实现方式到恢复机制完整拆一遍重点讲清楚三件事它和 Keyed State 的本质区别在哪、三种状态原语ListState、UnionListState、BroadcastState分别适合什么场景、以及 checkpoint 恢复时并行度变化到底会发生什么。适合已经写过 Flink 作业、但对状态管理还停留在“能用就行”阶段的同学也适合正在做数据管道、需要自己实现 Source/Sink 连接器的开发者。提示本文所有代码示例基于 Flink 1.17 之后的 DataStream API不同版本在状态原语命名和接口上略有差异迁移时注意核对。2. Operator State 与 Keyed State 的本质区别2.1 从“状态归谁管”说起理解 Operator State最直接的方式是问一句这份状态归谁管Keyed State 的归属是key。你在KeyedStream上调用RuntimeContext.getState()Flink 会根据当前记录的 key 去对应的 key group 里找状态。同一个 key 永远路由到同一个并行子任务所以状态天然是隔离且一致的。它的生命周期跟着 key 走key 不再出现状态也可能被清理取决于配置。Operator State 的归属是算子并行实例operator instance。一个并行度为 3 的算子就有 3 份独立的 Operator State每份只属于对应的那个 subtask。它跟数据里有没有 key、key 是什么完全无关。典型场景就是 Source 记录消费位点、Sink 维护待刷新的缓冲批次。这个区别带来一个很实际的后果Operator State 不支持按 key 访问你拿不到“某个 key 的状态”只能拿到“我这个 subtask 的状态”。如果你需要按维度聚合、按用户隔离那必须用 Keyed State如果你需要每个并行实例维护自己的进度或缓冲Operator State 才是对的工具。2.2 并行度变化时的重分配行为差异两者在 checkpoint 恢复、尤其是改变并行度时的表现差别非常关键。Keyed State 的重分配是“按 key group 重新切分”。Flink 把整个 key 空间划分成固定数量的 key group默认是最大并行度的整数倍恢复时无论并行度怎么变同一个 key 的状态总能被正确路由到新的 subtask。这个过程对用户透明你几乎不用关心。Operator State 的重分配则取决于你用的是哪种状态原语。ListState 在恢复时如果并行度变了Flink 会把所有 subtask 的状态元素合并成一个列表再均匀重新分配。UnionListState 更特殊它会把全量状态发给每一个 subtask。BroadcastState 则是每个 subtask 都持有完整副本天然就是广播语义。我用一个表格把关键差异列清楚方便对照维度Keyed StateOperator State归属单位Key算子并行实例访问方式按 key 访问按 subtask 访问并行度变化重分配按 key group 自动切分取决于原语均分/全量/广播典型场景聚合、去重、窗口计算Source 位点、Sink 缓冲、广播配置是否需要 keyBy必须不需要支持的状态类型Value/List/Map/Reducing/AggregatingList/UnionList/Broadcast2.3 一个容易踩的认知误区很多人以为“没有 keyBy 就不能用状态”这是错的。Operator State 恰恰是在没有 keyBy 的情况下使用的。反过来也有人以为 Operator State 更“底层”所以更灵活其实它在访问能力上比 Keyed State 弱得多——你不能按任意维度检索只能整体读写。我个人的判断标准很简单如果状态需要跟数据内容里的某个字段绑定用 Keyed State如果状态只跟“我这个并行实例处理到哪了、缓存了什么”有关用 Operator State。这条线划清楚选型基本不会错。3. 三种 Operator State 原语怎么选3.1 ListState最常用的均分式状态ListState 是 Operator State 里最常用的原语语义是“每个 subtask 维护一个列表”。它适合那种每个并行实例独立维护一批元素、恢复时可以重新均分的场景。典型例子是 Source 连接器记录当前消费的分区或位点。假设一个 Source 并行度是 4每个 subtask 负责若干数据分片它把“我负责哪些分片、消费到哪个位置”存进 ListState。checkpoint 时这些信息被持久化恢复时如果并行度还是 4各归各位如果并行度变成 2Flink 会把原来 4 份列表合并再均分成 2 份每个新 subtask 拿到一部分分片继续消费。代码上实现CheckpointedFunction接口是标准做法public class MySource extends RichSourceFunctionString implements CheckpointedFunction { private ListStateString offsetState; private ListString pendingOffsets new ArrayList(); Override public void snapshotState(FunctionSnapshotContext context) throws Exception { offsetState.clear(); for (String offset : pendingOffsets) { offsetState.add(offset); } } Override public void initializeState(FunctionInitializationContext context) throws Exception { ListStateDescriptorString descriptor new ListStateDescriptor(offset-state, String.class); offsetState context.getOperatorStateStore() .getListState(descriptor); if (context.isRestored()) { for (String offset : offsetState.get()) { pendingOffsets.add(offset); } } } }这里有两个细节值得说。第一snapshotState里先clear()再add()是因为 ListState 的写入是追加语义不清空会重复累积。第二initializeState里用context.isRestored()判断是否是恢复启动只有恢复时才需要把历史状态读回内存首次启动时列表本来就是空的。注意ListState 的均分行为在并行度缩小时是合并再均分但不保证元素均匀到每个 subtask 数量完全一致只保证尽量均衡。如果你的业务对分片归属有强要求比如某个分片必须由固定 subtask 处理需要在代码里自己做二次分配。3.2 UnionListState全量广播式状态UnionListState 和 ListState 的接口几乎一样区别在恢复行为它会把 checkpoint 时所有 subtask 的状态合并后完整发给每一个 subtask。也就是说恢复后每个 subtask 都持有全量状态。这个特性适合什么场景最典型的是需要全局视图但又不想用广播流的场景。比如某个算子需要知道所有并行实例的分片分配情况用来做负载均衡决策。用 UnionListState每个 subtask 恢复后都能看到完整的分片列表自己决定接管哪些。但它的代价也很明显状态量乘以并行度。如果状态本身很大并行度又高内存和恢复时间都会爆炸。我见过一个作业用 UnionListState 存了几十万条记录并行度 16恢复时每个 subtask 都要拉全量checkpoint 恢复时间从几十秒涨到十几分钟。所以用它之前一定要算一笔账状态大小 × 并行度 实际内存占用。3.3 BroadcastState配置分发的专用工具BroadcastState 严格来说属于 Operator State 的一个特殊分支它的使用方式和前两者差别较大必须配合广播流BroadcastStream使用。典型场景是动态配置下发一条低吞吐的配置流广播到所有下游 subtask每个 subtask 维护一份完整配置副本处理主数据流时实时查这份配置。它的特点是每个 subtask 持有全量副本天然广播语义只能通过BroadcastProcessFunction或KeyedBroadcastProcessFunction访问广播侧只能写、不能读非广播侧只能读、不能写这是 Flink 的强制约束防止状态不一致MapStateDescriptorString, Rule ruleDescriptor new MapStateDescriptor(rules, String.class, Rule.class); BroadcastStreamRule ruleStream configStream.broadcast(ruleDescriptor); DataStreamEvent result eventStream .connect(ruleStream) .process(new BroadcastProcessFunctionEvent, Rule, Output() { Override public void processElement(Event event, ReadOnlyContext ctx, CollectorOutput out) throws Exception { ReadOnlyBroadcastStateString, Rule rules ctx.getBroadcastState(ruleDescriptor); Rule rule rules.get(event.getType()); if (rule ! null) { out.collect(applyRule(event, rule)); } } Override public void processBroadcastElement(Rule rule, Context ctx, CollectorOutput out) throws Exception { ctx.getBroadcastState(ruleDescriptor).put(rule.getType(), rule); } });选型上我的经验是能用 ListState 就别用 UnionListState能用广播流就别硬塞 UnionListState。BroadcastState 在配置分发这个场景上比 UnionListState 更专业语义更清晰状态管理也更高效。4. Checkpoint 与恢复机制深度拆解4.1 snapshotState 和 initializeState 的调用时机实现CheckpointedFunction时这两个方法的调用时机直接决定了状态正确性必须搞清楚。snapshotState在每次 checkpoint 触发时被调用由 Flink 的 checkpoint 协调器驱动。它执行时算子可能还在处理数据所以 Flink 会先做一个轻量级的锁或 barrier 对齐确保状态快照的一致性。你在里面做的事就是把内存里的状态同步到 state backend。initializeState只在算子初始化时调用一次在 open() 之前。它负责两件事注册状态描述符、以及在恢复场景下把持久化状态读回内存。注意它比open()早所以如果你在open()里依赖了状态数据要确保initializeState已经把它准备好。一个常见的错误是在initializeState里做耗时操作比如远程调用这会拖慢整个作业的启动。状态读取本身是本地或 backend 操作应该很快如果有额外逻辑建议放到open()里。4.2 并行度变化时的重分配全过程这是 Operator State 最容易被误解的部分我按 ListState 走一遍完整流程。假设初始并行度 4每个 subtask 的 ListState 内容如下subtask 0: [A, B]subtask 1: [C]subtask 2: [D, E]subtask 3: [F]checkpoint 时这四份列表被分别持久化。现在把并行度改成 2 重启恢复过程是Flink 从 checkpoint 读出四份列表合并成 [A, B, C, D, E, F]按新并行度 2 均分得到 subtask 0: [A, B, C]subtask 1: [D, E, F]具体分配顺序取决于实现不保证严格按此切分每个新 subtask 在initializeState里通过offsetState.get()拿到自己那份如果是 UnionListState第 2 步变成subtask 0 和 subtask 1 都拿到 [A, B, C, D, E, F] 全量。这里有个实操要点你的业务逻辑必须能处理“恢复后分片归属变了”的情况。比如 Source 连接器恢复后某个 subtask 可能拿到之前不属于它的分片代码里要能正确接管并继续消费而不是假设分片固定。4.3 状态后端对 Operator State 的影响Operator State 的存储方式取决于 state backend。HashMapStateBackend 把状态放在 JVM 堆上读写快但受内存限制EmbeddedRocksDBStateBackend 把状态放在本地磁盘的 RocksDB 里支持超大状态但读写有序列化开销。对 Operator State 来说ListState 和 UnionListState 在 RocksDB backend 下会被序列化存储所以你的状态元素必须可序列化。BroadcastState 因为要频繁读取做配置查询通常建议状态量不要太大否则每次查询都走序列化会拖慢处理。我的经验是Source/Sink 的位点类状态通常很小用哪个 backend 都行但如果 Operator State 里存了批量缓冲数据量可能很大这时候 RocksDB backend 更稳妥代价是 flush 时的序列化开销要算进延迟预算。5. 实操手写一个带 Operator State 的批量 Sink5.1 需求与设计光讲概念不够我带你写一个真实可用的批量 Sink从上游接收记录每个并行 subtask 维护一个缓冲列表凑够 N 条或超过 T 毫秒就批量写入外部存储。用 Operator State 保存未刷新的缓冲保证 checkpoint 恢复后不丢数据。设计要点用 ListState 存缓冲因为每个 subtask 的缓冲独立恢复时均分即可用 ProcessingTimeService 或定时线程触发超时 flushcheckpoint 时把当前缓冲快照进去恢复时读回5.2 核心代码实现public class BatchSinkT extends RichSinkFunctionT implements CheckpointedFunction { private final int batchSize; private final long flushIntervalMs; private ListStateT bufferState; private ListT buffer new ArrayList(); private transient ScheduledExecutorService scheduler; public BatchSink(int batchSize, long flushIntervalMs) { this.batchSize batchSize; this.flushIntervalMs flushIntervalMs; } Override public void open(Configuration parameters) throws Exception { scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::flushSafely, flushIntervalMs, flushIntervalMs, TimeUnit.MILLISECONDS); } Override public void invoke(T value, Context context) throws Exception { buffer.add(value); if (buffer.size() batchSize) { flush(); } } private synchronized void flushSafely() { try { flush(); } catch (Exception e) { // 记录日志避免定时线程静默退出 } } private synchronized void flush() throws Exception { if (buffer.isEmpty()) { return; } writeToExternal(buffer); buffer.clear(); } private void writeToExternal(ListT batch) throws Exception { // 实际写入逻辑比如 JDBC 批量插入、对象存储上传 } Override public void snapshotState(FunctionSnapshotContext context) throws Exception { bufferState.clear(); for (T item : buffer) { bufferState.add(item); } } Override public void initializeState(FunctionInitializationContext context) throws Exception { ListStateDescriptorT descriptor new ListStateDescriptor(sink-buffer, TypeInformation.of(...)); bufferState context.getOperatorStateStore().getListState(descriptor); if (context.isRestored()) { for (T item : bufferState.get()) { buffer.add(item); } } } Override public void close() throws Exception { if (scheduler ! null) { scheduler.shutdown(); } flush(); } }5.3 关键参数怎么定batchSize 和 flushIntervalMs 这两个参数直接决定吞吐和延迟的平衡。我的经验值batchSize 取决于外部存储的批量写入能力。JDBC 批量插入通常 500 到 2000 条一批比较合适太小网络往返多太大单次事务时间长。flushIntervalMs 决定最坏延迟。如果业务能接受 1 秒延迟设 1000如果要求秒级以内设 200 到 500但要相应调小 batchSize否则缓冲永远凑不满。这两个参数没有万能值必须结合外部存储的压测结果来定。我一般会先设一个保守值比如 batchSize500interval1000压测后看写入 QPS 和 P99 延迟再逐步调。注意定时 flush 和 invoke 触发的 flush 会并发访问 buffer必须加锁或用线程安全结构。上面代码用 synchronized 保证互斥简单可靠。如果追求极致性能可以用双缓冲切换但复杂度会上升。6. 常见问题与排查技巧实录6.1 恢复后数据重复或丢失这是 Operator State 最典型的问题根因通常是 checkpoint 和外部写入的原子性没对齐。场景Sink 在 flush 时把数据写入了外部存储但 checkpoint 还没完成此时作业失败。恢复时checkpoint 里保存的缓冲还是 flush 之前的状态于是这批数据被重新写入造成重复。解决思路有两条幂等写入外部存储支持按主键覆盖重复写不影响结果两阶段提交用 Flink 的 TwoPhaseCommitSinkFunction把外部写入纳入 checkpoint 事务我个人的选择是如果外部存储天然幂等比如按主键 upsert 的数据库直接用幂等写入最省事如果不行再上两阶段提交。两阶段提交会显著增加 checkpoint 时间不是所有场景都值得。6.2 并行度调整后状态分配不均ListState 恢复时是“合并再均分”但均分不保证每个 subtask 拿到的元素数量完全一致。如果状态元素大小差异很大比如有的分片数据量是别的几十倍可能出现某个 subtask 负载过高。排查方法在initializeState里打印每个 subtask 拿到的状态元素数量和大小观察分布。如果确实不均需要在业务层做二次均衡比如把大分片拆成更细的粒度再分配。6.3 BroadcastState 写入报错常见报错是“Cannot write to broadcast state from non-broadcast side”。这是因为 Flink 强制约束只有广播流的processBroadcastElement能写 BroadcastState主数据流的processElement只能读。如果你需要在主数据流侧更新配置正确做法是把更新请求发回广播流由广播侧统一写入。这个约束看起来麻烦但它保证了所有 subtask 的配置副本一致避免状态分裂。6.4 状态序列化失败Operator State 的元素必须可序列化。常见坑是用了匿名内部类或没有无参构造的类导致 Kryo 序列化失败。解决办法优先用 Flink 自带类型基本类型、POJO、TuplePOJO 要有 public 无参构造和 public 字段或标准 getter/setter实在不行显式注册 Kryo 序列化器排查时看异常栈里的SerializationException通常会指出具体是哪个字段或类出了问题。问题现象可能原因排查方向恢复后数据重复checkpoint 与外部写入未对齐检查是否幂等或用了两阶段提交状态分配不均元素大小差异大打印各 subtask 状态量考虑二次均衡BroadcastState 写入报错在非广播侧写入确认写入只在 processBroadcastElement序列化失败类型不可序列化检查 POJO 结构或注册序列化器恢复时间过长UnionListState 全量广播评估状态大小 × 并行度7. 我在实际项目里踩过的几个坑第一个坑是关于snapshotState的调用频率。我一开始以为它只在 checkpoint 时调用后来发现如果开启了增量 checkpoint 或者对齐超时调用行为会有细微差别。结论是不要在snapshotState里做任何有副作用的操作它应该只负责把内存状态同步到 state backend其他逻辑一律放到别处。第二个坑是 ListState 的clear()。我见过有同学在snapshotState里忘了 clear结果每次 checkpoint 都把历史状态重新 add 一遍状态越滚越大最后 checkpoint 直接超时。记住 ListState 是追加语义快照前必须清空。第三个坑关于并行度。有一次我把一个用 UnionListState 的作业并行度从 8 调到 32恢复时每个 subtask 都要拉全量状态内存直接打满作业起不来。后来改成 ListState 加业务层二次分配才解决。UnionListState 的并行度上限要提前算好状态大小乘以并行度不能超过单节点可用内存。最后一个经验是关于测试。Operator State 的正确性很难靠单元测试覆盖因为涉及 checkpoint 和恢复。我的做法是写一个集成测试用 MiniCluster 跑一遍完整流程启动、处理数据、触发 checkpoint、模拟失败、从 checkpoint 恢复、验证状态一致。这个测试跑起来慢但能挡住绝大多数状态相关的回归问题。这套东西我前后调了两周才稳定下来核心就一句话Operator State 的难点不在写而在恢复时的行为是否符合预期。把并行度变化、checkpoint 时机、外部写入原子性这三件事想清楚基本就不会出大问题。

相关新闻

Flink Operator State 深度解析:非键控状态管理实战指南

Flink Operator State 深度解析:非键控状态管理实战指南

1. 为什么 Operator State 值得单独拎出来讲Flink 的状态管理一直是流计算里最容易被低估的一块。大部分人刚上手的时候,注意力都放在窗口、水位线、Checkpoint 这些"显眼"的机制上,等到真正在生产环境里跑起来,才发现状态才是决定…

2026/10/17 7:41:11 阅读更多 →
RPA+大模型:打造外部群聊专家级自动应答系统

RPA+大模型:打造外部群聊专家级自动应答系统

1. 从一线需求说起:为什么群聊回应也能自动到“专家级”做运营和客户成功的朋友,大概率都经历过这种状态:手机里挂着四五个外部群,每个群都在问问题,有的问报价,有的问故障排查,有的纯粹是闲聊。…

2026/10/17 7:41:11 阅读更多 →
地质工程一体化实战:从地质建模到随钻导向与压裂设计的闭环

地质工程一体化实战:从地质建模到随钻导向与压裂设计的闭环

你要是问一个油气田开发工程师,最怕听到哪句话,十有八九是——“模型是这么说的,但井不是这么钻的。”这种地质和工程互相不认账的场面,在非常规油气、复杂油气藏开发里几乎每隔几天就来一次。模型画得再漂亮,现场不买…

2026/10/17 7:40:11 阅读更多 →

最新新闻

企业级多模态RAG知识库搭建实战:从0到1全流程复盘

企业级多模态RAG知识库搭建实战:从0到1全流程复盘

做企业级 RAG 知识库这件事,我一开始是真没当回事。直到接了一个装修公司项目,才发现自己太天真。这个项目不是网上那种“拿十几个PDF跑个问答Demo”的玩具,而是要把这家装修公司过去十几年积攒的图纸、合同、报价单、工地照片、客户聊天记录…

2026/10/17 8:21:20 阅读更多 →
AI品牌力建设指南:从种草提效到心智占领的系统路径

AI品牌力建设指南:从种草提效到心智占领的系统路径

想清楚一个问题:很多品牌都在讲“用AI做内容”,但真正把AI做成一套可复用的品牌增长系统的,并不多。我做营销技术咨询这几年,接触过不少品牌方的市场负责人,大家普遍的状态是——买了AI会员、尝试过生成热点文案、用数…

2026/10/17 8:21:20 阅读更多 →
Agentic RAG实践:查询路由与Web兜底设计,解决RAG上线三大坑

Agentic RAG实践:查询路由与Web兜底设计,解决RAG上线三大坑

如果你的RAG系统上线之后,用户问一句"你好",向量检索也能认认真真去知识库里找有没有"你好"的答案,那说明你正处在大多数RAG项目从Demo走向生产的第一道坎上。我之前给某公司的客服知识库做过一套问答系统,初…

2026/10/17 8:21:20 阅读更多 →
vLLM高并发推理引擎:PagedAttention与连续批处理实战指南

vLLM高并发推理引擎:PagedAttention与连续批处理实战指南

每当看到群里有人问“为什么我本地起的LLM服务一遇到并发就报错”,我就知道对方多半还没有接触过vLLM这类高吞吐推理引擎。单个请求跑起来很顺畅,模型回答质量也没问题,但只要并发一上来,显存占用就像被倒进了无底洞,速…

2026/10/17 8:21:20 阅读更多 →
GEO实战:让DeepSeek、Kimi等国产大模型主动引用你的内容

GEO实战:让DeepSeek、Kimi等国产大模型主动引用你的内容

1. 先别急着问“怎么做”,得先搞懂AI凭什么推荐你的内容1.1 为什么传统的SEO经验突然失灵了这几年做内容运营的朋友应该有一个共同的感受:在搜索框上那一套“霸屏”打法,放到AI问答里全都不太对劲。我以前给某个知识类站点做优化的时候&#…

2026/10/17 8:21:20 阅读更多 →
ADS-B Python环境搭建:从RTL-SDR驱动到航班数据解码

ADS-B Python环境搭建:从RTL-SDR驱动到航班数据解码

简介:这是一套面向Python开发者与航空数据爱好者的基础ADS-B消息处理工具库,适用于需要解析、模拟或调试ADS-B(自动相关监视)原始报文的初学者与中级实践者。资源提供完整的开源Python软件包,支持快速安装与即用式调用…

2026/10/17 8:20:20 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →