獲取所有文章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屬性來打開對單個域名的限制——並控制 對並發連接的數量進行限制,以免對服務器產生太大壓力(當然我們其實工作量本不大, 且博客園也不會那麼脆弱)。當然,這個限制也可以通過配置進行更改。