对于想要一个简单的个人博客,又对静态站点较为繁琐的发布流程感到厌烦的人来讲,上手简单、功能完备的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部分应当如下所示。

其中的关键部分是Cookie和X-WP-Nonce字段。我们接下来的目标便是自动化获取以上信息。
顺带一提,以下的代码大部分都是找AI搓的。
首先模拟登录。
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样式。
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。
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 ='开头的行")构建请求headers与body。
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请求并输出状态码。
response = requests.post("https://www.hacinsl.top/wp-json/simplystatic/v1/start-export?_locale=user",headers=headers, json=body)
print(response)等待服务器生成完毕。
time.sleep(300)以上操作的完整代码如下:
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脚本。
首先删除上次生成遗留的文件。
rm -R /www/wwwroot/wp/wp-content/uploads/simply-static/运行刚刚编写的Python程序,需要服务器安装Python环境。
/usr/bin/python3 /opt/wp_static.py使用rsync同步本地和远程主机的内容。使用--checksum选项以根据校验值判断文件异同,使用--delete选项以及时删除远程主机上存在的已不再使用的文件,通过密钥登录远程主机以避免输入密码。
/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/以上内容的完整代码如下:
#!/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身份每半小时运行一次上述脚本,即可完成远程静态站点的自动更新。