/
fish_data.py
674 lines (468 loc) · 20.3 KB
/
fish_data.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
# coding=utf-8
"""
``fish_data`` 包含的是一些与数据信息相关的函数,比如银行卡、身份证信息的生成和校验。
在我们进行一些开发测试、功能测试、自动化测试、压力测试等场景下,都需要模拟身份证、银行卡等信息。
fish_data 中的函数就是用在这样的场景。注意,这些函数不会生成真实的身份证和银行卡号。
"""
# 2018.12.9 v1.1.3 created by David Yi
# 2019.1.13 edit by David Yi, #202 优化 class CardBin(), class IdCard()
import re
import sqlite3
import os
from functools import lru_cache
# 2018.12.18
def sqlite_query(db, sql, params):
dir_path = os.path.dirname(os.path.abspath(__file__))
conn = sqlite3.connect(os.path.join(dir_path, 'db', db))
cursor = conn.cursor()
cursor.execute(sql, params)
values = cursor.fetchall()
cursor.close()
conn.close()
return values
class IdCard(object):
"""
校验身份证号、获取身份证校验位,获取随机生成身份证号所需身份代码等函数;
举例如下::
print('--- IdCard demo ---')
print('get_checkcode of "32012419870101001":', IdCard.get_checkcode('32012419870101001')[1])
print('check_number of "130522198407316471":', IdCard.check_number('130522198407316471')[0])
print('get_zone_info of "北京市":', IdCard.get_zone_info(area_str='北京市')
print('get_areanote_info of "北京(11)":', IdCard.get_areanote_info('11'))
print('---')
执行结果::
--- IdCard demo ---
get_checkcode of "32012419870101001": 5
check_number of "130522198407316471": True
get_zone_info of "北京市": [('110000', '北京市')]
get_areanote_info of "北京(11)": ([('110000', '北京市'), ('110100', '北京市市辖区'), ('110101', '北京市东城区'), ...
---
"""
# 计算身份证号码的校验位
# ---
# 2018.12.12 create by David.Yi, add in v1.1.4 github issue #143
# 2019.1.5 edit, v1.1.6 github issue #187, 修改函数名称
@classmethod
def get_checkcode(cls, id_number_str):
"""
计算身份证号码的校验位;
:param:
* id_number_str: (string) 身份证号的前17位,比如 3201241987010100
:returns:
* 返回类型 (tuple)
* flag: (bool) 如果身份证号格式正确,返回 True;格式错误,返回 False
* checkcode: 计算身份证前17位的校验码
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_checkcode demo ---')
# id number
id1 = '32012419870101001'
print(id1, IdCard.get_checkcode(id1)[1])
# id number
id2 = '13052219840731647'
print(id2, IdCard.get_checkcode(id2)[1])
print('---')
输出结果::
--- fish_data get_checkcode demo ---
32012419870101001 5
13052219840731647 1
---
"""
# 判断长度,如果不是 17 位,直接返回失败
if len(id_number_str) != 17:
return False, -1
id_regex = '[1-9][0-9]{14}([0-9]{2}[0-9X])?'
if not re.match(id_regex, id_number_str):
return False, -1
items = [int(item) for item in id_number_str]
# 加权因子表
factors = (7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
# 计算17位数字各位数字与对应的加权因子的乘积
copulas = sum([a * b for a, b in zip(factors, items)])
# 校验码表
check_codes = ('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')
checkcode = check_codes[copulas % 11].upper()
return True, checkcode
# 检查身份证号码是否能通过校验规则
# ---
# 2018.12.9 create by David Yi, add in v1.1.3, github issue #137
# 2018.12.13 edit, v1.1.4 github issue #145
# 2019.1.5 edit, v1.1.6 github issue #187, 修改函数名称
# some original source: https://zhuanlan.zhihu.com/p/24449773
@classmethod
def check_number(cls, id_number):
"""
检查身份证号码是否符合校验规则;
:param:
* id_number: (string) 身份证号,比如 32012419870101001
:returns:
* 返回类型 (tuple),当前有一个值,第一个为 flag,以后第二个值会返回具体校验不通过的详细错误
* flag: (bool) 如果身份证号码校验通过,返回 True;如果身份证校验不通过,返回 False
举例如下::
from fishbase.fish_data import *
print('--- fish_data check_number demo ---')
# id number false
id1 = '320124198701010012'
print(id1, IdCard.check_number(id1)[0])
# id number true
id2 = '130522198407316471'
print(id2, IdCard.check_number(id2)[0])
print('---')
输出结果::
--- fish_data check_number demo ---
320124198701010012 False
130522198407316471 True
---
"""
if isinstance(id_number, int):
id_number = str(id_number)
# 调用函数计算身份证前面17位的 checkcode
result = IdCard.get_checkcode(id_number[0:17])
# 返回第一个 flag 是错误的话,表示身份证格式错误,直接透传返回,第二个为获得的校验码
flag = result[0]
checkcode = result[1]
if not flag:
return flag,
# 判断校验码是否正确
return checkcode == id_number[-1].upper(),
# 输入包含省份、城市、地区信息的内容,返回地区编号,也就是身份证编码中的前6位内容
# ---
# 2018.12.14 12.16 create by David Yi, add in v1.1.4, github issue #139
@classmethod
@lru_cache()
def get_zone_info(cls, area_str, match_type='EXACT', result_type='LIST'):
"""
输入包含省份、城市、地区信息的内容,返回地区编号;
:param:
* area_str: (string) 要查询的区域,省份、城市、地区信息,比如 北京市
* match_type: (string) 查询匹配模式,默认值 'EXACT',表示精确匹配,可选 'FUZZY',表示模糊查询
* result_type: (string) 返回结果数量类型,默认值 'LIST',表示返回列表,可选 'SINGLE_STR',返回结果的第一个地区编号字符串
:returns:
* 返回类型 根据 resule_type 决定返回类型是列表或者单一字符串,列表中包含元组 比如:[('110000', '北京市')],元组中的第一个元素是地区码,
第二个元素是对应的区域内容 结果最多返回 20 个。
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_zone_info demo ---')
result = IdCard.get_zone_info(area_str='北京市')
print(result)
# 模糊查询
result = IdCard.get_zone_info(area_str='西安市', match_type='FUZZY')
print(result)
result0 = []
for i in result:
result0.append(i[0])
print('---西安市---')
print(len(result0))
print(result0)
# 模糊查询, 结果返回设定 single_str
result = IdCard.get_zone_info(area_str='西安市', match_type='FUZZY', result_type='SINGLE_STR')
print(result)
# 模糊查询, 结果返回设定 single_str,西安市 和 西安 的差别
result = IdCard.get_zone_info(area_str='西安', match_type='FUZZY', result_type='SINGLE_STR')
print(result)
print('---')
输出结果::
--- fish_data get_zone_info demo ---
[('110000', '北京市')]
130522198407316471 True
---西安市---
11
['610100', '610101', '610102', '610103', '610104', '610111', '610112', '610113', '610114', '610115',
'610116']
610100
220403
---
"""
values = []
if match_type == 'EXACT':
values = sqlite_query('fish_data.sqlite',
'select zone, areanote from cn_idcard where areanote = :area', {"area": area_str})
if match_type == 'FUZZY':
values = sqlite_query('fish_data.sqlite',
'select zone, areanote from cn_idcard where areanote like :area',
{"area": '%' + area_str + '%'})
# result_type 结果数量判断处理
if result_type == 'LIST':
# 如果返回记录多,大于 20 项,只返回前面 20 个结果
if len(values) > 20:
values = values[0:20]
return values
if result_type == 'SINGLE_STR':
if len(values) == 0:
return ''
if len(values) > 0:
value_str = values[0][0]
return value_str
# 2019.01.07 create by Hu Jun, add in v1.1.6, github issue #192
@classmethod
@lru_cache()
def get_areanote_info(cls, province):
"""
输入省份代码,返回地区信息;
:param:
* province_code: (string) 省份代码 比如:11
:returns:
* note_list: (list) 地区信息列表
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_areanote_info demo ---')
print(IdCard.get_areanote_info('11'))
print('---')
输出结果::
--- fish_data get_areanote_info demo ---
[('110000', '北京市'), ('110100', '北京市市辖区'), ('110101', '北京市东城区'), ...
---
"""
values = sqlite_query('fish_data.sqlite',
'select zone, areanote from cn_idcard where province = :province ',
{"province": province})
return values
# 2019.01.14 create by Hu Jun, add in v1.1.6, github issue #192
@classmethod
@lru_cache()
def get_province_info(cls):
"""
获取省份代码
:param:
:returns:
* province_list: (list) 省份代码列表
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_province_info demo ---')
print(IdCard.get_province_info())
print('---')
输出结果::
--- fish_data get_province_info demo ---
[('11',), ('12',), ('13',), ('14',), ('15',), ...
---
"""
values = sqlite_query('fish_data.sqlite',
'select distinct(province) from cn_idcard',
{})
return values
# 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243
@classmethod
def get_number_detail(cls, id_num):
"""
根据身份证号获取性别、省份、出生年月日信息
:param:
* id_num: (string) 要查询的银行卡号
:returns:
* flag: (bool) 是否查询成功
* info: (dict) 性别信息等
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_number_detail demo ---')
print(IdCard.get_id_num_detail('130522198407316471'))
print('---')
输出结果::
--- fish_data get_number_detail demo ---
(True, {'province': '130000', 'gender': '男', 'birth_date': '19840731'})
---
"""
if len(str(id_num)) != 18:
return False, {}
province = id_num[:2] + '0' * 4
gender = '男' if int(id_num[-2]) % 2 == 1 else '女'
birth_date = id_num[6:14]
return True, {'province': province,
'gender': gender,
'birth_date': birth_date}
# 2019.1.6 create by David Yi, #188 用 class CardBin 方法实现
class CardBin(object):
"""
校验银行卡号、获取银行卡校验位,获取银行卡、银行信息;
举例如下::
print('--- CardBin demo ---')
print('get_checkcode of "439188000699010":', CardBin.get_checkcode('439188000699010'))
print('check_bankcard of "4391880006990100":', CardBin.check_number('4391880006990100'))
print('get_bank_info of "招商银行":', CardBin.get_bank_info('招商银行')
print('get_cardbin_info of "CMB", "DC":', CardBin.get_cardbin_info('CMB', 'DC'))
print('---')
执行结果::
--- CardBin demo ---
get_checkcode of "439188000699010": 9
check_bankcard of "4391880006990100": False
get_bank_info of "招商银行": [('CMB', '招商银行')]
get_cardbin_info of "CMB", "DC": [('410062', 'CMB', 'DC', 16), ('468203', 'CMB', 'DC', 16), ...
---
"""
# 计算银行卡校验位
# ---
# 2018.12.18 create by David Yi, v1.1.4, github issue #154
# 2019.1.5 edit, v1.1.6 github issue #188, 修改函数名称
@classmethod
def get_checkcode(cls, card_number_str):
"""
计算银行卡校验位;
:param:
* card_number_str: (string) 要查询的银行卡号
:returns:
checkcode: (string) 银行卡的校验位
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_checkcode demo ---')
# 不能放真的卡信息,有风险
print(CardBin.get_checkcode('439188000699010'))
print('---')
输出结果::
--- fish_data get_checkcode demo ---
9
---
"""
total = 0
even = True
for item in card_number_str[-1::-1]:
item = int(item)
if even:
item <<= 1
if item > 9:
item -= 9
total += item
even = not even
checkcode = (10 - (total % 10)) % 10
return str(checkcode)
# 检查银行卡校验位是否正确
# ---
# 2018.12.18 create by David Yi, v1.1.4, github issue #155
# 2019.1.5 edit, v1.1.6 github issue #188, 修改函数名称
@classmethod
def check_bankcard(cls, card_number_str):
"""
检查银行卡校验位是否正确;
:param:
* card_number_str: (string) 要查询的银行卡号
:returns:
返回结果:(bool) True or False
举例如下::
from fishbase.fish_data import *
print('--- fish_data check_bankcard demo ---')
# 不能放真的卡信息,有风险
print(CardBin.check_bankcard('4391880006990100'))
print('---')
输出结果::
--- fish_data check_bankcard demo ---
False
---
"""
if isinstance(card_number_str, int):
card_number_str = str(card_number_str)
checkcode = card_number_str[-1]
result = CardBin.get_checkcode(card_number_str[0:-1])
return checkcode == result
# 输入银行名称,返回银行代码
# ---
# 2018.12.18 create by David Yi, add in v1.1.4, github issue #159
# 2019.1.5 edit, v1.1.6 github issue #188, 修改函数名称
@classmethod
@lru_cache()
def get_bank_info(cls, bankname):
"""
银行名称,返回银行代码;
:param:
* bankname: (string) 要查询的银行 名称,比如 招商银行
:returns:
* 返回 银行代号bank , 银行名称 bankname,一条记录为一个 tuple (a, b),然后组成 list 返回
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_bank_info demo ---')
print(CardBin.get_bank_info('招商银行'))
print('---')
输出结果::
--- fish_data get_bank_info demo ---
[('CMB', '招商银行')]
---
"""
values = sqlite_query('fish_data.sqlite',
'select bankcode,bankname from cn_bank where bankname=:bankname',
{"bankname": bankname})
return values
# 输入银行、借记贷记卡种类,返回有效的卡 bin
# ---
# 2018.12.17 create by David Yi, add in v1.1.4, github issue #149
# 2019.1.5 edit, v1.1.6 github issue #188, 修改函数名称
@classmethod
@lru_cache()
def get_cardbin_info(cls, bank, card_type):
"""
输入银行、借记贷记卡种类,返回有效的卡 bin;
:param:
* bank: (string) 要查询的银行代号,比如 ICBC, CMB
* card_type: (string) 银行卡类型,比如 CC 表示信用卡
:returns:
* 返回 cardbin, bank, 银行卡类型type, 银行卡长度 length,一条记录为一个 tuple (a, b, c, d),然后组成 list 返回
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_cardbin_info demo ---')
result = CardBin.get_cardbin_info('CMB', 'DC')
print(result)
print('---')
输出结果::
--- fish_data get_cardbin_info demo ---
[('410062', 'CMB', 'DC', 16), ('468203', 'CMB', 'DC', 16), ...
---
"""
values = sqlite_query('fish_data.sqlite',
'select bin,bankcode,cardtype,length from cn_cardbin where bankcode=:bank '
'and cardtype=:card_type',
{"bank": bank, "card_type": card_type})
return values
# 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243
@classmethod
@lru_cache()
def get_card_detail(cls, card_num):
"""
根据银行卡卡号,获取银行名称和银行卡类型
:param:
* card_num: (string) 银行卡号
:returns:
* flag: (bool) 是否查询成功的标识
* info: (dict) 银行名称和银行卡类型字典
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_card_detail demo ---')
result = CardBin.get_card_detail('6212836989522229131')
print(result)
print('---')
输出结果::
--- fish_data get_card_detail demo ---
(True, {'bank_name': '中国银行', 'card_type': 'DC'})
---
"""
# 根据 card_bin 以及卡号长度查询 bank_code 和 card_type
# card_bin 的长度从 10 - 3
for bin_len in list(range(10, 2, -1)):
value = sqlite_query('fish_data.sqlite',
'select bankcode,cardtype from cn_cardbin where bin=:bin '
'and length=:length',
{"bin": card_num[:bin_len], "length": len(card_num)})
if value:
bank_name = cls.get_bank_name_by_code(value[0][0])
return True, {'bank_name': bank_name,
'card_type': value[0][-1]}
return False, {}
# 2019.07.17 create by Hu Jun, add in v1.1.15, github issue #243
@classmethod
@lru_cache()
def get_bank_name_by_code(cls, bank_code):
"""
根据银行代号,获取银行名称
:param:
* bank_code: (string) 银行代号
:returns:
* info: (dict) 银行名称和银行卡类型字典
举例如下::
from fishbase.fish_data import *
print('--- fish_data get_bank_name_by_code demo ---')
result = CardBin.get_bank_name_by_code('ABC')
print(result)
print('---')
输出结果::
--- fish_data get_bank_name_by_code demo ---
中国农业银行
---
"""
value = sqlite_query('fish_data.sqlite',
'select bankname from cn_bank where bankcode=:bank_code ',
{"bank_code": bank_code})
return value[0][0]