IP地址定位系统源码实战:从数据源选型到查询服务搭建
简介IP地址精准定位系统源码 v1.0 是一套面向Web开发初学者与地图应用爱好者的轻量级查询工具在传统IP归属地查询只能定位到市级单位的基础上将结果细化到误差约100米的范围并直接在地图上标注显示适合用于访客地域分析、位置服务演示或课程设计参考。压缩包共152个文件约1.51MB其中png与jpg图片7430个用于界面素材与地图图标js脚本21个承担查询逻辑与地图交互css样式16个负责页面布局与主题另有php后端接口、htm页面及少量gif、txt等辅助文件整体结构完整、开箱即用。目前已有1014人学习下载说明该方案在同类资源中具备一定参考价值。读者可从中获取IP查询与地图定位的完整实现思路、前后端调用方式以及界面样式组织方法便于二次开发或嵌入自有项目。1. 从「IP地址精准定位系统源码 v1.0」说起它到底能定位到什么程度很多人第一次看到「IP地址精准定位系统源码 v1.0」这个标题脑子里冒出来的画面是输入一个 IP地图上直接标出某栋楼、某个小区甚至精确到门牌号。我先把结论摆在前面——纯靠 IP 地址做不到这个精度。任何声称能靠单一 IP 直接定位到具体门牌的源码要么是接了别的数据源要么就是在演示数据上做了手脚。那这个系统到底在做什么它解决的是「把一个 IP 映射到一个地理范围」的问题国家级、省级、城市级运气好能到区县级再往下就要靠运营商基站、GPS、WiFi 探针这些额外信息了。适合谁用做风控的、做日志分析的、做内容分发的、做反欺诈的工程师——你需要知道一个请求大概从哪个城市来用来做区域限流、异常登录检测、CDN 调度参考而不是用来抓人。我见过太多团队在这件事上翻车拿了一个免费 IP 库上线后发现同城识别率不到六成跨省误判一堆最后业务方骂街。所以这篇笔记我不打算只讲「怎么调 API」而是把「IP 定位系统」从数据源选型、本地库构建、查询服务封装到精度验证整条链路拆开讲清楚。源码 v1.0 这种命名通常意味着它是一个可跑通的最小闭环而不是一个开箱即用的生产级产品这一点心里要有数。2. IP 定位的数据从哪来三类数据源与选型逻辑2.1 为什么纯 IP 定位天生有精度天花板IP 地址本身是网络层逻辑地址它和物理位置之间没有强绑定关系。运营商在分配 IP 段时是按区域池化管理的但这个「区域」粒度可能是整个省也可能是几个地市共用一段。更麻烦的是动态 IP、NAT 共享、移动网络出口集中——一个基站出来几百个用户共用一个出口 IP你拿这个 IP 去查只能查到基站所在的城市甚至只是省会。所以 IP 定位的精度本质上取决于数据源在「IP 段—地理位置」这个映射上做了多细的标注。常见的数据获取方式有三类第一类是注册信息WHOIS/RIR。亚太、欧洲、北美各自的互联网注册机构会记录 IP 段的分配机构和国家但只到国家级别且更新滞后。用它做国家级判断够用做城市级完全不够。第二类是商业地理库。这类库通过运营商合作、网络测量延迟探测、路由追踪、用户上报等方式把 IP 段细化到城市甚至区县。精度相对高但需要付费且不同厂商覆盖质量差异很大。第三类是主动测量众包校正。自己布探测节点或者收集用户授权的位置回传反向修正 IP 库。这是大厂做法成本高但能拿到最适合自己业务场景的数据。对于「源码 v1.0」这种项目现实做法通常是本地离线库 在线 API 兜底。离线库保证查询速度和基本可用在线 API 在离线库查不到或者置信度低时补一刀。2.2 离线库和在线 API 怎么选、怎么配先给一个选型对比这张表是我自己在几个项目里踩过坑之后总结的维度离线库如开源 GeoLite2 类商业在线 API查询延迟微秒级本地内存/磁盘几十到几百毫秒受网络影响精度国家/省较稳城市波动大城市级相对稳区县看厂商成本一次性或免费按调用量计费更新频率自己定期拉取厂商维护隐私数据不出本地IP 会发给第三方适合场景高并发、日志批处理低频、对精度要求高我的建议是主链路走离线库风控等关键判定走在线 API 二次确认。不要把所有请求都打到在线 API一是贵二是延迟不可控三是对方限流时你整个服务跟着抖。离线库常见格式是 mmdbMaxMind DB 格式或 CSV。mmdb 的好处是查询快、支持内存映射适合做成常驻服务。CSV 适合自己导入数据库做二次加工。下面这段是我一般用来加载 mmdb 并做查询的最小代码import maxminddb # 打开本地 mmdb 文件modeMMAP 让操作系统做内存映射避免全量加载 reader maxminddb.open_database(data/geoip_city.mmdb, modemaxminddb.MODE_MMAP) def lookup(ip: str) - dict: try: record reader.get(ip) except Exception as e: # 非法 IP 或库中无记录统一返回空结构避免上层拿到 None 崩溃 return {country: None, city: None, lat: None, lon: None} if not record: return {country: None, city: None, lat: None, lon: None} return { country: record.get(country, {}).get(iso_code), city: record.get(city, {}).get(names, {}).get(zh-CN) or record.get(city, {}).get(names, {}).get(en), lat: record.get(location, {}).get(latitude), lon: record.get(location, {}).get(longitude), } if __name__ __main__: print(lookup(8.8.8.8))逻辑说明open_database用 MMAP 模式打开查询时不会把整个库读进内存适合几十 MB 到几百 MB 的库文件。get返回的是嵌套字典不同厂商字段名可能不同所以取值时用.get逐层兜底。参数上mode建议用 MMAP如果服务 QPS 很高且内存充足可以改成MODE_MEMORY换取更稳定的查询速度代价是启动时加载变慢、内存占用上升。在线 API 的封装要注意三件事超时、重试、降级。超时我一般设 300ms重试最多一次降级就是返回离线库结果并打标记。不要无限重试否则一个慢请求会拖垮线程池。2.3 把 IP 转成整数查询加速的关键一步不管用哪种库底层做范围匹配时IP 都会被转成整数。IPv4 是 32 位IPv6 是 128 位。自己写查询逻辑时这一步不能省否则字符串比较会慢得离谱。import ipaddress def ip_to_int(ip: str) - int: # strictFalse 允许传入带前导零等非标准写法生产环境建议 strictTrue return int(ipaddress.ip_address(ip)) def int_to_ip(n: int) - str: return str(ipaddress.ip_address(n)) # 示例判断某个 IP 是否落在指定区间 def in_range(ip: str, start: str, end: str) - bool: v ip_to_int(ip) return ip_to_int(start) v ip_to_int(end)逻辑说明ipaddress.ip_address同时支持 v4 和 v6返回的是对应版本的地址对象int()转换后 v4 是 0 到 2^32-1v6 是 0 到 2^128-1。参数上strict控制是否接受非规范写法线上建议开启严格模式避免脏数据混入。区间判断用整数比较比字符串前缀匹配可靠得多尤其是跨段边界时。3. 从零搭一个可用的 IP 定位查询服务3.1 目录结构与依赖先定好工程骨架源码 v1.0 这类项目最容易犯的错是所有逻辑堆在一个文件里跑通没问题一改就崩。我一般会先定一个最小骨架ipgeo/ ├── app/ │ ├── main.py # 服务入口 │ ├── lookup.py # 查询核心逻辑 │ ├── loader.py # 数据加载与热更新 │ └── models.py # 返回结构定义 ├── data/ │ └── geoip_city.mmdb ├── tests/ │ └── test_lookup.py └── requirements.txt依赖尽量少maxminddb做离线查询fastapiuvicorn做 HTTP 层pydantic做返回结构校验。不要一上来就上 Redis、上消息队列v1.0 阶段先把单机查询跑稳。pip install maxminddb fastapi uvicorn pydantic3.2 查询核心缓存、兜底与置信度标记查询逻辑要解决三个问题重复 IP 怎么快速返回、查不到怎么办、结果可不可信。我的做法是加一层 LRU 缓存查不到时返回结构化空值并标记sourcenone同时记录日志供后续分析。from functools import lru_cache from app.loader import get_reader lru_cache(maxsize100000) def _cached_lookup(ip: str) - tuple: reader get_reader() record reader.get(ip) if not record: return (None, None, None, None, none) country record.get(country, {}).get(iso_code) city (record.get(city, {}).get(names, {}).get(zh-CN) or record.get(city, {}).get(names, {}).get(en)) loc record.get(location, {}) return (country, city, loc.get(latitude), loc.get(longitude), offline) def lookup(ip: str) - dict: country, city, lat, lon, source _cached_lookup(ip) return { ip: ip, country: country, city: city, lat: lat, lon: lon, source: source, confidence: high if city else (low if country else none), }逻辑说明lru_cache的maxsize设 10 万按每个条目几百字节算内存占用可控。返回元组而不是字典是因为元组可哈希、缓存开销更小。confidence字段是给上层业务用的有城市算 high只有国家算 low什么都没有算 none。参数上maxsize要根据你的 IP 基数调整太小命中率低太大内存涨一般按「日活 IP 数 × 1.5」估算。3.3 HTTP 接口与批量查询别让单条查询拖垮吞吐单条查询接口好写但真实场景往往是批量一次日志分析要查几万个 IP。这时候如果还是一条条走 HTTP网络往返就把时间吃光了。我的做法是提供两个接口/lookup单条/batch批量批量接口限制单次最多 1000 个内部走本地库循环查询。from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List from app.lookup import lookup app FastAPI() class BatchReq(BaseModel): ips: List[str] app.get(/lookup) def single(ip: str): if not ip: raise HTTPException(status_code400, detailip required) return lookup(ip) app.post(/batch) def batch(req: BatchReq): if len(req.ips) 1000: raise HTTPException(status_code400, detailmax 1000 ips per request) return {results: [lookup(ip) for ip in req.ips]}逻辑说明BatchReq用 pydantic 做入参校验ips必须是字符串列表。批量上限 1000 是经验值再大单次响应体可能超过几 MB反而影响传输。参数上如果业务确实需要更大批量建议改成异步任务结果回调而不是硬撑单次请求。启动命令uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4workers数量一般设成 CPU 核数因为查询是 CPU 密集型内存映射字典查找不是 IO 密集型。设太多反而增加上下文切换开销。3.4 数据热更新不停服换库的两种做法IP 库需要定期更新但你不能每次更新都重启服务。常见做法有两种一是双缓冲加载新库到新 reader原子替换引用二是文件监听检测到库文件 mtime 变化后重新加载。import threading import maxminddb from pathlib import Path _reader None _lock threading.Lock() _db_path Path(data/geoip_city.mmdb) def get_reader(): global _reader if _reader is None: with _lock: if _reader is None: _reader maxminddb.open_database(str(_db_path), modemaxminddb.MODE_MMAP) return _reader def reload_reader(): global _reader new_reader maxminddb.open_database(str(_db_path), modemaxminddb.MODE_MMAP) with _lock: old _reader _reader new_reader # 旧 reader 不立即关闭等在途查询结束后由 GC 回收逻辑说明双检锁保证只初始化一次reload_reader先加载新库再替换引用避免替换瞬间查询失败。参数上旧 reader 不要手动 close因为可能有线程还在用它交给垃圾回收更安全。如果库文件很大加载耗时明显建议在低峰期触发更新。4. 精度验证与避坑那些让你怀疑人生的定位偏差4.1 怎么验证你的定位到底准不准不要凭感觉说「挺准的」。验证方法很简单准备一批已知位置的 IP比如你自己在不同城市机房部署的机器、合作方提供的样本跑一遍查询统计城市级命中率、省级命中率、经纬度误差中位数。指标计算方式可接受参考国家命中率国家正确数 / 总数99% 以上省级命中率省份正确数 / 总数85% 以上城市命中率城市正确数 / 总数60% 以上经纬度误差预测点与真实点球面距离中位数50km 以内算可用城市命中率 60% 听起来很低但这是行业常态。如果你的业务对城市级要求极高就必须上在线 API 或者自建测量体系别指望一个离线库解决所有问题。4.2 避坑清单五条血泪经验现象一本地测试全对上线后大量 IP 查不到。原因测试用的是公网知名 IP上线后遇到大量内网 IP、保留地址、运营商特殊段。解决查询前先过滤私有地址段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8这些直接返回sourceprivate不要浪费查询。现象二同一 IP 两次查询结果不一样。原因在线 API 和离线库混用或者库更新后旧缓存没失效。解决缓存 key 加上库版本号库更新时清空缓存在线和离线结果分开标记不要混在一个字段里。现象三移动网络 IP 定位全飘到省会。原因移动出口 IP 集中在省级网关。解决对移动网络段单独标记业务上接受省级精度不要强行做城市级判定。现象四批量查询内存暴涨。原因一次性把几万个 IP 的结果全塞进列表返回。解决分批处理每批 500 到 1000或者改成流式返回。lru_cache的 maxsize 也要设上限否则缓存本身会吃光内存。现象五经纬度直接拿来做距离计算结果偏差巨大。原因IP 库给的经纬度往往是城市中心点不是真实位置且不同库的中心点定义不同。解决距离计算只用于粗筛不要用于精确判定跨库对比时先做坐标对齐。注意IP 定位结果只能作为参考信号不要作为唯一判定依据。风控场景里IP 城市和用户常用城市不一致只能算一个弱特征权重给太高会误伤大量正常用户。5. 进阶技巧把定位结果用出「系统感」到这一步单点查询已经跑通了。但一个「系统」和「一个接口」的区别在于你怎么组织结果、怎么和业务结合。我分享几个自己常用的进阶做法。第一个技巧是结果分层。不要只返回一个城市名而是返回一个置信度分层结构country一定给province尽量给city带置信度lat/lon带误差半径。上层业务按需取用风控取省级展示取城市地图打点用经纬度加误差圈。这样一套数据能服务多个场景不用为每个场景单独查一遍。第二个技巧是离线库版本管理。每次更新库文件时记录版本号、更新日期、数据来源查询结果里带上db_version。出问题时能快速定位是哪一版数据导致的偏差。我一般会在库文件同目录放一个version.json加载时一起读进来。import json from pathlib import Path def load_version(db_path: str) - str: meta Path(db_path).with_suffix(.version.json) if not meta.exists(): return unknown with open(meta, r, encodingutf-8) as f: return json.load(f).get(version, unknown)逻辑说明版本文件和数据文件同目录同前缀加载时读取。参数上version字段建议用日期来源缩写比如20250101-offline方便排序和比对。第三个技巧是异常 IP 监控。统计查询结果为none或confidencelow的 IP 占比如果某天突然升高说明要么库过期了要么有异常流量。这个指标比单纯看 QPS 有用得多。我一般会按小时聚合超过阈值就告警。第四个技巧是和业务日志打通。把 IP 定位结果作为日志的一个字段写进去后续做用户行为分析时可以直接按城市维度聚合不用回头再查一遍。这一步的收益在排查问题时特别明显——你能快速看出某个城市的请求是不是突然异常。最后一个习惯永远保留原始 IP。定位结果可以丢原始 IP 不要丢。因为库会更新今天的错误定位明天可能就对了但你如果只存了定位结果就永远失去了重新计算的机会。这是我踩过最深的坑之一早期为了省存储把原始 IP 丢了后来换库想重新跑历史数据只能干瞪眼。这套东西做下来你会发现「IP 地址精准定位系统」的「精准」二字靠的不是某一个神奇的数据源而是分层、兜底、验证、监控这一整套工程习惯。源码 v1.0 只是起点真正让它变得可靠的是你对边界和误差的理解。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

基于ZooKeeper的Java配置服务工具包实战指南

基于ZooKeeper的Java配置服务工具包实战指南

简介:这是一套面向Java中高级开发者与分布式系统架构师的ZooKeeper配置服务实践工具包,聚焦解决微服务场景下配置集中管理、服务动态发现与分布式协同等核心问题。资源共39个文件,包含33个Java源码(涵盖客户端封装、监听器实现、配…

2026/10/10 3:06:09 阅读更多 →
乘车热度特征如何提升销量预测?GBDT与特征工程实战

乘车热度特征如何提升销量预测?GBDT与特征工程实战

简介:这是2019年CCF大数据与计算智能大赛“乘用车市场销量预测”赛道的冠军解决方案,面向机器学习竞赛爱好者、数据挖掘工程师及汽车行业数据分析人员。方案采用LightGBM与规则模型融合策略,将全部代码整合为单个Python脚本约500行&#xff0…

2026/10/10 3:06:09 阅读更多 →
IP地址精准定位系统源码v1.0:从IP到经纬度的工程化落地

IP地址精准定位系统源码v1.0:从IP到经纬度的工程化落地

简介:IP地址精准定位系统源码 v1.0 是一套面向Web开发初学者与地理定位功能开发者的实用项目源码,用于解决传统IP查询仅能定位到市级单位、精度不足的问题。该源码在常规IP归属地查询基础上引入地图定位能力,可将查询结果精确到百米级误差范围…

2026/10/10 3:06:09 阅读更多 →

最新新闻

红外目标检测数据集:格式转换与YOLO训练避坑指南

红外目标检测数据集:格式转换与YOLO训练避坑指南

简介:YOLO红外多目标检测数据集面向目标检测学习与算法验证场景,提供1000张真实场景高质量红外图片,经LabelImg标注后附带voc(xml)、coco(json)、yolo(txt)三种格式标签,分别存放于独立文件夹,并确保标注框质量较高&am…

2026/10/10 23:28:02 阅读更多 →
监控场景员工玩手机检测:YOLOv9训练实战与避坑指南

监控场景员工玩手机检测:YOLOv9训练实战与避坑指南

简介:面向监控场景下的员工玩手机识别需求,这份基于YOLOv9的深度学习目标检测项目提供了完整可运行的Python源码、训练好的模型权重与指标曲线,适合计算机相关专业学生用于毕业设计、课程实践或企业安防场景的算法验证。压缩包内共192个文件&…

2026/10/10 23:28:02 阅读更多 →
凯恩斯革命与宏观经济学:从IS-LM模型到Python代码模拟

凯恩斯革命与宏观经济学:从IS-LM模型到Python代码模拟

简介:这份PPT课件聚焦凯恩斯革命与宏观经济学的诞生,面向经济学专业学生、考研备考者及对经济思想史感兴趣的读者,帮助梳理凯恩斯理论体系的来龙去脉与核心命题。资源包内含1个ppt文件,约165KB,以幻灯片形式呈现&#…

2026/10/10 23:28:01 阅读更多 →
初学者必知:llm.txt是干什么用的?TaoToken 统一 Key 接入 AI 编程助手实操

初学者必知:llm.txt是干什么用的?TaoToken 统一 Key 接入 AI 编程助手实操

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 23:27:01 阅读更多 →
子序列动态规划四题详解:LCS、不相交的线、最大子序和、判断子序列

子序列动态规划四题详解:LCS、不相交的线、最大子序和、判断子序列

刷动态规划刷到第四十三天,说实话到这个阶段很多人已经有点晕了。前面的背包问题刚消化完,今天又上来四道子序列相关的题——1143.最长公共子序列、1035.不相交的线、53.最大子序和、392.判断子序列。如果你正在跟代码随想录的算法营,或者自己…

2026/10/10 23:27:01 阅读更多 →
拆解OpenClaw on Android的平台插件架构:L1/L2/L3三层依赖设计完全解读(开发者向)

拆解OpenClaw on Android的平台插件架构:L1/L2/L3三层依赖设计完全解读(开发者向)

移动开发AI 应用CLI开发工具 【免费下载链接】openclaw-android Run OpenClaw on Android with a single command — no proot, no Linux 项目地址: https://gitcode.com/gh_mirrors/op/openclaw-android 点击查看 免费下载 OpenClaw on Android 是一个在 Android&…

2026/10/10 23:27:01 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →