591 在屋主委托链路上做媒合:屋主提交委托 → 平台分发给中介 → 中介带看、回报。买家只出现在中介的回报里,平台手上没有任何独立信息可以核对。中介回报「本周带看 5 组」,这个 5 目前是纯声明。
中介不会把买家电话交给平台 —— 那是他的客户资源,也涉及个资。所以方案的输入只有:中介提交的买家条目(自填标识)+ 一条平台生成的短链。
条形为示意比例,实际分布待灰度数据。中介评级、线索分发若要用带看量,应该用 effective_count 而不是 claimed_count。
中介转发的东西,买家点开率取决于对买家有没有用。落地页应该是一个买家本来就想看的页面,采集是副产品:
关键 让中介有动机转发,比让买家有动机点击更重要 —— 中介不转发,这条链路根本不启动。所以短链必须做成「中介本来就要做的事」的更好版本,不能是额外负担。
https://591.tw/v/{token} # 验证短链
https://591.tw/v/aK9x2Qm4f7Bd # 示例,12 字符
token = base62(random_48bit) || base62(HMAC-SHA256(secret, random)[0:3])
└── 8 字符,2.8e14 空间 ──┘ └──── 4 字符校验位 ────┘
长度 12。服务端先本地验校验位,不通过直接 404,不查库。
| 设计选择 | 理由 |
|---|---|
| 不用自签名 payload(JWT 之类) | URL 变长且不可撤销;一旦泄露等于泄露「中介ID + 买家ID + 房源ID」的映射,业务数据挂在网上 |
| 不用自增 ID base62 | 可枚举。爬到别人的链接就能污染他人数据,也能反推平台的委托量 |
| 加 HMAC 校验位 | 扫描者不掌握 secret,构造不出合法 token。绝大多数扫描流量在不查库的情况下被挡掉,同时保护 DB |
| 随机 48 bit 而非 UUID | UUID base62 要 22 字符,转发到 LINE 里太长;48 bit + 速率限制足够 |
| 一条买家一个 token | 去重的前提。若一个中介一条通用链接,就分不出是哪个买家点的,整个方案失效 |
短链不携带任何语义,映射全放服务端,可随时撤销、改绑、加限制。
-- 短链主表:token → (中介, 买家条目, 房源, 委托)
CREATE TABLE verify_link (
token CHAR(12) PRIMARY KEY, -- 随机 + HMAC,不可枚举
entrust_id BIGINT NOT NULL, -- 委托单
property_id BIGINT NOT NULL, -- 房源
agent_id BIGINT NOT NULL, -- 中介:结果归因到他
buyer_ref_id BIGINT NOT NULL, -- 中介提交的买家条目
purpose ENUM('viewing','offer','lead') NOT NULL,
status ENUM('created','sent','clicked','judged','expired','revoked'),
max_clicks SMALLINT DEFAULT 10, -- 允许重复点;超额是风险信号,不阻断
created_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL, -- 建议 14 天,给中介转发留余裕
KEY idx_agent (agent_id, created_at),
KEY idx_entrust (entrust_id)
);
-- 中介提交的买家条目:不含电话,中介不给我们也不要
CREATE TABLE buyer_ref (
buyer_ref_id BIGINT PRIMARY KEY,
agent_id BIGINT NOT NULL,
label VARCHAR(64), -- 中介自填:代号 / 姓氏 / "王先生"
claimed_at DATETIME, -- 中介声称的带看时间
claimed_type VARCHAR(20),
created_at DATETIME
);
-- 点击事件:一次点击一行,不覆盖,全部留痕
CREATE TABLE verify_click (
click_id BIGINT PRIMARY KEY,
token CHAR(12) NOT NULL,
ts DATETIME(3) NOT NULL,
-- 网络层(302 阶段即可得,无需 JS)
ip VARBINARY(16),
ip_asn INT,
ip_geo VARCHAR(32),
ua_raw VARCHAR(512),
ja4 CHAR(36), -- TLS 指纹,抗 UA 伪造
referer VARCHAR(255),
-- 指纹层(落地页 JS 回传,加载即采集,无需用户动作)
fp_hash CHAR(64), -- Canvas+WebGL+Audio+字体+屏幕 综合
cookie_id CHAR(32), -- first-party,1 年
ls_id CHAR(32), -- localStorage 备份,抗 cookie 清除
screen VARCHAR(24),
tz VARCHAR(40),
ua_ch JSON, -- User-Agent Client Hints
channel ENUM('pc','touch','ios','android','line'),
-- 身份层(有就记,不主动索取、不弹窗)
x591_user_id BIGINT, -- 恰好登录着就能拿到
line_user_id VARCHAR(48), -- 仅 LIFF 形态,本方案不强求
app_device_id VARCHAR(64), -- 恰好装了 App 且被唤起
-- 行为
dwell_ms INT,
interacted TINYINT,
KEY idx_token (token, ts),
KEY idx_fp (fp_hash),
KEY idx_cookie (cookie_id)
);
-- 去重与过滤结果:一条 buyer_ref 一行
CREATE TABLE verify_result (
buyer_ref_id BIGINT PRIMARY KEY,
device_key CHAR(64), -- 归一化后的装置标识,去重就靠它
clicked TINYINT,
is_duplicate TINYINT, -- 与同委托其他条目撞装置
dup_of BIGINT, -- 合并到哪条
is_filtered TINYINT, -- 命中过滤规则
filter_reason JSON, -- 命中了哪些规则,供中介申诉时解释
judged_at DATETIME
);
去重的准确度全看 device_key 取得稳不稳。按可靠性降序取第一个非空值:
| 优先 | 取值 | 稳定性 | 可得率 |
|---|---|---|---|
| 1 | app_device_id | 最稳。装了 591 App 且被 Universal Link / App Links 唤起才有 | 低 |
| 2 | x591_user_id | 稳,且是「人」不是「装置」。买家恰好登录着 591 才有 | 中低 |
| 3 | cookie_id / ls_id | 较稳。同浏览器内长期有效;跨浏览器、跨 App WebView 失效;iOS 受 ITP 7 天限制 | 高 |
| 4 | fp_hash + IP /24 | 弱。同型号手机会撞;换网络会变。只作兜底 | 最高 |
unique_device 会系统性偏高,
也就是偏向对中介有利。
| 阶段 | 时机 | 拿到什么 | 买家感知 |
|---|---|---|---|
| ① 网络层 | 短链 302 之前,服务端记录 | IP、ASN、粗地理、UA、Accept-Language、JA4 TLS 指纹、Referer、点击时刻 | 无感 |
| ② 指纹层 | 落地页加载后 JS 异步 POST /v/collect |
Canvas/WebGL/AudioContext 哈希、字体、屏幕、时区、UA-CH、first-party cookie、localStorage ID、停留与交互;顺带读到的 591 登录态 | 无感(隐私政策须告知) |
没有第三段。不做 LIFF 授权弹窗、不做登录引导、不做 App 强制唤起 —— 那些属于身份认证方案,不在本次目标内,且会显著压低点击转化。
created ──(中介转发)──> sent ──(首次点击)──> clicked ──┐
│ │
│ (T+14d 或批量任务) ↓
│ judged ──> 回写 effective_count
└──(中介撤回/争议/风控)──> revoked expired ──> clicked = 0
IP + ASN + 粗地理、User-Agent、Accept-Language、JA4 TLS 指纹、Referer、点击时间戳。任何端都拿得到,也任何端都能伪造(改 UA、挂 VPN),只当辅助信号。
| 接收端 | 前置条件 | 预期可采集信息 | 对去重的影响 | 实测 |
|---|---|---|---|---|
| PC 端 桌面浏览器 |
· 买家在桌面点开 · JS 启用 · 非隐私窗口(否则 cookie 不持久) |
共通层 + · first-party cookie(1 年)+ localStorage ID · Canvas / WebGL / AudioContext 指纹 · 字体列表、screen、devicePixelRatio、时区 · hardwareConcurrency、deviceMemory · UA-CH:platform / arch / model · 恰好登录时的 591 UserID |
去重可靠 cookie 持久、指纹熵高。 macOS Safari 受 ITP,JS 写的 cookie 上限 7 天 → 跨周去重会漏 |
待测 |
| 触屏端 平板 / 触屏笔电 |
· 同 PC · 走系统浏览器而非 App WebView |
PC 端全部,外加: · maxTouchPoints、pointer: coarse · orientation、devicePixelRatio · 虚拟键盘引起的 viewport 变化 |
指纹熵最低 同型号 iPad 之间几乎无差异,
fp_hash 会撞 → 必须靠 cookie,兜底层不可用
|
待测 |
| iOS 端 Safari / WKWebView |
· 点开链接即可 · App device_id 需已装 591 App 且 Universal Link 生效(本方案不强求) |
共通层 + · 基础指纹(熵低) · first-party cookie(受限) · 恰好登录时的 591 UserID · 恰好唤起 App 时的 IDFV |
去重最弱 · ITP:document.cookie ≤ 7 天;无交互的 localStorage 7 天清除 · UA 不报机型,只报 iPhone · Canvas 加噪 / 锁定模式更严 · iCloud 私人转送 → IP 变 Apple 中继,ASN 无参考价值 |
待测 |
| 安卓端 Chrome / WebView |
· 点开链接即可 · App device_id 需已装 591 App 且 App Links 校验通过(本方案不强求) |
共通层 + · 指纹熵最高(GPU 型号分散、UA-CH 报机型) · cookie 无 ITP 限制,持久性最好 · 恰好唤起 App 时的 SSAID / OAID |
去重最可靠 但也是模拟器 / 群控最集中的一端 → 过滤规则要在这端最严 |
待测 |
| LINE 端 主场景 |
中介转发就是发 LINE,这一端占比会最高。 · 无额外前置条件 · 买家在 LINE 内建浏览器直接打开 · 不做 LIFF 授权(本方案不需要身份) |
共通层 + 基础指纹 · UA 含 Line/x.y.z → 可识别来源渠道· LINE WebView 内的 first-party cookie · 底层仍是 iOS WKWebView / Android WebView, 能力随宿主系统 备选 若后续要拿 LINE userId,需建 LINE Login channel + LIFF app + profile 授权,属于身份认证方案,不在本期
|
跨端去重会漏 LINE 内建浏览器的 cookie 存在独立容器,与系统浏览器不互通: 同一个人在 LINE 里点一次、又在 Safari 里点一次,会被算成两个装置 |
待测 |
fp_hash 碰撞率是多少? 拿真实设备测。撞得多,兜底层就只能当负向信号用(没撞 → 确实是不同装置;撞了 → 不一定是同一人)。测试域名 https://591.xx/v/{token}
测试 token 每端预生成 20 条,token → (中介ID, 买家ID) 映射预置在 verify_link
落地页 /v/landing?t={token},加载后 POST /v/collect
回收 按 channel 分组,导出 verify_click 全字段做覆盖率统计
覆盖率指标(每端分别统计):
click_rate = 点击数 / 转发数
fp_rate = 拿到 fp_hash / 点击数
cookie_rate = 拿到 cookie_id / 点击数
device_key_rate = 拿到任一级 device_key / 点击数 ← 决定去重覆盖面
fp_collision = 不同真人撞同一 fp_hash 的比例 ← 决定兜底层能不能用
cookie_ttl = cookie 实际存活天数(分端) ← 决定跨周去重
| 维度 | 规则 | 处理 |
|---|---|---|
| 同委托内 | 同一 device_key 在同一 entrust_id 下被报为多个 buyer_ref |
合并 保留最早一条,其余 is_duplicate=1。同一套房不会同一台手机来看两次还算两组 |
| 同中介跨房源 | 同一 device_key 在同一 agent_id 名下出现在 > 5 套不同房源 |
标记 不合并 —— 真买家找同一个中介看多套完全正常。超阈值只进人工抽查队列 |
| 跨中介 | 同一 device_key 出现在 ≥ 3 个不同 agent_id |
标记 同样不合并。买家货比三家很正常;但若集中在同一房源的多个中介,是同行互点的信号 |
| 类别 | 规则 | 强度 | 处理 |
|---|---|---|---|
| 中介自身 | device_key 命中该中介自己的登录装置(591 中介后台 / 中介 App 的登录设备记录) |
极高 | 剔除 |
| 点击 IP 与该中介近 30 天常用 IP 重合(含门店 WiFi 出口) | 中 | 标记 | |
device_key 命中同门店其他中介的登录装置 | 中高 | 标记 | |
| 环境异常 | WebGL renderer = SwiftShader / llvmpipe(模拟器特征) | 高 | 剔除 |
| Android UA 但 maxTouchPoints = 0;或 UA 与 UA-CH 自相矛盾 | 高 | 剔除 | |
| 数据中心 ASN / 已知 VPN 出口 | 中 | 标记 | |
| 行为异常 | 停留 < 2s 且无滚动无交互 —— 「点开就关」 | 中 | 标记 |
| 同一中介名下多条 token 在极短时间窗内被集中点击(脚本特征) | 高 | 剔除 |
filter_reason 必须留下命中的全部规则,中介申诉时能逐条解释。
直接扣分而给不出理由,比不做更伤中介侧的信任,而中介是这条链路的启动方 —— 他不转发,数据就是零。
「点击装置 = 中介自己的装置」是唯一一条几乎没有假阳性的规则,也是这套方案能真正挤掉水分的地方。而它的数据来源是现成的 —— 中介登录 591 中介后台和中介 App 的设备记录本来就有。
agent_device 表:agent_id × device_key × 最近登录时间,滚动 90 天窗口device_key 归一化口径必须与 verify_click 完全一致,否则对不上(cookie 不同域会失效,需以 App device_id 与指纹为主)591.tw/v/ 的 cookie 若不同域就关联不上 —— 建议短链域与主站同域,走同一个 first-party cookie 空间建议 这一条优先于五端实测做技术验证。如果 agent_device 关联不上,方案的过滤能力就只剩模拟器和脚本检测,价值会打对折。
effective_count 与未点击条目,有申诉入口。不展示过滤规则细节,避免被针对性规避effective_countbuyer_ref.label 是中介自填代号;不要因为「顺手」去要电话,那会把一个轻量方案变成个资项目verify_click 原始字段建议 180 天后降级为聚合特征,只保留去重结果与 filter_reason| 序 | 事项 | 状态 | 说明 / 产出 |
|---|---|---|---|
| 1 | 问题定义与能力边界 | 完成 | 本文 §1。定位为去重 + 轻过滤,不是防伪证明 |
| 2 | 短链 token 设计与表结构 | 完成 | 本文 §3,含 device_key 优先级 |
| 3 | 中介设备库关联验证 | 待做 | 优先级最高。验证 agent_device 能否与 verify_click 的 device_key 对上;跨域是主要风险,建议短链与主站同域 |
| 4 | 五端采集能力实测 | 待做 | 填 §4 矩阵实测列。优先跑 LINE 端(流量占比最高)与 fp 碰撞率 |
| 5 | 落地页产品形态 | 待做 | 做成「中介本来就要发给买家的房源资料页」,让中介有动机转发 |
| 6 | 去重阈值与过滤规则 | 待做 | 依赖第 4 项的碰撞率与 cookie 存活数据才能定阈值 |
| 7 | 灰度与评估 | 待做 | 指标:click_rate、device_key_rate、effective/claimed 比值分布、中介申诉率 |
click_rate 低到没有统计意义,
最后 effective_count 反映的是「哪个中介配合度高」,而不是「哪个中介带看真」。