1753973862036

在上個月的 Imgur 一直 temporarily over capacity 嗎?先檢查網路看看吧 提到,因為 Imgur 不給上傳圖片了,現在寫個文章還得開 VPN 才能貼圖片。

拖著拖著七月也要過了,決定趁休假的時候來把圖床搬一搬。

比起實作上的拖延,搬家目標則早早決定好要嘗試這篇「Imgur 封鎖台灣 IP,我把圖床搬到 Cloudflare R2 - Code and Me」提到的 Cloudflare R2

畢竟出口流量免費實在太香了,不愧是賽博菩薩。這就馬上前來皈依。


考慮到每位朋朋的部落格選型不同,先說明一下我家的狀況:

  • 使用 Hugo 建置,直接丟在 Github Pages
  • 圖片目前放在 Imgur

這代表有些較為暴力的操作可能只適用於我目前的狀況,例如直接在檔案裡搜尋 imgur 來抓圖等等(所以如果你是從關鍵字搜尋進來這篇文章的,請從右手邊的文章目錄直接前往你需要的小節)

這次圖床搬家主要進行了以下步驟:

  • 建立 Cloudflare R2 資源
  • 寫個 Powershell 腳本把目前部落格放在 Imgur 的圖片都抓下來
  • 順便用 WebP 壓縮一下圖片
  • 把圖片丟到 Cloudflare R2
  • 回來取代文章內的圖片連結
  • 使用 VSCode 的 Markdown-image 套件直接上傳圖片到 Cloudflare R2

如此如此,說搬就搬,這篇就紀錄一下這次的圖床搬家過程。

建立 Cloudflare R2 資源

既然都決定要搬家到 Cloudflare R2 了,首先當然是要建立一個 Cloudflare R2 資源。

建立 R2 的操作可以參考這篇「架設 Cloudflare R2 免費圖床,給 Hugo 靜態網站託管圖片 - Ivon 的部落格」的「新增 Cloudflare R2 bucket」小節,流程詳細還附圖,基本可以照著把 R2 儲存空間給建出來。

總之先到 Cloudflare 儀表板,把 R2 儲存體建出來,然後順手掛個自訂網域:

1753970440386

1753970454758

備註:如果想要自訂 R2 資源對外的網址,需要先讓 R2 和 Cloudflare 上存在的網域做綁定,也就是要在 Cloudflare 買網域或是其他地方買來掛在 Cloudflare 上(參考 Public buckets

因為我(之前為了改 Bluesky 的帳號名稱)已經在 Cloudflare 購買過網域了,所以可以直接用自定義網域來把圖片網址改成我要的網址。

如果你也想要自訂圖片網址,但在 Cloudflare 還沒有任何網域,可以先多找幾篇網域購買和託管到 Cloudflare 的文章(例如 Cloudflare 網域購買教學)參考參考,再決定要不要投入 $$ 買一個自己喜歡的名字。

建立完畢之後,可以先進貯體隨便上傳一張圖片,確認一下 URL 有沒有連到圖片:

1753970485658

弄個 Powershell 腳本把部落格的圖片先抓下來

建好 R2 空間之後,就要開始著手搬遷啦~

我的部落格採用 Hugo + Github Pages,所以文章內容也就是一堆 Markdown 檔案而已。這種狀況下,最快的方式就是直接搜出文章中的圖片連結就行。

幸好 2025 年的懶人是有福的,直接請 GPT 幫我產一組 Powershell 腳本,把 .md 檔拖出來,掃到圖片就抓下來存著,迅速搞定這一階段:

# see: https://gist.github.com/mufidu/f7b795f844f1ee4dc78e55123d5a398b

# 1. 指定資料夾
$folder    = "C:\Blog\content"
$outputDir = "C:\Users\xxx\Pictures\Blog-Image-Backup"

# 2. 建立目標資料夾(若不存在)
if (-not (Test-Path $outputDir)) {
    New-Item -Path $outputDir -ItemType Directory | Out-Null
}

# 3. 逐一掃描 .md 檔案
Get-ChildItem -Path $folder -Filter *.md -Recurse | ForEach-Object {
    $mdFile  = $_.FullName
    $content = Get-Content -Path $mdFile

    foreach ($line in $content) {
        # 4. 正則擷取 Markdown 圖片 URL
        if ($line -match '!\[[^\]]*\]\((?<url>https?://[^\)]+)\)') {
            $url      = $Matches['url']
            # 5. 以 URL 最後一節當作檔名
            $fileName = Split-Path -Path $url -Leaf
            $destPath = Join-Path $outputDir $fileName

            try {
                # 6. 下載圖片
                Invoke-WebRequest -Uri $url -OutFile $destPath -UseBasicParsing
                Write-Host "已下載: $url$destPath"
            }
            catch {
                Write-Warning "下載失敗: $url (`$_`)"
            }
        }
    }
}

插件:順便用 WebP 把圖片做一下壓縮

圖片下載完後,突然想起之前跑 PageSpeed Insights 時有被提示過圖檔太大的問題,決定趁這機會順便把圖壓成 WebP。

WebP 是 Google 推的圖片格式,從 developers.google 對 WebP 的介紹 來看,WebP 無損壓縮後的檔案大小能比 PNG 小 26%,而有損壓縮在同樣壓縮品下也能比 JPEG 小 25-34%。

補充:有些情況下跑完 WebP 反而會讓檔案大小變大,例如 JPG 轉 WebP(參見 Image size is increased when converted from jpg to webp with quality value 100

而在 WebP 常見問題文檔的「Can a WebP image grow larger than its source image?」這一小節有說明可能會導致檔案變大的場景,有遇到的朋友可以先確認看看。

因為我的部落格大多是 PNG 圖檔,文檔提到「Note that converting a JPEG source to lossy WebP, or a PNG source to lossless WebP are not prone to such file size surprises.」所以我仍然可以無腦轉 WebP,如果是跟我一樣都是 PNG 的朋友就別擔心了。

在開始動手之前,需要先確保環境中有 WebP 的工具。沒有的朋朋們請先到 Google for Developers 下載回來(為了方便後續使用,可以把 bin 的路徑丟進環境變數)

接著同樣產個小腳本來逐個把圖片壓成 WebP,這段除了好朋友 GPT 以外,還參考了這兩篇:

# 記得先下載 WebP 工具: https://developers.google.com/speed/webp/download

# 1. 設定來源與輸出資料夾
$inputFolder  = "C:\Users\xxx\Pictures\Blog-Image-Backup"
$outputFolder = Join-Path $inputFolder "WebP"

# 2. 建立輸出資料夾(若不存在)
if (-not (Test-Path $outputFolder)) {
    New-Item -Path $outputFolder -ItemType Directory | Out-Null
}

# 3. 定義可轉檔副檔名清單
$convertExts = @('.jpg', '.jpeg', '.png')

# 4. 遞迴取得所有檔案,並排除輸出資料夾本身
Get-ChildItem -Path $inputFolder -File -Recurse |
    Where-Object { $_.FullName -notlike "$outputFolder*"} |
    ForEach-Object {
        $src = $_.FullName
        $ext = $_.Extension.ToLower()

        if ($convertExts -contains $ext) {
            # 5a. 轉檔:jpg/jpeg/png → webp
            $dest = Join-Path $outputFolder ($_.BaseName + ".webp")

            # 使用 -q 指定壓縮品質(0-100, ex: -q 80)
            # 如果需要無損壓縮,可以使用 -lossless 參數
            # 但注意有損的 JPG 之類轉無損 WebP 反而會讓檔案變大哦
            & cwebp -lossless $src -o $dest > $null
            Write-Host "Converted: $src$dest"
        }
        else {
            # 5b. 其餘格式(例如 gif)直接複製
            # 畢竟後續還是都要上傳到新的圖床,不能轉的就直接過去吧
            $dest = Join-Path $outputFolder $_.Name
            Copy-Item -Path $src -Destination $dest -Force
            Write-Host "Copied:    $src$dest"
        }
    }

上傳圖片到 Cloudflare R2

現在我們有一卡車圖檔準備要大舉進攻 Cloudflare R2,按照上面的節奏。聰明的朋朋也許會想:
這傢伙又要叫 GPT 幫我們產腳本來上傳了吧?

但很可惜,我請 GPT 產腳本是因為我很懶,所以有更懶的方法存在時,我是連腳本都不考慮的。

這個步驟最懶的方法就是直接滑鼠點一點,畢竟 Cloudflare R2 在瀏覽器就可以拖檔案上傳了:

1753970510004

雖然瀏覽器上傳有每次一百個檔案的限制,但考慮到我部落格 拖稿嚴重 圖片不多,因此手動上傳也是分分鐘的事情,這個步驟就手動傳一傳收工。

補充:如果檔案太多,或是好想用下指令的方式上傳呢?請參考 R2 文檔的 Upload objects

因為前面的步驟只是下載檔案又上傳檔案,連檔名都沒更動,接著只要回去文章內文把原本有 i.imgur.com 的連結都替代成新的圖片網址就搞定囉~

補充:如果像我一樣,有些 JS 的操作會去呼叫圖片網址的話,可能會看到 CORS 的錯誤訊息,例如:Access to image at 'https://xxxx/xxxxxx.webp' from origin 'https://xxxxxx.github.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

這時候只要去 R2 儲存體的「設定」 找到 CORS 原則,把站台加上去就可以了。

1753970524223

使用 VSCode 的 Markdown-image 延伸模組直接上傳圖片到 Cloudflare R2

到這邊其實部落格的圖床遷移已經完成了,但因為我習慣用 VSCode 撰寫文章,之前都用 vscode-imgur 這款延伸模組,實現貼上圖片自動上傳 Imgur 的舒適體驗。

但現在圖床搬家到 Cloudflare R2 了,當然也要調整一下 VSCode 的上傳圖片工具。果斷採用開頭這篇「Imgur 封鎖台灣 IP,我把圖床搬到 Cloudflare R2」提到的 Markdown-image

另外在設定的過程中還參考了以下兩篇:


首先我們得申請一組權杖,可以參考 Cloudflare 官方文檔的 Authentication 操作,或是從 R2 頁面的右側進入 API 權杖的頁面:

1753970536936

新增一組能夠「物件讀取和寫入」的權杖後,會來到金鑰畫面。注意這組金鑰跟識別碼只會出現一次,請務必記下來,等等設定延伸模組的時候會用到:

1753970546886

建立完之後,進到我們要上傳圖片的貯體,進到設定頁面,這邊的名稱跟 S3 API 資訊等等也會用到:

1753970560756

接著讓我們回到 VSCode,到設定裡面找到 Markdown Image 的相關設定,把上傳圖片的方式改為「S3」(不是 Cloudflare 哦!)

1753970570818

最後就是把剛剛拿到的資訊一個一個填進去設定裡啦~

這邊提供我填的內容給需要的朋朋參考:

  • S3: Endpoint => R2 設定頁面的 S3 API
    • ps: 可以不用放最後面的儲存體名稱,反正等等 Bucket Name 要放
  • S3: Region => 直接填 auto 就好
  • S3: Bucket Name => 儲存體(貯體)名稱,抄 R2 設定頁面的名稱就好
    • ps: 如果 Endpoint 有包含儲存體名稱了,這邊會開成資料夾
  • S3: Access Key ID => 發金鑰時拿到的 存取金鑰識別碼
  • S3: Secret Access Key => 發金鑰時拿到的 秘密存取金鑰
  • S3: Cdn => 我們自定義的 CDN 路徑 + {filepath}"

1753970361767

都搞定之後就可以開個 .md 檔、複製個圖片,試試看 Shift + Alt + V 有沒有自動上傳囉~

小結

從 Imgur 爆炸到搬完圖片也拖了快兩個月,但幸好有蠻多前人遺跡和好夥伴 GPT 的協助,還算搬得順利。秉持著一個「做都做了」的理念,乾脆把圖床搬家過程也拿出來水一篇,喜得部落格文章數 +1

搬完之後想說圖片也壓縮了、R2 也有快取之類的,是不是再來跑一次 PageSpeed Insights,分數意外地比之前高不少,也算是一個意外撿到的驚喜了

1753971347624

參考資料