← 返回蜂巢洞察

程序化广告的运作原理

大多数关于 程序化广告 的教程都只停留在网页横幅这个层面。其实这很可惜,因为一旦你将这种概念应用到现实世界中,它会变得有趣得多。 如果你之前从未从事过广告行业的工作,也无需担心。你不需要任何广告行业的背景知识就能理解这些内容。只要你能阅读基本的Python代码,那就已经具备了所需的一切条件。 广告本身只是实现这一目标的一种手段而已。真正重要的技能是:如何将现实世界中那些杂乱无章的信息转化为软件能够处理的数据。 在这篇文章中,你将会了解什么是程序化广告,以及为什么它在网页上能够取得如此好的效果。你还会明白,为什么将这种技术应用到户外广告牌上实际上是一个数据建模的问题,并且你会通过编写简单的Pyt

大多数关于程序化广告的教程都只停留在网页横幅这个层面。其实这很可惜,因为一旦你将这种概念应用到现实世界中,它会变得有趣得多。

如果你之前从未从事过广告行业的工作,也无需担心。你不需要任何广告行业的背景知识就能理解这些内容。只要你能阅读基本的Python代码,那就已经具备了所需的一切条件。

广告本身只是实现这一目标的一种手段而已。真正重要的技能是:如何将现实世界中那些杂乱无章的信息转化为软件能够处理的数据。

在这篇文章中,你将会了解什么是程序化广告,以及为什么它在网页上能够取得如此好的效果。你还会明白,为什么将这种技术应用到户外广告牌上实际上是一个数据建模的问题,并且你会通过编写简单的Python代码来实践每一个步骤。

我们将涵盖的内容:

什么是程序化广告?

广告活动包含两个方面:一方面是拥有广告位的一方,比如新闻网站;另一方面则是希望投放广告的一方,比如鞋类品牌。几十年来,连接这两者通常需要通过电话、电子邮件和纸质文件来完成。

而程序化广告则用软件取代了这种人工操作的方式。没有人需要亲自去协商广告的投放位置,系统会自动分析广告位的情况,并在几毫秒内决定是否购买以及以什么价格购买。

当你打开一个网页时,这个网页会告诉广告交易平台某个广告位是空闲的,同时还会提供关于该页面及浏览者的相关信息。广告商的系统会实时参与竞标,最终胜出的广告会在页面完全加载之前显示出来。这个过程被称为实时竞价,而且每天会有数十亿次这样的交易发生。

有两个术语可以帮助你更好地理解这一概念:“库存”指的是卖家可以提供的广告位数量;“展示次数”则是指一个人看到某条广告的次数。

在互联网上,这种模式的运用速度非常快,而且几乎毫不费力。了解其中的原因是很有必要的。

为什么互联网让程序化购买变得如此简单

互联网在无意中使程序化购买成为可能。每一个网页广告位都带有预先设定的结构:它们都有地址,也就是URL,同时也有在页面上的具体位置。这些广告位都存在于一种结构明确的文档中,这种文档就是文档对象模型。此外,这些广告位还能反馈广告是否已经加载并被人看到。

从诞生之初,整个互联网环境就具备被机器读取的能力,因为当时就已经有机器在为它提供服务了。当自动购买系统出现时,它已经拥有一个结构清晰、便于操作的平台——因为这种媒介本身就已经完成了所有的数据建模工作。

请记住这一点,这是理解整个道理的关键。

广告牌带来的问题

现在,把同样的思路应用到广告牌上吧。你会发现,广告牌根本不具备那种方便的数据结构。

一个物理广告牌并不会标明自己的位置或朝向方向,也不会说明本周的收费标准或是否还有空位。在它的历史中,这些信息都存在于价格表和销售人员的记忆中,而不是任何程序可以查询的数据中。

关键在于:户外广告这种媒介之所以落后于互联网,并不是因为它的效果较差,而是因为它没有可供机器读取的信息接口。目标受众本来就在那里,但缺乏结构化的数据才是真正的问题所在。

因此,将程序化购买模式应用到现实世界中,真正的关键并不在于复杂的竞价算法,而在于数据建模。

让我们具体来看一下这个过程。整个流程分为四个步骤,每个步骤都体现了许多工程问题中常见的处理方式。

第一步:确定实体身份

同一个广告牌可能会出现在不同供应商提供的数据集中。每个供应商会给它起不同的名称,并给出略有差异的坐标信息。在进一步进行处理之前,必须先将这些物理对象统一成一条记录。这个过程被称为记录关联,在这里主要是根据地理位置来进行关联。

一个简单的判断方法是:如果两个记录所处的位置相近,并且名称相似,那么就可以认为它们代表的是同一个广告牌。要计算两个坐标之间的距离,可以使用哈弗辛公式

from math import radians, sin, cos, asin, sqrt

def haversine_m(lat1, lon1, lat2, lon2):
    """计算两个坐标之间的距离,单位为米。"""
    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
    a = sin((lat2 - lat1) / 2) ** 2 + \
        cos(lat1) * cos(lat2) * sin((lon2 - lon1) / 2) ** 2
    return 6371000 * 2 * asin(sqrt(a))

def same_panel(a, b, max_distance_m=25):
    close = haversine_m(a["lat"], a["lon"], b["lat"], b["lon"]) <等于 max_distance_m
    similar_name = a["name"].lower().split()[0] == b["name"].lower().split()[0]
    return close and similar_name

vendor_a = {"name": "I-95 North Bulletin", "lat": 25.7907, "lon": -80.1300}
vendor_b = {"name": "I-95 Bulletin #4421", "lat": 25.7908, "lon": -80.1301}

print(same_panel(vendor_a, vendor_b))  # True – 同一个广告牌,对应同一条记录

在实际系统中,会使用更为模糊的匹配规则以及更多的数据信号,但原理是一样的。首先需要进行实体识别;否则,后续的所有步骤都会导致重复计算同样的信息。

步骤2:将位置视为可计算的上下文来建模

一个坐标本身并不能代表目标受众。要想让某个广告位对购买系统来说具有实际意义,就必须将其表示为一个带有方向信息的点。然后,通过结合道路交通数据,才能判断究竟有哪些人会经过这个广告位。

有用的信息是需要通过计算得出的,而不是预先给定的:

def estimate_impressions(panel, traffic_by_road):
    """根据道路交通数据估算每日广告展示次数。"""
    daily_vehicles = traffic_by_road[panel["road_id."]
    # 只有朝广告位正面方向行驶的车辆才能看到它
    facing_share = 0.5
    avg_occupancy = 1.4  # 每辆车上的乘客人数
    return int(daily_vehicles * facing_share * avg_occupancy)

panel = {"id": "P-4421", "road_id": "I-95-N", "facing": "south"}
traffic = {"I-95-N": 120000}

print(estimate_impressions(panel, traffic))  # 84000

请注意发生了什么:原始数据只不过是地图上的一个点,而经过处理后,它就变成了购买系统可以用来进行分析的信息——也就是84,000次每日预计的广告展示次数。这种处理方式在数据分析中非常常见:原始数据加上外部信息,就能生成有用的信息。

步骤3:将可用性表示为时间安排,而不是布尔值

一个网络广告位要么当前是可使用的,要么不可使用。在代码中,这种状态通常用布尔值来表示,即“真”或“假”。然而,广告位的实际使用情况往往是按时间段来安排的,其状态也会随着时间的推移而发生变化。库存数量也是会被预订、释放的,因此我们需要实时数据,而不是某个静态的快照。

from datetime import date

class PanelSchedule:
    def __init__(self):
        self.bookings = []  # 一个包含(开始时间, 结束时间)元组的列表

    def book(self, start, end):
        if not self.is_available(start, end):
            raise ValueError("该时间段内广告位不可使用")
        self.bookings.append((start, end))

    def is_available(self, start, end):
        return all(end < b_start or start > b_end
                   for b_start, b_end in self.bookings)

schedule = PanelSchedule()
schedule.book(date(2026, 8, 1), date(2026, 8, 14))

print(schedule.is_available(date(2026, 8, 10), date(2026, 8, 20)))  # False
printschedule.is_available(date(2026, 8, 15), date(2026, 8, 31)))  # True

这个道理在很多地方都适用。每当你对现实世界进行建模时,都要思考某个数值是否真的是固定的。实际上,很多数值都会随着时间而发生变化。可用性、库存数量以及座位分布情况等等,其实都属于需要按时间段来表示的信息,而不是简单的布尔值。

步骤4:将价格表示为函数,而不是一个固定的数字

价目表上只显示了一个数字,但实际价格会根据日期、需求以及剩余库存量等因素而发生变化:

def price_for(base_rate, start, weeks_out, occupancy):
    """根据交货期和需求来计算价格。"""
    demand_multiplier = 1 + occupancy          # 市场越繁忙,价格越高
    urgency_discount = 0.9 if weeks_out > 8 else 1.0  # 提前预订可享受折扣
    return round(base_rate * demand_multiplier * urgency_discount, 2)

print(price_for(base_rate=3000, start=date(2026, 12, 1),
                weeks_out=19, occupancy=0.85))  # 4995.0
print(price_for(base_rate=3000, start=date(2026, 8, 1),
                weeks_out=2, occupancy=0.40))   # 4200.0

一旦价格计算成为一种函数,软件就能瞬间对比成千上万的广告位和日期。而这正是自动购买功能所需要的。

整合各项要素

现在你们已经解决了实体识别、目标受众分析、实时可用性检测以及动态定价等问题。有了这些基础,程序化数字户外广告在现实世界中的运作方式与在线上是一样的。软件可以规划跨各种物理屏幕的广告活动,自动购买广告位,近乎实时地进行调整,并测量广告效果。

其实,这一切都与互联网本身无关。关键在于这些数据是否具有结构化的框架,也就是说,这些数据是否拥有明确的格式,能让软件理解每条数据的含义。一旦为现实世界的数据建立起这样的结构化框架,自动化处理也就成为了可能。

通过练习培养直觉

进行这种练习并不需要特殊的权限。你可以选择任何一份杂乱无章的、基于地理位置的公共数据集,然后按照上述四个步骤来操作。一个很好的数据来源是OpenStreetMap,它提供了数百万个真实地点的免费数据。

首先,通过合并描述同一实际对象的记录来消除重复数据;其次,为每个数据点添加计算得出的上下文信息,而不要将原始记录视为完整的信息;第三,将某个字段建模为会随时间变化的变量,而不是固定值;最后,将处理后的结果封装在一个清晰的接口中,比如一个类或一个小型的API,以便其他系统能够使用它。

虽然你并没有真正构建出一个广告平台,但你已经掌握了那些让自动购买功能得以扩展到新领域的关键技能。

总结

一个好的抽象模型在面对现实世界时依然能够发挥作用。

当你在一个整齐有序的环境中初次学习某个概念时,很容易认为这种整齐性就是该概念本身的一部分。但实际上往往并非如此。只有当你将这个概念应用到混乱的环境中去,看看它是否仍然能够正常运作时,你才能真正了解这个概念的通用性。

程序化购买技术就是一个很好的例子——它从屏幕上转移到了道路旁的墙上。在其他领域,你也可能会发现类似的规律:一个充满潜力的领域,只需要一个结构化的框架就能被有效利用。

希望你们喜欢这篇文章。你可以通过在LinkedIn上与我联系

相关文章

技术实践

如何使用MONAI在超声数据上训练肿瘤分割模型

大多数分割教程都是从选择一个模型开始,将图像输入该模型中,然后调整超参数直到相关指标得到改善。但这种方法忽略了通常最为关键的一步:理解数据本身。 在本教程中,我们首先会对数据集进行详细分析,随后会根据这些分析结果来决定MONAI分割流程中的每一个设计细节。 我们将涵盖以下内容: 本教程适合谁? 关于数据集 什么是MONAI,为什么使用它? 什么是Dice评分? 第1部分——建模前的数据分析 类别平衡对分割结果的影响 患者数量对数据划分的影响 第2部分——构建分割流程 单一配置对象 按患者分组的数据划分方式 由快照自动选择的转换操作 模型、损失函数与评估指标 结果解读 预测结果可视化 失败模式比

阅读全文
技术实践

什么是HyDE?如何利用假设性文档来提升RAG的质量?

检索增强生成技术,通常被称为RAG,已成为利用大型语言模型构建应用程序时最常用的方法之一。 与让大型语言模型完全依据其训练数据来回答问题不同,RAG系统会从外部知识库中检索相关信息,并将这些信息作为上下文提供给模型。 其基本原理非常简单: 将用户的问题转化为嵌入向量。 在向量数据库中搜索语义上相似的文档片段。 将检索到的这些片段传递给大型语言模型。 基于这些片段生成答案。 然而,这个看似简单的过程存在一个重大缺陷:用户提出的问题与包含答案的文档在表达方式上可能存在很大差异。 例如,用户可能会提出这样的问题: 为什么我的AWS Glue作业在处理了几百万条记录后速度会显著下降? 而知识库中相关的

阅读全文
技术实践

从RPC到gRPC:了解远程过程调用、Protocol Buffers以及现代分布式系统的通信机制

任何应用程序在某些时候都需要与其他系统进行交互。移动应用会与后端服务进行通信;后端服务又会与支付网关对接;认证服务需要与用户服务进行交互;数据传输流程则要与存储系统相连。 问题不在于系统是否需要相互沟通,而在于应该如何实现这种沟通。 多年来,基于HTTP和JSON的REST架构一直是人们的首选方案。它运行稳定、使用简单,而且相关的开发工具也随处可见。然而,随着系统规模的扩大——无论是参与交互的服务数量增加、数据交换量增大,还是对实时通信的需求提高——REST架构逐渐暴露出了其局限性。 这时,远程过程调用、Protocol Buffers以及gRPC这些技术应运而生了。 通过这本手册,你将了解什

阅读全文
技术实践

演讲主题:从复制粘贴到组合开发:构建类似真实软件的智能代理程序

Jake Mannix讨论了如何让人工智能代理摆脱那些混乱、类似于“20世纪70年代的BASIC编程语言所使用的架构”。他说明了通过实现中间协议层,工程团队能够构建出具有版本控制功能的、结构清晰的“虚拟工具”。这种设计使得接口映射、动态数据结构转换以及运行时异常检测成为可能,从而能够在不降低系统运行速度的情况下,主动消除数据泄露风险。 作者:Jake Mannix

阅读全文