本博客现已从新浪SAE迁出,记录一下带图片迁出的实现方式。

首先应当去后台导出所有内容:

image

下载后得到的是WordPress eXtended RSS文件,扩展名是xml。

image

然后去SAE的后台把源码全下载下来。

由于SAE的WordPress好像是修改版的,我索性从官网下了个新版的,上面下载到的源码里我只是用以导入主题、样式、插件。

然后需要编码下载一下原博客中的图片,这里只要写一个简单的爬虫,爬遍所有文章,取得其中的图片就可以了。

我是用.Net Winform程序实现的:

image

图上的两条正则表达式:

http://skyd.sinaapp.com/archives/\d+
http://skyd-wordpress.stor.sinaapp.com.+?(png|jpg|jpeg|gif|bmp)

注意:我的文章URL是以数字命名的,你的如果不是就自己改改这里的表达式。

主要代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 网站文件下载
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
            backgroundWorker1.ProgressChanged += BackgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
            链接列表 = new Dictionary<string, bool> { { textBox1.Text, false } };
            访问页面链接表达式字符串 = textBox2.Text;
            下载文件链接表达式字符串 = textBox3.Text;
            button1.Enabled = false;
            backgroundWorker1.RunWorkerAsync();
        }

        private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            button1.Enabled = true;
        }

        public static string 下载文件链接表达式字符串 { get; set; }
        public static string 访问页面链接表达式字符串 { get; set; }
        public static bool 是否已全部访问 { get; set; }
        public static Dictionary<string, bool> 链接列表 { get; set; }

        private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                listBox1.Items.Add(e.UserState);
            }
            catch (Exception er)
            {
                er.Trace();
            }
        }

        private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                var c = new WebClient();
                c.Encoding = Encoding.UTF8;
                var areg = new Regex(访问页面链接表达式字符串, RegexOptions.IgnoreCase);
                var freg = new Regex(下载文件链接表达式字符串, RegexOptions.IgnoreCase);
                start:
                var isallok = true;
                foreach (var f in 链接列表.Where(q => q.Value == false).Select(q => q.Key).ToArray())
                {
                    StreamReader sr = new StreamReader(c.OpenRead(f), Encoding.UTF8);
                    var html = sr.ReadToEnd();
                    sr.Close();
                    foreach (Match t in areg.Matches(html))
                    {
                        var link = f.AsUriString().CombineRelativePath(t.Value);
                        if (!链接列表.Keys.Contains(link))
                        {
                            链接列表.Add(link, false);
                            isallok = false;
                            backgroundWorker1.ReportProgress(0, t.Value);
                        }
                    }
                    foreach (Match t in freg.Matches(html))
                    {
                        var uri = new Uri(f.AsUriString().CombineRelativePath(t.Value));
                        var path = Application.StartupPath.AsPathString().Combine("download") +
                                   uri.AbsolutePath.Replace("/", @"\");
                        var dir = path.AsPathString().DirectoryName;
                        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                        backgroundWorker1.ReportProgress(0, path);
                        c.DownloadFile(uri.OriginalString, path);
                    }
                    链接列表[f] = true;
                }
                if (!isallok) goto start;
            }
            catch (Exception er)
            {
                er.Trace();
            }

        }

    }
}

上面用到的扩展方法需要引用我的Core3类库中的Core项目。

程序执行效果:

image

执行后就会在程序所在目录下创建一个download目录,然后把所有图像都下载到其中,并保留原来的目录层级:

image

然后我们把新的博客搭建好,在网站根目录建立一个oldsite目录,把前面download目录中的文件全扔进去。

现在再编写程序,来将我们之前导出的包含所有博客内容的xml文件中的图片引用网址改变,使其都指向oldsite目录中:

            var file = new FileInfo(@"C:\Users\SkyD\Downloads\wordpress.2016-04-24.xml");
            var fs = file.Open(FileMode.OpenOrCreate);
            StreamReader sr = new StreamReader(fs, Encoding.UTF8);
            var text = sr.ReadToEnd();
            text = text.RegexReplace(@"http://skyd-wordpress.stor.sinaapp.com/(.+?(?:png|jpg|jpeg|gif|bmp))", "/oldsite/$1");
            fs.Seek(0, SeekOrigin.Begin);
            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);
            sw.Write(text);
            sw.Close();
            sr.Close();
            fs.Close();

运行上述代码前最好备份一下你的这个导出文件。

去博客后台导入修改好的xml文件,不要勾选导入附件功能,那个根本没用,若是有用我们也不至于要写爬虫来下这些图片了。

若是内容太多的话,一次运行超出时间报错的话,再尝试导入一次,这样前面已导入的文章都直接跳过了,会继续导入剩下的。

导入成功后,就搞定了文章和图片的迁移工作了,后面把主题和插件拷入对应的目录,然后进行配置就好了。

——————————————————

更新:

上面的实现之后,我又想到我原来在博客园的博客图片其实也是可以用类似的方法迁移到本地的,于是我用上面的爬虫程序去下载了我的博客园中的所有图片(PS:遍历博客园文章的时候有点问题,爬虫不能自如地遍历全站,猜想可能是博客园中某些模块是客户端生成或异步加载的,导致直接下载HTML源码的话不能正确解析,我只能手动让爬虫从每一个归档页爬起了~,费好半天才趴下全站图片),然后还是把它们保存到了oldsite目录中。

但是现在的问题是我新博客已经搞得差不多了,不想再重新替换xml文件内的链接,并再重新导入到站点了,重新导入势必还要删除所有已导入的文章,我觉得恐怕不好弄,我想也许可以在网页内嵌入一段JavaScript代码,用其来替换所有指向博客园的图像链接为指向本地。

如愿找到了一个名为 Custom JavaScript Editor 的插件:

image

它的功能就是为网站内嵌JS代码,并且还提供了一个不错的编辑器。

安装此插件后,在后台的“外观”里可以找到它:

image

这是我实现自动替换博客园图片指向功能的JavaScript代码:

(function ($) {
$("img").each(function(){
    $(this).attr("src",$(this).attr("src").replace("http://www.cnblogs.com/images","/oldsite"));
});
})(jQuery);

我尝试了这里主代码外围必须用那个方法来包裹,否则会提示“$”未定义,原因可能是博客引入的JS较多,有所冲突,具体参考了知乎的这则问题

设置好之后就OK了,看一下之前曾发表在博客园的一篇文章吧,完美转移来了!

另外,网站爬虫程序我也做了一点改动,使之更易用了一些,改动后的代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 网站文件下载
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
            backgroundWorker1.ProgressChanged += BackgroundWorker1_ProgressChanged;
            backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;
            链接列表 = new Dictionary<string, bool> { { textBox1.Text, false } };
            访问页面链接表达式字符串 = textBox2.Text;
            下载文件链接表达式字符串 = textBox3.Text;
            button1.Enabled = false;
            backgroundWorker1.RunWorkerAsync();
        }

        private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            button1.Enabled = true;
        }

        public static string 下载文件链接表达式字符串 { get; set; }
        public static string 访问页面链接表达式字符串 { get; set; }
        public static bool 是否已全部访问 { get; set; }
        public static Dictionary<string, bool> 链接列表 { get; set; }

        private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                listBox1.Items.Insert(0, e.UserState);
                this.Text = "总链接数" + 链接列表.Count;
            }
            catch (Exception er)
            {
                er.Trace();
            }
        }

        private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                var c = new WebClient();
                c.Encoding = Encoding.UTF8;
                var areg = new Regex(访问页面链接表达式字符串, RegexOptions.IgnoreCase);
                var freg = new Regex(下载文件链接表达式字符串, RegexOptions.IgnoreCase);
                start:
                var isallok = true;
                foreach (var f in 链接列表.Where(q => q.Value == false).Select(q => q.Key).ToArray())
                {
                    backgroundWorker1.ReportProgress(0, "goto " + f);
                    StreamReader sr = new StreamReader(c.OpenRead(f), Encoding.UTF8);
                    var html = sr.ReadToEnd();
                    sr.Close();
                    var mc = areg.Matches(html);
                    if (mc.Count == 0)
                    {
                        MessageBox.Show(html);
                    }
                    else foreach (Match t in mc)
                        {
                            var link = f.AsUriString().CombineRelativePath(t.Value);
                            if (!链接列表.Keys.Contains(link))
                            {
                                链接列表.Add(link, false);
                                isallok = false;
                                backgroundWorker1.ReportProgress(0, t.Value);
                            }
                        }
                    foreach (Match t in freg.Matches(html))
                    {
                        var uri = new Uri(f.AsUriString().CombineRelativePath(t.Value));
                        var path = Application.StartupPath.AsPathString().Combine("download") +
                                   uri.AbsolutePath.Replace("/", @"\");
                        backgroundWorker1.ReportProgress(0, path);
                        var dir = path.AsPathString().DirectoryName;
                        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                        backgroundWorker1.ReportProgress(0, path + "——ok");
                        c.DownloadFile(uri.OriginalString, path);
                    }
                    链接列表[f] = true;
                    Thread.Sleep(300);
                }
                if (!isallok) goto start;
                链接列表.Count.Trace("访问链接总数");
            }
            catch (Exception er)
            {
                er.Trace();
            }

        }

    }
}

分享或转载本博客站点内的所有原创内容时,都必须遵循此协议:

姓名标示-非商业性-相同方式分享 4.0 国际 (CC BY-NC-SA 4.0)

同时必须附加指向本文页面本博客首页的超链接。

除此之外的转载、分享方式都必须征得本博客作者的授权,否则将会诉诸法律。