Skip to content

信贷风控为什么要查 IP 归属地?Python 识别团伙进件的落地写法

做信贷的都知道一个现象:骗贷很少单干,都是团伙。一个中介带几十个客户,在同一家网吧、同一个 WiFi 里批量提交借款申请;资料全是包装的,只有 IP 骗不了人。IP 归属地查询在贷前信审里就是这么用的——不查不知道申请人"在哪",只靠手机号和身份证,根本挡不住进件端的第一波欺诈。这篇讲信贷场景具体怎么用,附一套 Python 可直接跑的规则。

一、信贷进件里的 IP 异常长什么样

  • 同 IP 高频进件:一个 IP 一天提交十几笔申请,基本可以断定是中介代办或团伙操作。正常借款人不会有这种操作路径。
  • 资料与属地矛盾:申请表填"我在上海工作",进件 IP 归属地却连着三天在某个县城——要么资料是假的,要么人是被中介组织起来的。
  • 机房 IP 进件:正常借款人用家庭宽带或手机流量,出口 IP 是 ISP 类型;大量 IDC 机房 IP 来进件,大概率是批量程序在跑。
  • 地域聚集:不同手机号、不同 IP,但归属地集中在同一个城市甚至同一个区,且申请时间挨得近——团伙"打一枪换一个地方"的特征。

二、方案设计

信审侧不追求实时拦截所有欺诈,目标是把可疑进件挑出来送人工复核。规则分四层:

  1. 频次层:同 IP 24 小时内进件超过阈值,直接打标;
  2. 聚集层:进件 IP 的省、市维度聚合统计,发现异常聚集;
  3. 一致性层:IP 归属地城市和申请资料城市做比对,不一致的降级处理;
  4. 类型层:能拿到 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。先跑起来积累两周数据,比憋一套大而全的反欺诈系统靠谱。