同一篇稿子发到 6 个平台,为什么只有这里的图片全裂了?

同一篇稿子发到 6 个平台,为什么只有这里的图片全裂了?

一稿多发最尴尬的瞬间:文章发出去 10 分钟,读者在评论区问「你的图呢?」

排查之后你会发现,问题往往不在你的 Markdown,而在图床和 Referer 校验。

本文把「多平台发文时图片为什么会裂」这件事讲清楚,并给出一套可以落地的处理方案。


现象:同一份源文件,三种结果

把同一份 Markdown 分发到不同平台,图片的表现通常分三类:

表现 典型原因
图片正常 平台站外图片不做来源校验,或自己的图床可以被外链
图片自动转存后正常 平台抓取外链图,上传到自己的图床后再替换链接
图片全裂 源图床对 Referer 做了校验,站外请求一律 403

第三类最容易被误判成「Markdown 写错了」。实际上链接是完全正确的,只是服务端拒绝了这次请求。


根因:Referer 校验

很多图床(尤其是云厂商对象存储的默认域名)会做防盗链:

http 复制代码
GET /direct/xxxx.png
Referer: 掘金站内地址
→ 403 Forbidden

而同一个地址,如果 Referer 是它自己的域名,就返回 200。

也就是说:图片能不能显示,取决于「谁在请求它」。

这也解释了一个很常见的困惑------「我本地预览明明是好的」:本地文件或者同域引用根本不会带上触发校验的 Referer。

一条命令验证

排查第一步,先确认是不是来源校验:

bash 复制代码
# 不带 Referer
curl -s -o /dev/null -w "%{http_code}\n" $URL

# 带上目标平台的 Referer
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Referer: 掘金站内地址" \
  "某 CDN 的直链地址"

两个结果不一致(200 / 403),基本可以定位就是防盗链。


陷阱:懒加载会让「自动检测」给出假阴性

很多人用「图片加载失败」来判断裂图,写法大致是:

js 复制代码
const bad = [...document.images].filter(img => img.naturalWidth === 0);

这个判断在懒加载 面前是失真的:还没滚动到的图片,naturalWidth 也是 0,但它并没有裂。

更可靠的做法是看 src 的最终形态:

js 复制代码
const imgs = [...document.querySelectorAll('.article-content img')];
const hosts = imgs.map(i => {
  try { return new URL(i.currentSrc || i.src).host; }
  catch { return 'INVALID'; }
});
console.table(hosts);

判断标准变成两条:

  1. src 是否指向该平台自己的图床域名(转存成功);
  2. 是否仍是原始外链域名(可能被拒)。

对站点自己的域名,再补一次 HTTP 探测,就能得到确定结论,而不是靠 naturalWidth 猜。


解决方案:从「一份稿子」到「每平台一份」

方案一:发布前批量转存(推荐)

思路很简单:目标平台自己的图床,才是唯一不会 403 的来源。

流程:

复制代码
解析 Markdown 中的图片链接
  → 下载到本地临时目录
  → 上传到目标平台图床
  → 用返回的新链接替换原文
  → 再执行发布

用 Node 实现核心两步并不复杂:

js 复制代码
// 1) 下载源图,保留原始扩展名
async function download(url, dir) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`download ${res.status}: ${url}`);
  const ext = (new URL(url).pathname.match(/\.(png|jpe?g|gif|webp)$/i) || ['.png'])[0];
  const file = path.join(dir, crypto.randomUUID() + ext);
  await fs.writeFile(file, Buffer.from(await res.arrayBuffer()));
  return file;
}

// 2) 串行转存,避免把平台图床打限流
for (const link of links) {
  const local = await download(link, tmpDir);
  const uploaded = await uploadToPlatform(local);   // 各平台接口/交互不同
  md = md.replaceAll(link, uploaded);
}

两个实践要点:

  • 串行,不要并发。上传接口大多有限流,并发只会换来一批 429;
  • 保留扩展名 。不少平台的图床按后缀判断 Content-Type,丢了后缀会变成下载而不是显示。

方案二:为每个平台维护一个「图床版本」

如果平台数量固定、发文频率高,更省事的做法是:一份内容,多份链接版本。

bash 复制代码
jjmd/article.md      # 通用版(用平台 A 的图床)
c51md/article.md     # 51CTO 版(必须用站内图床)

用脚本维护一次映射表,之后每次发文只替换图片链接前缀即可。代价是要多维护几份文件,好处是发布环节零风险、零等待。


把「图片可用性」做成发布前的断言

最后一件事,也是最能省时间的:把检查前置到发布之前。

js 复制代码
function assertImagesResolvable(md, allowedHosts) {
  const links = [...md.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)].map(m => m[1]);
  const bad = links.filter(l => {
    try { return !allowedHosts.includes(new URL(l).host); }
    catch { return true; }
  });
  if (bad.length) {
    throw new Error(`以下图片不在允许的图床内,发布前必须转存:\n${bad.join('\n')}`);
  }
}

允许域名列表由各平台决定:站内图床 + 明确支持外链的域名,其余一律拦下。

这样做的价值不是「多检查一步」,而是把发布后才发现的问题 提前到发布前必然发现------后者是可以自动修复的,前者只能重发。


小结

  • 图片裂不裂,取决于图床的 Referer 校验,跟 Markdown 写法无关;
  • 用 curl 对比带/不带 Referer 的返回码,一秒定性;
  • 别用 naturalWidth 判断裂图,懒加载会骗你;
  • 想彻底解决,就把图片转存到目标平台自己的图床,并串行执行;
  • 最后,把图床域名白名单做成发布前断言。

一稿多发真正的工作量,从来不在写,而在适配。

相关推荐
用户842558130871 小时前
把「一文多发」做成脚本流水线:CDP、滑块、二维码、WAF、限流,这些坑我替你踩完了
ai编程
猛犸象限1 小时前
【Grok Bot 仓颉实践】 编译通过了,我没让它合并——验证和证据我怎么卡
ai编程
小鱼儿亮亮1 小时前
大模型调用的演进与架构全景解析:从 cURL 原语、多维技术栈、API 规范到 LangChain 生产级实战
ai编程
用户842558130871 小时前
Chrome 自动化「点了没反应」:5 个真实坑与一套排查法
ai编程
tz_zs2 小时前
【AI编程】使用笔记:Rule/Skill/Prompt
笔记·prompt·ai编程
tz_zs3 小时前
【AI编程】使用笔记:AI编程项目文档体系与框架
笔记·ai编程
Shockang12 小时前
OpenCode 版 OmO:职责分工、编辑校验与技能边界
ai编程
小虎AI生活13 小时前
ChatGPT for Chrome 扩展升级解读:从「你复制给它」到「替你操作浏览器」(附三步上手流程)
aigc·ai编程
金銀銅鐵13 小时前
[Java] 借助GUI展示class文件的内容(v1.0版本)
jvm·后端·ai编程