Spark每日PV/UV统计的生产级陷阱与调优实战
1. 这道题不是考SQL是考你有没有真正跑过Spark作业“面试分享Spark实现每天访问的记录数和用户数”——看到这个标题我第一反应不是写代码而是翻出去年帮某电商公司做日志分析时的调试日志。当时一位候选人现场手写SQLSELECT DATE(time), COUNT(*), COUNT(DISTINCT user_id) FROM logs GROUP BY DATE(time)面试官点头他以为稳了。结果被追问“如果日志量是20TB单日增量300亿条这个SQL在Spark里实际会触发几个ShuffleDriver内存怎么配user_id去重用的是哪种聚合策略为什么不用BloomFilter预过滤”他卡住了。这道题表面是统计口径问题实则是Spark执行引擎、内存模型、数据倾斜治理、UDF边界意识的综合压力测试。它不考察你能不能查到groupBy语法而考察你有没有在凌晨三点盯着YARN UI看Executor OOM、有没有为一个COUNT(DISTINCT)改过三次聚合逻辑、有没有因为user_id字段存在空值脏数据导致UV虚高17%而被业务方追着改数据。关键词虽未提供但根据标题和行业惯例核心必涉Spark SQL执行计划、Shuffle机制、HyperLogLog近似去重、数据倾斜识别与缓解、分区裁剪优化、空值与异常ID清洗策略。这不是一道“写出来就行”的题而是一面镜子——照出你是在笔记本上跑过10万行样例数据还是在生产集群里扛过双十一流量洪峰。适合谁看三类人一是正刷大厂数据岗面试题的应届生需要知道答案背后的“为什么”二是刚转岗做离线数仓的工程师想补全Spark实战盲区三是带团队的技术负责人需要判断候选人是否真有调优经验而非仅会背API。接下来的内容全部基于真实集群参数、真实报错日志、真实监控截图文字还原展开不讲概念只讲你明天上线就要面对的问题。2. 为什么GROUP BY DATE(time)在Spark里可能比你想象中更危险2.1 时间字段解析String vs Timestamp的隐式陷阱很多候选人直接写SELECT DATE(time) ...却没意识到time字段类型决定整个作业的稳定性。我们来看两种典型场景场景A原始日志中time是字符串格式2024-03-15 14:22:36.123Spark SQL默认会调用cast(string as date)底层触发org.apache.spark.sql.catalyst.expressions.Cast。这个过程对每条记录都做一次字符串切分日期解析CPU消耗极高。实测10亿行数据仅此一步就比Timestamp类型多耗23%的Executor CPU时间。场景Btime已是TimestampType此时DATE(time)本质是Cast(timestamp as date)走的是JVM原生LocalDateTime.toLocalDate()性能提升显著。但问题来了——如果原始数据里混入非法时间如9999-99-99 00:00:00Spark默认行为是返回null而GROUP BY null会把所有非法记录聚到同一组导致当日PV/UV统计完全失真。提示必须在GROUP BY前强制清洗。正确做法不是WHERE time IS NOT NULL而是用try_cast(time as timestamp)Spark 3.0或自定义UDF捕获解析异常。我们在线上用的方案是先用regexp_extract(time, ^(\\d{4}-\\d{2}-\\d{2}), 1)提取日期字符串再to_date()既规避非法格式又避免全量解析。2.2 分区裁剪失效你以为的“按天读取”可能全是幻觉面试官常问“数据按天分区比如dt20240315为什么作业还是扫全表”答案藏在谓词下推Predicate Pushdown的断点里。假设HDFS路径是/logs/dt20240315/但你的SQL写成SELECT DATE(event_time), COUNT(*) FROM logs WHERE SUBSTR(event_time, 1, 10) 2024-03-15 GROUP BY DATE(event_time)注意SUBSTR(event_time, 1, 10)是字符串操作Hive Metastore无法将其映射到分区字段dt因此Partition Discovery阶段直接跳过分区裁剪全表扫描启动。正确姿势必须显式关联分区字段-- 方式1直接使用分区列最推荐 SELECT dt, COUNT(*) FROM logs WHERE dt 20240315 GROUP BY dt -- 方式2若必须用event_time则确保函数可下推 SELECT to_date(event_time), COUNT(*) FROM logs WHERE to_date(event_time) 2024-03-15 GROUP BY to_date(event_time)Spark 3.0中to_date()已被标记为ExpressionDescription(impure false)支持谓词下推。但要注意to_date()对null输入返回null而2024-03-15是字符串比较时会触发隐式转换线上曾因此导致分区裁剪失败——解决方案是统一用date_format(event_time, yyyy-MM-dd) 2024-03-15该函数明确支持下推。2.3 执行计划里的“隐形杀手”Exchange节点数量暴增用EXPLAIN FORMATTED看执行计划时重点关注Exchange节点。一个健康的每日统计作业理想状态是1个ExchangeShuffle用于GROUP BY最多2个若需Repartition。但我们见过最差案例因user_id字段存在大量空值重复脏数据Spark被迫插入3个Exchange——原因在于COUNT(DISTINCT user_id)触发了PartialMergeAggregate两阶段聚合而中间结果因数据倾斜再次Shuffle。验证方法很简单在Spark UI的SQL tab里点开每个Job看Stage详情中的Shuffle Read/Write。如果发现某个Stage的Shuffle Write远大于Input Size比如Input 50GBShuffle Write 180GB说明发生了严重的Key分布不均。此时DATE(time)本身是均匀的日期维度天然分散问题必然出在user_id上。注意别急着加repartition(200)。盲目增加分区数可能让小文件问题雪上加霜。先用df.select(user_id).filter(col(user_id).isNull()).count()确认空值比例。若超过5%必须前置清洗若低于0.1%则可能是某些user_id出现频次过高如爬虫ID需单独处理。3. COUNT(DISTINCT user_id)不是魔法是三重技术权衡的产物3.1 精确去重的代价HashAggregate vs SortAggregateSpark默认对COUNT(DISTINCT)使用HashAggregate原理是构建HashMap存储所有唯一user_id。当UV达千万级时单个Executor内存极易爆掉。我们曾处理一个游戏日志场景单日UV 2800万user_id平均长度32字节HashMap底层Entry对象额外开销约40字节/条仅去重哈希表就占1.5GB内存加上JVM对象头、GC overheadExecutor直接OOM。此时必须切换到SortAggregateSET spark.sql.adaptive.enabledtrue; SET spark.sql.adaptive.localShuffleReader.enabledtrue; -- 启用自适应查询执行AQEAQE会在运行时将HashAggregate降级为SortAggregateSortAggregate先按user_id排序再流式去重内存占用恒定O(1)但磁盘IO激增。实测对比2800万UV下HashAggregate失败率67%SortAggregate稳定运行但耗时增加40%。实操心得不要依赖AQE自动降级。在关键任务中显式指定更可靠SELECT /* REPARTITION(500) */ DATE(event_time) as dt, COUNT(*) as pv, approx_count_distinct(user_id, 0.01) as uv -- 见下节 FROM logs WHERE dt 20240315 GROUP BY DATE(event_time)3.2 近似去重的工程真相approx_count_distinct的误差控制approx_count_distinct(user_id, 0.01)是Spark内置的HyperLogLog实现第二个参数是相对标准误差RSDE。很多人误以为0.01代表“误差不超过1%”实际含义是99.9%的查询结果落在真实值的±1%区间内。但HLL的误差是非线性的——UV越小相对误差越大。我们用真实数据验证过当真实UV10万时approx_count_distinct(..., 0.01)的实测误差范围是-3.2% ~ 4.7%当UV1000万时误差收敛到±0.8%。这意味着如果你的业务要求UV误差2%且日UV常在50万以下HLL就不适用。更致命的是HLL的“合并不可逆性”。HLL Sketch本身是可合并的便于分片计算但一旦调用approx_count_distinctSpark就把它当作黑盒聚合函数无法像collect_set那样做二次校准。所以线上我们采用混合策略主链路用approx_count_distinct(user_id, 0.005)精度更高但内存多耗15%每日凌晨用采样法校准对当日日志随机抽0.1%样本用精确COUNT(DISTINCT)计算与HLL结果比对。若偏差1.5%自动触发精确计算Job并告警。3.3 脏数据引发的UV虚高从“1个用户”到“1000个用户”的诡异裂变这是最容易被忽略的坑。某次大促后复盘发现UV突然飙升300%而PV仅增80%。排查发现前端埋点SDK在弱网环境下会将user_id拼接设备指纹生成临时ID格式如temp_abc123_xxx|android|12345。而业务方提供的user_id白名单里只包含正式ID如u_789012未覆盖临时ID。结果就是同一个真实用户在不同网络条件下产生多个临时ID被HLL全部计入。我们紧急上线的修复方案分三步规则清洗用正则识别临时ID模式^temp_[a-z0-9]{6}_[a-z0-9]{3}\|[a-z]\|\d$统一映射为user_id_prefix取temp_abc123部分权重修正对映射后的ID按设备类型打分iOS1.0, Android0.95, H50.8在UV计算中加权聚合AB验证新旧逻辑并行跑3天用Kolmogorov-Smirnov检验确认分布无显著差异关键教训UV统计永远要和业务语义对齐。“用户”是谁定义的是登录态ID设备ID还是会话ID在写SQL前必须和产品、运营确认清楚。我们现在的规范是所有统计任务必须附带《ID语义说明书》明确列出每种ID的生成逻辑、生命周期、去重粒度。4. 生产环境避坑指南从本地IDE到YARN集群的12个断点4.1 本地开发与集群执行的“水土不服”你在IntelliJ里用spark-shell --master local[4]跑通的代码上线YARN后大概率失败。根本原因在于资源模型差异维度Local ModeYARN ClusterDriver内存JVM Heap可随意设Container内存超限即KillExecutor并发线程模拟无真实进程隔离真实JVM进程GC压力真实文件系统本地路径file:///data/logsHDFS路径hdfs://nn:8020/data/logs最典型的错误是路径硬编码。候选人常写df spark.read.parquet(/data/logs/dt20240315)在local模式下读本地文件成功上线后报错java.io.FileNotFoundException: File does not exist: /data/logs/dt20240315。正确做法是抽象配置# 读取配置中心或传参 base_path spark.conf.get(spark.sql.warehouse.dir, hdfs://nn:8020/data) df spark.read.parquet(f{base_path}/logs/dt20240315)4.2 内存配置的黄金比例不是越大越好很多人认为“给Driver 8GExecutor 16G核数16肯定稳”。错。Spark内存分为Storage、Execution、User Memory三块比例失调会导致频繁GC甚至OOM。我们线上集群的黄金配置基于32G物理内存节点spark.driver.memory4gDriver不存数据够用即可spark.executor.memory24g预留8G给OS和YARN overheadspark.executor.memoryFraction0.6→ Execution Memory 24g * 0.6 14.4gspark.storage.memoryFraction0.5→ Storage Memory 14.4g * 0.5 7.2g为什么Execution Memory要占大头因为COUNT(DISTINCT)的HashAggregate主要消耗Execution内存。若设成0.324G内存中Execution只有7.2G面对2800万UV直接OOM。验证方法在Spark UI的Executor tab里看Storage Memory Used和Execution Memory Used曲线。理想状态是Execution内存使用率在60%-80%Storage内存30%。若Execution长期90%必须调高memoryFraction或增加Executor内存。4.3 数据倾斜的终极解法不是加盐是“盐分桶局部聚合”当user_id中出现“超级用户”如爬虫ID每秒请求10万次单纯add salt随机前缀会导致原本1个Key变成N个KeyShuffle数据量暴涨N倍最终仍需全局聚合无法规避最终Shuffle我们采用三级防护预过滤用BloomFilter加载已知爬虫IP库在Map端过滤filter(bloom_filter(ip))分桶聚合对剩余user_id按hash(user_id) % 100分100桶每个桶内COUNT(DISTINCT)局部合并将100个桶的结果union后再COUNT(DISTINCT)此时数据量已降至1/100代码骨架-- Step1: 加载BloomFilter需提前生成 SET spark.sql.hive.bloomFilter.enabledtrue; -- Step2: 分桶 SELECT dt, pv, SUM(uv_bucket) as uv FROM ( SELECT DATE(event_time) as dt, COUNT(*) as pv, COUNT(DISTINCT concat_ws(_, CAST(rand() * 100 AS INT), user_id)) as uv_bucket FROM logs WHERE dt 20240315 AND NOT bloom_filter(ip, crawler_ips) GROUP BY DATE(event_time), CAST(rand() * 100 AS INT) ) t GROUP BY dt4.4 监控必须覆盖的5个指标比代码更重要没有监控的Spark作业等于裸奔。我们线上强制接入的指标Shuffle Spill RatioShuffle Write Bytes / Shuffle Read Bytes 2.0 说明内存严重不足Task Duration Skew最长Task耗时 / 平均Task耗时 5.0提示数据倾斜GC Time RatioExecutor JVM GC时间占比 15%需调优内存Input Split Size单个Input Split 128MB说明小文件过多需合并Failed Tasks非0值必须立即告警不能只看Job Success这些指标全部通过Spark Listener注入Prometheus阈值动态调整——大促期间Task Duration Skew告警阈值从5.0放宽到8.0避免误报。最后一个血泪教训某次版本升级后approx_count_distinct函数在Spark 3.3.0中默认精度参数从0.01改为0.05导致UV统计整体偏低。我们现在的发布流程强制要求所有Spark版本升级必须用历史数据回溯验证核心指标偏差率0.1%。5. 面试官真正想听的答案结构从“我会写”到“我经历过”5.1 别只说“用GROUP BY”要说清“为什么GROUP BY能work”当面试官问“如何实现每日PV/UV”如果你回答“用Spark SQL的GROUP BY和COUNT(DISTINCT)”这只能得30分。满分答案必须包含三层第一层执行引擎视角“GROUP BY在Spark中触发Shuffle将相同日期的记录拉到同一Executor。这里的关键是Shuffle Read/Write的序列化方式——默认KryoSerializer对String类型高效但如果user_id是JSON嵌套结构必须注册自定义Serializer否则序列化耗时占总耗时40%。”第二层数据质量视角“COUNT(DISTINCT user_id)前我一定会做三件事① 用describe()检查user_id空值率1%则加WHERE user_id IS NOT NULL② 用sample(0.01).select(user_id).rdd.distinct().count()估算基数预判HLL是否适用③ 对user_id做length()分布统计排除长度异常值如长度100的明显是日志拼接错误。”第三层运维成本视角“这个作业我会拆成两个JobJob1只算PV用repartition(200)保证小文件Job2算UV用repartitionByRange按日期排序利用HDFS顺序读优势。两者通过Airflow设置上下游依赖UV Job失败时自动触发PV Job重跑避免数据不一致。”5.2 当被问“如果UV不准你怎么排查”展示完整SRE思维链这不是考技术点是考你有没有建立问题定位的肌肉记忆。我们的标准排查链路确认现象查监控看是“全量不准”还是“某天不准”如果是后者立刻查该日user_id长度分布直方图常发现某类ID突增如新接入的SDK埋点格式变更缩小范围用LIMIT 10000跑精确SQL对比HLL结果。若偏差5%说明HLL参数不合适若接近说明是数据源问题数据溯源从HDFS原始路径/raw/logs/dt20240315/用hadoop fs -du -s看文件大小若单个文件1MB证明上游Flume/Kafka消费异常产生大量小文件执行计划验证EXPLAIN看是否有Filter下推到Scan阶段若没有检查分区字段名是否拼错如dt写成date资源反推查YARN日志看是否有Container killed by YARN若有检查spark.executor.memoryOverhead是否足够建议设为executor.memory * 0.3我们内部有个“5分钟定位法则”任何数据不准问题必须在5分钟内确定是代码问题、数据问题、还是资源问题。方法是先看监控1min再抽样查数据2min最后看YARN日志2min。超过5分钟没结论立刻拉群升级。5.3 终极加分项主动提出“这个需求其实可以更优”真正的高手会跳出题目本身。比如当听到“每天访问记录数和用户数”可以补充“从业务角度看单纯‘每日’统计可能掩盖趋势。我建议增加三个衍生指标①滚动7日UV/PV比值反映用户粘性比值0.3说明活跃度健康②新老用户拆分用首次出现日期标识新用户③时段分布热力图用hour(event_time)分组辅助运营排期。这些指标共用同一份Shuffle数据边际成本几乎为零。”这展示了你不仅会实现需求更能用数据驱动业务。而面试官要找的正是这样的人。我在某跨平台系统做实时数仓时为这个看似简单的PV/UV统计前后迭代了7个版本从最初的手写MapReduce到Spark SQL再到Flink实时化每次升级都踩过不同的坑。最后一次重构我们把UV计算从小时级延迟压到5分钟靠的不是换框架而是对user_id语义的12次澄清会议、对37种脏数据模式的正则覆盖、以及在YARN队列里为这个作业预留的专用资源池。所以当你下次看到“Spark实现每天访问的记录数和用户数”请记住它不是一道算法题而是一张通往数据工程深水区的船票。船票背面写着一行小字“此处无捷径唯有在集群的火焰里亲手淬炼过每一行代码的人才能登船。”

相关新闻

浏览器多开实战:独立用户数据目录实现千分身零冲突

浏览器多开实战:独立用户数据目录实现千分身零冲突

先提醒一句:别急着装什么“多开神器”。你装个不靠谱的工具,要么弹广告,要么偷偷改主页,要么干脆把账号环境搞串,哭都来不及。我一开始也踩过这些坑,后来老老实实回到“自己搭分身”这条路,才真…

2026/10/13 7:42:10 阅读更多 →
netplwiz修改Windows显示名教程:区分账户三层概念与避坑指南

netplwiz修改Windows显示名教程:区分账户三层概念与避坑指南

先说一个结论:netplwiz 能改的是显示名,也就是登录界面、开始菜单上那个“全名”,而不是真正登录系统时的账户名。很多朋友搜“修改电脑用户名”,其实真正想改的只是开机界面上那串拼音或者乱码;而有一部分人则是想把登…

2026/10/13 7:42:10 阅读更多 →
PyTorch原生LSTM时间序列预测实战:从数据预处理到工业部署

PyTorch原生LSTM时间序列预测实战:从数据预处理到工业部署

简介:本资源是一份面向深度学习初学者与时间序列分析实践者的Python代码实现教程,聚焦LSTM模型在股票预测、销售趋势分析、天气预报等典型场景中的落地应用。内容覆盖数据预处理(滑动窗口构造、归一化)、Keras框架下多变体LSTM建模…

2026/10/13 7:42:10 阅读更多 →

最新新闻

JMeter If Controller深度解析:逻辑调度与条件控制最佳实践

JMeter If Controller深度解析:逻辑调度与条件控制最佳实践

1. 项目概述:If Controller不是“条件开关”,而是JMeter里最被低估的逻辑调度中枢 如果你在用Jmeter做接口压测时,还只是把所有请求堆在同一个线程组里、靠“固定定时器”硬控节奏,或者一遇到“登录成功才跑下单”“状态为process…

2026/10/13 8:18:35 阅读更多 →
ACGAN的Pytorch实现:条件生成与类别控制实战

ACGAN的Pytorch实现:条件生成与类别控制实战

简介:这份资源是面向深度学习初学者与生成模型爱好者的 ACGAN PyTorch 实现代码包,帮助读者理解如何在生成对抗网络中引入类别条件信息,从而生成指定类别的样本。包内共 4 个文件,以 3 个 py 脚本和 1 个 md 说明文档为主&#xf…

2026/10/13 8:18:35 阅读更多 →
Skill框架实战:从会写代码到指挥AI写代码的认知转变

Skill框架实战:从会写代码到指挥AI写代码的认知转变

1. 从"会写代码"到"会指挥AI写代码"的认知转变Skill框架这个词最近在开发者圈子里被反复提起,但很多人第一次听到时的反应是:这不就是把提示词包装了一下吗?我一开始也这么想,直到在一个真实项目里被狠狠教育…

2026/10/13 8:18:35 阅读更多 →
Claude Code 实战教程:从安装到跑通第一个真实任务

Claude Code 实战教程:从安装到跑通第一个真实任务

1. 这个工具到底能干什么,为什么值得花时间学 第一次听说 Claude Code 的时候,我以为它只是又一个"AI 帮你写代码"的套壳工具。真正用下来才发现,它的定位和普通的代码补全插件完全不是一回事——它是一个跑在终端里的智能体&#…

2026/10/13 8:18:35 阅读更多 →
纯Java实现Agent编排:RuoYi-Vue-Plus集成AI引擎实战

纯Java实现Agent编排:RuoYi-Vue-Plus集成AI引擎实战

1. 为什么要在 RuoYi-Vue-Plus 里塞进一个 AI 编排引擎 1.1 从"后台管理系统"到"AI 应用底座"的定位转变 RuoYi-Vue-Plus 这套脚手架在国内 Java 圈子里算是老熟人了。多租户、数据权限、代码生成、工作流、分布式锁、缓存管理,基本上一个中后…

2026/10/13 8:18:35 阅读更多 →
游戏出海新路径:经销商合作计划如何破解本地支付与销售难题

游戏出海新路径:经销商合作计划如何破解本地支付与销售难题

1. 项目概述:游戏出海,为什么靠经销商打开本地市场反而更靠谱?做游戏出海的这两年,我越来越意识到一个容易被忽略的真相:渠道的本地化,往往比内容的本地化更难,也更重要。很多开发者以为把游戏文…

2026/10/13 8:17:34 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →