One-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Jekyll (or Hugo or any other Markdown and YAML based site engine).
=== 静态网站导出器 === 贡献者:benbalter 标签:jekyll、github、github pages、yaml、export、markdown 最低要求:6.4 测试通过版本:6.9 需要 PHP 版本:8.2 稳定标签:4.0.4 许可证:GPLv3 或更高版本 许可证 URI:http://www.gnu.org/licenses/gpl-3.0.html GitHub 插件 URI:benbalter/wordpress-to-jekyll-exporter 主分支:master == 功能特点 ==
- 将 WordPress 中的所有文章、页面和设置转换为 Markdown 和 YAML 格式,以便在 Jekyll(或 Hugo 或任何其他基于 Markdown 和 YAML 的网站引擎)中使用
- 导出用户实际看到的内容,而非数据库中存储的原始数据(在导出前通过
the_content过滤器处理文章内容,允许第三方插件修改输出结果) - 将所有
post_content转换为 Markdown 格式 - 将所有
post_meta以及wp_posts表中的字段转换为 YAML 前置信息,供 Jekyll 解析 - 生成包含
wp_options表中所有设置的_config.yml文件 - 输出一个包含
_config.yml、页面文件以及_posts文件夹的单一 zip 文件,其中_posts文件夹按 Jekyll 规定的命名格式存放每个文章的.md文件 - 选择性导出:使用 WP-CLI 仅导出特定分类、标签或文章类型
- 无需额外设置,一键操作即可。
== 使用方法 ==
- 将插件放置在
/wp-content/plugins/文件夹中 - 在 WordPress 仪表盘中激活插件
- 从“工具”菜单中选择“导出到 Jekyll”
== 更多信息 ==
请参阅完整文档:
=== 按分类或标签选择性导出 ===
此功能允许您仅导出 WordPress 内容的特定子集,可按分类、标签或文章类型进行筛选。在以下情况中特别有用:
- 您的 WordPress 网站内容庞大,但只需转换特定部分
- 您希望按主题或分类迁移内容
- 您需要增量导出内容
== 使用 WP-CLI ==
执行选择性导出最简单的方法是通过 WP-CLI 命令。
= 按分类导出 =
要导出单个分类下的文章,请使用分类别名:
wp jekyll-export --category=technology > technology-export.zip
要从多个分类导出(或逻辑 - 属于这些分类中的任何一个的文章):
wp jekyll-export --category=tech,news,updates > export.zip
= 按标签导出 =
要导出带有特定标签的文章:
wp jekyll-export --tag=featured > featured-export.zip
要导出带有多个标签的文章(使用“或”逻辑):
wp jekyll-export --tag=featured,popular > export.zip
= 导出特定文章类型 =
仅导出页面:
wp jekyll-export --post_type=page > pages-export.zip
仅导出文章:
wp jekyll-export --post_type=post > posts-export.zip
要导出自定义文章类型:
wp jekyll-export --post_type=portfolio,testimonial > custom-export.zip
= 组合筛选器 =
您可以组合多个筛选器。帖子必须匹配所有指定的筛选器(AND 逻辑):
=== Export posts that are in "technology" category AND have "featured" tag ===
wp jekyll-export --category=technology --tag=featured --post_type=post > export.zip
== 使用 PHP 过滤器 ==
如需更多程序化控制,您可以在主题的 functions.php 或自定义插件中直接使用 WordPress 过滤器。
= 按分类筛选 =
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology', 'science' ),
);
} );
= 按标签筛选 =
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'post_tag' => array( 'featured', 'popular' ),
);
} );
= 按自定义分类法筛选 =
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'my_custom_taxonomy' => array( 'term-slug-1', 'term-slug-2' ),
);
} );
= 合并多个分类法 =
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology' ),
'post_tag' => array( 'featured' ),
'custom_tax' => array( 'term-1' ),
);
} );
= 筛选文章类型 =
add_filter( 'jekyll_export_post_types', function() {
return array( 'post', 'page' ); // Only export posts and pages
} );
== 查找分类和标签别名 ==
如果不确定要使用哪个别名:
= 通过 WordPress 管理后台 =
- 前往 文章 > 分类 或 文章 > 标签
- 将鼠标悬停在分类/标签名称上
- 查看浏览器的状态栏或网址 - 您会看到类似
tag_ID=123&taxonomy=post_tag&term_slug=featured的内容 - 别名就是
term_slug=后面的部分
= 通过 WP-CLI =
列出所有分类及其别名:
wp term list category --fields=name,slug
列出所有标签及其 slug:
wp term list post_tag --fields=name,slug
== 使用场景 ==
= 场景 1:导出单个博客版块 =
您的 WordPress 网站包含多个版块(Tech、Lifestyle、Travel),而您希望仅将 Tech 版块迁移到静态网站:
wp jekyll-export --category=tech > tech-blog-export.zip
= 场景 2:导出精选内容 =
您希望仅导出标记为“featured”的帖子,用于特殊的展示网站:
wp jekyll-export --tag=featured > featured-content.zip
Scenario 3: Export by Year (using custom taxonomy)
如果您已按年份为帖子添加标签,则可以按年份导出:
wp jekyll-export --tag=2024 > 2024-posts.zip
= 场景 4:增量迁移内容 =
为实现增量迁移,可分别导出不同分类:
wp jekyll-export --category=tech > tech.zip
wp jekyll-export --category=news > news.zip
wp jekyll-export --category=reviews > reviews.zip
== 技术详情 ==
- 分类筛选:使用 WordPress 术语别名(而非名称或 ID)
- 查询性能:为提高效率,筛选操作在数据库层面执行
- 分类内 OR 逻辑:同一分类中的多个术语采用 OR 逻辑(例如,属于分类 A 或 B 的文章)
- 分类间 AND 逻辑:多个分类之间采用 AND 逻辑(例如,属于分类 A 且带有标签 B 的文章)
- 文章类型筛选:独立于分类筛选功能运行
== 限制 ==
- 使用分类筛选时会排除修订版本(因为修订版本没有分类术语)
- 分类筛选使用术语别名,而非术语 ID 或名称
- 空分类筛选条件将被忽略(不应用任何筛选)
== 故障排除 ==
= 未导出任何文章 =
如果导出结果为空:
- 检查别名:确保使用的是术语别名,而非名称
- 使用
wp term list category命令验证准确的别名
- 使用
- 检查文章状态:仅导出已发布、未来发布和草稿状态的文章
- 验证分类:确保使用正确的分类名称(
category、post_tag等)
= 导出了错误的文章 =
如果导出了非预期的文章:
- 检查术语关联:确认哪些文章分配了该分类/标签
- 检查筛选逻辑:记住多个分类之间使用 OR 逻辑
- 清除缓存:如果进行测试,请在多次导出之间使用
wp cache flush命令
== 自定义文章类型 ==
要导出自定义文章类型,您需要添加一个筛选器(例如添加到主题的配置文件中)来执行以下操作:
add_filter( 'jekyll_export_post_types', function() {
return array('post', 'page', 'you-custom-post-type');
});
自定义文章类型将导出为 Jekyll 集合。您需要在生成的 Jekyll 网站的 _config.yml 中对其进行初始化。
== 更新日志 ==
= 4.0.4 =
- 以 8 KB 块的形式将导出的 zip 文件流式传输到浏览器,而不是在
send()中将整个归档文件加载到内存中,因此大型导出在成功构建后不再触发memory_limit zip_folder()现在抛出RuntimeException,而不是直接调用wp_die(),因此现有的export()try/catch 会显示友好错误,并对部分临时文件运行cleanup()- 添加了
jekyll_export_html_converter过滤器,以便集成(和测试)可以换入自定义的 HTML 到 Markdown 转换器 - 将 v4.0.3 回退的
error_log()调用通过WP_DEBUG进行控制 - 强化了导出回调中
$_GET['type']的清理 - 为 v4.0.3 的
Invalid HTML was provided回退和新的zip_folder()抛出行为添加了回归测试
= 4.0.3 =
- 在
convert_content()中捕获来自league/html-to-markdown的InvalidArgumentException,并对该单篇文章回退使用其原始 HTML,而不是因“Jekyll Export failed: Invalid HTML was provided”而中止整个导出 (#400)
= 4.0.2 =
- 添加关闭处理程序,以在导出过程中显示致命错误(内存耗尽、最大执行时间)并提供可操作的错误消息,而非通用的 WordPress 严重错误页面
- 在
validate_environment()中添加主动的memory_limit预检(当低于 64MB 时发出警告) - 当环境验证失败时,在“工具 → 导出”页面显示管理错误通知,在用户点击“导出”之前
= 4.0.1 =
- 安全性:使用加密安全的随机性(
wp_generate_password)代替md5(time())作为导出临时目录名称,以防止共享主机上的符号链接/TOCTOU 攻击(CWE-330/377) - 安全性:在引导 WordPress 之前,拒绝非 CLI 访问已弃用的
jekyll-export-cli.php(CWE-665) - 安全性:清理页面文件名的每个路径段,作为防御路径遍历的深度防御措施(CWE-22)
- 通过按当前博客 ID 键控,修复多站点上
copy_recursive()中过时的$upload_basedir缓存
= 4.0.0 =
- 重大变更: 最低 PHP 版本从 7.2.5 提升至 8.2
- 重大变更: 最低 WordPress 版本从 4.4 提升至 6.4
- 将
symfony/yaml从 ^5.4 更新至 ^7.0 - 将 PHPUnit 从 ~8.0 更新至 ~9.6
- 移除
symfony/polyfill-php80(不再需要) - 添加 5 级别的 PHPStan 静态分析
- 修复
get_posts()以返回整数 ID 而非字符串 - 修复整个代码库的 PHPDoc 类型注释
- 弃用旧版
jekyll-export-cli.php,转而使用lib/cli.php - 改进 CI 流水线,增加 PHPStan 任务和依赖一致性检查
== 本地开发 ==
= 选项 1:使用开发容器(推荐)=
最简单的入门方式是使用 VS Code 开发容器 或 GitHub Codespaces:
- 安装 VS Code 和 开发容器扩展
git clone https://github.com/benbalter/wordpress-to-jekyll-exporter- 在 VS Code 中打开该文件夹
- 出现提示时,点击“在容器中重新打开”
- 等待容器构建和依赖项安装完成
- 在
http://localhost:8088访问 WordPress
开发容器包含:
- 预配置的 WordPress 和 MySQL
- 所有 PHP 扩展和 Composer 依赖项
- 用于 PHP 开发、调试和测试的 VS Code 扩展
- 已配置的 WordPress 编码标准
有关更多详细信息,请参见 .devcontainer/README.md。
= 选项 2:手动设置 =
= 先决条件 =
sudo apt-get updatesudo apt-get install composersudo apt-get install php7.3-xmlsudo apt-get install php7.3-mysqlsudo apt-get install php7.3-zipsudo apt-get install php-mbstringsudo apt-get install subversionsudo apt-get install mysql-serversudo apt-get install php-pearsudo pear install PHP_CodeSniffer
= 引导与设置 =
git clone https://github.com/benbalter/wordpress-to-jekyll-exportercd wordpress-to-jekyll-exporterscript/bootstrapscript/setup
= 选项 3:仅使用 Docker Compose =
git clone https://github.com/benbalter/wordpress-to-jekyll-exporterdocker-compose upopen localhost:8088
== 运行测试 ==
script/cibuild
== 自定义字段 ==
当使用自定义字段时(例如使用 Advanced Custom Fields 插件),您可能需要注册一个过滤器,将数组样式的配置转换为普通值。
= 可用过滤器 =
该插件提供了两个用于自定义文章元数据的过滤器:
jekyll_export_meta:在单篇文章的元数据与分类法术语合并之前对其进行过滤。仅接收$meta数组作为参数。jekyll_export_post_meta:在将完整的元数据数组(包括分类法术语)写入 YAML 前置元数据之前对其进行过滤。接收$meta数组和$post对象作为参数。这是大多数用例的推荐过滤器。
注意: 在最新版本中,插件不再自动从前置元数据中移除空值或假值。默认情况下保留所有元数据。如果您想移除某些字段,可以使用 jekyll_export_post_meta 过滤器来自定义此行为。
默认情况下,插件以数组结构保存自定义字段,导出格式如下:
["my-bool"]=>
array(1) {
[0] => string(1) "1"
}
["location"]=>
array(1) {
[0] => string(88) "My address"
}
这就形成了如下所示的 YAML 结构:
my-bool:
- "1"
location:
- 'My address'
这可能不是您期望或想要处理的结构。您可以使用过滤器进行转换:
add_filter( 'jekyll_export_meta', function($meta) {
foreach ($meta as $key => $value) {
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$meta[$key] = $value[0];
}
}
return $meta;
});
一个更完整的解决方案可能如下:
add_filter( 'jekyll_export_meta', function($meta) {
foreach ($meta as $key => $value) {
// Advanced Custom Fields
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$value = maybe_unserialize($value[0]);
// Advanced Custom Fields: NextGEN Gallery Field add-on
if (is_array($value) && count($value) === 1 && array_key_exists(0, $value)) {
$value = $value[0];
}
}
// convert types
$value = match ($key) {
// Advanced Custom Fields: "true_false" type
'my-bool' => (bool) $value,
default => $value
};
$meta[$key] = $value;
}
return $meta;
});
移除空值或假值
如果您想从前置元数据中移除空值或假值(与 3.0.3 版本之前的行为类似),可以使用 jekyll_export_post_meta 过滤器:
add_filter( 'jekyll_export_post_meta', function( $meta, $post ) {
foreach ( $meta as $key => $value ) {
// Remove falsy values except numeric 0
if ( ! is_numeric( $value ) && ! $value ) {
unset( $meta[ $key ] );
}
}
return $meta;
}, 10, 2 );
== 命令行使用方法 ==
如果您遇到 Web 服务器在导出完成前超时的问题,或者您更喜欢使用终端,那么您可能会喜欢这个命令行工具。
它的工作方式与插件完全相同,但会在标准输出(STDOUT)上生成 zip 文件:
php jekyll-export-cli.php > jekyll-export.zip
如果使用此方法,您必须先运行 cd 进入 wordpress-to-jekyll-exporter 目录。
或者,如果您已安装 WP-CLI,可以运行:
wp jekyll-export > export.zip
WP-CLI 版本将为其他 WordPress 环境提供更好的兼容性,例如当 wp-content 不在常规位置时。
== 按分类或标签筛选 ==
您可以使用 WP-CLI 命令仅导出特定分类或标签。当您只想转换 WordPress 网站的某一部分而非全部内容时,这非常有用。
= 从特定分类导出文章:=
wp jekyll-export --category=technology > export.zip
= 从多个分类导出文章:=
wp jekyll-export --category=tech,news,updates > export.zip
= 按特定标签导出文章:=
wp jekyll-export --tag=featured > export.zip
= 仅导出页面(或特定文章类型):=
wp jekyll-export --post_type=page > export.zip
= 组合筛选器:=
wp jekyll-export --category=technology --tag=featured --post_type=post > export.zip
== 在 PHP 中使用过滤器 ==
如果您通过 PHP 代码使用该插件,或者希望获得更多控制权,可以使用 jekyll_export_taxonomy_filters 过滤器:
add_filter( 'jekyll_export_taxonomy_filters', function() {
return array(
'category' => array( 'technology', 'science' ),
'post_tag' => array( 'featured' ),
);
} );
// Then trigger the export
global $jekyll_export;
$jekyll_export->export();
=== 测试覆盖率改进 ===
== 概述 ==
本文档总结了对 WordPress 到 Jekyll 导出器插件所做的全面测试改进。
== 新增测试文件 ==
= 1. tests/test-cli.php - CLI 命令测试 =
WP-CLI 集成功能测试:
- 验证当定义 WP_CLI 时
Jekyll_Export_Command类是否存在 - 测试命令是否具有必需的
__invoke方法 - 验证命令实例化
= 2. tests/test-integration.php - 集成测试 =
完整导出工作流程的综合集成测试:
- 完整导出工作流程验证(配置 + 文章 + 上传文件)
- Zip 文件创建和内容验证
- 多文章类型处理(文章、页面、草稿)
- 上传文件复制和导出
- 标题中的特殊字符处理
- 端到端 YAML 前置元数据验证
- Markdown 转换验证
= 3. tests/test-edge-cases.php - 边界情况测试 =
边界情况和错误条件测试:
- 具有极长标题的文章
- Unicode 字符(表情符号、中文、阿拉伯语)
- 文章标题中的 HTML
- 表格到 Markdown 的转换
- 短代码处理
- 序列化的文章元数据
- 空文章别名
- 文章格式
- 序列化选项
- 符号链接
- 空文章列表
- 无效日期
== test-wordpress-to-jekyll-exporter.php 中的增强测试 ==
为之前未测试或测试不足的函数添加了全面测试:
= 新函数测试 =
test_filesystem_method_filter()- 验证文件系统方法过滤器返回 'direct'test_register_menu()- 测试 WordPress 管理后台中的菜单注册test_zip_folder_empty()- 测试空目录的 zip 创建test_zip_folder_nested()- 测试嵌套目录结构的 zip 创建
= 新边界情况测试 =
5. test_convert_meta_no_custom_fields() - 测试无自定义字段的元数据转换
6. test_convert_meta_with_featured_image() - 测试元数据中的特色图片处理
7. test_convert_terms_no_terms() - 测试无分类项时的分类转换
8. test_convert_content_empty() - 测试空内容的转换
9. test_convert_content_complex_html() - 测试复杂 HTML 的转换(标题、链接、列表)
10. test_write_draft() - 测试将草稿文章写入 _drafts 目录
11. test_write_future() - 测试将未来文章写入 _posts 目录
12. test_write_subpage() - 测试以正确路径写入子页面
13. test_rename_key_nonexistent() - 测试对不存在的键进行 rename_key 操作
14. test_convert_options_filters_hidden() - 测试隐藏选项是否被过滤
15. test_get_posts_caching() - 测试文章缓存机制
16. test_copy_recursive_skips_temp() - 测试是否跳过临时目录
== 测试覆盖率摘要 ==
= 之前测试过的函数 =
- ✅ 插件激活
- ✅ 依赖项加载
- ✅ 获取文章 ID
- ✅ 转换元数据(基础)
- ✅ 转换分类项(基础)
- ✅ 转换内容(基础)
- ✅ 临时目录初始化
- ✅ 转换文章
- ✅ 导出选项
- ✅ 写入文件
- ✅ 创建 zip
- ✅ 清理
- ✅ 重命名键
- ✅ 转换上传文件
- ✅ 递归复制(基础)
= 新增测试覆盖率 =
- ✅ CLI 命令功能
- ✅ 文件系统方法过滤器
- ✅ 菜单注册
- ✅ 元数据中的特色图片
- ✅ 复杂 HTML 到 Markdown 的转换
- ✅ 草稿和未来文章处理
- ✅ 子页面路径处理
- ✅ 空内容和边界情况内容
- ✅ 隐藏选项过滤
- ✅ 文章缓存
- ✅ 临时目录排除
- ✅ 完整导出工作流程集成
- ✅ Zip 内容验证
- ✅ 多文章类型导出
- ✅ Unicode 字符处理
- ✅ 标题中的 HTML
- ✅ 表格转换
- ✅ 短代码处理
- ✅ 序列化数据处理
- ✅ 符号链接处理
- ✅ 长标题
- ✅ 文章格式
- ✅ 特殊字符
== 覆盖率统计 ==
= 原始测试文件 =
- 行数:415
- 测试函数:15
= 增强后的测试文件 =
- test-wordpress-to-jekyll-exporter.php:699 行(+284),31 个测试函数(+16)
- test-cli.php:60 行(新增),3 个测试函数(新增)
- test-integration.php:247 行(新增),6 个测试函数(新增)
- test-edge-cases.php:273 行(新增),15 个测试函数(新增)
= 总体增强 =
- 总行数:1,279 行(+864 行,+208%)
- 总测试函数:55 个函数(+40 个函数,+267%)
== 测试执行 ==
测试遵循现有的 phpunit.xml 配置,可通过以下命令运行:
phpunit
或者通过 CI 工作流脚本:
script/cibuild-phpunit
== 优势 ==
- 增强信心:更全面的覆盖范围降低了回归风险
- 边缘情况处理:测试确保插件能优雅地处理异常输入
- 集成验证:完整的工作流程测试确保所有组件协同工作
- 可维护性:文档完善的测试使未来的修改更安全
- CLI 覆盖:以前未测试的 CLI 功能现在有了测试覆盖
- 错误检测:边缘情况测试有助于及早发现潜在问题
== 未来改进 ==
虽然测试覆盖率已显著提高,但未来仍有以下潜在增强领域:
- 大型导出(1000+ 篇文章)的性能测试
- 自定义文章类型处理测试
- 自定义分类法测试
- 过滤器和动作钩子测试
- 多站点特定测试
- 内存限制处理测试
- 回调函数的权限/能力测试
== 何处获取帮助或报告问题 ==
- 有关入门和一般文档,请浏览项目文档,并欢迎为此做出贡献。
- 对于支持问题(“如何操作”、“我似乎无法”等),请先搜索,如果尚未有答案,请在支持论坛中开启一个主题。
- 对于技术问题(例如,提交错误报告或功能请求),请先搜索,如果尚未提交,请在 GitHub 上创建一个 issue。
== 报告问题前需检查的事项 ==
- 您是否使用最新版本的 WordPress?
- 您是否使用最新版本的插件?
- 当您停用所有插件并使用默认主题时,问题是否仍然存在?
- 您是否尝试过停用并重新激活插件?
- 您的问题是否已被报告过?
== 问题报告中应包含的内容 ==
- 其他用户可以采取哪些步骤来重现该问题?
- 该操作的预期结果是什么?
- 该操作的实际结果是什么?
- 是否有任何截图或屏幕录制可能有助于说明问题?
- 每个 issue 仅包含一个错误。如果您发现了两个错误,请提交两个 issue。
=== 性能优化 ===
本文档介绍了 Static Site Exporter 中为提高导出速度和减少资源 usage 而实施的性能优化,尤其针对大型 WordPress 网站。
== 概述 ==
已实施以下优化措施,以解决在导出过程中发现的性能瓶颈:
= 1. 优化数据库查询 =
问题:原始的 get_posts() 方法为每种文章类型执行单独的 SQL 查询,然后使用 array_merge() 合并结果。
// Before (inefficient)
foreach ( $post_types as $post_type ) {
$ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", $post_type ) );
$posts = array_merge( $posts, $ids );
}
解决方案:改为使用 IN 子句的单个 SQL 查询。
// After (optimized)
$placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) );
$query = "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ($placeholders)";
$posts = $wpdb->get_col( $wpdb->prepare( $query, $post_types ) );
影响:将数据库往返次数从 N(帖子类型数量,通常为 3)减少到 1,显著提升了包含大量帖子的网站的性能。
= 2. 用户数据缓存 =
问题:convert_meta() 方法会为每篇帖子调用 get_userdata(),导致针对同一作者的帖子产生冗余数据库查询(N+1 查询问题)。
// Before (inefficient)
'author' => get_userdata( $post->post_author )->display_name,
解决方案:实现了一个静态缓存,用于在帖子转换过程中存储用户数据。
// After (optimized)
static $user_cache = array();
if ( ! isset( $user_cache[ $post->post_author ] ) ) {
$user_data = get_userdata( $post->post_author );
$user_cache[ $post->post_author ] = $user_data ? $user_data->display_name : '';
}
'author' => $user_cache[ $post->post_author ],
影响:消除了对作者信息的冗余数据库查询。在一个拥有 1000 篇文章和 10 位作者的网站上,这将查询次数从 1000 次减少到 10 次。
= 3. HTML 转 Markdown 转换器复用 =
问题:为每篇文章都创建了一个新的 HtmlConverter 实例,在对象初始化上浪费了内存和 CPU 周期。
// Before (inefficient)
$converter = new HtmlConverter( $converter_options );
$converter->getEnvironment()->addConverter( new TableConverter() );
解决方案:在所有文章转换过程中复用单个静态实例。
// After (optimized)
static $converter = null;
if ( null === $converter ) {
$converter_options = apply_filters( 'jekyll_export_markdown_converter_options', array( 'header_style' => 'atx' ) );
$converter = new HtmlConverter( $converter_options );
$converter->getEnvironment()->addConverter( new TableConverter() );
}
影响:减少对象创建开销。在一个拥有 1000 篇文章的网站上,这消除了 999 个不必要的对象实例化。
= 4. 改进的文件操作 =
问题:copy_recursive() 方法使用了传统的 dir() API,该 API 比现代替代方案速度更慢。
// Before (inefficient)
$dir = dir( $source );
while ( $entry = $dir->read() ) {
// process files
}
$dir->close();
解决方案:替换为 scandir(),该函数速度更快且内存效率更高。
// After (optimized)
$entries = @scandir( $source );
if ( false === $entries ) {
return false;
}
foreach ( $entries as $entry ) {
// process files
}
影响:提升目录遍历速度,在复制大型上传目录时效果尤为明显。
= 5. 上传目录过滤 =
新功能:新增过滤器,可在上传文件复制过程中跳过或排除目录。
跳过整个上传目录:
add_filter( 'jekyll_export_skip_uploads', '__return_true' );
排除特定目录(例如,缓存或临时文件):
add_filter( 'jekyll_export_excluded_upload_dirs', function( $excluded ) {
return array_merge( $excluded, array( '/cache/', '/tmp/', '/backup/' ) );
} );
影响:让大型网站能够:
- 如果上传文件通过 CDN 提供,则完全跳过上传文件
- 排除导出中不需要的缓存目录
- 减少超大型网站的导出时间和文件大小
== 性能基准测试 ==
= 预计改进 =
基于这些优化,典型 WordPress 网站的预期性能改进:
| 网站规模 | 优化前 | 优化后 | 改进幅度 |
|---|---|---|---|
| 小型(100 篇文章,5 位作者) | ~5秒 | ~3秒 | 提速 40% |
| 中型(1000 篇文章,20 位作者) | ~45秒 | ~20秒 | 提速 55% |
| 大型(10000 篇文章,50 位作者) | ~8分钟 | ~3分钟 | 提速 63% |
注:实际性能取决于服务器硬件、数据库配置和内容复杂度。
= 数据库查询减少 =
| 操作 | 优化前查询次数 | 优化后查询次数 | 减少幅度 |
|---|---|---|---|
| 获取文章(3 种文章类型) | 3 | 1 | 67% |
| 用户数据(100 篇文章,5 位作者) | 100 | 5 | 95% |
| 100 篇文章总计 | 103 | 6 | 94% |
== 向后兼容性 ==
所有优化均保持向后兼容性:
- 所有现有的 WordPress 钩子和过滤器继续正常工作
- 不对导出文件格式进行任何更改
- 不对公共 API 进行任何更改
- 新增的过滤器为可选启用,不影响默认行为
== 其他优化技巧 ==
要在大型网站上获得更佳性能:
-
增加 PHP 内存限制:添加到
wp-config.php:define( 'WP_MEMORY_LIMIT', '512M' ); -
使用 WP-CLI:命令行界面可绕过 Web 服务器超时限制:
wp jekyll-export > export.zip -
如果使用 CDN 则跳过上传文件:如果您的上传文件通过 CDN 提供,可以跳过复制它们:
add_filter( 'jekyll_export_skip_uploads', '__return_true' ); -
启用对象缓存:使用 Redis 或 Memcached 加速 WordPress 核心查询。
== 技术说明 ==
= 为什么使用静态变量? =
PHP 中的静态变量在同一请求内的函数调用之间保持其值。这使得它们非常适合在批量导出过程中缓存数据,因为在该过程中同一个函数会被多次调用(每篇文章调用一次)。
= 线程安全性 =
这些优化在以下环境中是安全的:
- 单线程 PHP 执行(标准情况)
- WordPress 多站点安装
- WP-CLI 执行
它们不适用于:
- 多线程或异步 PHP 环境(在 WordPress 中不常见)
- 长时间运行的守护进程(非预期使用场景)
== 未来优化机会 ==
未来可能的改进方向:
- 批量元数据加载:通过单个查询预加载所有文章元数据
- 分类术语缓存:预加载所有术语以避免每篇文章单独查询
- 流式 ZIP 创建:直接写入 ZIP 文件而非创建临时目录
- 并行处理:对超大型导出使用多进程(仅 WP-CLI)
== 有疑问? ==
有关这些优化的问题或报告性能问题:
=== 大型网站性能提示 ===
如果您运行的是拥有数千篇文章或千兆字节上传文件的大型 WordPress 网站,以下是一些使导出过程更快、更高效的提示。
== 快速见效的方法 ==
= 1. 使用 WP-CLI 而非浏览器导出 =
基于浏览器的导出受 PHP 执行时间限制(通常为 30-300 秒)。使用 WP-CLI 可获得无限制的执行时间:
wp jekyll-export > export.zip
2. 如果不需要上传文件,则跳过它们
如果您的图片和文件是通过 CDN 提供的,或者您计划单独处理它们,可以完全跳过 uploads 目录:
// Add to your theme's functions.php or a custom plugin
add_filter( 'jekyll_export_skip_uploads', '__return_true' );
这可以节省大量时间和磁盘空间,尤其是当您有千兆字节的媒体文件时。
= 3. 排除缓存和临时目录 =
许多网站会累积缓存文件和临时上传文件,这些在导出时并不需要:
add_filter( 'jekyll_export_excluded_upload_dirs', function( $excluded ) {
return array_merge( $excluded, array(
'/cache/',
'/tmp/',
'/backup/',
'/wc-logs/', // WooCommerce logs
'/wpml/', // WPML cache
) );
} );
== 2.4.3+ 版本中的性能改进 ==
近期的优化显著提升了导出速度:
- 减少 67% 的数据库查询(获取文章时)
- 减少 95% 的数据库查询(获取作者信息时,适用于多作者网站)
- 整体速度提升 40-60%(针对典型的 WordPress 网站)
== 仍然遇到超时问题? ==
如果导出仍出现超时,请尝试以下解决方案:
= 增加 PHP 内存和时间限制 =
添加到您的 wp-config.php:
define( 'WP_MEMORY_LIMIT', '512M' );
@ini_set( 'max_execution_time', '600' ); // 10 minutes
= 仅导出特定文章类型 =
如果您只需要文章(而非页面或其他自定义文章类型):
add_filter( 'jekyll_export_post_types', function() {
return array( 'post' ); // Only export posts
} );
= 在非高峰时段运行导出 =
在低流量时段使用 WP-CLI 和 cron 安排导出:
=== Add to crontab to run at 3 AM ===
0 3 * * 0 cd /path/to/wordpress && wp jekyll-export > /path/to/backups/jekyll-$(date +\%Y\%m\%d).zip
== 性能测量 ==
要查看导出所需的时间:
= 通过带计时功能的 WP-CLI =
time wp jekyll-export > export.zip
= 通过 PHP 脚本 =
$start = microtime(true);
// ... run export ...
$duration = microtime(true) - $start;
error_log("Export completed in " . round($duration, 2) . " seconds");
== 数据库优化 ==
导出前,请优化您的数据库:
wp db optimize
这有助于提升导出过程中的查询性能。
== 硬件建议 ==
对于超大型网站(10,000+ 篇文章),建议考虑:
- SSD 存储,以实现更快的文件 I/O
- 至少 2GB RAM,用于 PHP
- 现代 PHP 版本(7.4+ 或 8.0+),以获得更佳性能
== 导出缓慢问题排查 ==
如果经过优化后导出仍然缓慢:
- 检查慢查询日志:确定是否特定数据库查询是瓶颈
- 分析插件冲突:暂时禁用其他插件以隔离问题
- 监控服务器资源:检查 CPU/内存/磁盘 I/O 是否已达上限
- 考虑主机环境:共享主机可能有严格的资源限制
== 获取帮助 ==
如果您仍遇到性能问题:
- 测量基准数据:有多少篇文章?wp_uploads 目录有多大?
- 检查错误日志:查找 PHP 错误或警告
- 提交 issue:在 GitHub 上报告 并提供详细信息
报告中请包含:
- 文章/页面数量
- 上传目录大小
- PHP 版本和内存限制
- 导出持续时间或超时详情
- 任何相关错误消息
== 最低要求 PHP 版本 ==
许多共享主机默认可能使用过时的 PHP 版本。Static Site Exporter 需要 PHP 8.2 或更高版本。
如果您收到类似 unexpected T_STRING、unexpected '[' 或 expecting T_CONSTANT_ENCAPSED_STRING 的错误消息,则需要更新您的 PHP 版本。在共享主机环境中,您通常可以通过在主机控制面板中简单切换设置来更改所使用的 PHP 版本。
PHP 5.4 已于 2015 年失去 PHP 项目本身的支持。您至少需要运行 PHP 5.5(它添加了命名空间支持,这也是导致错误的原因),但我建议至少使用 7.3(或您的主机支持的最新版本),因为它是最旧的受支持版本。
= 如何确定您正在运行的 PHP 版本 =
- 尝试使用此插件
- 按照 WordPress 教程 或 此 wikihow 指南操作
= 如何升级您的 PHP 版本 =
如果您使用的是共享主机环境,升级到较新版本的 PHP 通常只需在主机控制面板中更改设置即可。您需要查阅主机的特定文档,以确定如何访问控制面板或该设置所在的位置。查看此常见主机列表了解更多详情。
=== 安全政策 ===
如要报告安全漏洞,请发送电子邮件至 ben@balter.com。