代码改变世界

一次批量修改博客文章的经验(上):准备工作

2010-01-04 18:57  Jeffrey Zhao  阅读(6809)  评论(16编辑  收藏  举报

这几天赋闲在家,除了看书和还债(如RSS订阅),终于把一直以来想做却拖着的事情完成了:批量去除博客文章段首的空格。这个过程并不难,只需要按部就班地去做就行了,一切资料都可以在互联网上搜索到。不过我还是打算记录一下,也是为了今后再做类似工作时有个参考,少走一些弯路。

前言

我是个略有些强迫症的人,希望很多东西可以统一。例如,几个月前我才在RSS订阅里输出了全文——那是因为博客园终于提供这个统一设置的选项了。其实在此之前就有很多朋友建议我开放全文,但我一直没有做。不是我追求PV,而是我只能做到所有的新文章输出全文,对于旧文章则必须一篇一篇地去修改——如果不修改,不就不统一了吗?但手动修改实在太繁琐,于是便一直没有去做。

同样的,在我以前的文章中,每段段首都是空两格的,但是现在感觉没有什么必要,于是最近的几十篇文章都顶格写了。这个“不统一”我便“容忍”了,因为我知道博客园提供了MetaWeblog API,这样我理论上可以写一段程序来批量修改之前的文章内容。只可惜,直到现在我才下决心这么做。

整个过程分几步完成,在此一一记录一下。

XML RPC与MetaWeblog

MetaWeblog是一个通用的博客内容修改接口,许多博客都实现了协议,因此我们可以使用Windows Live Writer这样的工具来写文章。网上关于这个协议最好的描述文档我认为是MSDN上的MetaWeblog API Reference——这其实是Windows Live Space服务所公开的接口。从理论上来说,博客园也应该实现完全相同的功能,但是实际使用上来看,还是有一些区别。由于任务的性质,这里我们自然以博客园为准,我也不再去追究到底谁是真正符合标准的做法了。

MetaWeblog API使用了基于XML RPC的调用方式。XML RPC使用HTTP来传输一段XML来表示一个远程调用,与SOAP不同,XML RPC非常简单,他的传输内容您一看就懂。我只是在开发过程中使用Fiddler简单查看了一下Windows Live Writer与博客园的通信,如果您感兴趣的话也可以仔细研究一下XML RPC。

在.NET上调用XML RPC服务可以利用开源的XML-RPC.NET类库来简化操作。虽然MSDN上提供了一段基于XML-RPC.NET的演示代码,但是您也可以看出其实这段代码无比粗略,而且我根本没跑通,差点让我认为XML-RPC.NET非常不成熟。但我看了XML-RPC.NET的文档之后才意识到,其实这个类库使用起来非常简单。因此,我在这里建议您忽略MSDN上的示例代码,而以XML-RPC.NET为准——甚至只要首页上的几行代码您就可以明白了。

当然,您也可以继续阅读这篇文章,用于完成简单的工作已经足够了。

使用MetaWeblog API修改博客园文章

使用XML-RPC.NET调用MetaWeblog API非常容易,我们只要根据API的样式来定义“类型”和“接口”就可以了。例如:

public class Post
{
    [XmlRpcMember("postid")]
    public int PostID;

    [XmlRpcMember("dateCreated")]
    public DateTime CreateTime;

    [XmlRpcMember("title")]
    public string Title;

    [XmlRpcMember("description")]
    public string Content;

    [XmlRpcMember("categories")]
    public string[] Categories;
}

public interface IMetaWeblogProxy : IXmlRpcProxy
{
    [XmlRpcMethod("metaWeblog.getPost")]
    Post GetPost(string postId, string userName, string password);

    [XmlRpcMethod("metaWeblog.editPost")]
    bool UpdatePost(string postId, string userName, string password, Post post, bool publish);
}

首先我们定义了一个Post类型,其中有多个字段,每个字段上标记了在XML RPC结构中的名称。此外,我们又定义了一个IMetaWeblogProxy接口类型,XML-PRC.NET会根据根据接口的签名自动与远程服务进行交互。这里有个问题,便是Post的PostID字段到底是哪个类型。MSDN上描述,与Windows Live Space的API使用的是字符串,而博客园使用的确实32位整数——如前文所述,我们这里以博客园为准。

调用接口很容易:

var client = XmlRpcProxyGen.Create<IMetaWeblogProxy>();
client.Url = "http://www.cnblogs.com/JeffreyZhao/services/metaweblog.aspx";

string userName = "JeffreyZhao";
string password = "...";
string postId = "1629216";

var post = client.GetPost(postId, userName, password);
post.Content += "<p>Hello World!</p>";
client.UpdatePost(postId, userName, password, post, true);

以上,便在ID为1629216的文章内容后添加一行“Hello World”字样了。

异步调用

F#并不是万能的,之前我们也看到说,由于F#缺少诸多语言特性,在XML构造方面的方便程度并不如C#。但是,我这里还是选择使用F#,关键的因素还是在于其异步支持实在是太方便了。我们的任务需要大量的网络IO操作,而其中高性能的关键还是在于利用异步IO操作。那么在F#中,我们又该如何异步访问MetaWeblog API呢?

其实XML-RPC.NET已经为我们提供了支持,例如我们可以这样定义上面GetPost和UpdatePost的“异步版本”:

type IMetaWeblogProxy =
    inherit IXmlRpcProxy

    [<XmlRpcBegin("metaWeblog.getPost")>]
    abstract BeginGetPost : string -> string -> string -> AsyncCallback -> obj -> IAsyncResult

    [<XmlRpcEnd("metaWeblog.getPost")>]
    abstract EndGetPost : IAsyncResult -> Post


    [<XmlRpcBegin("metaWeblog.editPost")>]
    abstract BeginUpdatePost : string -> string -> string -> Post -> bool -> AsyncCallback -> obj -> IAsyncResult

    [<XmlRpcEnd("metaWeblog.editPost")>]
    abstract EndUpdatePost : IAsyncResult -> bool

完整的代码(如Post类型的定义)您可以从文末的链接里获得。这里我们直接定义了Begin/End两个方法的签名,与C#版本相比省略了参数名,如果您希望保证可读性也可以在定义时补上。只要我们使用这种方法定义了接口,标记了方法,XML-RPC.NET便可以为我们生成一个代理对象。自然,我们也可以在F#中使用XmlRpcProxyGen.Create方法:

let createProxy() = XmlRpcProxyGen.Create<IMetaWeblogProxy>()

在实际使用的时候,我们可以按常规来扩展一下IMetaWeblogProxy接口:

type MetaWeblog.IMetaWeblogProxy with

    member p.GetPostAsync(postId, userName, password) =
        let beginGet (ac, o) = p.BeginGetPost postId userName password ac o
        Async.FromBeginEnd(beginGet, p.EndGetPost)

    member p.UpdatePostAsync(postId, userName, password, post, publish) =
        let beginUpdate (ac, o) = p.BeginUpdatePost postId userName password post publish ac o
        Async.FromBeginEnd(beginUpdate, p.EndUpdatePost)

于是之前的C#代码便可以改写成如下的F#异步工作流:

let workflow = async {
    let client = MetaWeblog.createProxy()
    client.Url <- "http://www.cnblogs.com/JeffreyZhao/services/metaweblog.aspx"

    let userName, password, postId = "JeffreyZhao", "...", "1629216"
    let! post = client.GetPostAsync(postId, userName, password)
    post.Content <- post.Content + "<p>Hello World!</p>"
    return! client.UpdatePostAsync(postId, userName, password, post, true)
}

当然,以上代码只是进行了“定义”,在实际运行时您还需要使用某种方法来执行这个异步工作流。

本文代码

相关文章