Appearance
「未成年打赏退款」怎么核实是不是本人操作?用登录 IP 属地串一条证据链(Python 实现)
上个月一个客服群里在传同一份截图:家长拿着孩子的户口本要求全额退款,金额四万八,理由是「孩子趁大人睡着刷的」。平台看了下单记录——连续四个晚上,每晚二十多笔,付款流畅、没有输错密码、没有换设备。客服的疑问很朴素:这到底是不是孩子干的?
这不是道德问题,是举证问题。法院判退款时看的也不是谁哭得更真,而是「能不能证明涉案时段的操作人不是账号实名本人」。而这类案子里,能被固定下来的客观数据不多:设备、支付通道、登录 IP 属地,几乎是全部。
一、纠纷卡在哪儿
《未成年直播打赏可退还》这条司法解释出来之后,实操里冒出一堆边界情况:
- 家长白天用账号,孩子晚上接着用同一台手机,设备和账号都「不像外人」;
- 成年人自己打赏完了后悔,拿孩子的身份来申请退款;
- 账号借给亲戚家孩子用,实名人和操作人确实不一致,但设备一直在同一个家。
这几类案子,靠「人脸识别」只能覆盖到实名环节,覆盖不到「谁在按付款键」。能覆盖到的东西,只剩网络侧留下的痕迹:每一笔充值、每一次登录,请求都带着一个出口 IP。把 IP 翻成归属地,就能回答一个很具体的问题——这几笔打赏发生的时候,网络出口和这个账号平时的出口,是不是一回事。
二、先划边界,别把线索当结论
说清楚 IP 属地能干什么、不能干什么,不然规则一上线就误伤:
- IP 属地是出口网络的属地,不是人在哪。同一户人家,宽带和手机流量可能落在同一个省,也可能一个是本地运营商、一个是跨省漫游。
- 精度是城市级。接口返回的经纬度是城市中心点,拿它算「离家多少公里」是错的。
- 三大运营商的家宽 IP 段是动态分配的,同一城市换段很正常,别一看 IP 变了就报警。
- 单看属地证明不了任何人的身份。它的作用是产出「需要人工进一步核实」的线索,最终定性还得靠问询和监护人沟通记录。
一句话:IP 属地不负责判决,负责把「值得追问的样本」从海量订单里挑出来。
三、Python 实现:从登录日志到证据链
思路分三层:先给每个账号建属地基线(争议时段之外的登录记录),再看争议时段落没落在基线附近,最后把偏离的样本按账号聚合成一段可读的举证描述。缓存和限速放在查询函数里,别让免费额度被打满。
python
# dashang_evidence.py —— 未成年打赏退款:用登录 IP 属地拼证据链
# 依赖:仅标准库(Python 3.8+)
import json, time, urllib.parse, urllib.request
from collections import defaultdict, Counter
API = "https://ip9.com.cn/get" # 不传 ip 查当前请求方,?ip= 查指定 IP
_cache, _last = {}, 0.0
def geo(ip):
"""查 IP 归属地。带进程内缓存 + 约 55 次/分钟限速,免费版够用。"""
global _last
if ip in _cache:
return _cache[ip]
gap = 1.1 - (time.time() - _last) # 留余量,避免撞 60 次/分钟
if gap > 0:
time.sleep(gap)
_last = time.time()
req = urllib.request.Request(
API + "?ip=" + urllib.parse.quote(ip),
headers={"User-Agent": "Mozilla/5.0 (ip9-evidence-demo)"}, # 默认 UA 会被 403
)
try:
with urllib.request.urlopen(req, timeout=5) as r:
body = json.loads(r.read().decode("utf-8"))
except Exception as e:
return {"_err": "网络异常: %s" % e}
if body.get("ret") != 200: # 非法 IP 是 400,限速是 429
return {"_err": "ret=%s" % body.get("ret")}
_cache[ip] = body["data"]
return body["data"]
def place(d):
"""把返回字段压成「省+市」这种可比对的地名;境外只到国家。"""
if "_err" in d:
return "查询失败"
prov, city, country = d.get("prov") or "", d.get("city") or "", d.get("country") or ""
if not prov:
return country or "未知"
return prov if not city or city == prov else "%s%s" % (prov, city)
# 行为日志:时间、账号、事件、出口 IP(生产环境从登录/支付网关按行取)
logs = [
("2026-09-20 20:11", "u_88213", "login", "111.18.31.7"), # 陕西铜川
("2026-09-22 21:02", "u_88213", "login", "223.104.38.19"), # 北京(手机流量)
("2026-09-28 20:35", "u_88213", "login", "111.18.31.7"),
("2026-10-03 23:02", "u_88213", "pay", "45.192.78.22"), # 香港机房
("2026-10-03 23:06", "u_88213", "pay", "45.192.78.22"),
("2026-10-04 23:40", "u_88213", "pay", "120.79.4.66"), # 深圳云主机
]
DISPUTE_DAYS = ("2026-10-03", "2026-10-04") # 申请退款指向的争议时段
baseline = defaultdict(Counter) # 账号 -> {属地: 次数},只统计争议时段之外
dispute = defaultdict(list) # 账号 -> [(时间, 事件, IP, 属地)]
for ts, uid, ev, ip in logs:
p = place(geo(ip))
if ts[:10] in DISPUTE_DAYS:
dispute[uid].append((ts, ev, ip, p))
else:
baseline[uid][p] += 1
for uid, rows in dispute.items():
base = baseline.get(uid)
known = {p for p, _ in base.most_common(3)} if base else set() # 常见的三个属地当基线
print("账号 %s —— 争议时段样本 %d 条" % (uid, len(rows)))
print(" 历史属地基线: %s" % ("、".join(sorted(known)) or "样本不足,无基线"))
outside = []
for ts, ev, ip, p in rows:
if not known:
flag = "无基线可比"
elif p in known:
flag = "与基线一致"
else:
flag = "偏离基线"
outside.append(p)
print(" [%s] %-5s %-16s %-10s %s" % (ts[11:], ev, ip, p, flag))
print(" 结论: 争议时段有 %d 条操作落在 %s,需向监护人核实这些网络出口\n"
% (len(outside), "、".join(sorted(set(outside))) or "-"))跑出来是什么样:这个账号平时稳定在「陕西铜川」和「北京」两个移动出口,10 月 3 日夜里两笔支付却来自香港的一个机房地址,10 月 4 日又换到深圳的云主机段。这种「基线一致 + 争议时段突然漂到机房网段」的组合,就要在工单里写清楚——不是直接拒赔,是提示客服去问「这几笔是不是在境外网络下操作的、当时谁在旁边」。
四、落地注意事项
限速和缓存别省。 免费版是 60 次/分钟/IP,一套退款工单要拉几十上百条记录,不缓存重复 IP 很容易撞 429。进程内缓存只够单机用,多实例部署就换 Redis,键是 IP,过期时间一小时起步。
记得带 User-Agent。 实测 Python 标准库 urllib 默认的 UA(Python-urllib/3.x)会被接口返回 403,随手加一个 UA 就正常;curl 和 requests 默认 UA 不受影响,但自建 HTTP 客户端时容易踩到。
基线窗口要选对。 拿最近七天当基线,会被出差、回家、换手机这些正常波动污染;行业里普遍用 30 到 90 天。窗口内的记录还得剔除掉争议时段本身,否则自己把自己洗白了。
境外地址不必然是作弊。 加速器、跨境办公、境外手机卡都会让出口跑到境外。所以规则输出的是「样本」不是「结论」,处置建议一栏要留人工判断的空间。
涉及未成年人,数据使用得收着点。 只保留举证必需的时间、事件类型和归属地,别把完整访问日志存成明文堆在工单里;个案处理完按内部留存期限清理。IP 属地接口调用本身不涉及内容,但日志链路要考虑个保法下最小必要原则。
给客服交付的东西要能直接抄。 上面打印的那几行,抓进工单就够用了:账号、时段、属地、是否偏离基线。别让客服自己去翻译 IP。
总结
这类退款纠纷的解法不是「查出一个真相」,而是把可核验的客观线索整理成一份能经得起追问的材料。IP 归属地查询在里面扮演的是最轻量的一环——不加密、不注册、一次 HTTP 请求把 IP 翻成地名,剩下的是把规则和窗口调对。
接口就一个:https://ip9.com.cn/get?ip=xxx(IPv4/IPv6 都支持,不传参数查当前请求方),返回里的 prov、city、country、isp 足够支撑上面这套基线比对。更多字段和 VIP 能力(区县、ip_type、ip_asn)以官网 https://www.ip9.com.cn 为准。