Appearance
信贷风控为什么要查 IP 归属地?Python 识别团伙进件的落地写法
做信贷的都知道一个现象:骗贷很少单干,都是团伙。一个中介带几十个客户,在同一家网吧、同一个 WiFi 里批量提交借款申请;资料全是包装的,只有 IP 骗不了人。IP 归属地查询在贷前信审里就是这么用的——不查不知道申请人"在哪",只靠手机号和身份证,根本挡不住进件端的第一波欺诈。这篇讲信贷场景具体怎么用,附一套 Python 可直接跑的规则。
一、信贷进件里的 IP 异常长什么样
- 同 IP 高频进件:一个 IP 一天提交十几笔申请,基本可以断定是中介代办或团伙操作。正常借款人不会有这种操作路径。
- 资料与属地矛盾:申请表填"我在上海工作",进件 IP 归属地却连着三天在某个县城——要么资料是假的,要么人是被中介组织起来的。
- 机房 IP 进件:正常借款人用家庭宽带或手机流量,出口 IP 是 ISP 类型;大量 IDC 机房 IP 来进件,大概率是批量程序在跑。
- 地域聚集:不同手机号、不同 IP,但归属地集中在同一个城市甚至同一个区,且申请时间挨得近——团伙"打一枪换一个地方"的特征。
二、方案设计
信审侧不追求实时拦截所有欺诈,目标是把可疑进件挑出来送人工复核。规则分四层:
- 频次层:同 IP 24 小时内进件超过阈值,直接打标;
- 聚集层:进件 IP 的省、市维度聚合统计,发现异常聚集;
- 一致性层:IP 归属地城市和申请资料城市做比对,不一致的降级处理;
- 类型层:能拿到
ip_type的,机房 IP 进件直接进黑名单池。
调用 IP 归属地接口时注意三点:进件是低频场景(一天几千笔撑死),免费版 60 次/分钟完全够;对已查过的 IP 做本地缓存,同一个 IP 短时间重复进件没必要重复查;接口异常时放行而不是拦截——信贷最怕因为风控抖动误杀正常客户。
三、Python 实现
python
import json
import time
import urllib.request
from collections import Counter, defaultdict
API = "https://ip9.com.cn/get"
def query_ip(ip: str, cache: dict):
"""查单个 IP 归属地。免费版无鉴权,60 次/分钟/IP,记得缓存。"""
if ip in cache:
return cache[ip]
try:
req = urllib.request.Request(f"{API}?ip={ip}",
headers={"User-Agent": "risk-check/1.0"})
with urllib.request.urlopen(req, timeout=3) as resp:
body = json.loads(resp.read().decode("utf-8"))
data = body.get("data") or {}
# ret=400 说明 IP 非法,直接当查不到处理,不抛异常
cache[ip] = data if body.get("ret") == 200 else {}
return cache[ip]
except Exception:
# 网络抖动/超时:放行,交给下一层规则兜底
return None
def check_applications(apps: list[dict]) -> list[dict]:
"""apps: [{'ip': '1.2.3.4', 'city': '上海', 'time': 169...}]"""
cache = {}
ip_cnt = Counter(a["ip"] for a in apps)
city_cnt = defaultdict(int)
for a in apps:
info = query_ip(a["ip"], cache) or {}
a["prov"] = info.get("prov", "")
a["city"] = info.get("city", "")
if info.get("isp"):
a["isp"] = info["isp"]
# ip_type 是 VIP 字段,免费版返回里没有,有 key 的自行解析
if info.get("ip_type"):
a["ip_type"] = info["ip_type"]
city_cnt[(a["prov"], a["city"])] += 1
for a in apps:
risk = []
if ip_cnt[a["ip"]] >= 3: # 同 IP 多笔
risk.append("同IP高频进件")
if a.get("city") and a.get("city") != a["city_claim"]:
risk.append("归属地与资料城市不符")
if a.get("ip_type") == "IDC": # VIP 字段
risk.append("机房IP进件")
if city_cnt[(a["prov"], a["city"])] >= 5: # 地域聚集
risk.append("同城聚集进件")
a["risk"] = risk
time.sleep(0.1) # 留出限速余量,批量时按需调整
return apps
if __name__ == "__main__":
apps = [
{"ip": "114.114.114.114", "city_claim": "南京", "time": 1690000000},
{"ip": "114.114.114.114", "city_claim": "南京", "time": 1690000100},
{"ip": "114.114.114.114", "city_claim": "上海", "time": 1690000200},
{"ip": "8.8.8.8", "city_claim": "北京", "time": 1690000300},
]
for a in check_applications(apps):
print(a["ip"], a.get("prov", "?"), a.get("city", "?"),
a["city_claim"], a["risk"])字段说明:免费接口返回 country/prov/city/isp/lng/lat/long_ip/big_area 等,prov 是省、city 是市;area(区县)和 ip_type(ISP/BUS/IDC)、ip_asn 是 VIP 字段。上面示例里 8.8.8.8 只返回国家(美国)和运营商,省市级为空——境外 IP 常见这种情况,代码里用 get() 兜底就不会报错。
四、落地注意事项
- 阈值别拍脑袋:同 IP 3 笔这个数,先用一周历史进件数据回放,看正常客户的分布再定。正经人一天最多一两笔。
- 城市不一致≠欺诈:出差、异地务工都是正常场景。规则只负责"降级到人工复核",别直接拒。
- 记录快照:进件时把 IP 归属地、经纬度、运营商存进案件表。贷后催收、团伙串并案时,这些字段是回溯的抓手。
- 误伤兜底:接口超时一律放行,靠频次层和人工兜;风控要的是稳定,不是某一次查得准。
总结
信贷风控查 IP 归属地,核心就一句话:把"聚集"和"矛盾"找出来。同 IP 高频、同城聚集、资料与属地不一致、机房 IP 进件,这四类信号用免费接口就能覆盖大半,代码量也就上面这些。数据源用 https://ip9.com.cn/get?ip=<进件IP> 即可,免费版返回省市、运营商和城市中心经纬度;要区县和 ip_type(判断机房)再上 VIP,具体见官网 https://www.ip9.com.cn。先跑起来积累两周数据,比憋一套大而全的反欺诈系统靠谱。