最近我在处理一批历史访问日志,需要把几百万条IP归到省市和运营商,翻出了两套离线数据库来对比。一套是用了很多年的纯真社区版,另一套是 MaxMind 的 GeoLite2。原以为只是一次普通的格式切换,结果折腾下来发现,纯真社区版把数据格式从老旧的 txt/dat 全面切换到 CZDB 之后,整个使用体验和之前完全不在一个量级。
这也是我第一次认真把国产免费IP库和国际主流免费IP库放在同一张工作台上对比。如果你也在选型、也想弄清楚 CZDB 到底是什么、或者正在纠结要不要从 GeoLite2 迁过来,这篇文章应该能帮你省掉不少弯路。
1. 为什么我突然盯上了纯真社区版CZDB
1.1 场景倒逼:离线IP库何时成了必需品
先说我的实际场景。我在做网络日志分析和安全运营相关的工作,每天都有大量来源IP需要解析归属地。以前图省事,直接调在线API,但量一上来问题就暴露了:并发有限制、单次请求有延迟、数据要过公网、而且日志里经常夹着内网IP和畸形地址,一顿清洗下来接口费用和等待时间都让人头疼。
后来所有解析全部迁到离线库,IP归属解析变成了本地函数调用。这么做的好处是显而易见的:吞吐高、没有外部依赖、不用担心服务限速,而且对日志中的敏感信息也相对友好,因为IP不需要离开本机就能完成归属判断。离线IP库这时候就不是“锦上添花”了,而是数据处理链路里的一个基础组件。
既然要选离线库,市面上其实就两条主流路线:一条是国际通用的 GeoLite2,另一条就是国内老牌的纯真社区版。我原来的工程里两套都接入了,GeoLite2 负责全球粗粒度定位,纯真负责国内省市和运营商细化。但去年纯真社区版把格式一换,我这边老代码直接跑不起来,这才有了后面这一整套重新调研和对比的经历。
1.2 纯真社区版和CZDB格式是什么关系
纯真IP库在国内互联网圈子里算是老古董级别的存在了,最早的IP归属数据很多站长工具都在用。过去它的发布形态是一个压缩包,解压之后通常是 qqwry.dat 这种二进制文件,或者更早的 txt 明文文件。使用方式也简单,找对应的解析库读文件,然后传入IP查归属。
但老格式的问题也很明显:txt完全明文,动辄几十万行,加载慢;dat格式虽然做了索引,但不同解析器实现各不相同,经常出现同一个库换一个库解析结果就不一样的怪事。纯真官方后来把这套体系整体重构了,新格式就是 CZDB(ChinaZ DataBase)。
CZDB 不是简单换了个文件后缀,而是把数据结构、索引方式、校验机制都重做了一遍。文件本身变成了一个紧凑的二进制容器,单文件承载数据,加载后可以直接做二分查找,定位速度比过去解析 txt 高出一个数量级。更关键的是,从纯真官方发布 CZDB 版开始,原来散落在各处的 txt 版就不再作为标准分发渠道了,很多第三方站点的 txt 数据长期停在旧版本,这也是我建议大家直接切 CZDB 的根本原因。
1.3 确定换库之前,我列了一份选型清单
在动手改造之前,我给自己列了一份问题清单,后面所有验证都是围绕这些问题展开的:
- 库里有没有足够的国内IP和运营商数据,省份、城市、ISP 能不能稳定解析出来;
- 文件加载速度、单次查询耗时、内存占用是否可控;
- 数据更新频率怎么样,能不能跟上运营商IP段调整的速度;
- 用什么编程语言接入,有没有官方或社区维护的解析插件;
- 数据文件本身有没有校验机制,防止下载损坏或者被篡改;
- 最终商用或者集成进现有系统时,许可要求是否明确。
如果你也在评估IP库,这份清单可以直接拿去用。不要只看“能不能查到IP”,文件格式、可维护性、更新机制这些后置成本,才是真正影响长期使用的关键项。
2. CZDB格式深度拆解:新版纯真IP库到底改了什么
2.1 CZDB和传统.dat/txt的本质区别
老一代的纯真txt格式,结构上就是一个文本行一个IP段的列表,典型的行内容大概是“1.0.1.0 - 1.0.3.255 福建省福州市 电信”。这种格式优点是肉眼可读,缺点也极其突出:行数多、解析慢、文件胖、没有索引,要找一个IP必须从头扫到尾。
后来广泛流行的 qqwry.dat 解决了部分性能问题,引入了二分索引,但格式定义非常隐晦。不同解析器对偏移量的处理有细微差别,同一个 dat 文件,用不同库查出来的最后一个字段经常对不上。而且老格式没有任何完整性校验,文件下载一半断了或者被第三方修改过,程序不会报错,只是查出来的数据全是错的,极难排查。
CZDB 算是把这些问题系统地解决了一遍。首先是文件内部自带校验机制,加载时能确认文件没有被截断或者篡改。其次是数据结构更紧凑,索引和数据统一放在同一个二进制文件中,支持二分查找,也支持顺序遍历,查询时间复杂度从原来的O(n)降到了O(log n)。第三是格式设计上不再依赖特定解析器的隐式约定,官方提供了配套的解析插件,避免了过去“库和解析器不匹配”的尴尬。
2.2 文件结构、校验机制与加载原理
关于CZDB的内部细节,纯真官方公开过部分设计说明。它不是一个简单的“偏移量+字符串”拼凑文件,而是由文件头、索引区、数据区和带密钥的校验信息组成。文件头里记录了版本号、索引起始位置、索引长度等关键元数据,加载时先读文件头,再根据偏移量把索引整块载入内存,之后每次查询只需要在内存索引里做二分查找,定位到对应的数据块再读取归属文本。
这种做法有一个直接的好处:内存占用是可控的,索引结构占大头,数据区按需读取。实测在普通服务器上加载一个完整的社区版CZDB文件,内存增量只有几十MB,对大多数业务系统来说毫无压力。
校验机制是CZDB另一个很实用的改进。老dat文件下载完没法验证对错,CZDB因为内部包含校验信息,文件只要损坏或者被改动过,查询插件在加载阶段就会直接报错,不会带病运行。这一点在自动化更新流水线里尤其重要,我可以放心写脚本去定时下载新文件,不用担心下到一半的坏文件污染整个查询服务。
2.3 初次上手最容易踩的3个坑
我这次改造过程中踩过的坑,基本可以归纳成三条。
第一,老代码不能直接复用。网上还能搜到大量基于 qqwry.dat 的解析库,这些解析库读不了CZDB,而且纯真官方已经不再把 txt 作为社区版标准交付格式,所以最稳妥的做法是直接使用官方配套的CZDB解析插件,而不是继续维护老旧的第三方解析逻辑。
第二,数据文件版本和插件版本需要对齐。CZDB 设计上带版本概念,如果下载了最新数据文件却用很老版本的解析插件,有可能会出现加载异常。这不是文件坏了,是插件没有跟上新版本的解析协议。我建议在下发文件的同时,把插件版本也钉死在一个已知兼容的测试版本上,或者至少做一次加载自检。
第三,不要试图用文本编辑器打开或者手工修改CZDB文件。它就不是给人眼看的格式,强行改一个字节都可能导致校验失败。网上有些教程教人“解包CZDB”,实际上如果你只是做应用集成,完全没有必要也不应该去改原始文件,官方解析插件足够用了。
3. Python 读取CZDB IP库的完整实操
3.1 准备环境与获取CZDB文件
我平时的处理链路以 Python 为主,这里就直接以 Python 为例。环境上只需要标准库外加官方提供的 CZDB 解析插件,不需要额外安装数据库服务。下载最新版纯真社区版 CZDB 文件后,放在项目的数据目录里就行。
官方发布的解析插件目前以源码形式提供,里面包含了 Python 版本的查询封装。拿到插件后,目录结构大致是“插件本体+示例代码”,把插件目录加入 Python 的导入路径即可。数据文件不用解压,也不用改后缀,路径保持英文无空格最省事,Windows 上尤其要注意中文路径可能导致加载失败。
3.2 单条查询:从读文件到返回结果的完整代码
用官方配套插件查询一条IP,逻辑其实很简单。下面是一段接近真实用法的示例代码,具体类名以你下载的插件版本为准:
from czdb_lookup import DbSearcher # 初始化查询器 searcher = DbSearcher("pure_china_czdb_vip.czdb") # 单条查询 result = searcher.lookup("223.5.5.5") print(result)以我手头这份社区版数据为例,查询223.5.5.5返回的归属信息会包含国家、省份、城市和运营商字段,比如“中国 浙江 杭州 阿里云”,不同版本字段顺序可能略有差异。如果查询私网地址或者格式错误的IP,函数会返回空结果而不是抛异常,这一点在批量清洗日志时非常友好。
有一个细节值得注意:CZDB 解出来的归属文本是 UTF-8 编码,现代 Python 直接输出没有问题。但如果你接的是比较老的日志系统,默认编码可能是 GBK,输出前最好统一做一次编码转换,否则中文会乱码。
3.3 批量查询与性能实测
批量查询是日志分析里的高频操作。我的做法是先把所有待查询IP去重,放到一个列表里循环查询。实测下来,单线程循环查询一百万条去重后的IP,纯属本地函数调用,整体耗时在几秒到十几秒这个量级,远快于任何在线API方案。
性能瓶颈通常不在查询本身,而在输入输出。日志文件读取、IP去重、结果存储这几个环节对总耗时的影响比查询还大。我建议把待查询IP提前做好预处理,去掉空行、去掉私网地址段、去掉明显非法的字符串,尽量减少无效查询。
如果查询规模继续增大,比如千万级以上,还可以考虑多线程分片处理。CZDB 查询对象创建之后不会修改内部状态,多线程并发调用同一个实例在实测中表现稳定,不需要为每个线程单独创建查询器。
3.4 常用字段说明:国家、省份、城市、ISP到底怎么读
CZDB 社区版返回的主体字段相对固定,核心是归属地描述文本,里面一般包含国家或地区、省、市、运营商等维度。真实场景中我一般这样解析返回文本:
- 如果目标只需要省份级别,直接截取“省”关键字之前的文本;
- 如果需要城市级别,要小心“直辖市”这类特殊行政区,它们没有传统意义的城市层级;
- 运营商信息通常在文本末尾,常见的有电信、联通、移动、教育网等,解析时最好用包含匹配而不是精确匹配,因为数据里可能存在“电信“和“电信CDMA”之类的变体。
还有一个经验是:很多线上服务的IP归属展示只到城市级别,这是正常的。免费IP库的目标粒度普遍就是省市和运营商,不要指望拿到街道或者经纬度,那不属于这类库的能力范围。
4. GeoLite2 使用回顾与新老库对比
4.1 MaxMind GeoLite2 的基本用法
再来说说 GeoLite2。MaxMind 是老牌IP地理信息公司,GeoLite2 是它的免费产品线,主流文件是 GeoLite2-City.mmdb,包含全球范围的IP归属,可以细分到经纬度和时区。它的项目体系很有名,很多开源软件内置的IP归属功能背后都是它。
Python 接入 GeoLite2 通常用 geoip2 官方库,读取 mmdb 文件:
import geoip2.database reader = geoip2.database.Reader("GeoLite2-City.mmdb") response = reader.city("8.8.8.8") print(response.country.names.get("zh-CN")) print(response.city.name) print(response.location.latitude, response.location.longitude)mmdb 格式是 MaxMind 自己的二进制格式,查询性能和 CZDB 一样走索引查找,单次耗时也在毫秒级以下。GeoLite2 的优势在于全球覆盖和结构化字段,国家、省、市、经纬度、时区都是独立字段,做全球化业务时很方便。
但它也有绕不开的限制。Geo-Lite 系列免费库的国内IP定位精度一直相对一般,很多情况下只能精确定位到省份甚至只能到国家,运营商信息基本没有。另外下载需要注册账号并配置 License Key,更新机制也依赖 MaxMind 的分发渠道。
4.2 同一批IP下两个库的定位差异
为了直观对比,我用同一批IP在 CZDB 和 GeoLite2 上各跑了一遍,摘几个典型案例说一下。比如查询公共DNS223.5.5.5,CZDB 能直接给出浙江杭州并附带阿里云运营商标识,GeoLite2 也能到中国浙江,但运营商信息缺失;再比如119.29.29.29,CZDB 显示广东深圳腾讯云,GeoLite2 能定位到广东但城市信息经常只能是省级模糊值;还有一个南方城市宽带地址,CZDB 精确到市级运营商,GeoLite2 则只显示国家。
这里要把话说清楚:两边的数据源和侧重点本来就不一样。MaxMind 对全球尺度的覆盖是它的强项,国内细分本来就不是它的主战场;纯真社区版深耕国内多年,省市和运营商数据自然更细致。所以在我这种以中文日志分析为主要场景的工程里,CZDB 显然更适合做主力库。
下表是我在对比过程中整理的几项关键差异:
| 对比项 | 纯真社区版 CZDB | MaxMind GeoLite2 |
|---|---|---|
| 数据侧重点 | 国内省市与运营商细化 | 全球范围与结构化属性 |
| 国内定位精度 | 通常可到市级 | 常见只到省级 |
| ISP信息 | 有,常见运营商可识别 | 基本没有 |
| 文件体积 | 较小,单文件几十MB量级 | City库通常在几十MB以上 |
| 更新方式 | 官方定期发布新版本 | 需注册账号并管理 License |
| 格式 | CZDB 二进制带校验 | mmdb 二进制 |
| 查询速度 | 本地索引二分查找,毫秒级以下 | 同样毫秒级以下 |
| 解析成本 | 官方配套插件 | 官方库成熟、资料多 |
| IPv6 | 格式上有扩展空间,社区版以IPv4为主 | 支持良好 |
4.3 好与坏都写在明处:适用场景选择建议
经过一段时间的实际使用,我对两套库的定位有了比较清晰的认识。
如果你的业务主要是国内流量,需要把IP归属到城市和运营商,或者想做地域维度的数据分析,那纯真社区版 CZDB 是更顺手的选择。它的解析结果更贴近国内网络现状,而且更新频率相对稳定,下载一次新文件就能完成数据刷新。
如果你的业务是面向全球用户,需要经纬度、时区、ASN这类字段,或者你的系统本身就在多语言环境下运行,那 GeoLite2 仍然是绕不开的选项。
当然最有意思的做法是两套都接。我现在的工程就是双库模式:请求进来先用 CZDB 查国内归属,CZDB 返回空或查不到时再回退到 GeoLite2 拿全球数据。这样既保住了国内精度,又留住了全球兜底能力,整体体验是最好的。
5. 常见问题排查与避坑清单
5.1 查询出来的地理位置不准,问题出在哪
IP归属解析不准是这类库最容易被吐槽的问题。但深入排查后你会发现,大部分“不准”不是库坏了,而是数据版本落后。运营商调整IP段分配是常态,比如某个城市的宽带用户被重新规划到相邻城市的段,如果IP库半年没更新,查出来的归属自然就和现实对不上。
我的建议是给IP库建一条自动化更新任务。纯真社区版支持定期从官方发布页获取新文件,下载完成后放到指定目录,重启查询进程即可生效。更新频率不需要太高,一周一次对大多数场景都足够了。如果对实时性要求极高,那说明离线库方案本身就不适合你,应该考虑在线API方案。
还有一个容易忽略的点:IP归属不代表用户真实位置。很多公共Wi-Fi、CDN出口、云服务IP的归属地和实际用户所在地是两回事。解析结果只能作为参考维度,不能当成精确的用户地理位置依据。
5.2 CZDB更新后原来的代码报错怎么办
CZDB 刚推出那段时间,社区动荡比较大,最典型的现象就是:数据文件更新了,但代码还停留在旧版解析库,一加载就报错。这是因为 CZDB 在设计上带版本概念,新数据文件可能包含旧插件无法识别的结构变化。
解决办法很简单,优先使用官方配套插件,并且保持插件版本和数据版本同期更新。我再多说一句,如果你在网上找第三方封装的“CZDB解析库”,最好先确认它的活跃日期,太老的项目很可能没有跟上文件格式的迭代。
5.3 在线查和离线库结果不一致怎么办
很多人会拿一个IP同时去在线平台和离线库里查归属,发现结果不一致,立刻怀疑离线库有问题。实际上,在线平台用的库、数据版本、解析规则都可能不同,出现差异太正常了。
拿同一个IP查三个平台,经常能查出三个不同的城市。这不代表哪一家是坏的,只是数据源差异。真正评判一个库准不准,应该用足够大的抽样样本做统计对比,而不是拿个别IP下结论。我更倾向于把离线库结果当成基础主数据,参考在线平台做交叉验证,但不会因为个别差异就全盘否定某个库。
5.4 系统集成时要注意的编码与并发问题
最后说两个工程集成时的隐藏坑。第一是编码问题,CZDB 返回的中文是 UTF-8,老日志系统如果是 GBK,写入前必须统一编码,否则直接写进文件就是乱码。第二是并发问题,多线程场景下可以共享同一个查询器实例,但要注意在初始化阶段先做一次加载检查,确保数据文件有效,避免在运行过程中突然抛出异常。
另外,文件路径里尽量不要带中文和特殊符号,程序放在什么机器上跑,数据文件就尽量和代码目录一起部署,减少配置项出错的概率。这些都是小事,但日志分析链路一旦跑起来,任何一个环节的坑都会放大到整批数据上。
6. 一点选型建议(就当是我掏心窝的话)
两套库来回折腾了这么久,我最大的感受是:别迷信“国际大厂”,也别低估“国产老牌”。GeoLite2 在全球视野和字段结构化上确实成熟,但国内IP粒度一直是它的短板;CZDB 在省市和运营商定位上更接地气,而且新版格式解决了旧版文件校验和解析混乱的痛点。
我个人现在的做法是:两套库都放在工程里,默认查 CZDB,数据命中不到或者需要国际上细分字段时再回退到 GeoLite2。这种双库策略虽然多写一点兜底逻辑,但换来的是定位准确率和服务的稳定性,值。
如果你还在犹豫要不要切换,我给的建议是直接动手跑一遍对比。拿自己业务里最有代表性的几千条IP,分别在 CZDB 和 GeoLite2 上跑一下,看看哪些条目有差异、差异有多大,比看任何测评都直观。选IP库这事没有绝对的对错,只有适不适合你的场景。数据更新、字段粒度、许可约束,这些看上去不起眼的细节,会在长期维护中慢慢显现出差别来。