搜索全站
网站迁移 22 分钟阅读

从 Wix / 凡科 / 上线了迁移到 WordPress:导出限制下的内容搬运方案

SaaS 建站平台不给完整数据导出怎么办?本文讲清 Wix、凡科、上线了各自的导出能力边界,给出 RSS 抓取、站点地图遍历、页面解析三条搬运路径与图片落地方案。

U
UFO_PF 2019-06-04 · 阅读 22 分钟
从 网站迁移
阅读提示
本文约 8,491 字,预计阅读 22 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:SaaS 建站的”数据自由”代价

Wix、凡科建站、上线了这类 SaaS 建站平台的卖点是”拖拉拽、不用懂技术、半小时上线”。这个承诺是真的——但代价在你想要离开的时候才显现:你建的内容存在别人家的平台上,导出能力由平台决定,且大多数平台只提供”部分导出”或”根本不导出”。

Wix 支持导出站点,但导出的 WXR 文件结构特殊,且无法导出博客文章、电商产品、会员数据(需要逐个复制粘贴或使用第三方工具);凡科建站、上线了这类国内平台,通常只提供页面内容的手动复制或有限的数据导出,且免费版可能有导出限制。

于是实际可行的方案只剩下三条:用平台自带的导出功能(能导多少算多少)→ 用 RSS/站点地图抓取公开内容 → 用页面解析工具批量抽取正文和图片。第三条是兜底方案,也是本文的重点。

原理分析:三类平台的导出能力边界

平台 官方导出能力 能导出什么 导不出什么
Wix 支持导出站点为 WXR(工具 → 导出站点) 页面内容、图片、部分 SEO 设置 博客文章需单独处理、会员数据、电商订单、表单提交记录
凡科建站 后台提供有限导出,部分套餐支持 部分文本内容、产品数据(视套餐) 完整页面结构、样式、历史版本
上线了 提供内容导出,模板与交互需重建 文本内容、图片(视套餐) 模板结构、动画、第三方集成配置
易企秀 / 其他 H5 工具 基本不提供导出 几乎无 全部内容

为什么导出这么难? 技术上并不复杂,本质原因是商业模式:SaaS 建站的核心壁垒是”迁移成本”,数据可移植性越低,用户留存越高。所以选择 SaaS 建站时就该想到退出成本,这也是很多企业最终选择自建 WordPress 的原因。

抓取方案的合法性边界。 抓取自己拥有版权的站点内容是完全正当的。但要注意两点:一是尊重 robots.txt,如果目标站明确禁止抓取,先临时调整或用官方 API;二是控制请求频率,不要给源站造成压力(尤其是源站也在 SaaS 平台上运行时,高频请求可能触发平台的风控)。

抓取路径的可行性排序(按信息保真度从高到低):

  1. RSS / Feed —— 保真度最高,有标题、正文、发布时间、分类,但通常只包含最近 10–20 条;
  2. 站点地图 sitemap.xml —— 能拿到全部公开 URL 列表,再逐页解析;
  3. 全站镜像(wget –mirror) —— 能拿到完整 HTML,但正文抽取工作量大;
  4. 存档服务(Wayback Machine) —— 兜底,只能拿到历史快照,缺图片。

保姆级实操:四步把内容搬出来

步骤 1:先穷尽官方导出渠道。 这是成本最低的路径,别一上来就抓。

  • Wix: 后台「设置 → 导出站点」,生成下载链接,得到 .wxr.xml 文件。用 WordPress 导入器导入即可,但导入前要检查 XML 里的命名空间(Wix 导出的 WXR 与 WordPress 原生格式有差异,可能需要转换工具,如 “Wix to WordPress” 类插件)。
  • 凡科 / 上线了: 在后台找”数据导出””备份”入口,通常能导出产品数据为 CSV、文本内容为表格。拿到什么算什么,剩下的用抓取补齐。
# 检查导出文件是不是标准 WordPress WXR
head -20 export.xml

# 标准 WXR 的关键特征
grep -o 'xmlns:wp="http://wordpress.org/export/[0-9.]*/"' export.xml
grep -c '<item>' export.xml       # 统计条目数

步骤 2:用 RSS 抓取博客内容(信息最全)。 多数平台会提供 RSS 源:

# 常见的 RSS 地址格式(逐个试)
curl -sI https://用户名.wixsite.com/站点名/blog-feed.xml
curl -sI https://www.站点.com/feed
curl -sI https://www.站点.com/rss.xml
curl -sI https://www.站点.com/blog/rss.xml

# 确认可用后抓下来
curl -s https://www.站点.com/feed -o feed.xml

# 统计条目数(多数平台只输出最近 10-20 条)
grep -c '<item>' feed.xml

RSS 只有最近若干条时,尝试分页参数:

# 部分平台支持分页
curl -s 'https://www.站点.com/feed?paged=2' -o feed_2.xml
curl -s 'https://www.站点.com/blog-feed.xml?offset=20&limit=50' -o feed_p2.xml

步骤 3:从站点地图拿全量 URL,逐页解析。 这是覆盖度最高的方案:

# 1. 找站点地图
curl -s https://www.站点.com/robots.txt | grep -i sitemap
curl -s -o /dev/null -w '%{http_code}\n' https://www.站点.com/sitemap.xml

# 2. 提取所有 URL
curl -s https://www.站点.com/sitemap.xml | grep -oP '(?<=<loc>)[^<]+' > all_urls.txt

# 3. 处理 sitemap index(索引套索引的情况)
curl -s https://www.站点.com/sitemap.xml | grep -oP '(?<=<loc>)[^<]+' | while read -r u; do
  curl -s "$u" | grep -oP '(?<=<loc>)[^<]+'
done >> all_urls.txt

sort -u all_urls.txt -o all_urls.txt
wc -l all_urls.txt

步骤 4:批量抓取并解析页面。 关键是要限速、要设置 UA、要能处理 SaaS 平台的 JS 渲染:

# 方案 A:wget 全站镜像(适合纯静态渲染的站点)
wget --mirror \
     --convert-links \
     --adjust-extension \
     --page-requisites \
     --no-parent \
     --wait=1 \
     --random-wait \
     --limit-rate=200k \
     --user-agent="Mozilla/5.0 (compatible; SiteMigration/1.0)" \
     --exclude-directories=/account,/cart,/checkout \
     -P /tmp/old_site \
     https://www.站点.com/
#!/usr/bin/env python3
# fetch_pages.py —— 逐页抓取并抽取正文(带限速与重试)
import time, random, os, re, csv
import requests
from bs4 import BeautifulSoup

URLS   = [l.strip() for l in open("all_urls.txt", encoding="utf-8") if l.strip()]
OUT    = "pages.csv"
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; SiteMigration/1.0)"}
DELAY  = (1.0, 2.5)        # 随机延时区间,避免触发风控

CONTENT_SELECTORS = [
    "[data-hook='post-content']",   # Wix 博客正文容器
    ".post-content", ".entry-content", ".article-content",
    "article", "main", "#content",
]

rows = []
for i, url in enumerate(URLS, 1):
    try:
        r = requests.get(url, headers=HEADERS, timeout=20)
        if r.status_code != 200:
            print(f"[{i}/{len(URLS)}] 跳过 {url} -> {r.status_code}")
            continue
    except Exception as e:
        print(f"[{i}/{len(URLS)}] 失败 {url}: {e}")
        continue

    r.encoding = r.apparent_encoding or "utf-8"
    soup = BeautifulSoup(r.text, "html.parser")

    # 标题
    h1 = soup.find("h1")
    title = h1.get_text(strip=True) if h1 else (soup.title.get_text(strip=True) if soup.title else url)

    # 正文容器:按优先级尝试
    body = None
    for sel in CONTENT_SELECTORS:
        body = soup.select_one(sel)
        if body:
            break
    body = body or soup.body or soup

    # 清理脚本、样式、平台自带的分享与评论区
    for t in body.find_all(["script", "style", "noscript", "iframe"]):
        t.decompose()

    content = str(body)

    # 收集图片 URL(Wix 的图片通常带 data-pin-url 或在 srcset 里)
    imgs = []
    for img in body.find_all("img"):
        src = img.get("src") or img.get("data-src") or ""
        if src.startswith("//"):
            src = "https:" + src
        if src.startswith("http"):
            imgs.append(src)

    rows.append({
        "url": url, "title": title, "content": content,
        "images": "|".join(imgs[:20]),
    })
    print(f"[{i}/{len(URLS)}] OK {title[:40]}")

    time.sleep(random.uniform(*DELAY))

with open(OUT, "w", newline="", encoding="utf-8-sig") as fp:
    w = csv.DictWriter(fp, fieldnames=["url", "title", "content", "images"])
    w.writeheader(); w.writerows(rows)
print(f"完成:{len(rows)} 页 -> {OUT}")

Wix 平台的特殊处理: Wix 的页面大量依赖 JS 渲染,静态抓取拿到的 HTML 可能只有骨架。解决办法有两个:一是用其提供的 ?_escaped_fragment_= 参数(部分版本支持),二是用无头浏览器(Playwright / Puppeteer)渲染后再取 DOM。

# 用 Playwright 渲染 JS 后再抓取(需先 pip install playwright && playwright install chromium)
python3 -c "
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    b = p.chromium.launch()
    pg = b.new_page()
    pg.goto('https://www.站点.com/blog/post-1', wait_until='networkidle')
    html = pg.content()
    open('/tmp/rendered.html','w',encoding='utf-8').write(html)
    b.close()
print('渲染完成')
"

步骤 5:图片落地与路径替换。 SaaS 平台的图片 URL 通常很长且带参数,必须下载到本地:

#!/usr/bin/env bash
# download_images.sh —— 批量下载图片到本地 uploads
IMG_DIR=/var/www/html/wp-content/uploads/imported
mkdir -p "$IMG_DIR"
cd "$IMG_DIR"

# 从 pages.csv 的 images 列提取所有 URL 并去重
cut -d'|' -f1- /dev/null 2>/dev/null
python3 -c "
import csv
urls=set()
for r in csv.DictReader(open('pages.csv',encoding='utf-8-sig')):
    for u in r['images'].split('|'):
        if u.startswith('http'): urls.add(u)
open('img_urls.txt','w').write('\n'.join(urls))
print(len(urls),'张图片待下载')
"

# 逐张下载,带限速与超时
while read -r u; do
  fname=$(echo "$u" | md5sum | cut -c1-12).jpg
  [ -f "$fname" ] || curl -sL --max-time 20 --limit-rate 500k -o "$fname" "$u"
done < img_urls.txt

chown -R www-data:www-data "$IMG_DIR"

然后在导入脚本里把正文中的原图 URL 替换为本地路径,并建立 URL 对照表。

步骤 6:导入 WordPress 并处理 URL。 SaaS 平台的 URL 结构与 WordPress 完全不同(Wix 是 /post/{slug}、凡科可能是 /column-123.html),必须做 301:

# 用导入时保存的原 URL 生成映射(沿用前面的做法)
update_post_meta( $post_id, '_saas_old_url', $old_url );
# Wix 常见结构:/post/文章别名 → WordPress 的 /别名/
rewrite ^/post/(.+)$ /$1/ permanent;

# 凡科类:带数字的栏目页需逐条映射
map $request_uri $saas_new {
    default           "";
    /column-123.html  /products/;
    /column-124.html  /about/;
}

常见踩坑

  1. 以为官方导出能拿到全部数据。 实际上博客、电商订单、表单记录几乎都导不出来,必须提前核对。
  2. 抓取速度太快被平台风控。 表现为 403 或返回验证页。必须加随机延时(1–3 秒)并设置合理的 User-Agent。
  3. SaaS 平台是 JS 渲染,抓到空壳 HTML。 表现为正文为空。用无头浏览器渲染,或找平台提供的静态化入口。
  4. 图片 URL 带签名参数,过期后失效。 Wix 等平台的图片 URL 可能带时效性参数,必须下载本地化,不能热链。
  5. 免费版平台的子域名无法迁移。 比如 xxx.wixsite.com/yyy,绑定独立域名后 URL 结构会变,迁移前先确认最终 URL 形态。
  6. 导出内容里有大量平台注入的追踪脚本。 抓取后必须清理 script、iframe 和平台自带的统计代码,否则会影响新站性能。
  7. 案例:一次”数据导不出来”的项目止损。 客户用某国内 SaaS 平台做了三年的企业站,有 260 篇行业文章。决定迁 WordPress 后发现后台只有”导出产品数据”功能,文章完全无法导出,客服明确表示不支持。团队先用 sitemap.xml 拿到 260 个 URL,写爬虫逐页抓取,但平台有频率限制,超过每分钟 30 次请求就返回验证码页面。最后他们把延时调到 3 秒、分三天抓完,用 BeautifulSoup 抽取 [data-hook='post-content'] 容器,图片全部下载本地化。整个迁移耗时一周,其中四天在抓数据。教训:选择 SaaS 建站平台时,应该把”数据可导出”作为一项明确的评估指标写进选型清单;已经在用的,建议定期(比如每季度)自己备份一次内容,别等到要迁移时才发现没有出口。

总结

SaaS 迁移的核心现实是”能拿多少拿多少,剩下的靠抓取”。执行顺序:先试官方导出(Wix 的 WXR、凡科的 CSV)→ 试 RSS(含分页参数)→ 从 sitemap 拿全量 URL → 限速抓取 + 解析(JS 站点用无头浏览器)→ 图片全部下载本地化 → 导入 WordPress 并配置 301。抓取阶段一定要限速,并且优先处理高价值页面(有外链、有流量的),不要为了几个无流量的边缘页面耗费过多时间。数据搬完之后,网站改版 URL 映射 里的 301 配置是保住收录的关键,而抓取到的 HTML 需要按 静态 HTML 迁移到 WordPress 里的方法清洗后再入库。

U
UFO_PF作者

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

留下你的想法

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