×

《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)

万邦科技Lex 万邦科技Lex 发表于2026-10-09 09:47:29 浏览30 评论0

抢沙发发表评论

《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)

先拍结论:
1688 / 阿里系图片域的核心真相是:
img.album.upload / image.upload 是把图放进“阿里图片空间 / alicdn 图片服务”;album.* 管相册/分组/目录;而商品详情里返回的 img.alicdn.com / cbu01.alicdn.com 图片默认带 Referer 防盗链。
跨境 ERP / 闲鱼 / Mercari / TikTok 场景里:不要热链 1688 图片,不要相信“上传完 URL 永久不变”,不要以为 CDN 上的图删了就立刻消失,也不要以为换同名图 URL 会变。

一、接口边界先理清

接口/域
角色
关键事实
img.album.upload / image.upload
上传到阿里图片空间
返回 pictureId + url(通常是 https://cbu01.alicdn.com/...)
album.list / img.album.list
查相册/分组
是“商家图片空间”视角,不是商品图视角
album.category.list
相册分类
用于商家后台整理,ERP 一般不用
img.delete / album.picture.delete
删图
删的是图片空间资源;CDN 边缘可能还有缓存
商品 API 返回的 picUrl / imageList / skuPicUrl
供货侧图片
域名多在 img.alicdn.com / cbu01.alicdn.com,带防盗链
⚠️ 1688 开放平台不同文档里会看到 image.upload / img.upload / img.album.upload 等写法。
生产代码里别硬编码 method 名,用配置项;以当前开放平台“图片空间”文档为准。

二、防盗链:为什么直接 <img src="https://cbu01.alicdn.com/..."> 会 403

阿里图片 CDN 会看请求头里的:
Referer: https://www.1688.com/        # 过
Referer: https://my-erp.com/          # 大概率 403 / 返回占位图
表现:
  • 浏览器里打开商品页图片正常

  • 嵌到你自己前台 / 跨境平台草稿页就裂图

  • 服务端 requests.get 不带 Referer 有时能下,有时被风控拦

  • 用户侧展示直接挂掉

正确策略只有一条:下载转存自有对象存储,前端永远不出现 alicdn.com 原链。

三、CDN 缓存:你以为的“即时生效”都是错觉

1. 上传后

  • 返回 URL 立即可读:不一定

  • 不同边缘节点同步:有延迟

  • 大图/水印图处理:可能几十秒后才稳定

2. 删除后

  • 图片空间里查不到了

  • CDN 边缘还可能返回旧图几分钟到几小时

  • 下游已经缓存了 URL → 更久

3. 覆盖上传 / 同名替换

  • 阿里图片空间很多场景不是覆盖原图,而是新资源新 URL

  • 如果你自己对象存储用 sku_123.jpg 覆盖:

    • 对象存储本身可能立刻变

    • 但前面套了 CDN → 旧图还在

    • 解决:加版本号 / hash 文件名 / 主动刷缓存


四、生产架构:1688 图 → 自有对象存储 → 自有 CDN

1688 picUrl
   │
   ▼
下载代理(带 UA / 超时 / 重试 / 去水印检测)
   │
   ▼
图片治理(格式/长边/体积/白底/水印/色情违禁)
   │
   ▼
对象存储:oss/s3/r2
   ├─ /1688/{offer_id}/{sku_id}/{sha256[:8]}.jpg
   │
   ▼
自有 CDN / 签名外链
   │
   ▼
闲鱼 / Mercari / TikTok / 前台页面
铁律:
  • 数据库存 internal_object_key,不存 https://cbu01.alicdn.com/...

  • 下游只拿 cdn.example.com/1688/xxxx.jpg?v=hash

  • 1688 原链最多当 source_url 审计用


五、Python:图片转存服务(带防盗链/重试/去重)

# media/image_migrator.py
import hashlib
import io
import time
from dataclasses import dataclass
from typing import Optional

import requests
from PIL import Image


@dataclass
class MigratedImage:
    offer_id: str
    sku_id: str
    source_url: str
    object_key: str
    cdn_url: str
    width: int
    height: int
    bytes: int
    sha256: str


class ImageMigrator:
    def __init__(
        self,
        object_storage,
        cdn_host: str,
        timeout: float = 12.0,
        max_bytes: int = 5 * 1024 * 1024,
        user_agent: str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    ):
        self.oss = object_storage
        self.cdn_host = cdn_host.rstrip("/")
        self.timeout = timeout
        self.max_bytes = max_bytes
        self.session = requests.Session()
        self.session.headers.update({"User-Agent": user_agent})

    def _download(self, url: str) -> bytes:
        last_err = None
        for attempt in range(3):
            try:
                # 带 1688 referer 提高下载成功率,但不是给前端用的
                resp = self.session.get(
                    url,
                    headers={"Referer": "https://www.1688.com/"},
                    timeout=self.timeout,
                )
                if resp.status_code == 200 and resp.content:
                    if len(resp.content) > self.max_bytes:
                        raise ValueError("image too large")
                    return resp.content
                last_err = f"status={resp.status_code}"
            except Exception as e:  # noqa
                last_err = str(e)
            time.sleep((2 ** attempt) * 0.5)
        raise RuntimeError(f"download failed: {url} {last_err}")
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
    def _normalize(self, raw: bytes) -> bytes:
        img = Image.open(io.BytesIO(raw)).convert("RGB")
        w, h = img.size
        max_side = 2000
        if max(w, h) > max_side:
            scale = max_side / max(w, h)
            img = img.resize((int(w * scale), int(h * scale)))
        buf = io.BytesIO()
        img.save(buf, format="JPEG", quality=82)
        return buf.getvalue()

    def migrate(self, offer_id: str, sku_id: str, source_url: str) -> MigratedImage:
        raw = self._download(source_url)
        processed = self._normalize(raw)
        sha = hashlib.sha256(processed).hexdigest()
        img = Image.open(io.BytesIO(processed))
        w, h = img.size

        key = f"1688/{offer_id}/{sku_id}/{sha[:8]}.jpg"
        self.oss.put(key, processed, content_type="image/jpeg")

        return MigratedImage(
            offer_id=offer_id,
            sku_id=sku_id,
            source_url=source_url,
            object_key=key,
            cdn_url=f"{self.cdn_host}/{key}?v={sha[:8]}",
            width=w,
            height=h,
            bytes=len(processed),
            sha256=sha,
        )
使用侧:
migrated = migrator.migrate(offer_id="654321", sku_id="abc123", source_url=sku_pic)
listing.images.append(migrated.cdn_url)
# 永远别把 source_url 直接写进 listing.images

六、album.* 的坑:相册不是商品图库

很多人以为:
“我调 album.list 就能拿到这个供应商所有商品图”
错。
album.* 是:
  • 商家图片空间

  • 商家自己传的图

  • 相册分组 / 分类目录

  • 和“当前 offer 的 SKU 图”不是同一视角

踩坑:
  1. 相册里有图,但商品已经下架 → 图还在

  2. 商品图用了相册外的 CDN 资源 → album.list 查不到

  3. 商家删相册图 → 商品还可能显示(CDN 缓存)

  4. ERP 拿相册当商品主数据源 → 漏图/错图

正确顺序:
  • 商品主图 / SKU 图:从商品详情接口取

  • 相册接口:只用于“商家授权你帮他管图/批量上传/清理空间”


七、上传图片的隐藏约束

  • 格式:JPG/PNG/WebP 常见,GIF 有些场景不支持

  • 大小:单图常有限制(如 ≤5MB),具体看当前图片空间文档

  • 场景码:product_main / detail / sku 等,传错会被商家后台归类到奇怪目录

  • 水印:阿里图片服务可能返回带水印图;转存前要检测

  • 返图 URL:

    • pictureId 稳定

    • url 稳定但不保证“内容不变”

    • 不要拿 URL 字符串做图片去重,要用 sha256

  • 批量上传:

    • 别同步循环

    • 走队列 + 限流 + 死信


八、CDN 缓存治理(对象存储侧)

def object_key_for_image(offer_id, sku_id, raw_bytes):
    sha = hashlib.sha256(raw_bytes).hexdigest()
    return f"1688/{offer_id}/{sku_id}/{sha[:10]}.jpg"

# 永远不覆盖旧 key:
#   内容变了 → sha 变 → key 变 → URL 变 → 没有缓存歧义

# 如果必须覆盖固定 key:
def invalidate(cdn_client, key):
    # 对象存储:删/覆写
    # CDN:提交刷新任务(有配额,别每张图都刷)
    cdn_client.refresh([f"https://cdn.example.com/{key}"])
规则:
  • 1688 原图:不缓存前端可访问 URL

  • 自有图:内容寻址(sha[:10])最优

  • 固定业务 key(sku_123.jpg):更新后必须 CDN 刷新 / 加 ?v=timestamp

  • 别让用户浏览器直连 *.alicdn.com


九、合规边界(别踩版权雷)

转存 1688 图 ≠ 你可以随便用:
  • 供应商官网图 / 品牌图 / 模特图:可能含品牌授权

  • 跨境平台(TikTok/Mercari/Shopee)查侵权:原图搬过去会被投诉

  • 正确做法:

    • 供应商明确授权可跨境销售

    • 去水印但别伪造图

    • 品牌 Logo / 人脸 / 版权插画做检测

    • 侵权图进“禁止刊登池”


十、和前几篇收口

  • 《1688 商品 API》:picUrl/skuPicUrl 是源,不是终态

  • 《1688 跨境分销》:跨境刊登必须转存,不然海外前台 403

  • 《统一采购适配层》:SupplyOfferRef 上要挂 images: list[InternalMediaRef],不挂 alicdn_url

  • 《1688 订单/物流》:退货/补发时,商家发来的新图也要走同一套迁移器

  • 本文:图片域的唯一铁律 = “阿里图只进,不出;出了就自己存。”


十一、一句话收口

img.upload 是把图交给阿里;album.* 是商家整理自己图;
商品里的 alicdn 图带 Referer 防盗链、CDN 有缓存、删除不即时、覆盖有歧义。
跨境 ERP 的正解不是“怎么绕过防盗链”,而是:
下载 → 治理 → 内容寻址 → 存对象存储 → 自有 CDN 出图 → 1688 原链只留审计。

群贤毕至

访客