一张卡片被夹在两道闸门之间,闸门内的图片仍停在旧版本上

一、旧图不是代码错误:三层不一致各管一种现象

做分享卡片的人迟早撞上同一个现象:抓取视角读回来的字段已经是新的,用户分享出去却还是旧图。这时候最容易做的三件事——重新构建、改声明写法、把图片换个格式再传——全都无效,因为它们针对的不是出问题的那一层。

页面被外部看到的样子,要穿过三层:我们部署的、抓取器能读到的、抓取器愿意重新读的。它们各自的失效长得不一样,先把对照关系钉死。

层 典型现象 判定动作 归口
部署层 自己强制刷新页面也读到旧内容,图片地址直接返回旧文件或失败状态 分别请求页面地址与图片地址,比对响应里的版本标识与文件指纹 发布流程与静态资源
渲染层 浏览器里卡片信息齐全,用不带脚本的方式取原始响应却找不到声明 以不执行脚本的方式抓一次初始文档,逐字段确认声明是否在里面 服务端渲染
缓存层 抓取视角读回的是新值,分享出去仍是旧图;读回时刻早于换图时刻 从外部抓取视角读回字段并记下时刻,与上线时刻对齐 不改代码,按窗口等

判错层的代价很具体:在缓存层的问题上去改渲染代码,会引入一次没有必要的发布;在渲染层的问题上去等缓存,会白等一个完整窗口还查不出原因。所以顺序不能倒——先确认东西上线了,再确认抓取器能看见,最后才讨论它多久重新看一次。部署层的验证有个省事的做法:给每次发布留一个带版本标识的探测地址,用它回答“线上到底是哪一版”,不要靠记忆和构建日志。

二、抓取器的三条硬约束,每条配一道上线前检查

抓取侧的行为有三条约束,它们不是“建议”,而是决定卡片能不能出图的前提。

第一,抓取器不执行页面脚本。直接后果是:由脚本在运行时补进去的声明,抓取器看不见。卡片字段必须出现在服务端返回的初始文档里。上线前检查:以不执行脚本的方式取一次原始响应,逐项确认图片地址、标题、描述都在其中;如果项目把整页做成客户端渲染,这一步必须单独设卡,不能靠“我自己打开看着挺好”过关。

第二,抓取器不支持需要鉴权的资源。直接后果是:分享图不能放在登录之后的地址上。依赖会话或凭证的地址、内网地址、带临时签名的地址,都会在抓取侧变成一张取不到的图。上线前检查:用不带任何凭证的一次性请求取图片,确认返回成功状态;把“图片必须匿名可取”写进资源目录的准入条件,而不是等出图失败再回头查权限。

第三,抓取器不应重定向,也不接受被规则屏蔽的路径。直接后果是:换域名、换目录、加或去尾斜杠这类看似无害的改动,会让旧链接静默失效——不报错,只是回退。上线前检查:声明里写的地址直连必须返回成功状态,链路上不该有跳转;同时确认抓取屏蔽规则没有覆盖到这个目录。三条的共同前提是图片公开可访问且返回成功状态,这一条要每次发布都测,而不是首次上线测一次。

三、声明一致性最容易出的两个错

字段齐了不等于卡片对。两类错出得最多。

一是声明的宽高与实际图片不符。抓取侧的卡片展示区按声明的尺寸决定怎么裁,声明与实际不一致就出现裁切、拉伸或留白——而且不同展示位表现不一样,本地看常常看不出来。处置办法是把宽高变成机器算出来的:构建阶段读取图片真实尺寸并自动填入声明,禁止手写这两个值;图片本身则按卡片展示区的横竖形态准备,比例和主体位置在切图时就定好,不要指望声明去修正构图。

二是同一页面声明多张图片。声明多图在语义上是允许的,但抓取侧可能随机回退到其中一张,于是“我们期望的那张”变成一个概率事件,同一链接在不同时刻分享出不同图。处置办法是每个页面固定一张主图,其余图片放正文而不是放声明;模板层要能挡住“列表页复用详情模板时把首图循环写进声明”这类写法。

顺带一条常被过度处理的:现代图片格式在抓取侧已被普遍支持,不需要为兼容把所有分享图统一降级成旧格式。降级只在你自己的图片处理链路上有意义,把它当成抓取兼容措施是额外成本。

四、滞后窗口里的四条运营纪律

抓取缓存的策略是强一致优先、低频更新:一个链接被抓过之后,改图片不会即时生效,自动刷新周期以小时计。公开拆解里给出的量级在一到两天,这个数字只用来理解节奏,不该被当成流程设计的依据——真正要落的是四条纪律。

  1. 活动图上线后设一段不许频繁改图的窗口。每改一次,就要重新按小时级别等待;为了对齐设计稿反复微调同一张分享图,是把一次发布变成几次不确定的出图。
  2. 确需换图时用可寻址的新文件名,而不是覆盖同名文件。加上内容指纹或版本后缀,让新链接对应新版本,旧链接留给缓存自然过期;覆盖同名文件反而最难判断,因为你无法确定外部拿到的哪一次是新的。
  3. 换图窗口内,活动页不得作为唯一转化入口。卡片不准就是不准,这段时间需要有备用入口和一套不依赖图的说法,把转化路径建立在可核对的文字与地址上。
  4. 把手动刷新入口当可选项而不是依赖。是否提供、以什么条件提供、提供到什么程度,都不由我们控制,本篇也不给出任何调用方式;流程设计只按“存在一个不由我们单方决定的滞后窗口”这一条来排。

这四条里最贵的是第二条和第四条,因为它们都要求你在发布机制上先做决定:文件命名策略、以及是否允许任何环节把手动刷新当成前提。

五、把“卡片正确”写成可验证项

“上线后卡片看起来对”不是验证,“抓取侧读回的内容与构建产物一致”才是。做法是在发布清单里固定加一项:以外部抓取视角读回声明字段并截图留档,同时记下读回时刻与当次的版本标识。

留档要比对的字段是固定的那几项:图片地址、声明宽度、声明高度、替代文本,加上标题与描述。比对基准是构建产物里的期望值,而不是浏览器里脚本跑完之后的最终态——否则渲染层的问题会被这个步骤掩盖过去。截图之外,版本标识要能和发布记录对上,回答两个问题:我们上线的是哪一版,抓取侧读到的是哪一版。这两问的答案不一致时,才需要去判断是滞后还是漏发布。

这份留档有三个用处:滞后判定(新链接第一次读回仍旧,就是窗口未过期)、争议定位(活动进行中卡片究竟是什么内容,只有留档能证明)、回归基线(字段被模板改动吃掉时,下一次留档会立刻显出差异)。它是投入很小但事后极难补的一项,所以必须写成放行条件,而不是可选步骤。

六、一份投入两处收益,以及要对客户说的那句实话

这套检查不是只为分享卡片服务。同一份服务端返回的声明文本,也是站外索引读取的内容:不执行脚本、不接受鉴权资源、不跟随重定向这些限制,对分享和对索引同样生效。所以第五节那一次留档,同时回答了“外部能不能读到”和“读到的是哪一版”两个问题——一份投入,两处收益,这也是我们把它放进发布流程而不是放进运营临时动作的原因。

有一类冲突要单独治理:抓取屏蔽策略的初衷通常是挡住内部路径与预览环境,却顺手把分享资源目录一起挡了。这类事故的特点是所有内部测试都通过,只有站外出图失败。处置办法是把“必须公开”的资源目录单独列成白名单,纳入变更评审:新增任何屏蔽规则,都要检查它与白名单有没有交集;鉴权策略同理,不允许把分享图挂到需要凭证的路径下。洹拓在交付里把无脚本可读、无凭证可取、无跳转可达这三项写成放行条件,任一项不满足就不安排以卡片为准的投放。

最后一句要给客户的诚实表述:分享卡片的生效时间不由我们单方决定。任何以卡片为准的营销时点,都要在计划里预留一个完整的刷新窗口,并且把窗口内的兜底入口先定下来——这件事的正确做法是提前排期,而不是在出图失败之后临时找技术手段抢救。