利用MetaWeblog API进行Blog的批量处理
考虑到隐私问题,决定把部落格(Windows Live Space)中一些敏感的文(在特定的Category中)批量删除。这篇文就叙述一下如何利用MetaWeblog API和F#来进行这样的操作。
Windows Live Space的API是标准的MetaWeblog API,支持getPost, newPost, deletePost这样的基本操作,这些操作一般都需要PostID。所以只要拿到我们想要删除的文章的PostID就可以批量处理了。
但问题在这个API里面没有类似getAllPosts的接口,相类似的只有getRecentPosts,而且最多只能拿到最近20个。因此光从API中我们无法知道所有文章的PostID。在这里卡了一会。但是观察最近20个文章的PostID可以发现,它符合这样的模式:
5E76F306F39CCB3A!5038
5E76F306F39CCB3A!5027
5E76F306F39CCB3A!4936
…………
前面都是相同的字符串,变化的只是后面的数字而已。这样就提供了一种思路,我们手动观察部落格里最小的ID和最大的ID是多少,由此生成类似的候选PostID,然后一个个用getPost去尝试这是不是合法的PostID。下面就好办了。
所以算法基本就是:
生成候选ID
|> 过滤为合法ID
|> 过滤为指定Category的ID
|> 删除!
利用F#的异步操作可以并行处理大量的I/O请求。这里采用了MSDN提供的MetaWeblogAPI实现,和xml-rpc.NET(因为MetaWeblog API是基于xml-rpc协议的)。同样道理还可以实现批量搬家,示例代码如下。可惜似乎由于Windows Live Space服务器并发连接数限制,I/O加速效果并不太明显(加速比约为2倍)。
open System;
open System.Net;
open System.IO;
open MetaWeblogApi;
let mutable mw = new MsnSpacesMetaWeblog();
let username = "YourOldUserName";
let password = "YourOldSecretWord";
mw.Credentials <- new NetworkCredential(username, password);
let mutable mw2 = new MsnSpacesMetaWeblog();
let username2 = "YourNewUserName";
let password2 = "YourNewSecretWord"
mw2.Credentials <- new NetworkCredential(username2, password2);
let VerifyPostId id =
async{
try
let postId = "5E76F306F39CCB3A! or your own prefix" + id.ToString();
let post = mw.getPost(postId, username, password);
printf "%s\t" post.postid;
printfn "%s" post.title;
return post.postid;
with
| _ -> return "";
}
let FilterPost id =
async{
let post = mw.getPost(id, username, password);
if (not(post.title = "") && post.categories |> Array.exists(fun x -> x = "Your categary")) then
printf "%s\t" id;
printfn "%s" post.title;
return id;
else
return "";
}
let CopyPost id =
async{
let mutable post = mw.getPost(id, username, password);
post.dateCreated <- DateTime.Now; //or the server will return error that illegal publish date.
mw2.newPost("MyBlog", username2, password2, post, true) |> ignore;
printf "%s\t" post.postid;
printfn "%s" post.title;
}
let DeletePost id =
async{
let post = mw.getPost(id, username, password);
mw.deletePost("MyBlog", id, username, password, true) |> ignore;
printfn "%s" id;
}
printfn "Skim post ids...";
let ids =
[114..5366] //that depends on the Id range of your blog
|> List.map(fun x -> VerifyPostId x)
|> Async.Parallel
|> Async.RunSynchronously
|> Array.filter(fun x -> not(x = ""));
File.WriteAllLines("PostIds.txt", ids);
printfn "Filter posts...";
let postIdsToCopy =
File.ReadAllLines("PostIds.txt")
|> Array.map (fun x -> FilterPost x)
|> Async.Parallel
|> Async.RunSynchronously;
File.WriteAllLines("PostIdsToCopy.txt", postIdsToCopy);
printfn "Copy posts...";
File.ReadAllLines("PostIdsToCopy.txt")
|> Array.map (fun x -> CopyPost x)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore;
printfn "Deleting old posts..."
File.ReadAllLines("PostIdsToCopy.txt")
|> Array.map (fun x -> DeletePost x)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore;