实操
微信文章复制图片无法显示?脚本转存图片到R2
python转存图片到CloudflareR2
2026-06-10在微信发布了文章,想转到其他平台发布,图片死活都无法显示。试过剪藏、有道云笔记等,均无法显示。最后折腾出以下方案,按步骤执行即可。
1、下载简悦浏览器插件
进入官网,找到浏览器插件,选择对应浏览器(我用的Chrome)图标点击

点击下图按钮,进入浏览器应用商店下载

如果发现无法安装,根据上述官方给的方案解决。
2、浏览器打开微信文章,用简悦下载出Markdown格式。
下载的文件名默认为simpread-.md

3、获取R2的API Token
这里涉及到绑定支付方式,按Cloudflare官方页面操作一步步进行。

4、写换图片的Python脚本
让CC写的python脚本,可以将Markdown文件里的微信图片替换成R2图片。
脚本文件名:wx2r2.py。内容如下,请将自己R2里获取的Key替换到脚本中相应位置。
#!/usr/bin/env python3
"""
微信公众号文章图片迁移脚本
功能:读取 Markdown 文件 → 下载微信图片 → 上传到 Cloudflare R2 → 替换链接
用法:python wx2r2.py 你的文章.md
"""
import sys
import re
import os
import hashlib
import mimetypes
from pathlib import Path
from urllib.request import urlopen, Request
from urllib.error import URLError
import hmac
import hashlib
import datetime
import json
# ============================================================
# 👇 填入你自己的配置(为了不暴露,下面的我已经改为假的key了)
# ============================================================
ACCOUNT_ID = "bd79de23a6e65b5cc5f911c5331002c4"
ACCESS_KEY_ID = "6ee7411871d0ba5d6997b0c0977b82b3"
SECRET_KEY = "68fd8e0a07c2ef30bb01ba33f218328118728dff8da79eff78b4d06505de92ba"
BUCKET_NAME = "blog-images"
CUSTOM_DOMAIN = "https://images.icareold.com" # R2 自定义域名
# ============================================================
ENDPOINT = f"https://{ACCOUNT_ID}.r2.cloudflarestorage.com"
REGION = "auto"
# ---------- AWS Signature V4(R2 兼容 S3 协议)----------
def _sign(key, msg):
return hmac.new(key, msg.encode(), hashlib.sha256).digest()
def _get_signing_key(secret, date_str, region, service):
k = _sign(("AWS4" + secret).encode(), date_str)
k = _sign(k, region)
k = _sign(k, service)
k = _sign(k, "aws4_request")
return k
def upload_to_r2(data: bytes, object_key: str, content_type: str) -> str:
"""上传二进制数据到 R2,返回公开访问 URL"""
now = datetime.datetime.utcnow()
date_str = now.strftime("%Y%m%d")
time_str = now.strftime("%Y%m%dT%H%M%SZ")
host = f"{ACCOUNT_ID}.r2.cloudflarestorage.com"
url = f"{ENDPOINT}/{BUCKET_NAME}/{object_key}"
payload_hash = hashlib.sha256(data).hexdigest()
headers_to_sign = {
"host": host,
"x-amz-content-sha256": payload_hash,
"x-amz-date": time_str,
"content-type": content_type,
}
signed_headers = ";".join(sorted(headers_to_sign.keys()))
canonical_headers = "".join(
f"{k}:{v}\n" for k, v in sorted(headers_to_sign.items())
)
canonical_request = "\n".join([
"PUT",
f"/{BUCKET_NAME}/{object_key}",
"",
canonical_headers,
signed_headers,
payload_hash,
])
credential_scope = f"{date_str}/{REGION}/s3/aws4_request"
string_to_sign = "\n".join([
"AWS4-HMAC-SHA256",
time_str,
credential_scope,
hashlib.sha256(canonical_request.encode()).hexdigest(),
])
signing_key = _get_signing_key(SECRET_KEY, date_str, REGION, "s3")
signature = hmac.new(signing_key, string_to_sign.encode(), hashlib.sha256).hexdigest()
authorization = (
f"AWS4-HMAC-SHA256 Credential={ACCESS_KEY_ID}/{credential_scope}, "
f"SignedHeaders={signed_headers}, Signature={signature}"
)
req = Request(url, data=data, method="PUT")
req.add_header("Authorization", authorization)
req.add_header("x-amz-date", time_str)
req.add_header("x-amz-content-sha256", payload_hash)
req.add_header("Content-Type", content_type)
req.add_header("Host", host)
with urlopen(req) as resp:
if resp.status not in (200, 201, 204):
raise RuntimeError(f"上传失败,HTTP {resp.status}")
return f"{CUSTOM_DOMAIN}/{object_key}"
def download_image(url: str) -> tuple[bytes, str]:
"""下载图片,返回 (数据, content_type)"""
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36",
"Referer": "https://mp.weixin.qq.com/",
}
req = Request(url, headers=headers)
with urlopen(req, timeout=30) as resp:
data = resp.read()
ct = resp.headers.get("Content-Type", "image/jpeg").split(";")[0].strip()
return data, ct
def content_type_to_ext(ct: str) -> str:
ext = mimetypes.guess_extension(ct)
mapping = {
".jpe": ".jpg",
".jpeg": ".jpg",
None: ".jpg",
}
return mapping.get(ext, ext or ".jpg")
def url_to_object_key(url: str, ct: str) -> str:
"""用 URL 的 MD5 作为文件名,避免重复"""
name = hashlib.md5(url.encode()).hexdigest()
ext = content_type_to_ext(ct)
return f"wechat/{name}{ext}"
def process_markdown(md_path: str):
path = Path(md_path)
if not path.exists():
print(f"❌ 文件不存在:{md_path}")
sys.exit(1)
content = path.read_text(encoding="utf-8")
# 匹配 Markdown 图片语法 
pattern = re.compile(r'!\[([^\]]*)\]\((https://mmbiz\.qpic\.cn[^)]+)\)')
matches = pattern.findall(content)
if not matches:
print("✅ 未找到微信图片链接,无需处理。")
return
print(f"📷 发现 {len(matches)} 张微信图片,开始处理...\n")
url_map = {} # 原始 url → R2 url
seen_urls = set()
for alt, url in matches:
if url in seen_urls:
continue
seen_urls.add(url)
print(f" ⬇️ 下载:{url[:80]}...")
try:
data, ct = download_image(url)
except Exception as e:
print(f" ❌ 下载失败:{e}")
continue
object_key = url_to_object_key(url, ct)
print(f" ⬆️ 上传:{object_key}")
try:
r2_url = upload_to_r2(data, object_key, ct)
except Exception as e:
print(f" ❌ 上传失败:{e}")
continue
url_map[url] = r2_url
print(f" ✅ 完成:{r2_url}\n")
# 替换内容
new_content = content
for old_url, new_url in url_map.items():
new_content = new_content.replace(old_url, new_url)
# 输出新文件
out_path = path.with_stem(path.stem + "_r2")
out_path.write_text(new_content, encoding="utf-8")
print(f"\n🎉 处理完成!已保存到:{out_path}")
print(f" 共替换 {len(url_map)} 张图片")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法:python wx2r2.py 你的文章.md")
sys.exit(1)
process_markdown(sys.argv[1])5、执行脚本,获取图片地址为R2的文章
将简悦下载的文件和python脚本放在同一文件夹,终端命令行先进入文件夹目录,再运行命令执行脚本
python3 wx2r2.py simpread-.md

等待执行完成,可看到文章里的图片地址已经更换完成,再处理到其他平台就不怕图片无法显示了。