简介《Python网络数据挖掘实战》面向具备一定Python基础、希望系统掌握网络数据提取与分析方法的开发者与数据方向学习者围绕网络内容挖掘、结构挖掘与使用挖掘三大分类展开并延伸至个性化客户服务、网络搜索、网络跟踪与过程挖掘等应用场景。全书从Python编程基础、基本HTML标签与常用库讲起逐步过渡到网络爬虫的工作原理、合法性边界以及数据提取与预处理流程最后深入网络意见挖掘中的数据处理、分词与特征提取以及社交网络分析的Python实现形成从概念到落地的完整学习路径。资源包共1个文件为PDF格式电子书整体约39.57MB便于在电脑或平板上直接阅读与检索。目前已有94人学习适合作为网络挖掘入门与实战参考帮助读者理解爬虫合规要点、掌握文本与社交网络数据的处理思路。1. 网络数据挖掘为什么“抓得到”不等于“挖得出”很多人第一次写爬虫把网页 HTML 抓下来那一刻就以为任务完成了。可真正跑过完整链路的人都知道抓取只是起点后面还有清洗、分词、特征抽取、结构分析、关系建模任何一环翻车前面抓得再多也是白费。网络数据挖掘要解决的核心问题恰恰不是“怎么把页面下载下来”而是“怎么从半结构化的网页里稳定地提炼出可计算的知识”。这本书把网络挖掘拆成内容挖掘、结构挖掘、使用挖掘三条主线再用 Python 把每条线落到可执行的脚本上这个组织方式本身就值得先搞懂它决定了你后面写代码时数据该往哪个方向流。搞懂这套分层你才不会把爬虫、解析、建模混成一锅粥。2. 网络挖掘的三条主线内容、结构、使用到底在挖什么网络挖掘这个词听起来很宽落到实现层面它其实是在回答三个不同的问题。内容挖掘问的是“这个页面在说什么”结构挖掘问的是“这些页面之间怎么连”使用挖掘问的是“用户在这些页面上做了什么”。三者共享同一份原始数据来源但处理路径完全不同。常见的实现思路是先把网页当成一个带标签的文本容器再根据任务目标决定抽取哪一层信息。理解这个分类不是为了背概念而是为了在写代码之前就知道该调用哪类库、该设计什么样的数据结构。2.1 内容挖掘从标签树里剥离出正文内容挖掘的对象是网页的可见文本和媒体资源。一个 HTML 文档在解析后是一棵树真正有用的正文往往埋在大量导航、广告、页脚之间。Python 里常用的路径是用解析库把文档转成 DOM 树再按标签路径或文本密度定位正文块。这里的关键机制是“结构先验”网页作者用标签表达语义挖掘程序反过来利用这些标签做过滤。比如连续多个p标签包裹的文本块大概率是正文而nav、footer里的短链接列表大概率是噪声。这个判断不是绝对的所以实际脚本里通常要配合文本长度阈值和链接密度两个指标一起用。from bs4 import BeautifulSoup def extract_main_text(html): soup BeautifulSoup(html, html.parser) # 先移除脚本和样式它们不贡献可见正文 for tag in soup([script, style, nav, footer]): tag.decompose() # 按段落聚合过滤掉过短的碎片 paragraphs [p.get_text(stripTrue) for p in soup.find_all(p)] return \n.join(p for p in paragraphs if len(p) 40)这段代码的逻辑很直白先做减法把确定不是正文的标签整棵子树删掉再做聚合只保留长度超过阈值的段落。decompose()会连同子节点一起移除比逐个extract()更彻底。阈值 40 不是固定值新闻类页面可以调低论坛类页面可能要调高因为短回复多。这里没有用正则去匹配正文是因为正则对嵌套标签的容错太差一旦页面结构微调就会大面积失效。内容挖掘的稳定性很大程度上取决于你选的过滤策略是否贴合目标站点的模板特征。2.2 结构挖掘把超链接当成有向图的边结构挖掘把整个站点或整个网络看成一个有向图页面是节点超链接是边。这个视角的价值在于它让“重要性”变成可计算的量。一个被很多页面指向的页面在链接分析里通常被认为更重要这就是链接分析算法的基本假设。实现上你需要先构建邻接关系再在这个关系上迭代计算权重。常见的做法是用字典存源页面 - 目标页面集合的映射然后按算法要求做多轮传播。def build_link_graph(pages): graph {} for url, html in pages.items(): soup BeautifulSoup(html, html.parser) targets set() for a in soup.find_all(a, hrefTrue): href a[href] # 只保留站内绝对路径过滤锚点和外部链接 if href.startswith(/) and not href.startswith(//): targets.add(href) graph[url] targets return graphbuild_link_graph返回的是一个邻接表每个键是页面地址值是该页面指向的站内页面集合。用set而不是list是为了去重同一个页面里重复链接很常见。过滤条件只保留以/开头的路径是为了把站内结构和外部引用分开避免图被外部节点污染。这个图结构可以直接喂给后续的权重迭代逻辑也可以用来做连通性分析。结构挖掘的难点不在建图而在图的规模站点一大邻接表的内存占用和迭代收敛速度都会成为瓶颈所以实际系统里往往要先做采样或分块。2.3 使用挖掘日志里的行为序列才是原始素材使用挖掘的输入不是网页本身而是用户访问留下的记录比如访问日志、点击流、会话序列。它的目标是从行为序列里还原出模式哪些页面经常被连续访问哪些路径导致流失哪些用户群体有相似的行为轨迹。实现上第一步是把原始日志解析成结构化记录第二步是按用户和时间窗口切分会话第三步才是在会话上做频繁模式或聚类分析。这里最容易翻车的地方是会话切分时间窗口设得太短一个完整访问被切成几段设得太长不同意图的访问被混在一起。from datetime import datetime, timedelta def split_sessions(records, gap_minutes30): sessions, current [], [] for rec in sorted(records, keylambda r: r[ts]): if current and rec[ts] - current[-1][ts] timedelta(minutesgap_minutes): sessions.append(current) current [] current.append(rec) if current: sessions.append(current) return sessionssplit_sessions按时间间隔切分会话gap_minutes是判定阈值。排序是必须的因为原始日志不一定按时间有序。这个函数只做了切分没有做用户隔离实际使用时应该先按用户 ID 分组再调用。30 分钟这个默认值来自常见的会话定义但不同业务场景差异很大电商浏览和文档查阅的合理间隔完全不同。使用挖掘的产出往往不是一条条记录而是统计特征或模式集合所以数据结构的选型要提前想清楚否则后面做聚合时会反复重构。3. 从页面到结构化数据解析、抽取与预处理的实现细节抓取回来的原始 HTML 只是字节流要变成能参与计算的数据中间隔着一整套解析和清洗流程。这一层的设计质量直接决定了后面所有分析的上限。很多项目在原型阶段跑得通一上规模就崩问题往往出在这里解析器选型不当、编码处理缺失、抽取规则和页面模板耦合过紧。把这一层拆开看它其实包含三个可独立验证的环节文档解析、字段抽取、数据规范化。3.1 解析器选型容错性和速度的取舍HTML 解析器大致分两类一类追求严格按规范建树一类追求在畸形文档上也能给出结果。网页现实里充斥着未闭合标签、错误嵌套、非法字符所以容错性通常是第一优先级。Python 生态里常见的解析库在底层用的解析策略不同有的基于纯 Python 实现便于调试但速度一般有的绑定底层解析引擎速度快但对异常结构的处理方式不完全一致。选型时要看目标站点的文档质量结构规范的站点可以用速度更快的方案结构混乱的站点则要优先保证不抛异常。关注点严格解析倾向容错解析倾向畸形标签处理可能直接报错自动补全或跳过解析速度通常较快视实现而定调试便利性依赖底层引擎纯 Python 更易追踪适用场景结构稳定的站点来源杂乱的聚合抓取这张表不是让你背结论而是提醒你解析器的行为差异会传导到抽取结果。同一个页面用不同解析器建出来的树可能不一样字段定位的路径也可能失效。稳妥的做法是在项目早期就用一批真实页面做回归测试确认解析器在边界情况下的表现而不是等上线后才发现某个页面解析出空值。3.2 字段抽取选择器写死还是规则自适应字段抽取的常见做法是用 CSS 选择器或 XPath 定位目标节点。这种方式写起来快但和页面模板强耦合模板一改就全挂。另一种思路是基于文本特征和相对位置做自适应抽取比如“标题通常是页面里字号最大的短文本”“价格通常出现在包含货币符号的节点附近”。自适应规则更耐模板变化但误判率也更高需要配合人工校验。实际项目里往往是两者结合核心字段用选择器保证准确率辅助字段用规则兜底。def extract_fields(soup): result {} # 主标题优先走结构化选择器 title_node soup.select_one(h1) or soup.select_one(title) result[title] title_node.get_text(stripTrue) if title_node else None # 价格用文本特征兜底适配模板微调 for node in soup.find_all(stringTrue): text node.strip() if text.startswith(¥) or text.startswith($): result[price] text break return result这段代码体现了分层抽取的思路title走确定性的选择器路径price走特征匹配。select_one返回第一个匹配节点找不到时返回None所以后面做了空值判断。价格匹配只取了第一个命中项这在列表页上会出错实际使用时应该限定在详情页的特定区域内。字段抽取的规则要写成可配置的形式而不是散落在函数里否则每接一个新站点就要改代码维护成本会迅速失控。3.3 预处理编码、去重与规范化抽取出来的文本还不能直接用。编码问题是最常见的坑响应头声明的编码、页面 meta 里声明的编码、实际字节编码三者不一致时直接解码会得到乱码。稳妥的做法是先按响应头解码失败再尝试 meta 声明最后用容错模式兜底。去重则是另一个容易被忽视的环节同一内容通过不同 URL 重复出现的情况非常普遍不去重会让后续统计严重偏斜。规范化包括统一空白字符、统一大小写、剥离多余标点这些操作看似琐碎但直接影响分词和特征抽取的质量。提示编码探测不要只依赖单一来源响应头、meta 标签、字节特征三者交叉验证能显著降低乱码概率。预处理阶段还要考虑存储格式。文本量小时可以直接放内存量一大就必须落盘而落盘格式的选择会影响后续读取效率。结构化程度高的数据适合列式存储半结构化文本适合按行存储。这个决策要在预处理阶段就定下来因为格式转换本身也是有成本的。4. 意见挖掘与社交网络分析文本情感和关系结构的建模路径前面几章处理的是“页面”和“链接”这一层要处理的是“人说了什么”和“人之间怎么连”。意见挖掘把文本映射到情感极性社交网络分析把关系映射到图结构。两者看似不相关底层却有共同点都是把非结构化的原始素材压缩成低维表示再在这个表示上做计算。理解这个共性你就能明白为什么分词质量、特征选择、图构建方式会反复出现在不同任务里。4.1 分词与特征抽取情感分析的输入从哪来意见挖掘的第一步是把句子切成词元。中文没有天然空格分隔分词结果直接决定后续特征的质量。常见的实现思路是用统计分词模型配合领域词典做修正。分词之后要做特征抽取最朴素的做法是词袋模型把文本表示成词频向量更进一步可以用词权重或嵌入向量。情感分析的关键在于特征要和情感极性相关停用词、中性词要过滤掉否定词和程度副词要特殊处理否则“不好”和“好”会被当成相似文本。def tokenize_and_filter(text, stopwords): # 简化示例按空白和标点切分实际应接入分词模型 tokens [t for t in text.replace(, ).replace(。, ).split() if t] return [t for t in tokens if t not in stopwords and len(t) 1]这个函数只做了最基础的切分和停用词过滤len(t) 1是为了去掉单字噪声。真实场景里应该替换成成熟的分词组件并加入词性过滤只保留名词、动词、形容词这类承载情感的词性。特征抽取之后情感判定可以用词典匹配也可以用分类模型。词典方法的可解释性强但覆盖有限模型方法泛化好但需要标注数据。选哪种取决于你有没有可用的训练集以及业务对可解释性的要求。4.2 网络构建对称关系和非对称关系的区别社交网络分析里关系分两种对称关系和非对称关系。好友关系通常是对称的关注关系通常是非对称的。这个区别在数据结构上体现为邻接矩阵是否对称在算法上体现为能否直接套用无向图的方法。构建网络时节点是用户或实体边是关系边的权重可以表示互动频率或关系强度。常见的实现是用字典嵌套或专门的图库来存规模小时字典足够规模大时要考虑稀疏存储。def build_directed_graph(edges): graph {} for src, dst, weight in edges: graph.setdefault(src, {})[dst] weight # 非对称关系不自动补反向边 return graphbuild_directed_graph只添加单向边setdefault保证源节点存在字典内层存目标节点和权重。如果关系是对称的需要在循环里同时补dst - src。这个区别看起来小但会影响后续的连通性分析和中心性计算。非对称图上的“可达”是有方向的对称图上则没有方向概念混用会导致结论完全错误。构建阶段还要处理孤立节点和自环孤立节点在部分算法里会导致除零自环则会虚增节点的度。4.3 连通性与中心性从图结构读出关键节点图建好之后下一步是从结构里读出信息。连通性回答的是“哪些节点属于同一个群体”中心性回答的是“哪些节点在结构上更重要”。常见的中心性指标包括度中心性、介数中心性、接近中心性它们从不同角度刻画重要性。度中心性看直接连接数介数中心性看经过该节点的最短路径比例接近中心性看该节点到其他节点的平均距离。实现上度中心性可以直接从邻接表统计介数和接近中心性需要跑最短路径算法。def degree_centrality(graph): total len(graph) if total 1: return {} return {node: len(neighbors) / (total - 1) for node, neighbors in graph.items()}degree_centrality用节点连接数除以最大可能连接数做归一化total - 1是排除自身后的最大连接数。这个指标计算简单但对图规模敏感节点数很少时区分度不高。介数中心性的计算复杂度高得多大规模图上通常要用近似算法或采样。选择哪个指标取决于你要回答的问题找意见领袖看度中心性找信息瓶颈看介数中心性找传播效率看接近中心性。指标选错结论就会偏离业务目标。5. 避坑与排查网络数据挖掘里最容易翻车的几件事这一章记录的是实际跑链路时反复出现的问题。每一条都按现象、原因、解决来写目的是让你在遇到类似情况时能快速定位而不是从头排查。5.1 抓取成功但抽取结果为空现象请求返回 200HTML 也有内容但字段抽取全是空值。原因通常是页面内容由前端脚本渲染原始响应里只有骨架没有实际数据或者解析器选错建树结果和预期不符。解决方式是先检查原始响应里是否包含目标文本如果不包含就要换数据来源或改用能执行脚本的抓取方式如果包含就打印解析后的树结构确认选择器路径是否匹配。5.2 分词结果把关键情感词切碎现象情感分析准确率异常低人工检查发现“不满意”被切成“不”和“满意”。原因是没有加载领域词典分词模型把否定结构和情感词拆开了。解决方式是在分词前加入自定义词典把常见否定组合和领域术语注册进去同时在特征抽取阶段对否定词做特殊标记避免极性被反转后丢失。5.3 链接图规模爆炸导致内存溢出现象站点稍大建图阶段内存占用飙升进程被系统杀掉。原因是邻接表把每个页面的所有出链都存了下来重复链接和参数化 URL 让边数远超预期。解决方式是在建图前做 URL 规范化去掉无意义的查询参数对重复边去重并对超大站点做分块建图最后再合并。5.4 会话切分把一次访问拆成多段现象使用挖掘得到的会话长度普遍偏短行为模式分析结果失真。原因是时间窗口设得太小或者日志时间戳精度不够导致相邻请求被误判为不同会话。解决方式是先统计请求间隔的分布根据分布的中位数或分位数来定阈值而不是拍脑袋定一个固定值。5.5 编码不一致导致文本乱码现象抽取出的中文文本出现乱码部分页面正常部分页面异常。原因是不同页面的编码声明不一致统一按一种编码解码必然出错。解决方式是逐页探测编码响应头、meta、字节特征交叉验证解码失败时用容错模式并记录异常页面后续单独处理。6. 设计取舍为什么这套分层值得照着走把整条链路拆成内容、结构、使用三条主线再在每条线下分解析、抽取、建模三层这个组织方式的价值不在于概念完整而在于它把“不确定性”隔离在了可控的边界内。抓取层的不确定性来自网络和页面变化解析层的不确定性来自文档结构建模层的不确定性来自数据分布。分层之后每一层都可以独立测试和替换某一层出问题不会直接污染其他层。这种隔离在原型阶段看不出好处一旦数据量上来、来源变杂就是能不能维护下去的分水岭。从实现角度看这套分层也对应着不同的技术选型逻辑。解析层优先考虑容错性和可调试性因为页面结构不可控抽取层优先考虑规则的可配置性因为站点模板会变建模层优先考虑算法和数据的匹配度因为不同任务对特征的要求差异很大。把这些取舍提前想清楚比事后重构省力得多。我一般会这么理解网络数据挖掘的工程难度大部分不在算法本身而在数据从原始字节到可用特征的这段路上这段路走得稳不稳决定了后面所有分析有没有意义。验证这套思路是否落地有个简单的办法拿一批真实页面跑完整链路记录每个环节的失败率和失败原因再针对失败率最高的环节做加固。如果失败集中在解析层就补解析器的回归测试如果集中在抽取层就把规则抽成配置并加校验如果集中在建模层就回头检查特征和任务是否匹配。这个循环跑几轮链路的稳定性会有明显提升。注意分层不是为了增加抽象层数而是为了让每一层的输入输出可验证。层与层之间的契约越清晰排查问题时定位越快。从那以后我每次搭这类链路都会先把每一层的输入输出契约写下来再动手写代码跑不通时先对照契约找断点而不是凭感觉改参数。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Python网络数据挖掘实战:内容、结构与使用挖掘全解析 简介:《Python网络数据挖掘实战》面向具备一定Python基础、希望系统掌握网络数据提取与分析方法的开发者与数据方向学习者,内容覆盖从网络挖掘概念到Python落地的完整链路。全书先梳理网络内容挖掘、结构挖掘与使用挖掘的分类,并延伸至个性化… · 2026/11/3 10:54:24
射频自动化测试序列配置框架:从扫频实战到二次开发避坑 简介:面向高通芯片测试、射频验证与产线调试工程师的技术文档,围绕QSEQ测试序列系统梳理序列设计、环境与参数配置、执行记录及结果分析等核心环节,帮助读者理解如何组织复杂测试流程、定位执行异常并衔接QRCT相关操作。压缩包内共1个PDF文件… · 2026/11/3 10:53:23
卷积码与Viterbi译码MATLAB仿真及误码率排查 简介:这份文档围绕卷积码编译码算法研究与实现展开,面向学习数字通信、信道编码及维特比译码的本科生与工程入门者,可用于课程设计、毕业设计选题参考和MATLAB仿真复现。包内仅含1个doc文件,压缩包约991KB,主体为排版完… · 2026/11/3 10:53:23
Redis键过期机制原理与实战应用详解 1. Redis键过期机制的核心原理Redis作为内存数据库的标杆产品,其键过期机制的设计直接影响着内存使用效率和应用稳定性。当我们在Redis中设置过期时间时,实际上是在操作一个独立的过期字典(expires字典),这个字典与主字… · 2026/11/3 11:43:23
专业内容创作指南与处理不完整信息建议 1. 理解输入内容的缺失根据您提供的输入信息,项目标题为"111111",其他字段均为空。这种情况通常意味着:可能是测试输入或占位符用户可能误操作提交了不完整信息系统传输过程中出现了数据丢失2. 专业内容创作的基本原则作为专业内容… · 2026/11/3 11:43:23
SQL索引优化实战:从原理到性能提升150倍的秘诀 1. 为什么SQL索引如此重要? 我至今还记得第一次遇到生产环境SQL查询超时的场景——一个原本只需要0.1秒的简单查询,在数据量增长到百万级后突然需要15秒才能返回结果。当时紧急排查发现,问题就出在没有正确使用索引上。这个教训让我深刻认识到… · 2026/11/3 11:43:23
Git版本回退原理与实战指南 1. 为什么我们需要版本回退? 在团队协作开发中,代码库就像是一个不断生长的有机体。想象一下这样的场景:你花了三天时间开发一个新功能,提交了十几个commit后突然发现——这个功能方向完全错了!或者更糟,你… · 2026/11/3 11:43:23
UE5 JSON解析:TJsonReader类族与性能优化实践 1. UE5 JSON解析基础与TJsonReader类族概述在Unreal Engine 5的C开发中,处理JSON数据是日常开发中无法回避的任务。引擎内置的JSON解析系统提供了一套高效、类型安全的处理方案,其核心就是TJsonReader类及其派生类。这套解析器采用流式处理模式ÿ… · 2026/11/3 11:43:23
WinForms开发中的死锁问题与解决方案 1. 死锁的本质与四大必要条件 在WinForms应用开发中,当我们在UI线程与后台任务之间频繁切换时,稍不注意就可能陷入死锁的泥潭。死锁就像两个人在狭窄走廊相遇,谁也不肯后退一步,结果谁都过不去。从技术角度看,死锁是指… · 2026/11/3 11:42:22
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46