用Python爬虫打造自动化SEO巡检工具:从TDK提取到反爬避坑
简介面向网站优化人员与Python学习者的SEO数据采集与分析工具以“SEO网络蜘蛛侠”命名通过自动化爬虫帮助用户完成关键词挖掘、元信息检查、链接结构分析、内容质量与加载速度检测、死链排查等高频SEO任务。程序内置可执行版本无需复杂环境配置即可在Windows下直接运行压缩包共6个文件整体约786KB包含主程序、配置文件、使用说明文档、网址导航页面及数据文件。其中txt文件用于记录抓取结果htm文件提供参考网址入口ini负责保存运行参数配合文档可快速上手。已有380人学习下载适合希望借助Python技术提升网站搜索引擎可见性、需要常用SEO自动化脚本的初、中级运营或开发者参考学习。使用时注意遵守目标网站robots协议合理控制抓取频率避免给服务器造成压力。整体而言这份资源既提供了可直接使用的工具也展示了爬虫在SEO数据分析中的典型应用思路对学习爬虫与搜索优化结合的读者具有参考价值。1. SEO Python 爬虫程序从手动采集到自动化数据管线做过 SEO 的人都懂这种痛站点规模一大页面标题、描述、关键词、URL 状态码、外链分布全靠手工维护一个几百页的站点光巡检一轮就得搭进去大半天。这份 SEO Python 爬虫程序资源就是把这摊活从「手动开十几个浏览器标签页复制粘贴」变成「一条命令跑完整站巡检」的落地工具包。它不解决算法排名问题只解决数据采集与监控的效率问题站内 URL 批量抓取、TDK 信息提取、响应状态码校验、robots 规则检查、站点地图生成与检查全部用 Python 实现跑完输出结构化数据入库。适合三类人天天被运营催更的 SEO 工程师、管着大量站点又缺人手的技术站长、想用脚本替代重复劳动但不想从零写爬虫的开发者。接下来我从环境搭建讲起把代码模块、参数调优和反爬踩坑按实际使用顺序拆开照着抄就能跑。2. 爬虫环境与选型为什么是 requests BeautifulSoup 而不是 Scrapy2.1 资源包里的依赖构成与 Python 版本选择这份资源的核心依赖非常克制没有引入重型框架。主程序基于 Python 3.8 以上版本开发用到的第三方库集中在 requests、beautifulsoup4、lxml 三个外加一个用于 URL 去重的可选依赖bloom-filter 或者直接内存集合。这种选型的理由很直接SEO 场景的爬虫大多是中小型站点巡检单次采集量级在几千到几万个 URL 之间requests 的同步请求配合合理延时完全够用而 Scrapy 的异步框架在这个量级反而是过度设计——不仅学习曲线陡调试时还得跟 Twisted 的报错信息较劲出了问题不好定位。我建议先跑通这套轻量方案如果后续站点量级涨到十万级再考虑迁移异步爬虫。虚拟环境建议用 venv 创建避免污染系统 Python。依赖安装直接走 requirements.txt资源包里的版本锁得非常保守requests 2.28.x、beautifulsoup4 4.11.x、lxml 4.9.x这三个版本组合经过充分测试不会出现新版本 API 变更导致的兼容问题。安装命令如下python3 -m venv seo_crawler_env source seo_crawler_env/bin/activate pip install -r requirements.txt这里-r参数指定依赖文件路径安装完成后可以用pip list核对版本。注意不要用sudo pip install直接装到系统环境后续换机器部署时容易遇到权限和版本冲突问题。2.2 requests 与 BeautifulSoup 的分工边界整个爬虫程序的架构分两层requests 负责网络层处理 HTTP 连接、请求头、超时、重试BeautifulSoup 负责解析层处理 HTML 结构提取、编码转换、DOM 查询。这两层职责必须分开不要混在一起写。我见过有人用正则表达式直接从 HTML 里抠 title短期能用遇到压缩过或者属性顺序变化的页面就崩。BeautifulSoup 的 find 方法配合 lxml 解析器对不规范 HTML 的容忍度远高于正则。请求头的伪装是另一个关键点。很多初学者直接裸奔 requests 默认 UA 去请求站点大概率被弹验证码或者直接返回 403。资源包里内置了浏览器 UA 池默认从池里随机取一个 User-Agent 使用。session 对象的复用也值得注意——同一 session 会保持 cookies 和连接池连续请求时有明显速度优势import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } session requests.Session() resp session.get(https://example.com, headersheaders, timeout(5, 15)) soup BeautifulSoup(resp.text, lxml)参数含义timeout传元组(5, 15)表示连接超时 5 秒、读取超时 15 秒比只传一个整数更精细resp.text会自动按响应头里的 charset 解码但遇到部分老站 charset 声明错误时需要手动指定编码这点后面避坑章节细说。2.3 项目目录结构与入口脚本设计资源包解压后是标准的 Python 工程结构没有花哨的框架包装每个脚本职责单一入口统一放在main.py。核心文件包括spider.py采集逻辑、parser.py页面解析与字段提取、storage.py数据落库、config.yaml站点配置与规则参数。这种拆分方式的好处是部署到新站点时只需改配置文件不用动代码逻辑。config.yaml是整套工具的枢纽采集范围、请求频率、字段提取规则全在这里定义。一个典型的配置长这样site: start_urls: - https://example.com/ allowed_domains: - example.com max_urls: 5000 crawl_delay: 1.0 timeout: 10 seo_fields: - title - meta_description - meta_keywords - h1 - h2 - canonical - robots_meta - status_codecrawl_delay是请求间隔秒数调太大会拖慢速度调太小容易触发反爬建议0.5 ~ 2.0之间按站点情况试。max_urls是单次巡检上限站点的降级处理和输出文件命名规则在代码里有默认实现不改也能跑通默认流程。3. 三个核心采集模块URL 发现、TDK 提取与状态码巡检3.1 URL 发现与队列管理从起始页 BFS 展开爬虫程序的第一步是 URL 发现。这里用的是广度优先策略从起始页出发解析页面里的所有链接过滤外部域名和明显非页面资源图片、CSS、JS然后继续抓取新发现的链接直到全站覆盖或者达到max_urls上限。队列管理用collections.deque实现先进先出保证层级展开顺序配合一个set集合做去重避免循环链接导致死循环from collections import deque def discover_urls(start_url, allowed_domains, max_urls5000): queue deque([start_url]) visited set() collected [] while queue and len(visited) max_urls: url queue.popleft() if url in visited: continue visited.add(url) collected.append(url) page_links extract_links(url) for link in page_links: if link.startswith(http) and domain_check(link, allowed_domains): if link not in visited: queue.append(link) return collecteddomain_check是内部函数核心逻辑是用urlparse取出域名前缀做白名单比对防止顺着外部链接爬丢。max_urls防止站点有动态生成页面时无限繁殖。注意这里的extract_links是从parser.py里导入的负责把a标签的href属性提取出来并拼接成绝对地址——相对链接必须用urljoin拼接否则会产出大量无用的相对路径。实际跑的时候会发现很多站的版权页和隐私政策页是孤立无链的只有站内深层入口所以我在资源里额外放了一个站点地图sitemap.xml辅助发现模块自动解析loc标签补充 URL。这个模块默认开启建议保持开启能把 URL 覆盖率提高 15% 到 30%。3.2 TDK 提取title、description、keywords 与 H 标签结构化页面信息的提取是 SEO 数据巡检的重头戏。用 BeautifulSoup 的find方法逐字段提取注意三个细节title标签可能包含空格或换行需要strip()清理meta标签的name属性值大小写不定要对 key 做小写化匹配缺失字段必须记录为None而不是空字符串这样后续统计缺失率时才准确。提取逻辑的稳定版本如下def extract_seo_fields(soup): title soup.find(title) title_text title.get_text().strip() if title else None description None keywords None for meta in soup.find_all(meta): meta_name meta.get(name, ).lower() if meta_name description: description meta.get(content, ).strip() or None elif meta_name keywords: keywords meta.get(content, ).strip() or None h1_list [h.get_text().strip() for h in soup.find_all(h1)] canonical soup.find(link, relcanonical) canonical_href canonical.get(href) if canonical else None return { title: title_text, meta_description: description, meta_keywords: keywords, h1_count: len(h1_list), canonical: canonical_href, }这里h1_count的记录很有业务价值——一页多个 H1 是常见 SEO 问题新手容易漏检。把提取结果返回为字典格式后续入库和输出报表直接映射列名。字段缺失时用None而不是空串还有个好处写入 SQLite 后查询时可以用IS NULL做条件过滤一眼看到哪些页面缺描述、缺关键词优先级最高的整改对象直接生成列表。这就是数据采集和人工抽查的区别——批量出的「缺失清单」比肉眼扫主页可靠得多。3.3 状态码巡检与响应监测重定向、404 与超时判定状态码巡检模块基于 requests 的allow_redirects参数控制。SEO 场景里 301/302 重定向是否配置正确直接影响链接权重传递所以巡检时分两步首先关闭自动重定向收集首次响应码再记录最终落地 URL 和最终状态码两者一对比就能发现「重定向链断裂」和「死循环跳转」两类问题def check_status(session, url, timeout10): first_response session.get(url, allow_redirectsFalse, timeouttimeout) first_status first_response.status_code final_url url final_status first_status if first_status in (301, 302, 307, 308): final_response session.get(url, allow_redirectsTrue, timeouttimeout) final_url final_response.url final_status final_response.status_code return { original_url: url, first_status: first_status, final_url: final_url, final_status: final_status, redirect_count: count_redirects(session, url), }count_redirects用session.max_redirects结合响应历史对象来计算跳转次数。这个巡检模块跑完一轮你能拿到一份格式化的站点健康报告哪些页面 200 正常、哪些 404 需要做 301 到替代页、哪些页面跳转了 5 次以上需要清理重定向链。资源包里默认输出 CSV 和 SQLite 两种格式CSV 直接交给运营同学在 Excel 里筛选SQLite 留给后续做趋势监控和历史对比。这里有个细节timeout参数建议不要小于 10 秒部分老站的后台响应慢超时设太短会误报大量抓取失败混淆真实问题。4. 避坑与常见问题5 个用这份爬虫最容易踩的坑4.1 抓回大量gzip乱码页面现象采集结果里一部分页面 title 提取出来是乱码尤其在老站上成批出现。原因响应头里声明Content-Encoding: gzip但使用resp.text时 requests 没有正确解压或者部分老站的压缩编码声明与实际不符BeautifulSoup 拿到的是一段压缩后的二进制流。解决requests 正常情况下会透明处理 gzip 解压但如果用了自定义适配器或者某些代理配置压缩处理会被绕过。我在这份资源里加了手动校验用resp.content的前两个字节做魔数判断0x1f 0x8b是 gzip 签名必要时直接用gzip.decompress手动解压后再交给 BeautifulSoup同时resp.encoding没拿到有效值时就指定 utf-8 或 gb18030 重试。从那以后我每趟巡检完都先跑一遍乱码检测全乱码率大于 5% 就直接报警。4.2 链接归一化缺失导致抓取名存实亡现象URL 去重集合越抓越大巡检数量远超站点实际页面数大量重复页面被当作独立 URL 入库。原因同一页面可能通过?utm_sourcexxx、#anchor、http与https混用等多个变体出现字符串级别去重分不清它们是同一个页面。解决入队列前做两步归一化先去锚点#及后面片段再用urlparse拆分查询参数并删除utm_*和spm这类统计参数最后强制统一成 https 前缀。资源包里的normalize_url函数就是干这个的默认开启。从那以后我每次给站点导入旧数据前都强制把这套归一化跑一遍URL 总量至少下降三分之一。4.3 请求频率过快触发验证码与 IP 限制现象连续抓取几百页后响应状态码突然变成 403 或者频繁返回验证码页面。原因巡检时请求间隔固定、请求头单一站点风控模块识别出机器行为特征把整个 IP 段拉黑。解决一是把请求间隔从固定值改成随机区间比如random.uniform(0.8, 2.5)二是 UA 池随机切换必要时按访问时段加Accept-Language等辅助头三是给 session 挂一个简单的指数退避重试器遇到 403 时暂停 30 秒再试探一次。这套组合拳打下来绝大多数小站的反爬不会跟你死磕。我在资源里已经实现了smart_sleep和retry_with_backoff两个函数直接用即可。4.4 SQLite 写入大量数据时锁库现象巡检 4000 个页面后程序报database is locked错误入库中断之前的数据全丢。原因SQLite 单线程逐条插入并且每插入一条就 commit如果中间有别的进程占着库就会出现锁冲突。解决改用事务批量提交攒够 200 条做一次executemany提交前统一放同一个事务里另外给连接开timeout30的超时参数防止极端情况锁死。改完之后同样 4000 页的数据写入从 8 分钟缩到 40 秒这个优化效果是最明显的。4.5 sitemap.xml 抓取遇到跨域限制现象站点做好 sitemap 放在cdn.example.com/sitemap.xml但程序只抓主域名下的导致 sitemap 发现模块完全没用。原因allowed_domains过滤逻辑把 CDN 域名的链接当作外链丢掉了。解决在config.yaml里增加额外的sitemap_domains配置项允许列表额外放行 sitemap 文件所在域名。我在资源包里默认放行了对象存储和 CDN 域名段省得再改代码。5. 验证与进阶把爬虫程序变成 SEO 日常巡检的自动化工具跑通基础采集流程后这套程序真正发挥价值的地方是定时巡检和数据对比。我在实际使用中形成了一套固定流程每周跑一次全站巡检产出三个文件——报告 CSV、完整数据库、错误汇总 JSON。CSV 给运营做人工复核SQLite 存历史JSON 喂给监控看板展示缺失项数量和重定向异常趋势。定时任务的实现建议直接用系统 cron 而不是 Python 内部死循环原因是不占常驻内存失败了系统会自动重跑。cron 配置从一个每天凌晨三点执行巡检的示例入手0 3 * * * cd /path/to/seo_crawler /path/to/venv/bin/python main.py --config config.yaml --output reports/$(date \%Y-\%m-\%d).csv这段配置里$(date %Y-%m-%d)是 Shell 的日期命令用于每天生成带日期的新文件。在 crontab 里记得转义百分号\%不然日期命令会执行失败。执行权限方面cron 跑的系统用户建议用独立账号不要把巡检脚本挂到 root 下万一出问题权限边界清晰。进阶用法我这边有两种经过实测的玩法。一种是「变化对比模式」把上次巡检的标题和描述存成快照本次巡检时逐页比对有变化的自动打标生成 diff 报告。这个模式用来监控站内大规模改版效果特别有用——哪类页面被批量改了标题哪个栏目临时撤换了描述一屏看全。另一种玩法是基于提取的 TDK 数据构建一个简单的内容覆盖热力图把标题长度、描述长度按区间分段统计各段落的页面数量分布能快速定位「标题超长截断了」「描述写得太短」这两种高频问题。最后一层是验证手段。巡检程序跑完不能只看有没有报错要用抽样核对来验证采集准确性。我习惯从结果里随机抽 20 个 URL用浏览器手动打开比对 title 和 description对的越摸越准错的立刻回查解析函数是不是漏了某些标签结构。注意在config.yaml里加了verify_sample: 20配置项程序会自动把抽样 URL 单独输出到一个文件省去自己随机挑。有一次抽样发现某个栏目的页面带了经过转义的 JSON 结构作为meta标签内容BeautifulSoup 默认解析方式提取时取到内容里残留了\u0026之类转义符后来在extract_seo_fields里加了一段 HTML 实体反转义处理之后的巡检结果就干净多了。从那以后我要求自己在每次巡检任务跑完后强制走一遍抽样核对流程不然数据入库了心里也不踏实——数据是脏的后面所有分析和报告都是白搭。希望这份拆解能让你用这套爬虫程序时少走几步弯路。本文还有配套的精品资源点击获取

相关新闻

Python快速可视化SEG-Y地震数据:三步实现剖面绘制与批量抽检

Python快速可视化SEG-Y地震数据:三步实现剖面绘制与批量抽检

简介:这份资源面向地质勘探、地震预测与环境监测领域的技术人员及Qt开发者,提供一套基于Qt3.3框架的地震数据快速可视化实现方案,重点解决大规模地震记录的读取、处理与图形化展示问题。压缩包共77个文件,约2.07MB,以h…

2026/10/16 17:55:23 阅读更多 →
Android应用目录结构全解析:从Gradle配置到资源文件管理

Android应用目录结构全解析:从Gradle配置到资源文件管理

很多刚接触Android开发的朋友,第一次用Android Studio新建项目时,看到左侧那一大坨目录树往往会有点懵。我当年也是从一个个文件夹点开、再关掉,折腾了好几天才搞清楚哪个目录是存代码的、哪个目录是放图片的、哪个文件是注册Activity的。这篇…

2026/10/16 17:54:23 阅读更多 →
ecosim:基于C+OpenGL的生命演化模拟器实战指南

ecosim:基于C+OpenGL的生命演化模拟器实战指南

简介:ecosim是一款面向Linux平台开发者的交互式生态系统与进化模拟器,使用C语言与OpenGL实现,融合遗传算法、Boids群体行为模型等核心机制,适用于计算生物学、人工生命、复杂系统教学与算法可视化研究。资源包共24个文件&#xff…

2026/10/16 17:54:23 阅读更多 →

最新新闻

C#脚本编辑器实现:基于Roslyn的语法感知与安全执行

C#脚本编辑器实现:基于Roslyn的语法感知与安全执行

简介:本资源是一套基于C#实现的轻量级脚本编辑器完整源码,面向.NET开发者、自动化工具开发者及工业视觉软件二次开发人员,解决在自有应用中快速集成可编译、可调试脚本扩展能力的实际需求。压缩包共156个文件,含23个核心C#源文件&…

2026/10/16 18:34:14 阅读更多 →
烽火S5700三层交换机配置实战:登录、VLAN、路由与排错指南

烽火S5700三层交换机配置实战:登录、VLAN、路由与排错指南

简介:烽火通信官方发布的 S5700 系列三层千兆路由交换机操作手册(V1.0)是一份面向企业网、校园网及 ISP 场景的 CLI 配置指南,适用于工程技术人员、工程开通人员、设备维护人员和网络管理人员,可帮助读者系统掌握该系列…

2026/10/16 18:34:14 阅读更多 →
5 分钟用 SlackKit 开发你的第一个 Slack Bot:RTM 机器人完全教程

5 分钟用 SlackKit 开发你的第一个 Slack Bot:RTM 机器人完全教程

【免费下载链接】SlackKit Build Slack apps, in Swift 项目地址: https://gitcode.com/gh_mirrors/sl/SlackKit 点击查看 免费下载 SlackKit 是一个用 Swift 编写的开源工具包,让你几行代码就能构建 Slack 应用:通过 RTM(Real T…

2026/10/16 18:34:14 阅读更多 →
AI招聘工具如何重塑春招人才争夺战

AI招聘工具如何重塑春招人才争夺战

1. 春招战场为什么变成了“极速博弈”每年的春季招聘,本质上是一场掐着秒表进行的人才争夺战。企业端急着补缺口、抢项目节点,候选人端忙着海投、赶场、比offer,双方都被压缩在短短一两个月内完成大量匹配动作。以往那种“挂个职位慢慢等简历…

2026/10/16 18:34:14 阅读更多 →
测开准备 Day03:Python工程化与深度学习基础认知

测开准备 Day03:Python工程化与深度学习基础认知

提到“测开准备”,很多人第一反应是刷接口用例设计、背测试理论、把自动化工具点得飞起。但以我在测试开发岗位摸爬滚打几年的体感来说,真正决定你能不能从“会写脚本”跨越到“能搭框架、能定方案”的,首先是 Python 功底够不够工程化&#…

2026/10/16 18:34:14 阅读更多 →
DeepSeek-R1本地部署原理:RL训练解耦与ollama推理优化

DeepSeek-R1本地部署原理:RL训练解耦与ollama推理优化

简介:本资源是一份面向AI初学者与本地部署实践者的DeepSeek大模型技术详解PDF,聚焦大语言模型原理、本地化部署全流程及强化学习驱动的R1推理模型训练方法。内容覆盖本地部署动因(隐私保护、离线可用、零成本适配)、Transformer架…

2026/10/16 18:33:13 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* 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 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战: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/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/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/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 阅读更多 →