把 WordPress 官方历史版本保存下来:一次 886 个版本的归档记录

把 WordPress 官方历史版本保存下来:一次 886 个版本的归档记录

最近又折腾了一件比较“老”的事情:把 WordPress 官方提供的历史版本尽可能完整地保存下来。 豆子一直比较喜欢保存一些以前的软件版本。很多软件在当年到处都能下载,但过了几年以后,原来的下载地址可能没了,第三方网站也可能消失。等真正想找的时候,反而不一定还能找到。 WordPress 也是一样。 现在大家安装 WordPress,基本都是直接下载最新版,很少有人会去关心十几年前的版本。不过对豆子来说,这些历史版本本身就是互联网发展过程中的一部分,所以还是想把它们保存下来。

先从官方历史版本页面开始

WordPress 官方其实一直保留着历史版本下载页面: https://wordpress.org/download/releases/ 这个页面基本可以看作 WordPress 官方历史版本档案库,里面能够找到从早期版本一直到现在的正式版本。 不过页面里面除了正式发布版本之外,还能看到 Beta、RC、Alpha、Nightly 以及 MU 等版本。 豆子的需求比较简单:只保存正式发布的 WordPress ZIP 安装包。 所以 Beta、RC、Alpha、Nightly、MU 这些版本就不考虑了。

为什么没有一个一个手动下载

最开始其实也想过手动下载。 但是看了一下版本数量以后就放弃了。 WordPress 从早期版本一直发展到现在,正式版本加起来已经是几百个。如果一个一个点下载,再一个一个建立目录保存,实在太麻烦了。 既然官方页面本身就有完整的下载链接,那就干脆让程序自己把链接找出来,然后自动下载。 豆子的要求也没有那么复杂:
  • 只要正式版本
  • 只下载 ZIP
  • 按照 1.X、2.X、3.X 这样的大版本分类
  • 保持官方原始文件名
  • 已经下载过的文件不要重复下载
  • 下载失败以后可以重新运行程序继续下载
这样基本就够用了。

先统计一下到底有多少正式版本

在真正开始下载之前,豆子先写了一个简单的 PowerShell 正则,把官方页面中的 WordPress ZIP 文件名提取出来。 核心匹配规则其实很简单:
$pattern = 'wordpress-([0-9]+(?:\.[0-9]+)*(?:-(?:platinum|gold))?)\.zip'
这里特意保留了 -platinum-gold 这种历史版本名称。 例如:
wordpress-1.0-platinum.zip
wordpress-1.2.zip
wordpress-2.0.zip
wordpress-4.9.26.zip
wordpress-6.8.2.zip
wordpress-7.1.zip
然后按照主版本号进行统计。

最终统计结果

实际从 WordPress 官方历史版本页面中统计出来,一共有: 886 个正式版本 ZIP 文件。 按照大版本分类以后是:
版本 数量
1.X 9
2.X 41
3.X 150
4.X 357
5.X 217
6.X 105
7.X 6
合计 886
看到这个数量的时候还是有点意思的。 尤其是 4.X,一个大版本就有 357 个正式 ZIP,确实不少。 当然,这里统计的是正式版本,并不是 WordPress 历史上所有曾经出现过的开发版本。

有些版本中间没有连续编号

在统计的时候还发现了一些版本号并不是完全连续的情况。 例如:
3.9.37
3.9.39
中间没有 3.9.38。 还有:
5.0.4
5.0.6
中间没有 5.0.5。 6.5 这一系列也存在类似情况。 刚开始还以为是自己的程序漏掉了版本,后来重新检查官方页面,确认这些确实就是官方当前保存的正式发布版本。 所以这种情况不用自己补,也没有必要为了“连续编号”去找第三方文件。 既然目标是做官方历史版本归档,那就按照官方实际存在的文件保存即可。

目录结构也尽量简单

豆子没有按照每一个小版本再建立目录,而是直接按照大版本分类。 最终目录结构就是这样:
WordPress 官方历史版本/
├── 1.X/
├── 2.X/
├── 3.X/
├── 4.X/
├── 5.X/
├── 6.X/
└── 7.X/
例如:
WordPress 官方历史版本/
├── 1.X/
│   ├── wordpress-1.0-platinum.zip
│   ├── wordpress-1.2.zip
│   └── ...
│
├── 2.X/
│   ├── wordpress-2.0.zip
│   ├── wordpress-2.0.1.zip
│   └── ...
│
├── 4.X/
│   ├── wordpress-4.9.zip
│   ├── wordpress-4.9.1.zip
│   └── ...
│
└── 7.X/
    ├── wordpress-7.0.zip
    └── wordpress-7.1.zip
这样以后自己找版本的时候也比较直观。

为什么没有继续使用 Cloudflare Worker 下载

其实最开始豆子是考虑过直接使用 Cloudflare Worker 来做的。 Worker 本身也确实可以访问 WordPress 官方下载地址,然后把文件下载下来,再进一步处理。 但是实际测试以后发现一个问题:前面十几个文件通常没什么问题,连续下载一段时间以后,就开始出现 Network Error。 这让豆子感觉 Worker 并不太适合拿来做这种连续、大量的历史文件下载任务。 毕竟这不是下载一个文件,而是一次要处理几百个 ZIP。 而且豆子的目标只是把文件保存下来,并不是一定要把所有事情都塞进 Cloudflare Worker。 所以后来干脆换了一个最简单的办法。

最后改成 Windows + curl 下载

Windows 现在本身就可以直接使用 curl.exe,不用为了这个小任务再安装 Python,也不用额外安装什么下载工具。 实际测试以后,直接从 Windows 下载反而非常稳定。 下载地址统一使用 WordPress 官方的:
https://downloads.wordpress.org/release/
例如:
https://downloads.wordpress.org/release/wordpress-6.8.2.zip
所以最后的下载流程就变得非常简单:
  1. 读取 WordPress 官方历史版本页面
  2. 自动找出正式版本 ZIP
  3. 提取版本号
  4. 根据主版本号决定保存到哪个目录
  5. 使用 curl.exe 下载
  6. 已经存在的文件直接跳过
  7. 下载失败自动重试
  8. 最后统计成功、跳过和失败数量

下载程序

为了以后重复使用,豆子把程序拆成了两个文件。 一个是启动程序:
WordPress官方历史版本下载.bat
另外一个是真正负责工作的 PowerShell: BAT 文件本身非常简单,主要就是调用 PowerShell:
@echo off
chcp 65001 >nul

title WordPress Official Release Archive Downloader

echo.
echo ============================================================
echo       WordPress Official Release Archive Downloader
echo ============================================================
echo.

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0WordPress官方历史版本下载.ps1"

echo.
echo ============================================================
echo                         Finished
echo ============================================================
echo.

pause
WordPress官方历史版本下载.ps1

# ============================================================
# WordPress Official Historical Release Downloader
# ============================================================
# Source:
# https://wordpress.org/download/releases/
#
# Rules:
# - Official formal releases only
# - ZIP only
# - Exclude Beta / RC / Alpha / Nightly / MU
# - Archive 1.X - 7.X
# - Skip existing files
# - Retry failed downloads
# - Download directly from downloads.wordpress.org
# ============================================================

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8

$ErrorActionPreference = "Stop"

$BaseDir = Split-Path -Parent $MyInvocation.MyCommand.Path

$Root = Join-Path $BaseDir "WordPress 官方历史版本"

$ArchiveUrl = "https://wordpress.org/download/releases/"

Write-Host ""
Write-Host "============================================================"
Write-Host " WordPress Official Historical Release Downloader"
Write-Host "============================================================"
Write-Host ""

# ------------------------------------------------------------
# Create root directory
# ------------------------------------------------------------

if (!(Test-Path -LiteralPath $Root)) {

    New-Item `
        -ItemType Directory `
        -Path $Root `
        -Force | Out-Null
}

# ------------------------------------------------------------
# Create version directories
# ------------------------------------------------------------

for ($major = 1; $major -le 7; $major++) {

    $dir = Join-Path $Root ("{0}.X" -f $major)

    if (!(Test-Path -LiteralPath $dir)) {

        New-Item `
            -ItemType Directory `
            -Path $dir `
            -Force | Out-Null
    }
}

# ------------------------------------------------------------
# Read official archive
# ------------------------------------------------------------

Write-Host "[1] Reading official release archive..."
Write-Host ""

try {

    $response = Invoke-WebRequest `
        -Uri $ArchiveUrl `
        -UseBasicParsing `
        -TimeoutSec 60

    $html = $response.Content
}
catch {

    Write-Host ""
    Write-Host "[FAILED] Cannot read official archive."
    Write-Host $_.Exception.Message
    Write-Host ""

    pause
    exit 1
}

Write-Host "[OK] Official archive loaded."
Write-Host ""

# ------------------------------------------------------------
# Extract release ZIP filenames
#
# Important:
# Match filename instead of complete URL.
# This is the parser already verified successfully.
# ------------------------------------------------------------

$pattern = 'wordpress-([0-9]+(?:\.[0-9]+)*(?:-(?:platinum|gold))?)\.zip'

$matches = [regex]::Matches(
    $html,
    $pattern,
    [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
)

$versions = @{}

foreach ($m in $matches) {

    $v = $m.Groups[1].Value.Trim()

    # Normal formal release
    if ($v -match '^\d+(?:\.\d+)*$') {

        $versions[$v] = $true

        continue
    }

    # Historical formal release
    if ($v -match '^\d+\.\d+-(?:platinum|gold)$') {

        $versions[$v] = $true
    }
}

# ------------------------------------------------------------
# Sort versions numerically
# ------------------------------------------------------------

$sortedVersions = $versions.Keys | Sort-Object {

    $n = $_ `
        -replace '-platinum$','' `
        -replace '-gold$',''

    [version]$n
}

$total = $sortedVersions.Count

Write-Host "============================================================"
Write-Host " Detection Result"
Write-Host "============================================================"
Write-Host ""

Write-Host ("Official release ZIP count: {0}" -f $total)

Write-Host ""

# ------------------------------------------------------------
# Statistics
# ------------------------------------------------------------

for ($major = 1; $major -le 7; $major++) {

    $count = @(
        $sortedVersions |
        Where-Object {
            $_ -match "^$major\."
        }
    ).Count

    Write-Host ("  {0}.X : {1}" -f $major, $count)
}

Write-Host ""

# ------------------------------------------------------------
# Download statistics
# ------------------------------------------------------------

$downloaded = 0
$skipped = 0
$failed = 0

$index = 0

Write-Host "============================================================"
Write-Host " Starting download"
Write-Host "============================================================"
Write-Host ""

# ------------------------------------------------------------
# Download each release
# ------------------------------------------------------------

foreach ($version in $sortedVersions) {

    $index++

    # Determine major version
    if ($version -notmatch '^(\d+)\.') {

        continue
    }

    $major = [int]$Matches[1]

    # Only archive 1.X - 7.X
    if ($major -lt 1 -or $major -gt 7) {

        continue
    }

    $folder = Join-Path `
        $Root `
        ("{0}.X" -f $major)

    $file = "wordpress-$version.zip"

    $target = Join-Path `
        $folder `
        $file

    $url = "https://downloads.wordpress.org/release/$file"

    Write-Host ""
    Write-Host "------------------------------------------------------------"
    Write-Host ("[{0}/{1}] {2}" -f $index, $total, $file)
    Write-Host "------------------------------------------------------------"

    # --------------------------------------------------------
    # Existing file
    # --------------------------------------------------------

    if (Test-Path -LiteralPath $target) {

        try {

            $size = (
                Get-Item -LiteralPath $target
            ).Length

            if ($size -gt 1024) {

                Write-Host "[SKIP] File already exists."
                $skipped++

                continue
            }

            Remove-Item `
                -LiteralPath $target `
                -Force `
                -ErrorAction SilentlyContinue
        }
        catch {

            Remove-Item `
                -LiteralPath $target `
                -Force `
                -ErrorAction SilentlyContinue
        }
    }

    # --------------------------------------------------------
    # Download
    # --------------------------------------------------------

    $success = $false

    for ($try = 1; $try -le 4; $try++) {

        Write-Host ("Download attempt {0}/4..." -f $try)

        try {

            & curl.exe `
                -L `
                --fail `
                --retry 2 `
                --retry-delay 5 `
                --connect-timeout 30 `
                --max-time 900 `
                --output "$target" `
                "$url"

            if (
                $LASTEXITCODE -eq 0 `
                -and (Test-Path -LiteralPath $target)
            ) {

                $size = (
                    Get-Item -LiteralPath $target
                ).Length

                if ($size -gt 1024) {

                    $success = $true

                    break
                }
            }
        }
        catch {

            # Retry below
        }

        Remove-Item `
            -LiteralPath $target `
            -Force `
            -ErrorAction SilentlyContinue

        if ($try -lt 4) {

            Write-Host "Waiting 5 seconds before retry..."

            Start-Sleep -Seconds 5
        }
    }

    # --------------------------------------------------------
    # Result
    # --------------------------------------------------------

    if ($success) {

        Write-Host "[OK] Download completed."

        $downloaded++
    }
    else {

        Write-Host "[FAILED] Download failed."

        $failed++
    }

    # --------------------------------------------------------
    # Delay between files
    # --------------------------------------------------------

    Start-Sleep -Seconds 5
}

# ------------------------------------------------------------
# Final report
# ------------------------------------------------------------

Write-Host ""
Write-Host "============================================================"
Write-Host " Download Finished"
Write-Host "============================================================"
Write-Host ""

Write-Host ("Official releases : {0}" -f $total)
Write-Host ("Downloaded        : {0}" -f $downloaded)
Write-Host ("Already existed   : {0}" -f $skipped)
Write-Host ("Failed            : {0}" -f $failed)

Write-Host ""
Write-Host "Archive directory:"
Write-Host $Root

Write-Host ""
Write-Host "Run this program again to retry failed or missing files."
Write-Host "Existing files will automatically be skipped."

Write-Host ""
Write-Host "============================================================"

pause
以后需要继续下载的时候,直接双击 BAT 就可以。 PowerShell 程序会自动检查哪些文件已经存在。 所以即使中途关掉电脑,或者某几个版本下载失败了,下一次重新运行也不用从头开始。

实际运行情况

目前实际测试下来,Windows 直接下载还是比较稳定的。 早期版本的 ZIP 文件非常小,所以下载速度很快。 运行过程中已经连续下载了几十个版本,没有出现错误。 当然,越往后面的版本文件越大,整个任务所需要的时间也会明显增加。 豆子在程序里给每个文件之间增加了几秒钟的间隔,并且单个文件支持多次重试。 这样做主要也是为了稳一点。 反正这些文件又不是着急马上用,慢一点没关系,能够完整保存下来就行。

为什么豆子还是想把这些老版本保存下来

其实豆子并没有打算把这些几百个 WordPress 版本全部重新安装一遍。 保存它们的意义更多还是在于留档。 以前很多软件都是这样。 在软件最流行的时候,到处都有下载地址,根本不会觉得某个版本有什么稀奇。 但是十几年以后,情况就完全不一样了。 官方网站可能已经不再提供下载,原来的第三方下载站可能关闭,论坛附件也可能失效。 到了那个时候,再想找一个十几年前的软件版本,往往比想象中困难得多。 所以看到官方还保留着完整的历史版本,豆子觉得还是值得顺手保存一份。

以后再慢慢整理

目前豆子的想法也很简单。 先把这些官方 ZIP 全部保存下来,放在本地,然后再同步到 Google Drive。 至于以后要不要进一步做版本索引、自动校验文件、建立网页档案之类的东西,暂时都不考虑。 先把东西留住。 整理其实只是顺手的事情。 对于这种历史软件归档来说,豆子觉得最重要的还是这一点:趁现在还能找到,就先保存一份。 以后哪天真的需要某个老版本的时候,至少不用再满互联网到处找。 而且这些东西本身也是 WordPress 发展历史的一部分。 从早期的 1.X,一直到现在的 7.X,能够把这么多年的版本完整地放在自己的硬盘和网盘里,看着其实也挺有意思。 这次就先把 WordPress 的历史版本保存下来。 以后有时间,再继续整理其他以前折腾过的软件。

关键词: 软件收藏 , 历史版本 , PowerShell , WordPress

关于这两天博客发的测试文章事宜

这两天因为豆子宝贝才出院在家里,豆子觉得压力大的时候就想折腾一下博客,然后分散下生活的苦.所以这两天空暇的时候写了一个同步插件,为了测试就发了几篇测试日志,看备份站的同步是否执行.昨天基本上已经测试完成,所以把测试文章删除处理了. 这几天测试了两个同步插件,同步备份到wordpress和sablog,目前测试结果ok.算是一个小小的欣慰吧.

关键词: 远程同步 , 同步

DJ EmailPublish 1.8.1 测试

This is a test for email publish log. 测试日志功能。

Blogger 博客同步测试

同步测试发送到blogger博客.

关键词: 远程同步 , 同步 , blogger

太难了

2025年的8月31日,豆子宝贝凌晨1点半突发脑出血,导致右侧身体偏瘫,在新桥医院住院1个月后转入康复医院康复4个多月,目前语言和右侧肢体还是无法正常活动。也是从豆子宝贝生病以后,豆子这几个月都在医院陪伴,没有办法上班。临近春节,由于康复医院医生休息,所以也暂时出院回家休养,等节后再考虑去留问题。如果不是家里人和朋友接济,豆子都不知道怎么生活下去。真的太难了! 博客也会暂时停更了。

关键词: 脑出血 , 偏瘫

Free.fr 貌似再次更新改版(再次检测同步)

今天无意中登录free.fr,发现改版了,就闲着没事把新版本的翻译搞了一下。 主号: 子号: 闲着没事,就随便折腾了一下。仅供参考。 Sync Update Test 2

关键词: 翻译 , free.fr

挣扎着活着,但我没有停下

“路漫漫其修远兮,吾将上下而求索。” —— 屈原 不是没想过放弃, 只是放弃太容易, 而活着, 需要更多勇气

【引言】

这不是一篇鸡汤,也不是一封自怜的长信。只是写给此刻还在坚持、在黑暗中寻找微光的自己。或许很久没有更新,是因为连表达的力气都失去了。而今天,豆子终于坐下来,写点什么。不是为了说服谁,也不是为了倾诉给谁,只是想对自己说一声: 还在努力活着,已经很不容易。

【正文】

确实,好久没有心情更新博客了。过去这几个月,发生了太多太多的事情,让豆子至今都提不起精神来写点什么。或许,在可见的未来,也不会有什么更新了吧。 大家常说“随遇而安”,可豆子既安不了,也不能安。现在,只是想尽力地活下去,仅此而已。有时候,豆子也清楚自己的情绪偏向负面,可那些挥之不去的念头,总是在深夜悄悄涌上心头。那一堆数据和数字,豆子已经懒得再去管了。此刻,只想先把眼前的每一天过下去,哪怕只是挣扎着前行,也许这就是希望的开始。 可说这些的时候,豆子自己也未必真的确定。未来会走向何方,眼前是一团迷雾,看不清方向。既不愿随波逐流,却又无力改变现状。尤其是人到中年,身体的每况愈下,内心的焦虑不安,再加上被现实一遍遍碾压的疼痛,让人愈发迷茫。曾有人说:活在当下,过好眼前最重要。毕竟,未来充满不确定。 正因如此,豆子更不愿让亲人重复自己的路。这一切,也许可以用两个字来概括:挣扎。

【内心独白 · 诗】

《只想努力地活下去》 有时候,我连呼吸都觉得沉重, 像被无形的手按住了胸口, 但我知道,那不是病, 是生活的重量。 人到中年,身体开始悄悄背叛, 精神却还在负重前行。 我不想随波逐流, 可水太深,岸太远。 我想看清前方的路, 可眼里满是生活的烟尘。 数字成了一堆麻木的符号, 时间像漏沙,一点点滑走。 我对自己说,先活下去, 哪怕只是挣扎着活。 不是没想过放弃, 只是放弃太容易, 而活着, 需要更多勇气。 我不知道希望在哪里, 可我知道, 有些人, 值得我继续走下去。

【结语】

也许,有人会看到这些文字; 也许,没有人会看见。 但它真实地存在过,是某个夜晚的我, 与自己的一次静静交谈。 如果未来的豆子再回来看到这一页, 请记得:你曾在最黯淡的时刻,也没完全熄灭过心里的光。

关键词: 负重前行 , 生活的重量 , 放弃 , 挣扎 , 勇气

一则声明

本以为之前eu.org被盗一事就算是这样过去了,没想到居然还有后续。无意中发现,盗号狗在盗号以后,留下的单页上居然留的豆子的邮件地址。真的是怪事年年有,今年特别多。真不知道,豆子是怎么招惹到了这只盗号狗。 更让豆子不可思议的是,豆子其中一个申请过za.net免费米的邮箱也被盗,关键的问题是,那个邮箱豆子还正常登录着,居然都被盗号狗偷走了,还把豆子的免费米给删除,然后把邮箱账号也删除了,豆子真的是哭笑不得。这几个免费米自从当年做了Proxy以后,豆子就没正儿八经的用过,也就是偶尔拿来测试下东西而已,居然没想到会有人看的上它。真的是奇葩他妈给奇葩开门----奇葩到家了! 豆子声明:本人未在其他任何地方留下自己邮箱账号,也从未偷盗别人账号,所有非豆子名下网站预留的豆子邮箱,豆子概不负责。那些(只)盗号狗真的是用心险恶!属于那种有娘生没娘养的。

关键词: 被盗 , 盗号狗 , za.net , eu.org