免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

用IP精准定位过滤无效流量:广告投放反作弊的30行Python代码实践

用IP精准定位过滤无效流量:广告投放反作弊的30行Python代码实践 前两天帮朋友救火一个投放账户客户做本地生活服务预算一天一万二开跑没两天钱就见底了。看后台数据点击率3.8%比同行平均值高一大截可转化数少得可怜。当天下午我把监测日志拉下来写了个脚本按IP清洗了一遍结果触目惊心快三成的点击落在一个机房IP段里这些流量压根没有真人参与只是把广告费吃干抹净还顺手把后续人群包的模型特征给带歪了。这故事听着是不是很熟悉广告投流里的无效流量问题不是少数账户的偶发事故而是行业里长期存在、每天都在发生的老大难。很多优化师第一反应是调出价、换素材、改定向试了一圈发现预算还是哗哗地溜走。其实问题不一定出在前端创意而是流量本身就不干净。这里想说的是一个很实用的切入思路靠IP精准定位来过滤无效流量用三十多行核心代码就能落地并且顺手把人群包的质量和后续模型训练的纯净度一起优化掉。这篇内容适合三类人看一类是天天被无效点击搞到崩溃的投放优化师一类是负责广告系统日志清洗和反作弊的数据开发还有一类是刚接触程序化广告、想知道流量侧怎么做质量把控的入门朋友。整个过程不需要特别重的基建一台普通的日志处理服务器加一份离线IP库就能跑起来逻辑清晰成本几乎可以忽略。1. 无效流量到底从哪来先对着日志找源头1.1 三类典型无效流量的特征和识别点做过滤之前一定要先搞清楚自己面对的流量是什么类型。不同的无效流量识别特征和清洗策略完全不一样一锅炖容易误伤真实用户。第一类是数据中心IP流量也就是IDA机房、云厂商分配出来的IP段。这类IP的注册主体是机房或云服务商不是家庭宽带也不是手机基站。可以理解为这种IP背后是一排服务器而不是一个真实的人。机器脚本刷曝光、刷点击、模拟注册大部分从这些IP段出去。识别特征是ASN号指向云厂商或IDC运营商IP信息库里的运营商字段会显示为电信机房阿里云腾讯云之类。第二类是非目标地域流量。广告计划明明定向了北京结果日志里出现大量广东、四川甚至海外的IP在点你的广告。造成这种情况的原因很多比如投放平台做流量聚合时带了外部广告位的杂质比如某些低质SDK在用户不知情的情况下后台请求广告再比如羊毛党、竞对在监控你的素材和落地页。无论哪种原因这类流量不仅浪费预算还会严重干扰投放模型对目标用户的判断。第三类是行为模式异常的流量。IP本身没有问题归属地也在目标范围内但行为不像真人同一IP短时间内几百次曝光、几十次点击点击间隔精确到毫秒或者一个IP关联的设备ID数量异常多。这类情况单看IP不一定拦得住但IP仍然是整个链路里最稳定的锚点因为设备ID可以清、Cookie可以被删IP是每次请求都躲不掉的信息。做个表格把这三类的关键识别点整理出来无效流量类型IP层特征典型动作过滤优先级数据中心/机房IPASN指向前缀为IDC、云厂商高频曝光、高频点击、注册异常高可放心拦截非目标地域IP归属地偏离投放定向点击后无后续行为、跳出率极高高结合白名单谨慎拦截行为异常IP归属地正常频率异常短时间大量重复请求中需配合频次规则1.2 为什么优先在IP层做文章市面上过滤无效流量的手段很多有设备指纹、行为序列、反作弊SDK、广告平台自己的风控策略。这些都有用但IP维度始终是性价比最高的一环。原因是IP处在网络协议的最底层任何请求都绕不开它。用户在点击广告那一毫秒内广告请求、页面加载、日志上传都会带上源IP服务器端不依赖任何客户端埋点就能拿到。相比设备指纹和CookieIP的伪装成本更高伪造IP往往会导致连接无法建立要真想换IP成本也不低。所以IP是一个相对稳定、覆盖面全、拿来就能用的判断特征。当然IP也不是万能的。真实业务里会遇到运营商NAT导致多人共享一个出口IP也会有企业办公室整个网络共用一个公网IP的情况后面会专门讲误杀问题。但正因为它廉价、高效、可实时判断把IP作为过滤链路的第一道闸门是最合理的架构选择。我的习惯是IP层挡掉明显的垃圾流量行为层再做二次甄别两层串起来用。2. 30行代码实现IP精准定位核心逻辑拆开讲2.1 设计思路过滤逻辑分三层别一棍子打死动手写代码之前先想清楚规则的组织方式。我的方案是三层过滤每一层解决一类问题。第一层是基础豁免。内网IP、保留IP、自家办公出口IP这类流量绝不能拦。虽然在公网日志里出现概率不高但一旦出现而没做豁免会把自家员工或者内部系统的请求误判成异常影响数据统计时很难排查。第二层是IDC机房IP黑名单。这是拦截无效流量的主力。我会维护一个CIDR格式的IP段列表把ASN归属为机房、云厂商的段加进去只要请求IP落在这个范围内直接标记为无效。第三层是地域白名单。按投放业务设定允许的地区范围凡是IP归属不在地域白名单内的请求全部标记为无效。这里用白名单而不是黑名单是因为多数广告账户的投放范围是明确的白名单更安全漏网流量少。代码上要保持逻辑纯粹一个函数只负责一次IP判断输入是IP字符串输出是动作标记。这样做的好处是可以灵活接入各种场景离线批处理可以逐行读取日志然后调用实时接口可以直接透传结果后续想加频次控制也容易。2.2 代码实现离线IP库加段匹配加规则动作直接上代码。这个脚本用到了三个核心依赖xdbSearcher是ip2region离线IP库的Python查询SDKbisect是Python标准库用来做二分查找socket和struct用来做IP地址和整数之间的转换。import socket import struct from bisect import bisect_right from xdbSearcher import XdbSearcher def ip2num(ip): return struct.unpack(!L, socket.inet_aton(ip))[0] def load_rules(path): rules [] with open(path, r) as f: for line in f: line line.strip() if not line or line.startswith(#): continue if / in line: ip, prefix line.split(/) mask (0xffffffff (32 - int(prefix))) 0xffffffff start ip2num(ip) mask end start | (0xffffffff ^ mask) else: start end ip2num(line) rules.append((start, end)) rules.sort() return rules, [r[0] for r in rules] searcher XdbSearcher(dbFileip2region.xdb) idc_rules, idc_starts load_rules(idc_blacklist.txt) geo_allow {中国} def filter_ip(ip): if ip.startswith((10., 192.168., 127.)): return pass n ip2num(ip) idx bisect_right(idc_starts, n) - 1 if idx 0 and idc_rules[idx][0] n idc_rules[idx][1]: return reject_idc res searcher.searchByIPStr(ip) if res: parts res.split(|) if parts[0] not in geo_allow: return reject_geo return pass for line in open(access.log, r): ip line.split()[0] action filter_ip(ip) if action pass: print(ip)这段代码的核心过滤函数filter_ip实际不到20行。ip2region.xdb是开源的离线IP库文件从项目发布页下载后放到脚本同目录就行idc_blacklist.txt是自定义的机房IP段规则文件格式每行一个IP或CIDR段支持#注释。逐段解释一下逻辑。ip2num把点分十进制IP转成32位整数这是做IP段区间判断的前提。IPv4地址的本质就是一个整数只是平时写成人可读的格式而已。load_rules读取机房黑名单规则支持单IP和CIDR两种写法排序后得到有序的start数组和对应的区间列表。bisect_right用来快速定位当前IP可能命中的区间位置再检查是否落在区间内这就是标准的二分查找匹配IP段时间复杂度O(logN)规则上万条也能毫秒级返回。XdbSearcher是本地离线查询不依赖外部网络请求单次查询在微秒级。它返回的结果类似中国|0|广东省|深圳市|电信用管道符分割后第一个字段是国家第二个字段是省份。拿国家字段和geo_allow白名单做比对不在白名单直接拦掉。2.3 代码还能怎么改接在线库、加缓存、上消息队列上面的版本适合日志离线批处理和中小规模的实时查询但真实线上的需求往往会更复杂。结合我自己的落地经验提供几个改造方向。如果想用商业IP库更精细地识别IDC段可以把xdbSearcher换成MaxMind GeoIP2或者ipinfo.io这类支持ISP和ASN查询的服务。它们的离线数据库会标注IP的组织类型比如hosting、business、residential直接按org字段匹配就能自动识别大部分机房段不需要手工维护黑名单。缺点是这些库通常要付费且体积比ip2region大不少加载时间更长。如果查询量很大比如每天过亿次的判断就要考虑给filter_ip的结果加缓存。因为同一个IP在短时间内会出现无数次典型的做法是用LRU缓存缓存键可以是IP本身值直接存过滤动作。还有更激进的方案是把整个IP规则集加载到Redis里做成IP段前缀树查询走Redis的bitmap或者自研分段索引。不过大部分场景下单机加一个Python字典缓存就够了。如果要做实时拦截通常还会加一层消息队列。日志先打到Kafka消费端按批次调用filter_ip有效的请求继续往下游送无效的单独落到一个审计表。这样做既不会因为IP查询服务抖动拖垮主链路又能保留完整的拦截日志用于后续分析。3. 从日志清洗到人群包优化完整落地流程3.1 第一步把原始日志整理成可计算的会话表很多人拿到日志就直接开跑脚本其实不对。过滤IP之前先要把原始日志规整成结构化的会话表否则后面分析和排查都无从下手。一份标准的广告投放日志至少要包含这些字段请求时间、用户IP、广告计划ID、创意ID、媒体ID、设备ID、事件类型曝光或点击、目标链接。如果日志是Nginx格式用一条简单的Python解析就能转成DataFrame。关键在于去重和会话切分。同一个设备在同一广告位上短时间内重复曝光不一定是异常但重复点击就是需要关注的行为信号。我会把同IP、同设备ID、同计划ID的请求按时间窗口聚合先算出一个会话级别的事件序列再交给过滤脚本判断。这个步骤的目标是让每条日志不再是孤立的请求而是一个可追踪的行为链条。有了这层结构IP过滤的结论才能往下游传递。3.2 第二步离线清洗跑批与实时拦截两种姿势离线跑批是比较容易上手的方案。每天凌晨把前一天的会话表跑一遍filter_ip把命中reject_idc和reject_geo的日志打上无效标签生成一份无效流量日报。同时把有效流量单独抽出来按广告计划、创意、媒体维度重新统计CTR和CVR。投放优化师第二天看到的就是清洗后的数据至少不会被虚假点击带偏。实时拦截适合转化率极高、预算消耗特别快的账户。这时候如果还等第二天离线清洗预算早就烧完了。做法是在广告点击回传接口上加一个过滤服务收到点击请求先查一次IP命中黑名单直接丢弃不往数据后台和计费系统传。这个服务需要一个快速判断的IP库通常预加载到内存性能取决于数据库和匹配逻辑。我见过做得好的方案单机支撑每秒几千次查询完全没问题。实际项目中我的建议是两个方案并行实时拦截负责止损离线跑批负责分析和调优。实时挡掉明显的机房流量离线复盘时再根据结果优化规则和人群包。3.3 第三步用过滤结果反哺人群包和投放策略这是整个方案的升值点。无效流量清洗完不只是省预算关键是让后续的人群包质量提高一个量级。做法是先把每一段拦截日志关联的设备ID、媒体ID整理出来在广告平台的DMP里生成一个低质流量排除包投放下一次活动时直接放进排除定向。这相当于给人群包做了一个减脂手术把那些永远不会转化的机器流量从目标人群里拿掉。另一边把有效流量里产生点击但未转化的用户、以及产生转化的用户分别打包成种子人群。这些种子人群经过了IP层过滤成分比原来的全量点击人群干净得多。用它们做相似人群扩展时模型的训练样本是可靠的扩出来的量级和精准度都会明显改善。说的直白一点之前的模型可能是被刷量数据教歪了清洗后它才能学到真正的目标用户长什么样。这条链路跑顺之后投放策略的人为调整也会轻松很多。因为数据清洗后出价模型和创意优化的反馈都更真实优化师可以更放心地依赖系统自动调优而不是天天手动补刀。3.4 第四步效果验证看什么指标上线这套过滤逻辑之后一定要有一套明确的效果评估方法。不要等到投放结束才看数据那时候已经晚了。我习惯用下面这几项指标做对比。重点关注CTR变化、CVR变化、CPC变化和最终转化成本。表格里是我一次落地项目中的真实对比数据方便大家直观感受效果验证应该怎么算指标过滤前过滤后变化说明曝光10000072000无效曝光被拦截点击38002300刷量点击大幅下降CTR3.8%3.2%会下降因为剔除了假点击注册280330真实转化数量反而增加CVR7.37%14.35%转化率提升近一倍CPC2.632.08单次点击成本明显下降注册成本35.7114.48最终效果核心指标这里有个容易误判的点过滤之后CTR通常会下降很多优化师一看CTR变低就着急以为效果变差了。恰恰相反CTR下降说明原来高得异常的点击率是刷量堆出来的不是素材变好了。真正要看的是CVR和最终成本这两个指标才是决定ROI的胜负手。4. 实操中的坑与排查经验能少走一个是一个4.1 IP库不更新过滤规则迟早失效IP段不是静态的。云厂商和IDC运营商为了扩容会不断从APNIC等机构申请新的地址段。如果一个机房黑名单或IP归属库半年不更新新增的机房段基本就是漏网之鱼。很多人一开始发现过滤效果很好几个月后感觉没差别了多半是库过期了。所以部署这套方案时要顺带把更新机制做好。ip2region的库文件建议每周更新一次MaxMind的付费库一般按官方周期自动下发自定义的idc_blacklist.txt也要建立月度复核机制定期把最近被标记为异常的IP段加进去。更新后还要跑一遍回归确认没有因为数据版本切换导致误杀率飙升。4.2 运营商NAT出口IP是误杀重灾区这是新手最容易踩的坑。移动网络下大量用户会共享同一个公网出口IP尤其是晚高峰时段一个IP背后可能是一整片居民区的用户。如果规则写得太凶看到某个IP请求量高就顺手拉黑后果是误杀一大片真实用户转化率照样不会好看投诉反而先来了。处理这类情况的关键在于区分IP类型。家庭宽带和手机基站的出口IP即使是共享的也属于住宅或移动网络不应该出现在机房黑名单里。判断方法很简单看IP归属库里的运营商字段如果是电信、联通、移动且归属为城市级大概率是正常的运营商出口。真正需要担心的是ASN字段指向云厂商和IDC的地址那才是不正常的高危流量。4.3 拦截规则上线前先做灰度观察别一上来就把所有流量拦住。任何规则都存在误判的可能尤其是地域白名单这类粗粒度规则。我踩过的教训是曾经把某个省份加进地域白名单后发现部分大客户办公网的流量也被拦了因为他们的分公司出口IP恰好落在目标省外。如果直接全量拦截客户就找你上门了。稳妥的做法是灰度。规则先以标记不拦的模式跑两天看看命中量级、分布和误判情况确认没问题再切到拦截。拦截动作也建议先只作用于离线清洗链路不碰实时计费等离线链路验证效果稳定后再逐步打开实时拦截开关。这里可以整理一个常见问题速查表症状可能原因解决思路拦截量突然暴增IP库过期、规则文件误改检查库版本回滚规则转化率没有提升无效流量本来不转化过滤后基数变小但真实转化没被激活延长观察周期同步优化落地页和素材实时接口变慢查询逻辑太重、缓存没生效增加LRU缓存、预热IP库真实用户被误杀NAT出口IP被当成异常只拦IDC段不动住宅和移动段过滤后数据波动大量级过小统计噪声放大加长回看窗口按周对比4.4 性能不够时三步把它提速如果日志量很大纯Python逐行查询可能成为瓶颈。解决思路有三个层次。第一层是给查询加内存缓存前面说过同一个IP在日志里高频出现缓存命中率通常很高。第二层是换数据库索引方式把IP段加载进Redis的有序集合或者自研的二叉树索引查询耗时可以降到纳秒级。第三层是并行化用多进程或者扩展到Spark处理离线日志按IP哈希分片后各跑各的最后汇总。绝大多数过亿级日志的清洗用第一层加第二层就足够。只有实时要求特别高的场景才需要考虑复杂的分布式方案。不要一上来就把架构做得太重简单方案往往维护成本最低。还有一个追查问题的技巧每次规则调整都要给命中规则加上不同标记。比如机房黑名单返回reject_idc地域白名单返回reject_geo规则热更新时新增的段返回reject_idc_new。这样后续报表里可以清晰看到是哪个规则拦了多少流量哪条规则误杀变高一眼就能定位。这个习惯帮我省掉过无数次排查时间强烈建议养成。我自己在实际操作中的体会是过滤无效流量和优化人群包是一个持续迭代的过程不是跑一次脚本就一劳永逸。每次投放活动结束后花十分钟把这次命中的异常IP段追加到黑名单把低质媒体ID维护进排除包长期累积下来投放账户的流量质量会肉眼可见地变干净。最后再分享一个小技巧把filter_ip的决策结果做成一份按小时聚合的监控报表一旦某条规则的拦截量出现陡增或陡降系统自动告警很多问题都能在预算受损之前被拦下来。
返回列表