Appearance
品牌怎么抓窜货?用 IP 归属地核验经销商下单地和收货地(Python 实现)
某家电品牌渠道总监半夜收到一封邮件:华东大区三个经销商,上个月订的货全发到了同一个华南仓库。价格体系眼看要崩——窜货商低价收、高价卖,正规经销商压着库存卖不动,投诉电话打爆了总部。这种事靠人工抽查订单根本查不完,一天几千单,翻到猴年马月。
窜货(串货)是品牌渠道管理的老大难,但有个细节很多人没注意:经销商在系统里下单,后端是留了 IP 的。下单 IP 的归属地、收货地址、以及这个经销商平时常用的下单城市,三个信息一对,异地窜货单基本藏不住。这篇文章用 Python 写一套自动核验脚本,批量把订单表跑一遍,输出嫌疑窜货清单。
一、窜货单在数据上长什么样
正常的经销商订单:经销商在自家门店/办公室下单,IP 归属地 ≈ 收货地址所在省 ≈ 常用下单地,三者一致或同省。
窜货单的特征是「两头分开」:
- A 类:A 省经销商下单,收货地址直接填 B 省(货到了别处才暴露);
- B 类:A 省经销商下单,收货地址写 A 省,但下单 IP 归属地在 B 省——这是最阴的一种,货根本没进过 A 省仓库,直接从生产地或窜货商处发走;
- C 类:一个 IP 短时间内给多个不同收货地址下单——典型的集中下单、分散发货。
三类里,B 类完全靠 IP 归属地才能发现,这就是核验脚本的核心价值。
二、方案设计
- 订单表导 CSV:订单号、经销商编号、下单 IP、收货省/市、下单时间;
- 每个 IP 查归属地(调
https://ip9.com.cn/get?ip=xxx,免费版 60 次/分钟,批量必须限速 + 缓存); - 三方比对:下单 IP 省份 vs 收货省份 vs 该经销商历史常用省份;
- 规则引擎出结果:
- 同省或 IP 省份 ∈ 常用省份 → 放行;
- IP 省份 ≠ 收货省 → 标 A 级嫌疑(B 类窜货);
- 同一 IP 关联 ≥3 个不同收货省 → 标 B 级嫌疑(集中下单);
- 经销商第一次在陌生省份下单 → 标提醒(可能是出差,别一上来就当成窜货)。
- 输出嫌疑清单 CSV + 汇总统计。
三、Python 实现
python
import csv
import time
import requests
from collections import defaultdict
CACHE = {}
LAST_CALL = 0.0
def query_geo(ip):
"""查归属地,缓存 + 限速,免费版 60 次/分钟"""
global LAST_CALL
if ip in CACHE:
return CACHE[ip]
now = time.time()
if now - LAST_CALL < 1.1:
time.sleep(1.1 - (now - LAST_CALL))
try:
r = requests.get(f"https://ip9.com.cn/get?ip={ip}", timeout=5)
d = r.json()
LAST_CALL = time.time()
if d.get("ret") != 200:
return None
CACHE[ip] = d["data"]
return d["data"]
except Exception:
return None
def load_orders(path):
with open(path, encoding="utf-8-sig") as f:
return list(csv.DictReader(f))
def build_common_provs(orders):
"""每个经销商的历史常用省份(取出现次数最多的省份集合)"""
dealer_provs = defaultdict(list)
for o in orders:
dealer_provs[o["dealer"]].append(o["receive_prov"])
common = {}
for dealer, provs in dealer_provs.items():
cnt = defaultdict(int)
for p in provs:
cnt[p] += 1
common[dealer] = {p for p, c in cnt.items() if c >= 2}
return common
def main():
orders = load_orders("orders.csv")
common = build_common_provs(orders)
ip_prov = {} # IP -> 省份(查一次用多次)
ip_to_provs = defaultdict(set)
suspects = []
stats = {"total": len(orders), "pass": 0, "A": 0, "B": 0, "remind": 0}
for o in orders:
ip = o["ip"]
if ip not in ip_prov:
geo = query_geo(ip)
ip_prov[ip] = geo.get("prov") if geo else None
prov = ip_prov[ip]
if not prov:
continue # 非法 IP/查询失败,跳过
ip_to_provs[ip].add(o["receive_prov"])
# 第二遍:先算完同 IP 多省,再逐单定性
for o in orders:
prov = ip_prov.get(o["ip"])
recv = o["receive_prov"]
dealer = o["dealer"]
if not prov:
continue
flag = None
if prov != recv:
flag = "A" # 下单地 != 收货地:B 类窜货
elif len(ip_to_provs[o["ip"]]) >= 3:
flag = "B" # 同 IP 多省收货:集中下单
elif prov not in common.get(dealer, set()) and recv in common.get(dealer, set()):
flag = "remind" # 首次陌生省份下单,先提醒
if flag:
suspects.append({**o, "ip_prov": prov, "flag": flag})
stats[flag] += 1
else:
stats["pass"] += 1
with open("suspects.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=list(orders[0].keys()) + ["ip_prov", "flag"])
w.writeheader()
w.writerows(suspects)
print(f"订单总数 {stats['total']},正常 {stats['pass']},A 级嫌疑 {stats['A']},"
f"B 级嫌疑 {stats['B']},提醒 {stats['remind']}")
print("嫌疑单已输出 suspects.csv")
if __name__ == "__main__":
main()订单 CSV 长这样就能跑:
order_id,dealer,ip,receive_prov,receive_city,time
O1001,SH001,114.91.4.12,上海,上海市,2026-09-20 10:00:00
O1002,SH001,106.11.88.5,广东,广州市,2026-09-20 11:12:00注意脚本核验的是省份维度,跨省才算嫌疑,同城/同省不折腾——窜货的定义本来就是跨区域。命中缓存只查一次,几千单的订单表,实际调接口的次数远小于订单数,免费额度基本够用。
四、落地注意事项
- IP 证据是线索不是定罪:B 类单子里,经销商在展会出差下单、在总部开会下单都会误报。A 级嫌疑先人工复核,别直接停货,等窜货商连续踩中同一种模式再处理;
- 常用地白名单要渐进式维护:跑两周后把误报过的经销商城市加进白名单,准确率会明显上升;
- 组合信号更硬:IP 异地 + 下单价格异常(低于指导价)+ 收货地址为仓库/物流园,三个信号同时命中才触发停单,单个信号只记录;
- 合规:只取 IP 归属地做销售渠道分析,属于正常经营用途,但仍建议在经销商协议里写明「系统可能记录操作 IP」,避免争议;数据只留聚合结果,不留全部明细。
总结
窜货单的藏法再多,也绕不开一个事实:下单的人得用网络,网络的出口 IP 总是带着归属地。把「下单 IP 归属地、收货地、经销商常用地」三张表一对,A/B 两类窜货模式就现形了。上面脚本直接套:orders.csv 按格式导出,跑完拿到 suspects.csv,先人工复核再定策略。接口用免费版 https://ip9.com.cn/get?ip=xxx(60 次/分钟,批量场景记得限速 + 缓存),量大了再考虑 VIP。官网:https://www.ip9.com.cn