本文约 4,362 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:几千条数据靠手工复制是不现实的
迁移站、接 ERP、做采集站,都会碰到同一个问题:手上有几千条结构化数据(产品、房源、职位),要变成 WordPress 的文章,还要带上图片、分类和自定义字段。手工复制粘贴不现实,找人写一次性脚本则面临”下次数据更新怎么办”。
WP All Import 的定位就是这件事:给它一个 XML 或 CSV 文件,用鼠标把左边的源字段拖到右边的 WordPress 字段上,配好”怎么判断这条数据是否已经导入过”,剩下的交给它跑。免费版够做一次性导入;PRO 版支持从 URL 拉取文件、自动定时重跑、导入到自定义文章类型、导入图片到媒体库之外的更多目标(如 WooCommerce 变体)。
配套的导出插件(本站后面会单独讲)通常和它一起用,两者组合是同类站点之间搬数据的标准做法。
原理分析:三个必须先想清楚的概念
1. 记录路径(XPath / Root element)
XML 是一棵树,导入前必须告诉插件”哪一层节点是一条记录”。插件会让你在文件结构树里点选一个节点当记录根,选错会导致所有数据被塞进一篇文章。
2. 唯一标识符(Unique Identifier)
第二次导入时,插件靠什么判断”这条数据已有对应文章”?可选:文章标题、某个自定义字段(如 SKU)、文章 ID、Slug。选错会造成重复导入或覆盖错误。业务主键优先,没有就用标题。
3. 新增与更新的区分
「New Items」和「Existing Items」是两套独立策略:新增时可以决定默认状态、作者、分类;更新时可以决定”覆盖哪些字段””是否保留已有的特色图”。这两块配置分不清,是”导入一遍把已有内容冲掉”的头号原因。
| 配置项 | 作用 | 建议 |
|---|---|---|
| Records per iteration | 每次请求处理多少条 | 数据大时设 20–50,避免超时 |
| Download images | 是否把远程图片抓到媒体库 | 勾上,否则图片仍指向外站 |
| Search Media Library first | 是否先查重再上传 | 必须勾,否则重跑会重复上传 |
| Delete posts not in file | 文件里没有的条目是否删除 | 慎用,先备份 |
| Update existing | 是否更新已存在文章 | 按需,一般选”更新全部字段” |
保姆级实操
第一步:准备源文件
一个典型的产品 XML 长这样:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<product>
<sku>AC-1001</sku>
<name>工业离心风机</name>
<desc><![CDATA[<p>风量 3000m³/h,适用车间排风。</p>]]></desc>
<price>2800</price>
<category>通风设备</category>
<images>
<image>https://cdn.example.com/AC-1001-1.jpg</image>
<image>https://cdn.example.com/AC-1001-2.jpg</image>
</images>
</product>
</catalog>
CSV 则是首行为表头的文件(sku,name,desc,price,category)。CSV 不支持嵌套结构,图片多张时通常用分隔符串在一个单元格里。
第二步:上传文件并选记录根
「New Import → Upload a file」上传后,插件会展示结构树。把记录根选到 /catalog/product(即每一条 <product> 是一条记录),右侧会显示”共 N 条记录”。这一步务必核对数量与源文件一致。
第三步:选目标类型与唯一标识
Post Type 选 product(自定义文章类型需已在站点注册,参见 Custom Post Type UI 创建自定义文章类型),然后在「Existing Items」里把唯一标识设为 _sku 自定义字段。首次导入时全部是新增;第二次跑就会按 SKU 匹配更新。
第四步:拖拽映射
右边区域列出了所有可映射的目标:Title、Content、Excerpt、Featured Image、Images(图集)、Categories、Tags、Custom Fields。把左边的 {name[1]} 拖到 Title,{desc[1]} 拖到 Content,{sku[1]} 拖到 Custom Fields 里新建的 _sku。
字段值需要加工时,点右侧字段的「Function editor」,可以套用 PHP 函数:
[str_replace("元", "", {price[1]})]
[trim({name[1]})]
第五步:配置图片
把 {images/image[1]} 之类的节点拖到「Images」区域,并在下方勾选:
Download images hosted elsewhere:把外站图片抓到本站媒体库;Search through the Media Library for existing images before uploading new media:先查重,重跑导入不会攒出一堆image-1.jpg、image-2.jpg;Set the first image to the Featured Image:第一张自动设为特色图。
第六步:用钩子补收尾逻辑
界面能做的有限,剩下的交给钩子:
// 每条数据导入后执行:$update 为 true 表示是更新而非新增
add_action( 'pmxi_saved_post', function ( $post_id, $xml_node, $update ) {
$sku = (string) $xml_node->sku;
update_post_meta( $post_id, '_sku', $sku );
// 只在新增时自动发布,更新时保持原状态
if ( ! $update ) {
wp_update_post( array( 'ID' => $post_id, 'post_status' => 'publish' ) );
}
}, 10, 3 );
// 整次导入结束后做一次汇总,可发邮件或写日志
add_action( 'pmxi_after_xml_import', function ( $import_id, $import ) {
error_log( sprintf( 'import #%d finished, object=%s', $import_id, $import->options['custom_type'] ?? '' ) );
}, 10, 2 );
第七步:定时重跑
PRO 版的「Manage Imports → Scheduling」会给出一段 cron URL,把它交给服务器:
*/30 * * * * /usr/bin/wget -q -O - "https://mimangfei.com/wp-cron.php?import_key=XXXX&import_id=1&action=processing" >/dev/null 2>&1
如果你已经在系统 crontab 里接管了 WP-Cron,注意别让两套调度互相干扰,具体取舍见 WP-Cron 与系统 cron 配合配置。
常见踩坑
- 记录根选错。 选到
/catalog会导致只生成一篇文章,内容全是第一条。每次上传后先看右侧记录条数。 - 唯一标识选了标题但源数据标题会变。 供应商改一次产品名,下次导入就新增一条重复数据。能用 SKU 就用 SKU。
- 没勾图片查重,重跑三次后媒体库多了两千张重复图。 磁盘和备份体积一起爆炸。
desc里是 HTML,导入后被转义成纯文本。 内容字段里若源是 HTML,确认没有套strip_tags()之类的函数,并检查unfiltered_html能力——权限不足时<script>、<iframe>会被过滤。- 导入超时 502。 调小 Records per iteration,并把 PHP 的
max_execution_time与内存调高;Nginx 侧同时放宽fastcgi_read_timeout。 - 分类自动创建造成垃圾。 源文件里的分类名有错别字(”通风设备 “带空格),会生成两个同名不同 slug 的分类。导入前先清洗数据。
- 案例: 某外贸站每天从供应商 FTP 拉一次价格文件自动导入。某次供应商把 XML 的节点从
<product>改成了<item>,插件匹配不到记录根,导入结果”0 条记录”——但因为配置了”文件里没有的条目就删除”,插件直接把站上 1200 个产品全部删掉了。恢复靠的是前一天的完整备份。教训:开启”删除文件里没有的条目”之前,必须先有自动化备份,并且把删除阈值设保守。
总结
WP All Import 的使用要点是”先小后大”:先用只含 5 条记录的样本文件跑通全部映射,确认标题、正文、图片、分类、字段都对了,再换成全量文件。每次正式导入前保留一份数据库备份,尤其是要开删除策略时。
行动清单:核对记录条数 → 用业务主键做唯一标识 → 图片务必勾查重 → 用 pmxi_saved_post 补界面做不到的逻辑 → 定时重跑先手动验证一次再挂 cron。导入完成后若发现正文里的旧域名链接需要替换,接着做 批量替换文章内容 这一步。
