Skip to content

租房平台的假房源怎么识别?用发帖 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 为准。