├── MANIFEST.in ├── phone ├── __init__.py ├── phone.dat ├── test_phone.py ├── build_record.py └── phone.py ├── travis └── setup.py ├── .travis.yml ├── setup.py ├── .gitignore └── README.md /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include phone/phone.dat 2 | -------------------------------------------------------------------------------- /phone/__init__.py: -------------------------------------------------------------------------------- 1 | from .phone import Phone 2 | 3 | __all__ = ['Phone'] 4 | -------------------------------------------------------------------------------- /phone/phone.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ls0f/phone/HEAD/phone/phone.dat -------------------------------------------------------------------------------- /travis/setup.py: -------------------------------------------------------------------------------- 1 | from phone import Phone 2 | 3 | 4 | p = Phone() 5 | p.test() -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://travis-ci.org/lovedboy/phone 2 | language: python 3 | python: 4 | - 2.7 5 | - pypy 6 | - 3.3 7 | - 3.4 8 | - 3.5 9 | - nightly 10 | - pypy3 11 | 12 | branches: 13 | only: 14 | - master 15 | 16 | script: 17 | - python -m unittest discover 18 | - python setup.py -q install 19 | - python ./travis/setup.py 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from setuptools import setup, find_packages 3 | 4 | PACKAGE = "phone" 5 | NAME = "phone" 6 | DESCRIPTION = "手机号码库" 7 | AUTHOR = "ls0f" 8 | AUTHOR_EMAIL = "admin@lovedboy.com" 9 | URL = "https://github.com/ls0f/phone" 10 | VERSION = '0.4.5' 11 | 12 | setup( 13 | name=NAME, 14 | version=VERSION, 15 | description=DESCRIPTION, 16 | author=AUTHOR, 17 | author_email=AUTHOR_EMAIL, 18 | license="BSD", 19 | url=URL, 20 | include_package_data=True, 21 | packages=find_packages(), 22 | classifiers=[ 23 | 'License :: OSI Approved :: MIT License', 24 | 'Programming Language :: Python', 25 | 'Intended Audience :: Developers', 26 | 'Operating System :: OS Independent', 27 | ], 28 | zip_safe=False, 29 | ) 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | .idea/* 55 | -------------------------------------------------------------------------------- /phone/test_phone.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | from unittest import TestCase 4 | from phone import Phone 5 | 6 | 7 | class TestPhone(TestCase): 8 | 9 | def setUp(self): 10 | self.p = Phone() 11 | 12 | def tearDown(self): 13 | pass 14 | 15 | def test_get_phone_no_type(self): 16 | 17 | self.assertEqual(self.p.get_phone_no_type(1), '移动') 18 | self.assertEqual(self.p.get_phone_no_type(2), '联通') 19 | self.assertEqual(self.p.get_phone_no_type(3), '电信') 20 | self.assertEqual(self.p.get_phone_no_type(6), '移动虚拟运营商') 21 | self.assertEqual(self.p.get_phone_no_type(5), '联通虚拟运营商') 22 | self.assertEqual(self.p.get_phone_no_type(4), '电信虚拟运营商') 23 | self.assertEqual(self.p.get_phone_no_type(7), '广电') 24 | self.assertEqual(self.p.get_phone_no_type(8), '广电虚拟运营商') 25 | 26 | def test_find_on_assert_error(self): 27 | 28 | try: 29 | self.p.find(123) 30 | self.assertTrue(False) 31 | except AssertionError: 32 | pass 33 | 34 | def test_find_on_ok(self): 35 | 36 | res = self.p.find(1521147) 37 | self.assertEqual(res['zip_code'], '421000') 38 | self.assertEqual(res['area_code'], '0734') 39 | self.assertEqual(res['city'], '衡阳') 40 | self.assertEqual(res['province'], '湖南') 41 | self.assertEqual(res['phone_type'], '移动') 42 | 43 | def test_find_on_no_res(self): 44 | 45 | res = self.p.find(1991147) 46 | self.assertEqual(res, None) 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/lovedboy/phone.svg?branch=master)](https://travis-ci.org/lovedboy/phone) 2 | 3 | ## 手机号码库 4 | 5 | #### 安装 6 | 7 | 使用pip安装: 8 | 9 | ``` 10 | pip install phone 11 | 12 | ``` 13 | 或者直接clone源码。 14 | 15 | 使用: 16 | 17 | ``` 18 | from phone import Phone 19 | p = Phone() 20 | p.find(1888888) 21 | ``` 22 | 23 | ### 支持号段 24 | 13\*,15\*,18\*,14[5,7],17[0,6,7,8], 19[2,3,7,9] 25 | 26 | #### 记录条数 27 | 28 | 499527 (updated:2023年12月) 29 | 30 | #### 其他语言支持 31 | 32 | 下载[phone.dat](https://github.com/lovedboy/phone/raw/master/phone/phone.dat)文件,用其他语言解析即可。 33 | 34 | * [lua解析](https://gist.github.com/lovedboy/bbff19c91e3d98388d52),如果不支持bit32,用[这个](https://gist.github.com/lovedboy/fe7750e202572712615a) 35 | * [go解析](https://github.com/xluohome/phonedata) 36 | * [Node解析](https://github.com/conzi/phone) 37 | * [PHP解析](https://github.com/shitoudev/phone-location) 38 | 39 | #### phone.dat文件格式 40 | 41 | ``` 42 | 43 | | 4 bytes | <- phone.dat 版本号 44 | ------------ 45 | | 4 bytes | <- 第一个索引的偏移 46 | ----------------------- 47 | | offset - 8 | <- 记录区 48 | ----------------------- 49 | | index | <- 索引区 50 | ----------------------- 51 | 52 | ``` 53 | 54 | * `头部` 头部为8个字节,版本号为4个字节,第一个索引的偏移为4个字节(<4si)。 55 | * `记录区` 中每条记录的格式为"\<省份\>|\<城市\>|\<邮编\>|\<长途区号\>\0"。 每条记录以'\0'结束。 56 | * `索引区` 中每条记录的格式为"<手机号前七位><记录区的偏移><卡类型>",每个索引的长度为9个字节(` 1: 116 | locals()[sys.argv[1]]() 117 | else: 118 | build_record() 119 | -------------------------------------------------------------------------------- /phone/phone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import struct 5 | import sys 6 | 7 | __author__ = 'lovedboy' 8 | 9 | if sys.version_info > (3, 0): 10 | def get_record_content(buf, start_offset): 11 | end_offset = buf.find(b'\x00', start_offset) 12 | return buf[start_offset:end_offset].decode() 13 | else: 14 | def get_record_content(buf, start_offset): 15 | end_offset = buf.find('\x00', start_offset) 16 | return buf[start_offset:end_offset] 17 | 18 | 19 | class Phone(object): 20 | def __init__(self, dat_file=None): 21 | 22 | if dat_file is None: 23 | dat_file = os.path.join(os.path.dirname(__file__), "phone.dat") 24 | self.dat_file = dat_file 25 | with open(self.dat_file, 'rb') as f: 26 | self.buf = f.read() 27 | 28 | self.head_fmt = "<4si" 29 | self.phone_fmt = "= buflen: 88 | return 89 | 90 | buffer = self.buf[current_offset: current_offset + 91 | self.phone_fmt_length] 92 | cur_phone, record_offset, phone_type = struct.unpack(self.phone_fmt, 93 | buffer) 94 | 95 | if cur_phone > int_phone: 96 | right = middle - 1 97 | elif cur_phone < int_phone: 98 | left = middle + 1 99 | else: 100 | record_content = get_record_content(self.buf, record_offset) 101 | return Phone._format_phone_content(phone_num, record_content, 102 | phone_type) 103 | 104 | def find(self, phone_num): 105 | return self._lookup_phone(phone_num) 106 | 107 | @staticmethod 108 | def human_phone_info(phone_info): 109 | if not phone_info: 110 | return '' 111 | 112 | return "{}|{}|{}|{}|{}|{}".format(phone_info['phone'], 113 | phone_info['province'], 114 | phone_info['city'], 115 | phone_info['zip_code'], 116 | phone_info['area_code'], 117 | phone_info['phone_type']) 118 | 119 | def test(self): 120 | self.get_phone_dat_msg() 121 | for i in range(1529900, 1529999): 122 | print(self.human_phone_info(self.find(i))) 123 | 124 | 125 | if __name__ == "__main__": 126 | phone = Phone() 127 | phone.test() 128 | --------------------------------------------------------------------------------