跳到主要内容
竞争与价格情报

把公开目录变成价格历史的一条管线

人工去看竞争对手,得到的只是一张快照,没有办法和上周比较。我们做的这条管线在渲染后的浏览器里逐页读取公开目录,自己弄清一个站点的类目结构与翻页方式,在信任自己的抽取配方之前先证明它确实能取到真实商品,并且每一轮都写下一个价格点。之后出来的报告是一份差异而不是一堆数据,而且在有人签核之前不会送出去。

  • 行业零售与电商
  • 合作方式自研平台,内部开发
  • 状态端到端在跑,仍有未完成的部分
  • 价格情报
  • 公开网页数据采集
  • 竞争研究
  • 数据管线

一个闭环:采集、归一、比较、打包、签核

管线按每个客户各自的计划运行。它先在竞争对手自己的域名下抓取值得读的页面,再采集商品目录本身:一个类目接一个类目、一页接一页,都在渲染后的浏览器里进行。因为用 JavaScript 搭起来的目录,并不在一次普通请求返回的 HTML 里。评价与公开动态汇入同一个存储,所以产出的是一幅竞争对手的画像,而不只是一张价目表。

采集到的一切都会被归一到同一种形态。金额会按欧洲、土耳其和美国的书写方式解析成数字加币种。商品按身份更新,因此新的一轮是刷新而不是复制;首次出现与最近出现的日期,正是让新增与消失可以被算出来的东西。带价格的每一轮还会写下一个价格点,报告因此可以是一份差异而不是一堆数据。

工作台运行中:已采集的目录、旁边的评价分析,以及自己汇报进度的采集任务。竞争对手的名称在录像本身里就已做模糊处理。
挑战

难在哪里

读懂一个竞争对手不是抓取问题。这条管线正是针对下面三种失败设计的。

一张快照没有可比较的对象

人工看价格,得到的清单在抄下来的那一刻就已经旧了,而且背后什么都没有。品类团队真正要问的是上一次之后什么动了,再仔细的抄写也答不上来,因为此前的状态从来没有被保存下来。

目录不在页面源码里

今天的店铺在页面加载之后才搭出商品栅格,把图片挂在懒加载属性上,并按各自平台习惯的方式把一个类目拆到多页。去读类目页的源码,拿到的是一个空壳。

读取失败和空货架看起来一模一样

危险的不是崩溃,而是那种干净收尾、报告什么都没找到、实际上只是网络抖了一下的那一轮。这种结果和真实变化无法区分,还会污染之后的比较。

解决方案

我们做了什么

一个按计划运行的闭环:够得着目录、弄清它的结构、让数值可比,再把差异变成一个人愿意签字的东西。

真正够得着目录的采集

抓取只留在竞争对手自己的域名内,遵守 robots.txt,自我限速,限定走多远,并且先访问价格页和商品页,再看一般页面。目录采集是另一件事:抽取在渲染后的页面里进行,因为用 JavaScript 搭出来的栅格只有在那里才存在。

  • 只在同一域名内抓取,遵守 robots.txt,自我限速,先读商业页面。
  • 三个采集引擎自动回退:缺少一个可选依赖只会让抓取降级,而不是失败。
  • 查询参数翻页、下一页链接、单页列表和无限滚动,都是靠实地试探判断出来的,而不是靠假设。

模型提出、代码证明的结构

站点真正的类目来自它自己的链接和站点地图,然后再做分类。没有任何东西仅凭这次分类就被采纳:一道解析并抽取的关卡必须证明,提出的配方在它声称适用的页面上确实能取到商品。

  • 候选类目来自站点自身的导航和站点地图,每一个在使用前都要通过验证。
  • 一个抽取配方只有在被证明能取到商品之后才被信任,分类环节还有一条严格的禁止臆造规则。
  • 名称与价格的抽取会沿着一串备选逐级回退,并优先选择最短的干净匹配,避免把整张商品卡当成名称。

真正能拿来比较的数值

页面上的价格是一串文本:带币种符号,带一个在不同国家含义不同的千位分隔符,旁边往往还有一个折扣角标。所有内容在入库之前都会被解析成金额加币种;品牌则来自一份拒绝猜测的人工维护清单。

  • 金额按欧洲、土耳其和美国的书写方式解析,币种在旁边一并读出。
  • 百分比角标在解析前先被剥掉,最后一个带币种的金额胜出,因此折扣绝不会被当成价格存下来。
  • 商品跨轮次按身份去重,重新读取是更新一件商品,而不是复制出一件新的。

一份是差异的报告,和一个为它签字的人

带价格的每一轮都会写下一个历史点,所以下一轮本身就是一次比较。变化报告完全由磁盘上已有的数据算出:不调模型、不走网络、不加新表。渲染之后留下的是待审状态,而签核是唯一会发送的代码路径。

  • 涨了什么、跌了什么、哪些是新品、哪些消失了、有哪些新评价,全部确定性地算出来。
  • 一份表格数据包和一份幻灯片,相同输入渲染出逐字节相同的文件。
  • 在有人签核之前不会有任何东西进入客户的收件箱;没有配置邮件服务时,发送会直接拒绝,而不是记下一次并未发生的投递。
工程

让这份报告值得读的那些决定

四个关于做对、而不是关于好看的选择。

读取失败不是空货架

请求失败、页面加载了却是空的、页面上有商品,在代码里是三种不同的结果,而不是一种。一轮采集会依据这些证据把自己标为完成、部分完成或失败,因此一次短暂的网络故障绝不会以一份干净但空无一物的结果出现。

停止条件归代码所有

缺口检测是基于规则的,每一轮都有硬性上限;可选的模型评审是加在上面的一层,默认关闭。因此在有人刻意打开之前,一次扫描在模型上不产生任何花费,也没有任何语言模型来决定工作什么时候算完成。

不依赖数据库的顺序

时间顺序保存在读取时捕获的微秒级时间戳里,而不是插入顺序里,随机标识只作为最后的并列裁决。因此无论底下用的是哪个数据库,价格历史读出来都一样。

在唯一的收口处做隔离

每一次读写都要经过唯一的作用域收口,并由数据库自身的行级安全兜底;如果服务自己的运行角色可能绕过它,服务会拒绝启动。比较引擎继承这条边界,而不是重新实现一遍。

成效

改变了什么

只讲定性,因为这个项目交出的是一个能跑的闭环,而不是一个被测量过的结果。

历史

有了比较,而不只是快照

第一轮产出一份目录。之后的每一轮产出一份差异,因为背后的价格连同读取日期早已落在磁盘上。

诚实

会承认自己漏了什么的采集轮次

读不到东西的那一轮会说出来,并把自己标为部分完成。下游不必再去猜:某一段是空的,究竟是对手变了,还是一次请求失败了。

已签核

先经过一个人,再到客户

渲染报告和发送报告是刻意分开的两步。从来没有东西因为计划到点就自己发出去;一次健康探测还能不碰网络、也不在模型上产生任何花费地跑完整个闭环。

最后审阅:

有值得盯住的目录吗?

告诉我们你在意哪些来源,以及一份有用的报告应该说什么。我们会诚实地界定采集范围,包括我们会拒绝的部分。

开始一次对话