Appearance
二手交易平台怎么防「假同城」?用 IP 归属地校验发布地和聊天属地(PHP 实现)
「同城自提,当面验货」这六个字在二手交易里几乎是信任的代名词。买家愿意多跑十几公里,图的就是能看得见摸得着、不用先付款。也正因为这层信任,它成了灰产最好用的外衣。
套路很固定:商品标着「北京朝阳自提」,价格比同类低一截,买家一问,对方说「我出差了,让我朋友给你寄过去吧,你先付个定金」。等到定金打完,人就没影了。这类骗局里,卖家账号的实际登录位置常常离标注城市上千公里,甚至根本不在境内。
IP 归属地不能证明一个人是谁,但它能证明「这次发布、这次聊天从哪个城市发出」。二手平台的风控基本都会拿这条线索做交叉比对。这篇用 PHP 给一套完整的校验规则,包含发布地校验、私聊属地校验和分级处置。
一、场景问题:假同城的三种表现
第一种,标同城、人不在同城。 商品标注在 A 城,卖家发布那一刻的 IP 在 B 城甚至境外。买家看不到这层信息,只看到一个「同城自提」的标签,风险完全被隐藏了。
第二种,发布时在同城,聊天时人跑了。 更隐蔽的一种。发布的时候人确实在本市(可能刚出差回来,或者干脆是买了张本地卡),等到买家来咨询,已经换到别的城市甚至境外了。聊天记录里那句「我就在本地,随时可自提」和实际的聊天 IP 对不上,这本身就是最有价值的信号。
第三种,一个出口 IP 铺了几十个卖家账号。 单个账号看都正常,价格便宜、图片齐全、描述也像模像样。但它们的发布 IP 高度集中 —— 这是工作室批量铺货的典型特征,背后往往是一台服务器或者一个群控机房。
需要提前说清楚:这三种形态都是线索,不是定罪。二手平台上真实的异地场景太多了 —— 学生放假回老家还在卖东西、出差的人在酒店发帖、帮父母卖闲置的年轻人。所以规则的设计目标是把可疑商品挑出来提醒买家、降低推荐权重,而不是自动下架或者封号。
二、方案设计
数据来源。 商品表拿标注城市,访问日志拿发布 IP 和私聊 IP,归属地统一走 GET https://ip9.com.cn/get?ip=<ip>,带文件缓存(归属地变化慢,缓存 7 天)和 1 秒 1 次的限速(守住免费版 60 次/分钟的额度)。
规则组织。 每条规则只输出「命中/不命中 + 权重变化」,最后按总权重落处置档位。规则和处置分开的好处是:调阈值的时候不用改规则代码,风控同学自己就能试。
| 规则 | 条件 | 权重 |
|---|---|---|
| R1 | 发布 IP 城市 ≠ 商品标注城市(境内) | -30 |
| R2 | 商品标注同城,但发布 IP 归属境外 | -50 |
| R3 | 私聊 IP 城市均不在标注城市 | -20 |
| R3b | 私聊 IP 归属境外 | -20 |
| R4 | 同一出口 IP 关联 ≥5 个卖家账号 | -25 |
| R5 | 发布地与聊天地跨省 | 记录(不扣权) |
处置分档。 权重 ≤ -50:去掉同城标签,同时在聊天页给买家弹强提醒;≤ -20:同城板块曝光降权,只在聊天页写一行提示;> -20:正常展示,仅在后台留下研判记录。
三、PHP 实现
查询封装 + 规则引擎(fake_local_check.php,只依赖 PHP 标准库,HTTPS 走 openssl,不需要 curl 扩展):
php
<?php
const IP9_API = 'https://ip9.com.cn/get';
const CACHE_FILE = __DIR__ . '/ip_cache.json';
const CACHE_TTL = 7 * 86400;
const RATE_USLEEP = 1000000; // 免费版 60 次/分钟 -> 1 秒 1 次
/** 带文件缓存 + 限速的归属地查询;失败结果只缓存 60 秒 */
function ip9_lookup(string $ip): array
{
static $cache = null, $lastAt = 0.0;
if ($cache === null) {
$cache = file_exists(CACHE_FILE) ? json_decode(file_get_contents(CACHE_FILE), true) : [];
if (!is_array($cache)) { $cache = []; }
}
if (isset($cache[$ip])) {
$ttl = $cache[$ip]['ok'] ? CACHE_TTL : 60;
if (time() - $cache[$ip]['ts'] < $ttl) {
$cache[$ip]['hit'] = true;
return $cache[$ip];
}
}
$ctx = stream_context_create([
'http' => ['method' => 'GET', 'timeout' => 6, 'ignore_errors' => true,
'header' => "User-Agent: fakelocal/1.0\r\nAccept: application/json\r\n"],
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]);
$body = @file_get_contents(IP9_API . '?ip=' . rawurlencode($ip), false, $ctx);
$rec = ['ts' => time(), 'hit' => false, 'ok' => false, 'country' => '', 'prov' => '',
'city' => '', 'isp' => '', 'err' => ''];
if ($body === false) {
$rec['err'] = '请求失败或超时';
} else {
$j = json_decode($body, true);
if (!is_array($j)) {
$rec['err'] = '响应不是 JSON';
} elseif ((int)($j['ret'] ?? 0) !== 200) {
$rec['err'] = 'ret=' . ($j['ret'] ?? '?');
} else {
$d = $j['data'] ?? [];
$rec['ok'] = true;
$rec['country'] = (string)($d['country'] ?? '');
$rec['prov'] = (string)($d['prov'] ?? '');
$rec['city'] = (string)($d['city'] ?? '');
$rec['isp'] = (string)($d['isp'] ?? '');
}
}
$cache[$ip] = $rec;
file_put_contents(CACHE_FILE, json_encode($cache, JSON_UNESCAPED_UNICODE));
$now = microtime(true);
if ($now - $lastAt < 1.0) { usleep(RATE_USLEEP); }
$lastAt = microtime(true);
return $rec;
}规则部分,输入是「商品信息 + 发布 IP 的归属地 + 私聊 IP 的归属地数组」:
php
function judge(array $item, array $pubGeo, array $chatGeos, array $ipSellerCount): array
{
$hits = [];
$weight = 0;
$notifyBuyer = false;
$declared = $item['declared_city'];
$internalPub = $pubGeo['ok'] && ($pubGeo['country'] === '保留' || $pubGeo['isp'] === '内网地址');
// R1 发布 IP 城市与商品标注城市不一致
if ($pubGeo['ok'] && !$internalPub && $pubGeo['country'] === '中国'
&& $pubGeo['city'] !== '' && $pubGeo['city'] !== $declared) {
$weight -= 30;
$hits[] = "发布 IP 在{$pubGeo['prov']}{$pubGeo['city']},商品标注在{$declared}";
}
// R2 标注同城但发布 IP 在境外
if ($pubGeo['ok'] && !$internalPub && $pubGeo['country'] !== '中国'
&& $pubGeo['country'] !== '保留') {
$weight -= 50;
$notifyBuyer = true;
$hits[] = "商品标注{$declared},但发布 IP 归属{$pubGeo['country']}";
}
// R3 私聊过程中出现与标注城市不同的 IP
$chatCities = [];
foreach ($chatGeos as $g) {
if ($g['ok'] && $g['country'] === '中国' && $g['city'] !== '') {
$chatCities[$g['city']] = true;
}
}
if ($chatCities && !isset($chatCities[$declared])) {
$weight -= 20;
$notifyBuyer = true;
$hits[] = '聊天 IP 城市为 ' . implode('/', array_keys($chatCities)) . ",均不在{$declared}";
}
// R3b 私聊 IP 归属境外(商品标的是国内同城)
$chatOverseas = [];
foreach ($chatGeos as $g) {
if ($g['ok'] && $g['country'] !== '' && $g['country'] !== '中国'
&& $g['country'] !== '保留') {
$chatOverseas[$g['country']] = true;
}
}
if ($chatOverseas) {
$weight -= 20;
$notifyBuyer = true;
$hits[] = '聊天 IP 归属 ' . implode('/', array_keys($chatOverseas)) . ',不在境内';
}
// R4 同一 IP 关联大量卖家账号(工作室批量铺货)
if (($ipSellerCount[$item['seller_ip']] ?? 1) >= 5) {
$weight -= 25;
$hits[] = '同一出口 IP 关联 ' . $ipSellerCount[$item['seller_ip']] . ' 个卖家账号';
}
// R5 发布地与聊天地跨省:只记录
if ($pubGeo['ok'] && $chatCities && $pubGeo['city'] !== ''
&& !isset($chatCities[$pubGeo['city']])) {
$hits[] = "发布时在{$pubGeo['city']},聊天时到了" . implode('/', array_keys($chatCities)) . '(记录,不额外降权)';
}
// 拿不到数据:只记录原因,不参与研判
if (!$pubGeo['ok']) {
$hits[] = '发布 IP 无法定位:' . ($pubGeo['err'] ?: '未知') . '(不参与研判)';
} elseif ($internalPub) {
$hits[] = '发布 IP 是内网/保留地址(' . $pubGeo['isp'] . '),不做地域校验';
}
$action = '正常展示';
if ($weight <= -50) { $action = '去掉同城标签 + 前端强提醒买家'; }
elseif ($weight <= -20) { $action = '同城曝光降权 + 聊天页提醒买家'; }
return ['weight' => $weight, 'hits' => $hits, 'action' => $action, 'notify' => $notifyBuyer];
}喂进去的商品样例(items.json,每个商品带发布 IP 和最近几条私聊的 IP):
json
[
{"item_id":"IT-1006","title":"二手自行车","seller_id":"S-81","seller_ip":"114.114.114.114","declared_city":"南京","chat_ips":["114.114.114.114"]},
{"item_id":"IT-1007","title":"iPad 港版自提","seller_id":"S-82","seller_ip":"45.153.160.2","declared_city":"南京","chat_ips":["45.153.160.2"]},
{"item_id":"IT-1008","title":"显卡自提当面验","seller_id":"S-83","seller_ip":"58.213.1.1","declared_city":"南京","chat_ips":["45.153.160.2"]},
{"item_id":"IT-1009","title":"相机镜头同城面交","seller_id":"S-84","seller_ip":"180.101.49.12","declared_city":"上海","chat_ips":["223.5.5.5"]}
]四、实测输出
11 件商品、7 个卖家出口 IP 跑下来(下面摘了几条有代表性的):
待研判商品 11 件,涉及 7 个卖家出口 IP
IT-1006 二手自行车 标同城 南京 | 发布 IP 114.114.114.114 -> 江苏南京
同城权重 0 | 处置:正常展示
IT-1007 iPad 港版自提 标同城 南京 | 发布 IP 45.153.160.2
同城权重 -70 | 处置:去掉同城标签 + 前端强提醒买家
· 商品标注南京,但发布 IP 归属捷克
· 聊天 IP 归属 捷克,不在境内
IT-1008 显卡自提当面验 标同城 南京 | 发布 IP 58.213.1.1 -> 江苏南京
同城权重 -20 | 处置:同城曝光降权 + 聊天页提醒买家
· 聊天 IP 归属 捷克,不在境内
IT-1009 相机镜头同城面交 标同城 上海 | 发布 IP 180.101.49.12 -> 江苏南京
同城权重 -50 | 处置:去掉同城标签 + 前端强提醒买家
· 发布 IP 在江苏南京,商品标注在上海
· 聊天 IP 城市为 杭州,均不在上海
· 发布时在南京,聊天时到了杭州(记录,不额外降权)
IT-1010 办公椅自提 标同城 深圳 | 发布 IP 192.168.1.10
同城权重 0 | 处置:正常展示
· 发布 IP 是内网/保留地址(内网地址),不做地域校验
IT-1011 路由器 标同城 南京 | 发布 IP 999.1.1.1 -> (未定位)
同城权重 0 | 处置:正常展示
· 发布 IP 无法定位:ret=400(不参与研判)首跑 8.3 秒(7 次真实请求,每次间隔 1 秒),全缓存二跑 0.018 秒。几条判定值得展开:
IT-1008 是最值得警惕的一类。 发布 IP 在南京,和标注城市一致,单看发布环节完全正常;但买家来聊的时候,聊天 IP 已经在境外了。这种「发布时人在本地、聊着聊着出国了」的模式,正是「先骗取同城信任、再诱导脱离平台交易」的典型路径,所以它虽然只有 -20 分,也照样给买家弹提醒。
IT-1007 拿满了两条境外规则,-70 分。 发布和聊天都在境外,商品却标着南京同城自提,这种组合基本可以直接去掉同城标签。注意处置仍然是「提醒买家」,不是自动下架 —— 海外华人卖家里也有真实用户。
IT-1009 三条规则叠加到 -50 分。 标注上海、发布在南京、聊天在杭州,三个城市互不相同。这条数据既能说明「账号可能被转手或者有代运营」,也能说明「这个人的位置一直在变」,对买家来说都是值得知道的。
IT-1010 和 IT-1011 都不扣权。 192.168.1.10 是内网地址,接口返回 country=保留、isp=内网地址;999.1.1.1 直接 ret=400。这两类在风控脚本里最容易被写成「查不到 → 高风险」,结果平台自己的测试环境、内网访问、上游代理一亮相就刷一片告警。
R4 那条规则只扣 25 分,故意的。 一个共享出口 IP 挂 5 个卖家账号,可能是工作室,也可能是某个二手小店集中的写字楼群租网络。它单独不足以定性,但和「价格异常低」「商品图片重复」这类信号叠在一起就很有说服力了。
五、落地时要调整的几件事
权重和阈值要按自己的数据调。 上面这套权重是在「二三线城市为主、同城自提占比高的平台」上试出来的。如果你的平台跨省交易本来就是主流,R1 的 30 分就太重了,可以先降权记录一到两周,统计一下日均命中量再定阈值 —— 每天几万条提醒等于没有提醒。
聊天 IP 要看清是哪一个。 App 端和网页端的出口常常不同(同一个人的手机走 4G、电脑走宽带,可能落在两个城市),私聊几分钟内切换城市属于正常现象。真正有信号的是「聊天全程都远离标注城市」或者「短时间内跨省反复横跳」,所以规则里看的是城市的集合,而不是最后一条消息的 IP。
同城标签的「权重」要能被别的信号覆盖。 比如已经完成实人认证、有历史好评、近期成交记录的老卖家,被 R1 扣掉的曝光权重应该能靠账号信用补回来。单靠 IP 一条线索做降权,中长期会伤到平台的头部卖家。
识别工作室要看 ip_type,这是 VIP 版字段。 免费版能告诉你 IP 在哪个城市、哪家运营商,但分辨不了这个出口是家庭宽带还是 IDC 机房。批量铺货的工作室基本都用云主机跑脚本,出口是机房 IP —— 这个判断得靠 ip_type(ISP 家庭 / BUS 企业 / IDC 机房)和 ip_asn,只有 VIP 版提供。建议先用免费版把「同 IP 多账号」这条跑起来,确认真的有这类流量,再决定要不要上 VIP。
页面展示符合合规口径。 给买家看「卖家当前 IP 属地」时,境内展示到省、境外展示到国家就够了,不要展示城市甚至经纬度 —— 接口返回的经纬度是城市中心点,精度本来也不足以定位到人,但展示出来会引发不必要的隐私争议。另外原始 IP 属于个人信息,研判结论存「命中哪条规则」就行,日志里的 IP 记得做掩码。
代理和秒拨是这套规则的天花板。 灰产手里有大量代理 IP,切换属地的成本很低。IP 归属地只是「成本最低、覆盖最广」的第一道筛子,真正要拦住有组织的诈骗,还得叠上设备指纹、支付通道、账号注册时间和买家举报数据。别指望一条 IP 规则解决所有问题。
思路可以套用到所有「线上发布、线下见面」的场景:家政预约、房屋租赁、二手车看车。核心问题永远是同一个 —— 对方说自己在哪里,和他实际的网络位置对不对得上。接口 GET https://ip9.com.cn/get?ip=<ip> 免费版免注册、60 次/分钟/IP,返回 prov、city、isp、big_area、经纬度(城市中心点)这些字段;区县、ip_type、ip_asn 属于 VIP 版能力,具体口径以官网 https://www.ip9.com.cn 为准。