本文约 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 插件)做的事:
- 解析 XML:用的是 PHP 的 XMLReader/正则流式解析,不是一次性
simplexml_load_file——所以理论上支持大文件,但解析耗时仍受 PHP 执行时间限制; - 逐条
wp_insert_post():每篇文章一次插入 + 若干 meta 插入,这是慢的根源; - 下载附件:如果勾了”下载并导入附件”,每篇文章里的每张图都要
wp_remote_get从老站下载——这一步最慢、最容易超时; - 建立作者映射:老站作者要映射到新站用户。
瓶颈排序:附件下载 > 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
常见踩坑与失败案例
- 案例一:180MB 文件直接上传,进度条到 60% 卡死。根因是 PHP
max_execution_time300 秒不够。切成 200 篇一个分片后,每个 40 秒完成。 - 案例二:勾了”下载并导入附件”,导入 3000 篇文章耗时 7 小时,中途失败两次,重来又产生重复。教训:永远不要勾附件下载,用 rsync。
- 导入后文章全是乱码:XML 编码声明和实际不符,或者 FTP 用 ASCII 模式传了文件。用二进制模式传,或者直接在服务器上生成。
--authors=create创建了大量垃圾用户:导入前先在新站建好对应用户,用--authors=skip然后批量改post_author。- 导入后固定链接 404:
wp rewrite flush没跑。 - 案例三:导入后文章里的
<!-- wp:xxx -->区块注释丢失,前台布局全乱。原因是 WXR 在传输中被某些工具”格式化”了。传输 XML 必须用二进制,不要做任何转码。 - 导入后 featured image 丢失:
_thumbnail_id指向老站的附件 ID,新站 ID 不同。需要在导入后重设(或接受丢失,手工补)。 - 超大文件(>500MB):别用 importer,直接数据库迁移(mysqldump + search-replace)更靠谱。
总结
大文件导入的核心思路是分片 + 剥离附件 + 命令行执行:按年份或按 200 篇一片切割 WXR;--skip=attachment 跳过附件、用 rsync 单独同步 uploads;用 wp import 而不是浏览器上传,避免 HTTP 超时。导入前务必放宽 upload_max_filesize / max_execution_time / memory_limit 三件套。导入后跑一遍核对清单:文章数、附件数、分类标签、作者映射、重写规则、缩略图重建。超过 500MB 的迁移建议直接走数据库层面。
