搜索全站
报错排查 16 分钟阅读

WordPress 内容导入导出实战:大文件 WXR 分片导入与失败处理

导出 200MB 的 WXR 文件导不进去,卡在超时或内存耗尽。本文讲清 WXR 格式结构、导入失败的五个真实原因、分片切割导入的实操命令、附件拉取失败的补救,以及导入后的收尾检查清单。

U
UFO_PF 2017-10-21 · 阅读 16 分钟
Wo 报错排查
阅读提示
本文约 6,336 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:导出了 180MB,却怎么也导不进去

从一个老站导出全部内容,得到 180MB 的 XML 文件。在目标站用 WordPress Importer 上传,结果:要么上传就失败(受 upload_max_filesize 限制),要么上传成功但导入时超时,进度条卡在某个百分比,最后失败;即便勉强导入完,图片全是外链,文章里的作者全变成 admin。

这是内容迁移里最常见的一类问题。它的难点不在”导出”,而在大文件的传输与解析。

原理分析:WXR 是什么,导入时发生了什么

WXR(WordPress eXtended RSS)是 WordPress 自定义的 RSS 扩展格式,本质就是一个 XML 文件:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:wp="http://wordpress.org/export/1.2/">
<channel>
    <title>站点名</title>
    <link>https://old.com</link>
    <wp:wxr_version>1.2</wp:wxr_version>
    <item>
        <title>文章标题</title>
        <link>https://old.com/2026/01/post/</link>
        <pubDate>Mon, 01 Jan 2026 00:00:00 +0000</pubDate>
        <dc:creator><![CDATA[admin]]></dc:creator>
        <content:encoded><![CDATA[<p>正文</p>]]></content:encoded>
        <wp:post_id>123</wp:post_id>
        <wp:post_type>post</wp:post_type>
        <wp:status>publish</wp:status>
        <category domain="category" nicename="seo"><![CDATA[SEO]]></category>
        <wp:postmeta>
            <wp:meta_key>_thumbnail_id</wp:meta_key>
            <wp:meta_value><![CDATA[456]]></wp:meta_value>
        </wp:postmeta>
    </item>
</channel>
</rss>

导入过程(WordPress Importer 插件)做的事:

  1. 解析 XML:用的是 PHP 的 XMLReader/正则流式解析,不是一次性 simplexml_load_file——所以理论上支持大文件,但解析耗时仍受 PHP 执行时间限制;
  2. 逐条 wp_insert_post():每篇文章一次插入 + 若干 meta 插入,这是慢的根源;
  3. 下载附件:如果勾了”下载并导入附件”,每篇文章里的每张图都要 wp_remote_get 从老站下载——这一步最慢、最容易超时;
  4. 建立作者映射:老站作者要映射到新站用户。

瓶颈排序:附件下载 > XML 解析 > 数据库插入。

保姆级实操

第一步:导出(源站)

后台 工具 → 导出 → 选”所有内容” → 下载。

命令行(更可控,支持按类型):

# 全部
wp export --dir=/tmp/ --filename_format=all.xml

# 只要文章
wp export --dir=/tmp/ --post_type=post --filename_format=posts.xml

# 只要某个日期之后
wp export --dir=/tmp/ --start_date=2026-01-01 --post_type=post

大站建议按年份分批导出,天然分片:

for y in 2024 2025 2026; do
  wp export --dir=/tmp/exp/ --post_type=post \
    --start_date=${y}-01-01 --end_date=${y}-12-31 \
    --filename_format="posts-$y.xml"
done

第二步:放宽目标站的上传与执行限制

这是必须做的前置动作:

; php.ini
upload_max_filesize = 256M
post_max_size       = 256M
max_execution_time  = 600
memory_limit        = 512M
max_input_vars      = 5000

Nginx:

client_max_body_size 256m;
fastcgi_read_timeout 600;

Apache(.htaccess):

php_value upload_max_filesize 256M
php_value post_max_size 256M
php_value max_execution_time 600
php_value memory_limit 512M

改完确认:

wp eval 'echo ini_get("upload_max_filesize"), " / ", ini_get("max_execution_time"), "\n";'

第三步:切割 WXR(超过 50MB 就建议切)

用 Python 按 <item> 切分:

import re, os, sys

src = sys.argv[1]
per = int(sys.argv[2]) if len(sys.argv) > 2 else 200   # 每个文件多少篇

with open(src, encoding='utf-8') as f:
    xml = f.read()

head_end = xml.index('</channel>')
head = xml[:xml.index('<item>')]
tail = xml[head_end:]

items = re.findall(r'<item>.*?</item>', xml, re.S)
print('total items:', len(items))

os.makedirs('split', exist_ok=True)
for i in range(0, len(items), per):
    chunk = items[i:i + per]
    out = f'split/part-{i // per + 1:03d}.xml'
    with open(out, 'w', encoding='utf-8') as f:
        f.write(head + '\n' + '\n'.join(chunk) + '\n' + tail)
    print(out, len(chunk))

运行:

python split_wxr.py all.xml 200

得到若干 10MB 以内的小文件,逐个导入。

第四步:导入

装插件 WordPress Importer(工具 → 导入 → WordPress → 安装),逐个上传分片。

命令行方式(更快,跳过浏览器超时):

wp plugin install wordpress-importer --activate
for f in split/part-*.xml; do
  echo "导入 $f"
  wp import "$f" --authors=create --skip=attachment
done

两个关键参数:

  • --authors=create:自动创建不存在的作者(也可以用 --authors=skip 或映射到已有用户);
  • --skip=attachment:跳过附件导入,这是提速最关键的一步。附件用 rsync 单独同步。

第五步:附件单独同步(替代 importer 下载)

rsync -avP root@old-server:/var/www/old.com/wp-content/uploads/ \
  /var/www/new.com/wp-content/uploads/

chown -R www-data:www-data /var/www/new.com/wp-content/uploads
find /var/www/new.com/wp-content/uploads -type d -exec chmod 755 {} \;
find /var/www/new.com/wp-content/uploads -type f -exec chmod 644 {} \;

然后替换正文里的旧域名:

wp search-replace 'https://old.com/wp-content/uploads' \
  'https://new.com/wp-content/uploads' --all-tables --precise

第六步:导入后的收尾检查

# 数量核对
wp post list --post_type=post --format=count

# 有没有漏掉的 attachment
wp post list --post_type=attachment --format=count

# 分类标签是否齐全
wp term list category --fields=name,count
wp term list post_tag --fields=name,count --format=count

# 重建缩略图
wp media regenerate --only-missing --yes

# 刷新重写规则
wp rewrite flush
wp cache flush

第七步:常见问题逐项修

问题 A:作者全变成 admin

导入时作者映射没配对。批量改:

wp post update $(wp post list --author=1 --format=ids) --post_author=5

问题 B:文章重复导入

跑了两次导入。按标题去重:

wp eval '
global $wpdb;
$dups = $wpdb->get_results("
  SELECT post_title, COUNT(*) c, MIN(ID) keep_id
  FROM {$wpdb->posts}
  WHERE post_type=\"post\" AND post_status=\"publish\"
  GROUP BY post_title HAVING c > 1
");
foreach ($dups as $d) {
  $ids = $wpdb->get_col($wpdb->prepare(
    "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND ID != %d",
    $d->post_title, $d->keep_id
  ));
  foreach ($ids as $id) { wp_delete_post($id, true); echo "del $id\n"; }
}
'

问题 C:分类/标签没导过来

WXR 里如果没导出分类(勾选问题),会在导入时自动创建。检查:

wp term list category --format=csv

常见踩坑与失败案例

  1. 案例一:180MB 文件直接上传,进度条到 60% 卡死。根因是 PHP max_execution_time 300 秒不够。切成 200 篇一个分片后,每个 40 秒完成。
  2. 案例二:勾了”下载并导入附件”,导入 3000 篇文章耗时 7 小时,中途失败两次,重来又产生重复。教训:永远不要勾附件下载,用 rsync。
  3. 导入后文章全是乱码:XML 编码声明和实际不符,或者 FTP 用 ASCII 模式传了文件。用二进制模式传,或者直接在服务器上生成。
  4. --authors=create 创建了大量垃圾用户:导入前先在新站建好对应用户,用 --authors=skip 然后批量改 post_author。
  5. 导入后固定链接 404:wp rewrite flush 没跑。
  6. 案例三:导入后文章里的 <!-- wp:xxx --> 区块注释丢失,前台布局全乱。原因是 WXR 在传输中被某些工具”格式化”了。传输 XML 必须用二进制,不要做任何转码。
  7. 导入后 featured image 丢失:_thumbnail_id 指向老站的附件 ID,新站 ID 不同。需要在导入后重设(或接受丢失,手工补)。
  8. 超大文件(>500MB):别用 importer,直接数据库迁移(mysqldump + search-replace)更靠谱。

总结

大文件导入的核心思路是分片 + 剥离附件 + 命令行执行:按年份或按 200 篇一片切割 WXR;--skip=attachment 跳过附件、用 rsync 单独同步 uploads;用 wp import 而不是浏览器上传,避免 HTTP 超时。导入前务必放宽 upload_max_filesize / max_execution_time / memory_limit 三件套。导入后跑一遍核对清单:文章数、附件数、分类标签、作者映射、重写规则、缩略图重建。超过 500MB 的迁移建议直接走数据库层面。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。