获取所有文章ID
首先,我们便要下载所有文章了,这又该怎么做呢?虽然MetaWeblog API提供了 getRecentPosts方法用来获取最近的文章,但是这个接口却并不好用。例如,它只能用来 获取最新的几篇文章内容,但对我来说,我想修改的其实是很久之前的文章。那么,难道 要我下载全部500多篇文章才行吗?后来我统计了一下,所有文章大小存成文本文件大约 有10M,一个请求下载 10M内容还是有些夸张的——而且还看不到进度。因此,我最后打 算“曲线救国”,先着手获得所有公开文章的ID,再通过getPost接口获得文章内容。
MetaWeblog API并不提供获取所有文章ID的接口,但这并不影响我们从网页上直接进 行抓取。我们从博客园提供的“月份汇总”页面入手,即这样的一张页面。博客园的月份 汇总的URL非常有规律,获得它的HTML内容之后即可使用正则表达式来捕获文章ID了。您 可能会想,一篇文章可能会取别名(这样URL上就不显示ID了),而网页上各种URL也很多 ,有什么办法可以准确而方便地分析出文章ID吗?其实这个问题很简单,因为博客园为每 篇文章都放置了一个“编辑”链接,它的URL是.../EditPosts.aspx?postid=1633416,对 我们来说再方便不过了。
于是下载和捕获文章ID的方法可谓手到擒来:
type WebClient with
member c.GetStringAsync(url) =
async {
let completeEvent = c.DownloadStringCompleted
do c.DownloadStringAsync(new Uri(url))
let! args = Async.AwaitEvent(completeEvent)
return args.Result
}
let downloadPostIdsAsync (beginMonth : DateTime) (endMonth : DateTime) =
let downloadPostIdsAsync' (m : DateTime) =
async {
let webClient = new WebClient()
let url = sprintf "http://www.cnblogs.com/JeffreyZhao/archive/%i/%i.html" m.Year m.Month
let! html = webClient.GetStringAsync(url)
let regex = @"EditPosts\.aspx\?postid=(\d+)"
return [ for m in Regex.Matches(html, regex) -> m.Groups.Item(1).Value |> Int32.Parse ]
}
async {
let! lists =
Seq.initInfinite (fun i -> beginMonth.AddMonths(i))
|> Seq.takeWhile (fun m -> m <= endMonth)
|> Seq.map downloadPostIdsAsync'
|> Async.Parallel
lists
|> List.concat
|> List.sort
|> List.map (fun i -> i.ToString())
|> fun lines -> File.WriteAllLines("postIds.txt", lines)
}
下载文章ID的任务由downloadPostIdsAsync函数完成,它接受beginMonth和endMonth 两个DateTime参数来表示月份的区间,我们将从中获取所有的文章ID。 downloadPostIdsAsync函数会生成一个异步工作流,执行这个工作流便会将所有的文章 ID进行排序,并保存至postIds.txt文件中去,一行一个。获取单月的文章ID由内部的 downloadPostIdsAsync'这个辅助函数负责,它会构造出下载单个月份文章ID的异步工作 流,再由外部函数合并而成——换句话说,所有月份的文章将同时进行异步下载,提高效 率。
我们可以在main方法中执行downloadPostIdsAsync函数,这样postIds.txt文件中便会 出现所有的文章ID了:
System.Net.ServicePointManager.DefaultConnectionLimit <- 10
Blogging.downloadPostIdsAsync (new DateTime(2006, 9, 1)) (new DateTime(2008, 12, 1))
|> Async.RunSynchronously
由于WebClient基于WebRequest对象实现,而WebRequest受到ServicePointManager控 制,因此我们要设置其DefaultConnectionLimit属性来打开对单个域名的限制——并控制 对并发连接的数量进行限制,以免对服务器产生太大压力(当然我们其实工作量本不大, 且博客园也不会那么脆弱)。当然,这个限制也可以通过配置进行更改。