Appearance
租房平台的假房源怎么识别?用发帖 IP 属地 + 房源信息交叉校验(PHP 实现)
有条新闻很多中介都在转:有人自称「房东直租」,配图是精装修两居,月租比同小区低两千。记者顺藤摸瓜发现是一整条引流链——房源是假的,目的把人导到小程序里看「真房源」。报道里有一句细节值得抄下来:这些转租信息的发帖 IP 地址多显示为广西,而房源标的是另一座城市的小区。
这句话把一件本来很玄的事说破了:假房源在信息层面几乎完美,图片能盗、描述能编、价格能随便填,唯独发帖时那台设备连着的网络出口,是编不出来的。
一、平台审核卡在哪一步
房源审核大体分三段:资质(身份证、房产证)、信息(面积、户型、价格)、真实性(房在不在、价对不对)。前两段能靠 OCR 和抽查,第三段最贵——得有人打电话、有人实地跑。
价格异常、图片重复这些规则早就被中介摸透了:一张图换个滤镜、价格每次加五十块、描述改两个字,就能绕过大部分指纹。属地维度是另一条线,它不看内容,只看「谁在什么地方发的」。
三类行为在属地维度上会露出来:
- 异地发帖:账号实名在一座城市,发帖出口长期在另一座城市,房源又标在第三座城市;
- 批量铺帖:同一个出口 IP 或同一小段 IP,短时间在多个城市铺了几十套房源;
- 属地未知或境外:出口落在机房、境外节点,正常房东几乎不会这么发帖。
二、把规则想清楚再写代码
三点先定死:
- 精度是城市级。接口返回的经纬度是城市中心点,
area(区县)在 IP9 属于 VIP 字段,免费版拿不到,所以规则只能按省/市比。 - 房东出差、回老家发帖都正常,单条异常不处置,聚合才处置。
- 归属地是「网络出口在哪」,不是「房子在哪」。它是线索,最终还得人工核。
规则最后落成三条计分项:城市不符 +2、同 IP 当日批量发帖 +3、境外/机房出口 +2。分数进人工复核队列,分数过半自动转入「限制展示」。
三、PHP 实现
一个函数负责查归属地(curl + 静态缓存 + 限速),一个函数对一批房源打分。缓存放进程内数组只适合脚本任务,常驻进程换成 Redis 更实际。
php
<?php
// fake_listing_audit.php —— 租房假房源属地核验(PHP 8+)
const API = 'https://ip9.com.cn/get';
function ip_geo(string $ip): array {
static $cache = [], $last = 0.0;
if (isset($cache[$ip])) return $cache[$ip];
$gap = 1.1 - (microtime(true) - $last); // 免费版 60 次/分钟,留点余量
if ($gap > 0) usleep((int)($gap * 1_000_000));
$last = microtime(true);
$ch = curl_init(API . '?ip=' . urlencode($ip));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => false,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($raw === false) return $cache[$ip] = ['_err' => 'curl: ' . $err];
$body = json_decode($raw, true);
if (!is_array($body) || ($body['ret'] ?? 0) !== 200) { // 非法 IP 返回 400
return $cache[$ip] = ['_err' => 'ret=' . ($body['ret'] ?? 'parse')];
}
return $cache[$ip] = $body['data'];
}
function place_of(array $d): string {
if (isset($d['_err'])) return '查询失败';
$country = $d['country'] ?? '';
$prov = $d['prov'] ?? '';
$city = $d['city'] ?? '';
if (($d['country_code'] ?? '') !== 'cn') { // 境外通常只到国家/城市
return $prov === '' ? $country : $country . $prov;
}
if ($prov === '' && $city === '') return $country ?: '未知'; // 内网、保留地址
if ($city === '' || $city === $prov) return $prov;
return $prov . $city;
}
/** 给一批房源打分:返回每条的得分与命中原因 */
function audit(array $listings): array {
$byIp = [];
foreach ($listings as $l) { $byIp[$l['ip']][] = $l['id']; }
$out = [];
foreach ($listings as $l) {
$d = ip_geo($l['ip']);
$ipCity = place_of($d);
$isp = $d['isp'] ?? '';
$score = 0;
$hits = [];
// 规则一:发帖属地和房源所在城市不符(按省市做包含比对,避免「浙江杭州」对「杭州」误判)
$same = $ipCity !== '查询失败'
&& (str_contains($ipCity, $l['listing_city']) || str_contains($l['listing_city'], $ipCity));
if ($ipCity !== '查询失败' && !$same) {
$score += 2;
$hits[] = "发帖属地({$ipCity}) 与房源城市({$l['listing_city']}) 不符";
}
// 规则二:同一出口 IP 当天批量铺帖
if (count($byIp[$l['ip']]) >= 5) {
$score += 3;
$hits[] = '同一出口 IP 当日发帖 ' . count($byIp[$l['ip']]) . ' 条';
}
// 规则三:境外出口,或运营商字段看着像机房
$isCn = ($d['country_code'] ?? '') === 'cn';
$idc = (stripos($isp, 'cloud') !== false || stripos($isp, 'data') !== false
|| stripos($isp, '机房') !== false || stripos($isp, 'idc') !== false);
if (!$isCn && $ipCity !== '查询失败') {
$score += 2;
$hits[] = '境外出口(' . $ipCity . ')';
} elseif ($idc) {
$score += 2;
$hits[] = '出口疑似机房运营商:' . $isp;
}
$out[] = [
'id' => $l['id'],
'score' => $score,
'ip_city'=> $ipCity,
'verdict'=> $score >= 4 ? '转人工并限制展示' : ($score >= 2 ? '进复核队列' : '正常'),
'hits' => $hits,
];
}
return $out;
}
$listings = [
['id' => 'L1001', 'ip' => '58.30.0.0', 'listing_city' => '北京'],
['id' => 'L1002', 'ip' => '43.133.110.148', 'listing_city' => '杭州'],
['id' => 'L1003', 'ip' => '43.133.110.148', 'listing_city' => '成都'],
['id' => 'L1004', 'ip' => '43.133.110.148', 'listing_city' => '武汉'],
['id' => 'L1005', 'ip' => '43.133.110.148', 'listing_city' => '西安'],
['id' => 'L1006', 'ip' => '43.133.110.148', 'listing_city' => '长沙'],
];
foreach (audit($listings) as $r) {
printf("%s 分数=%d %s 发帖属地=%s\n", $r['id'], $r['score'], $r['verdict'], $r['ip_city']);
foreach ($r['hits'] as $h) echo " - {$h}\n";
}这段跑下来,L1001(北京房源、北京出口)干干净净;L1002 到 L1006 来自同一个境外机房出口,分别标着五个不同城市,三条规则全部命中,分数都是 7——这正是「一条 IP 铺五城房源」的典型形状,交给人工打两个电话就能定性。
四、落地时容易踩的坑
别只按城市硬比对。 房源标「北京」、发帖在「河北廊坊」是通勤常态,规则里要允许同城群(京津冀、长三角)豁免。真正常见的是「北京房源 + 广东出口 + 同一 IP 当日在五个城市发帖」这种组合。
批量规则一定有误报。 房产中介、物业公司、公寓运营方的出口就是同一根宽带,一天发几十条很正常。所以批量项要么给白名单账号降权,要么改成「同一 IP + 多个不同城市」才算异常,别一刀切。
请求要缓存,别让一条日志打爆额度。 房源列表里重复 IP 很多,先按 IP 去重再查,缓存至少留一小时。
留一份处置记录。 判定「限制展示」的,要把得分、命中原因、属地快照一起存下来。中介申诉时你能解释清楚,监管抽查时也有据可查。IP 属地查询本身不涉及内容数据,但发帖日志属于用户数据,留存期限按平台自己的规则来。
境外出口不等于黑产。 做跨境房源、海外华人租房的小平台也有正常的境外发帖。规则输出的始终是「待核实样本」。
总结
假房源的识别难在信息层可以被完美伪造,属地是少数伪造不了、又不用人工跑腿的维度。上面的规则不需要机器学习,一个 curl 函数加三条计分项就能跑起来,先上线筛样本,再慢慢用人工复核的结果调阈值。
用的接口是 https://ip9.com.cn/get?ip=xxx,免费版不用注册、60 次/分钟,返回 country、prov、city、isp 这些字段刚好够用;要区县级精度或 ip_type(区分家用宽带和 IDC 机房)得看 VIP 能力,细节以 https://www.ip9.com.cn 为准。