数据抓取与HTTP代理实战:构建稳健商业数据流水线的四层架构
1. 项目概述当数据成为新石油我们如何“开采”在商业世界里数据早已不是简单的数字堆砌而是驱动决策、洞察趋势、创造价值的“新石油”。但和石油一样原始数据深埋在地下需要一套高效、可靠的“开采”技术才能将其转化为可用的能源。这个“开采”过程就是我们常说的数据抓取。它不仅仅是技术人员的工具更是连接公开信息海洋与私有商业智能之间的关键桥梁。无论是分析市场动态、监控竞争对手价格、追踪社交媒体舆情还是整合分散的公开数据源数据抓取都是第一步也是最基础、最核心的一步。然而现实中的“数据油田”并非不设防的露天矿场。目标网站为了保护自身服务器资源、防止恶意攻击或维护商业利益普遍设置了访问频率限制、IP封锁、验证码等重重防线。直接、粗暴的抓取行为无异于开着钻探车硬闯警戒区结果往往是IP被迅速封禁抓取任务戛然而止。这时HTTP代理就扮演了“隐形斗篷”和“轮换身份”的关键角色。它能让你的抓取请求看起来像是来自全球不同地区、不同网络的普通用户有效规避反爬机制保障数据抓取流程的稳定与持续。简单来说数据抓取是“矛”负责精准获取目标信息HTTP代理是“盾”负责保护“矛”的行动不被发现和拦截。两者结合才能构成一套驱动商业智能的可靠数据流水线。本篇文章我将从一个多年数据抓取实践者的角度深入拆解如何将这两者有效结合构建稳健、高效的数据获取方案并分享那些在官方文档里找不到的实战心得与避坑指南。2. 核心思路构建稳健数据流水线的四层架构要理解数据抓取与HTTP代理的协同工作不能只停留在“用一个库、配一个代理IP”的层面。我们需要一个系统性的架构思维。我将一个完整的、可用于商业智能的数据抓取系统抽象为四个层次目标层、策略层、代理层、执行与处理层。这个模型能帮你清晰地规划每一个环节。2.1 目标层定义“要什么”与“从哪里要”这是所有工作的起点也是最容易被忽视却至关重要的环节。目标定义不清后续所有努力都可能白费。数据需求精准化不要笼统地说“抓取某电商网站的商品信息”。你需要明确是商品标题、价格、销量、评论数、库存状态还是商品描述、图片链接、规格参数是否需要历史价格数据需求的粒度决定了抓取的复杂度和策略。目标源分析对目标网站进行“侦查”。手动访问几个页面用浏览器开发者工具F12观察网络请求Network tab。关键看几点数据加载方式是传统的HTML静态加载还是通过JavaScript动态渲染即数据是通过后续的XHR/Fetch请求获取的这决定了你该用requestsBeautifulSoup还是必须上Selenium或Playwright。接口规律如果是动态加载查看获取数据的API接口URL、请求参数如页码page、每页数量limit、时间戳t等和请求头特别是User-Agent,Referer, 有时还有特定的Authorization令牌。找到规律就能模拟请求效率远高于渲染整个页面。反爬措施留意是否有验证码点选、滑块、请求频率是否稍快就返回错误码如429、是否对同一IP短时间内的访问量敏感。2.2 策略层设计“如何要”的智能方案基于目标分析制定具体的抓取策略这是技术实现的核心蓝图。请求模拟策略这是通过代码模仿浏览器行为。核心是构造合理的HTTP请求头。一个最基本的、能通过大多数基础检查的请求头应该包含headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, Connection: keep-alive, Upgrade-Insecure-Requests: 1, }注意User-Agent要定期更新使用一个固定的古老版本容易被识别为爬虫。可以维护一个列表随机选择。频率控制策略这是对目标网站的尊重也是自我保护。绝对避免无间隔的连续请求。应在每个请求之间加入随机延时。import time import random def random_delay(): # 随机延时1到3秒模拟人类阅读间隔 time.sleep(random.uniform(1, 3))对于大规模抓取需要更精细的控制例如使用令牌桶或漏桶算法来平滑请求流量。错误处理与重试策略网络不稳定、代理失效、目标服务器临时错误都是常态。你的代码必须健壮。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retries Retry(total3, # 总重试次数 backoff_factor0.5, # 退避因子延迟时间 backoff_factor * (2^(重试次数-1)) 秒 status_forcelist[500, 502, 503, 504, 429]) # 遇到这些状态码才重试 session.mount(http://, HTTPAdapter(max_retriesretries)) session.mount(https://, HTTPAdapter(max_retriesretries))这个配置会让你的请求在遇到服务器错误或频率限制429时自动重试最多3次且每次重试间隔逐渐延长。2.3 代理层部署“隐身衣”与“替身军团”这是应对IP封锁的核心。HTTP代理在此层发挥作用。你需要管理一个代理IP池并智能调度它们。代理类型选择数据中心代理来自云服务商机房的IP成本低、速度快但容易被识别和封禁因为IP段集中且特征明显。住宅代理来自真实家庭宽带网络的IP隐匿性极高最像真实用户但成本高、速度可能不稳定。移动代理来自蜂窝移动网络的IP隐匿性最强适合对反爬极其严格的场景如某些社交平台成本最高。选择建议对于大多数商业数据抓取如商品价格、新闻文章、公开财报高质量的数据中心代理或混合代理池已足够。只有面对顶级反爬系统时才需考虑住宅或移动代理。代理池管理与调度获取与验证从代理服务商获取IP列表后必须进行有效性验证。写一个脚本定时访问一个稳定的测试网站如http://httpbin.org/ip检查返回的IP是否与代理IP一致以及响应速度和状态码。存储将有效的代理IP格式如ip:port存入数据库如Redis或内存队列中并记录其最近一次的成功使用时间、响应速度、失败次数等元数据。调度策略随机调度简单但可能不均匀。优先级调度根据响应速度、成功率给代理IP打分优先使用高分的。会话保持对于需要登录或维持会话的抓取任务同一个任务链应尽量使用同一个代理IP避免因IP切换导致会话失效。2.4 执行与处理层实现“自动化”与“价值化”这一层是代码落地和数据转化的地方。任务队列与并发控制使用Scrapy框架、Celery任务队列或自己用asyncioaiohttp实现异步抓取。关键是要控制并发度过高的并发会给代理池和目标服务器带来巨大压力导致IP被批量封禁。通常单个目标网站的并发数建议在5-10以下。数据解析与清洗用BeautifulSoup、lxml或parsel解析HTML用json模块处理API返回数据。清洗包括去除空白字符、转换格式如日期字符串转datetime对象、处理缺失值。数据存储根据数据量和后续使用方式选择文件CSV, JSON、数据库MySQL, PostgreSQL, MongoDB或数据仓库。结构化数据存SQL半结构化或文档型数据存NoSQL。监控与告警监控抓取成功率、代理IP消耗速度、数据质量如字段缺失率。设置告警当成功率持续下降或代理IP库存告急时及时通知负责人。3. HTTP代理的深度应用与实战技巧理解了架构我们再聚焦于HTTP代理这个关键组件。它的使用远不止在requests里加一个proxies参数那么简单。3.1 代理的集成与最佳实践在Python的requests库中使用代理的基本方式如下import requests proxies { http: http://user:passwordproxy_ip:proxy_port, https: http://user:passwordproxy_ip:proxy_port, # 注意很多代理的https协议也使用http连接 } response requests.get(https://target-site.com/data, proxiesproxies, timeout10)实操心得timeout参数至关重要必须设置。一个失效的代理会导致请求一直挂起没有超时控制整个程序就可能卡死。建议连接超时和读取超时分开设置如timeout(3, 10)。对于需要高并发的场景aiohttp配合代理池是更优选择import aiohttp import asyncio async def fetch(session, url, proxy): try: async with session.get(url, proxyproxy, timeoutaiohttp.ClientTimeout(total10)) as resp: return await resp.text() except Exception as e: print(f请求失败: {e}, 代理: {proxy}) return None async def main(): proxy_pool [http://proxy1:port, http://proxy2:port, ...] # 你的代理池 async with aiohttp.ClientSession() as session: tasks [] for i, proxy in enumerate(proxy_pool): url fhttps://target.com/page/{i} task fetch(session, url, proxy) tasks.append(task) results await asyncio.gather(*tasks) # 处理results # asyncio.run(main())3.2 代理池的自治管理与智能剔除一个健康的代理池需要自我管理和净化。以下是核心管理逻辑健康检查定期如每5分钟用池中所有代理去访问一个稳定的、对代理友好的测试页。记录响应时间、状态码。评分与降级为每个代理设立一个初始分数如100分。请求成功则加分如1失败则扣分如-10。响应时间超过阈值也扣分。当分数低于某个值如20分将其移入“隔离区”或直接剔除。隔离与复活对于暂时失败的代理不要立即永久删除。可以将其放入一个“冷却”队列等待一段时间如10分钟后再次进行健康检查如果通过则恢复使用。这能应对目标网站临时性的IP封锁封几分钟。流量记录记录每个代理成功抓取的数据量或请求次数。这有助于分析代理的质量成本比。你可以用Redis的Sorted Set有序集合来实现这个池子成员的分数就是代理的“健康分”每次检查后更新分数获取代理时优先取分数高的。3.3 应对高级反爬代理之外的组合拳即使有了优质代理面对复杂的反爬机制仍需多管齐下。User-Agent轮换和代理IP一样User-Agent也需要一个池子随机轮换。可以从开源项目或自己收集一批最新的、常见的浏览器UA字符串。Cookie与会话管理对于需要登录的网站使用requests.Session()对象来维持Cookie。注意Session对象和代理绑定。一个Session生命周期内最好使用同一个代理避免会话混乱。请求参数随机化观察目标网站API有些会校验Referer来源页有些会带一个随机token或时间戳_t。你需要从初始页的HTML或之前的响应中提取这些参数并构造到后续请求中。渲染型爬虫的代理集成使用Selenium或Playwright时代理配置方式不同。Selenium示例Chromefrom selenium import webdriver options webdriver.ChromeOptions() options.add_argument(--proxy-serverhttp://proxy_ip:proxy_port) # 如果需要认证通常需借助插件或使用带认证信息的代理URL格式但Selenium原生支持不佳常需额外插件 driver webdriver.Chrome(optionsoptions)Playwright示例Playwright的代理API更友好支持HTTP和SOCKS且自带认证支持。from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(proxy{ server: http://proxy_ip:proxy_port, username: your_username, password: your_password }) page browser.new_page() page.goto(https://target.com)重要提示渲染型爬虫资源消耗大、速度慢且代理IP消耗极快因为加载一个页面会产生数十个网络请求。除非目标数据必须通过JS渲染才能获得否则应优先尝试分析并模拟其背后的API接口。4. 从抓取到商业智能数据 pipeline 构建抓取到的原始数据只是原材料必须经过加工才能用于分析。这就需要一个简单的数据 pipeline。标准化与清洗去重根据业务主键如商品ID日期去除重复记录。格式化统一日期格式、货币单位、计量单位。处理缺失与异常对于缺失的价格字段是置为NULL还是用前值填充对于明显异常的价格如0.01元或99999元需要设定规则过滤或标记。结构化将嵌套的JSON或杂乱的HTML文本解析成规整的数据库表字段。存储与聚合清洗后的数据存入业务数据库如MySQL的staging表。根据分析需求定期如每天运行聚合任务生成聚合表。例如计算每个商品当天的平均价格、最低最高价、与竞争对手的价差等。可视化与洞察连接BI工具如Tableau, Power BI, Metabase或开源的Superset。建立数据看板监控核心指标如整体价格走势、特定品类竞争情况、库存变化预警等。设置自动化报告定期将关键洞察通过邮件或企业通讯工具发送给业务团队。一个简单的聚合示例SQL-- 假设有原始价格表 product_prices_raw -- 清洗后存入 product_prices_clean INSERT INTO product_prices_clean (product_id, competitor, price, date, crawled_at) SELECT product_id, competitor, CAST(REPLACE(price, ¥, ) AS DECIMAL(10,2)) as price, -- 清洗价格 DATE(crawled_at) as date, -- 提取日期 crawled_at FROM product_prices_raw WHERE price IS NOT NULL AND price 0 AND price 100000; -- 过滤异常 -- 创建每日聚合视图 CREATE VIEW product_daily_summary AS SELECT product_id, date, COUNT(*) as sample_count, AVG(price) as avg_price, MIN(price) as min_price, MAX(price) as max_price FROM product_prices_clean GROUP BY product_id, date;5. 实战避坑指南与伦理边界最后分享一些用真金白银和时间换来的经验教训。5.1 技术避坑清单坑1代理IP质量参差不齐。免费代理99%不可用商用代理也要仔细甄别。务必先买试用套餐用你的实际目标网站进行测试评估其匿名性是否透明代理、成功率、速度和稳定性。坑2忽视请求头细节。有些网站会检查Accept-Language、Sec-Fetch-*等头信息。用浏览器正常访问一次在开发者工具的Network标签里右键点击请求 - Copy - Copy as cURL然后粘贴到工具如https://curlconverter.com/转换成Python代码可以获取到最接近真实的请求头。坑3超时设置不当。没有设置超时或设置过长会导致程序在遇到问题时代理时无限等待。全局设置默认超时import requests; requests.utils.default_headers lambda: {‘Timeout’: ‘10’}或在使用时显式指定。坑4错误处理不完善。网络请求可能失败解析可能因为页面结构变动而失败。使用try-except包裹核心步骤记录详细的错误日志包括当时使用的代理、URL、请求参数便于事后复盘。坑5数据存储瓶颈。小规模数据存CSV没问题但日增百万级数据时直接写入CSV或单表MySQL会导致性能急剧下降。考虑分库分表、使用时序数据库或先写入消息队列如Kafka再批量入库。5.2 法律与伦理边界这是比技术更重要的红线。遵守robots.txt这是网站与爬虫之间的基本协议。访问https://target-site.com/robots.txt查看哪些路径是允许或禁止爬取的。虽然这不是法律但尊重它是良好的行业惯例。识别并规避个人信息抓取公开数据时如果意外抓取到电话号码、邮箱、住址等个人敏感信息应立即丢弃并停止相关抓取模式。GDPR、CCPA等法规对个人信息保护极其严格。控制访问频率你的抓取行为不应影响目标网站的正常服务。将请求频率控制在人类浏览的水平并尽量在网站流量低谷期如凌晨进行大规模抓取。审查服务条款很多网站的用户协议中明确禁止自动化数据抓取。在启动大型商业抓取项目前最好进行法律咨询。数据用途限定将抓取的数据用于内部分析、趋势研究通常是安全的。但未经许可直接复制其内容到自己的网站进行展示“盗版”或用于训练与原网站直接竞争的AI模型则存在很高的法律风险。数据抓取是一门在技术、策略与规则间寻找平衡的艺术。HTTP代理是这项艺术中不可或缺的工具但它不是“免死金牌”。真正的“密码”在于对目标系统的深刻理解、对自身技术的精细打磨以及对商业伦理和法律边界的清醒认知。构建一个稳定、高效、负责任的数据抓取系统是一个持续迭代和优化的过程。从一个小而准的目标开始逐步完善你的代理池、错误处理和数据管道你会发现数据驱动的决策视野将为你和你的业务打开一扇全新的大门。

相关新闻

Windows右键菜单管理终极指南:用ContextMenuManager告别臃肿系统

Windows右键菜单管理终极指南:用ContextMenuManager告别臃肿系统

Windows右键菜单管理终极指南:用ContextMenuManager告别臃肿系统 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否厌倦了Windows右键菜单越来越…

2026/10/4 8:15:10 阅读更多 →
计算机网络核心协议与分层模型精讲:从TCP/IP到HTTP的实战解析

计算机网络核心协议与分层模型精讲:从TCP/IP到HTTP的实战解析

1. 项目概述:为什么我们需要这份总结?如果你正在为计算机网络的期末考试、考研复试或者技术面试而焦头烂额,面对海量的名词和错综复杂的简答题感到无从下手,那么这份总结就是为你准备的。计算机网络这门课,知识点既“碎…

2026/10/13 11:03:15 阅读更多 →
Windows Docker 安装配置全攻略:从 WSL2 原理到实战避坑指南

Windows Docker 安装配置全攻略:从 WSL2 原理到实战避坑指南

1. 项目概述:为什么要在Windows上折腾Docker? 如果你是一名在Windows环境下工作的开发者、运维或者技术爱好者,最近可能频繁听到“Docker”这个词。它就像一个魔法盒,能把你的应用和它运行所需的一切环境(代码、运行时…

2026/10/3 13:31:45 阅读更多 →

最新新闻

JDK 27量子安全新特性:KEM与ML-DSA重塑Java公钥密码体系

JDK 27量子安全新特性:KEM与ML-DSA重塑Java公钥密码体系

最近技术社区里有一条消息热度很高——JDK 27 的首个新特性曝光,方向锁定在量子安全。我第一反应不是"又出个花活",而是打开手边的迁移清单看了一眼:RSA 证书、ECDSA 签名、TLS 握手、JAR 签名,凡是依赖公钥密码的地方&…

2026/10/13 15:39:37 阅读更多 →
macOS下构建可控MNMP开发环境:Nginx+MySQL+PHP精准配置指南

macOS下构建可控MNMP开发环境:Nginx+MySQL+PHP精准配置指南

1. 项目概述:为什么在 macOS 上坚持用 MNMP 而不是 MAMP 或一键包?“Mac-环境搭建”这个关键词背后,藏着一群真实用户的日常困境:前端开发者切完 React 组件要本地跑 PHP 后端接口,全栈同学想复现生产环境的 Nginx My…

2026/10/13 15:39:37 阅读更多 →
Statsmodels一元线性回归:从跑通到读懂的完整指南

Statsmodels一元线性回归:从跑通到读懂的完整指南

1. 为什么 Statsmodels 的一元线性回归值得单独拿出来讲很多人第一次接触线性回归,都是从 scikit-learn 的LinearRegression开始的。几行代码拟合完,.coef_和.intercept_一取,预测一跑,感觉事情就结束了。但真正做过数据分析项目的…

2026/10/13 15:39:37 阅读更多 →
Agent从Demo到生产:四层核心壁垒与稳定性改造实战

Agent从Demo到生产:四层核心壁垒与稳定性改造实战

1. 为什么你的Agent演示很惊艳,上线就崩溃 做过Agent项目的朋友大概率都经历过这个场景:本地跑一个Demo,接上大模型,挂几个工具函数,对话流畅、任务完成得漂漂亮亮,录个屏发到群里,大家都说“这…

2026/10/13 15:39:37 阅读更多 →
JAK-STAT信号通路深度拆解:从分子机制到靶向治疗与实验指南

JAK-STAT信号通路深度拆解:从分子机制到靶向治疗与实验指南

开题即点题:JAK-STAT 信号通路到底是什么?这个在免疫学、血液科、肿瘤科乃至风湿免疫领域论文里刷屏的名词,很多人第一反应是——这是一个从细胞膜到细胞核的经典信号传导链条。对,但又不完全是。我这一篇不打算给你画教科书式的信…

2026/10/13 15:39:37 阅读更多 →
DCM4CHE多帧DICOM解析实战:从像素流到可计算单帧图像

DCM4CHE多帧DICOM解析实战:从像素流到可计算单帧图像

1. 项目概述:为什么多帧图像处理是医学影像工作流里的“隐形关卡”在某高校医学影像实验室做数据预处理时,我第一次被DCM4CHE处理多帧图像的问题卡住整整三天——不是报错,而是结果“看起来对”,但下游算法一跑就崩。后来发现&…

2026/10/13 15:38:35 阅读更多 →

日新闻

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

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

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

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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →