WordPress全站静态化

对于想要一个简单的个人博客,又对静态站点较为繁琐的发布流程感到厌烦的人来讲,上手简单、功能完备的WordPress也许是一个不错的选择。

然而,搭建一个动态站点需要PHP和数据库环境,这对于内存和性能捉襟见肘的廉价云主机而言并不现实。何况在公网环境下,动态站点所面临的安全风险更是显著大于只有静态文件的网站。

那么,既然个人博客的一切操作都只由自己完成,为什么不能让WordPress运行在本地,定期生成静态站点交由远程主机对外访问呢?本文就是要探讨一种将WordPress全站静态化的实现方式。

而且,不花钱。


材料准备

  • 一台具备公网访问能力的廉价主机
  • 一台搭建了WordPress站点的电脑(本文示例安装Linux系统)

正式开始

首先,我们要解决如何生成静态站点的问题。

Simply Static可以帮助我们便捷地生成静态文件。安装并启用该插件,在插件页面-SETTINGS-General调整相关配置,然后点击Gernerate即可。

我个人的习惯是将/wp-content/uploads/目录加入Additional Files and Directories,以便显示一些媒体文件和额外脚本。

需要注意的是,Simply Static插件存储临时文件和静态站点的目录在/uploads下,如果将uploads目录加入额外文件,会导致更新静态站点时额外包含我们不希望出现的文件。下文进行自动化实现时将会采取额外操作以避免此问题。

在生成完成后,理论上/www/wwwroot/wp/wp-content/uploads/simply-static/temp-files下就会出现静态站点的内容。其中的目录里是文件形式的网站内容,同时还会生成前者的压缩形式。进行同步时按需进行选择。

接下来要搞定自动同步。我们没必要要求自己的静态站点实时更新,所以只需定期生成并同步即可。然而,免费版的Simply Static并不支持自动生成,我们需要想办法让「用户点击Generate按钮」的操作自动化。

正常情况下,当我们点击Generate按钮后,浏览器会向https://your.site/wp-json/simplystatic/v1/start-export?_locale=user

发送一个POST请求,以此触发服务器的生成操作。

这样的POST请求当然需要鉴权,通过调试工具可知,该请求的headers部分应当如下所示。

其中的关键部分是CookieX-WP-Nonce字段。我们接下来的目标便是自动化获取以上信息。

顺带一提,以下的代码大部分都是找AI搓的。

首先模拟登录。

Python
session = requests.Session()
login_url = "https://www.hacinsl.top/wp-login.php"

login_data = {
    "log": "username",
    "pwd": "password",
    "wp-submit": "登录"
}
session.post(login_url, data=login_data)

从中提取cookie并转换为JSON样式。

Python
def process_cookies(cookie_text):
    pattern = r'<Cookie (\w+)=([^ ]+) for'
    matches = re.findall(pattern, cookie_text)
    
    cookies = [f"{name}={value}" for name, value in matches]
    result = "; ".join(cookies)
    
    return result
auth_cookies = process_cookies(str(session.cookies)[20:-1])

模拟访问管理页面并解析JSON以获取WP-Nonce

Python
admin_url = "https://www.hacinsl.top/wp-admin/"
admin_page = session.get(admin_url)

match = re.search(r'var wpApiSettings\s*=\s*({.*?});', admin_page.text, re.DOTALL)
if match:
    json_str = match.group(1)
    print("找到JSON部分:", json_str)
    
    try:
        data = json.loads(json_str)
        nonce = data.get('nonce')
        print(f"nonce的值是:{nonce} ")
    except json.JSONDecodeError:
        print("JSON解析失败")
else:
    print("没有找到以'var wpApiSettings ='开头的行")

构建请求headersbody

Python
headers = {
    "Host":"www.hacinsl.top",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
    "Referer": "https://www.hacinsl.top/wp-admin/admin.php?page=simply-static-generate",
    "Content-Type": "application/json",
    "Cookie": auth_cookies,
    "Connection": "keep-alive",
    "X-WP-Nonce":nonce
}

body = {
    "blog_id":"1",
    "type":"update"
    }

发送POST请求并输出状态码。

Python
response = requests.post("https://www.hacinsl.top/wp-json/simplystatic/v1/start-export?_locale=user",headers=headers, json=body)

print(response)

等待服务器生成完毕。

Python
time.sleep(300)

以上操作的完整代码如下:

Python
import requests
import json
import re
import time


def process_cookies(cookie_text):
    pattern = r'<Cookie (\w+)=([^ ]+) for'
    matches = re.findall(pattern, cookie_text)
    
    cookies = [f"{name}={value}" for name, value in matches]
    result = "; ".join(cookies)
    
    return result

session = requests.Session()
login_url = "https://www.hacinsl.top/wp-login.php"
admin_url = "https://www.hacinsl.top/wp-admin/"

login_data = {
    "log": "username",
    "pwd": "password",
    "wp-submit": "登录"
}
session.post(login_url, data=login_data)

auth_cookies = process_cookies(str(session.cookies)[20:-1])
print(session.cookies)
print(auth_cookies)


admin_page = session.get(admin_url)

match = re.search(r'var wpApiSettings\s*=\s*({.*?});', admin_page.text, re.DOTALL)
if match:
    json_str = match.group(1)
    print("找到JSON部分:", json_str)
    
    try:
        data = json.loads(json_str)
        nonce = data.get('nonce')
        print(f"nonce的值是:{nonce} ")
    except json.JSONDecodeError:
        print("JSON解析失败")
else:
    print("没有找到以'var wpApiSettings ='开头的行")

headers = {
    "Host":"www.hacinsl.top",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
    "Referer": "https://www.hacinsl.top/wp-admin/admin.php?page=simply-static-generate",
    "Content-Type": "application/json",
    "Cookie": auth_cookies,
    "Connection": "keep-alive",
    "X-WP-Nonce":nonce
}

body = {
    "blog_id":"1",
    "type":"update"
    }

response = requests.post("https://www.hacinsl.top/wp-json/simplystatic/v1/start-export?_locale=user",headers=headers, json=body)

print(response)

time.sleep(300)

接下来搞定文件同步的部分,使用shell脚本。

首先删除上次生成遗留的文件。

Bash
rm -R /www/wwwroot/wp/wp-content/uploads/simply-static/

运行刚刚编写的Python程序,需要服务器安装Python环境。

Bash
/usr/bin/python3 /opt/wp_static.py

使用rsync同步本地和远程主机的内容。使用--checksum选项以根据校验值判断文件异同,使用--delete选项以及时删除远程主机上存在的已不再使用的文件,通过密钥登录远程主机以避免输入密码。

Bash
/usr/bin/rsync -avz --checksum --delete -e "ssh -i /home/username/.ssh/id_rsa" /www/wwwroot/wp/wp-content/uploads/simply-static/temp-files/simply-static-1-*/ hacinsl@misaka.hacinsl.top:/www/wwwroot/wp/

以上内容的完整代码如下:

Bash
#!/bin/bash
rm -R /www/wwwroot/wp/wp-content/uploads/simply-static/
/usr/bin/python3 /opt/wp_static.py
/usr/bin/rsync -avz --checksum --delete -e "ssh -i /home/hacinsl/.ssh/id_rsa" /www/wwwroot/wp/wp-content/uploads/simply-static/temp-files/simply-static-1-*/ hacinsl@misaka.hacinsl.top:/www/wwwroot/wp/
echo "Done."

接下来设置一个定时任务,以root身份每半小时运行一次上述脚本,即可完成远程静态站点的自动更新。