一次批量修改博客文章的经验(上):准备工作
2010-01-04 18:57 Jeffrey Zhao 阅读(6815) 评论(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) }
当然,以上代码只是进行了“定义”,在实际运行时您还需要使用某种方法来执行这个异步工作流。
相关文章
- 一次批量修改博客文章的经验(上):准备工作
- 一次批量修改博客文章的经验(下):操作过程