之前在開發部落格園新聞用戶端的時候,需要擷取部落格園的新聞資料,最早開發出來的版本使用的是手機版的 html 解釋的方式。效果算是做出來了,但是感覺擷取到的資料太備援,于是便查了一下有沒有相應的接口。皇天不負有心人,部落格園果然開放了些接口供我們使用。
部落格服務接口:http://wcf.open.cnblogs.com/blog/help
新聞服務接口:http://wcf.open.cnblogs.com/news/help
部落格服務:
Uri | Method | Description |
48HoursTopViewPosts/{itemCount} | GET | 48小時閱讀排行 |
bloggers/recommend/{pageIndex}/{pageSize} | 分頁擷取推薦部落格清單 | |
bloggers/recommend/count | 擷取推薦部落格總數 | |
bloggers/search | 根據作者名搜尋部落客 | |
post/{postId}/comments/{pageIndex}/{pageSize} | 擷取文章評論 | |
post/body/{postId} | 擷取文章内容 | |
sitehome/paged/{pageIndex}/{pageSize} | 分頁擷取首頁文章清單 | |
sitehome/recent/{itemcount} | 擷取首頁文章清單 | |
TenDaysTopDiggPosts/{itemCount} | 10天内推薦排行 | |
u/{blogapp}/posts/{pageIndex}/{pageSize} | 分頁擷取個人部落格文章清單 |
新聞服務:
GetData | 擷取新聞清單 | |
hot/{itemcount} | 擷取熱門新聞清單 | |
item/{contentId} | 擷取新聞内容 | |
item/{contentId}/comments/{pageIndex}/{pageSize} | 擷取新聞評論 | |
recent/{itemcount} | 擷取最新新聞清單 | |
recent/paged/{pageIndex}/{pageSize} | 分頁擷取最新新聞清單 | |
recommend/paged/{pageIndex}/{pageSize} | 分頁擷取推薦新聞清單 |
可以看見,部落格園官方團隊還算是挺厚道的,基本的接口都開放出來了。(除了部落格文章按分類擷取-_-|||部落客我的碎碎念)
由于需要盡可能使我們封裝好的類庫盡可能在多個平台使用,于是乎就想到了使用可移植類庫(PCL)來開發。
建起項目

當然是通通選上,哪知道以後哪天會不會心血來潮再搞個什麼平台的用戶端。
因為就分兩大類,于是果斷碼上 BlogService 和 NewsService 兩個靜态類。
作為相應服務的入口。
接下來,看見新聞的接口比較少,先寫這部分。
測試 GetData 接口。
啥玩意?!2012年的資料!那這個就不理他了。。。
接下來就是 hot(擷取熱門新聞清單)這個接口。
測試下:http://wcf.open.cnblogs.com/news/hot/10
工作良好,于是開始寫這個接口的封裝。
在 NewsService 中寫上
public static async Task<IEnumerable<News>> HotAsync(int itemCount)
{
// TODO
}
會發覺編譯不通過(廢話)。
于是建立一個News類先。修改方法。
編譯!
不通過!!!
看錯誤清單:
不是4.5了麼?怎麼不能用 async?
解決方法:http://www.cnblogs.com/h82258652/p/4119118.html(注:本文為總結性文章,是以時間線的跳躍你們要跟上)
裝好巨硬給我們的異步更新檔包後就可以編譯通過了。
接下來觀察接口傳回的xml文檔,可以發現每一個entry節點就相當于一條新聞。
根據entry分析,完善News類:
1 using System;
2
3 namespace SoftwareKobo.CnblogsAPI.Model
4 {
5 /// <summary>
6 /// 新聞。
7 /// </summary>
8 public class News
9 {
10 /// <summary>
11 /// Id。
12 /// </summary>
13 public int Id
14 {
15 get;
16 internal set;
17 }
18
19 /// <summary>
20 /// 标題。
21 /// </summary>
22 public string Title
23 {
24 get;
25 internal set;
26 }
27
28 /// <summary>
29 /// 摘要。
30 /// </summary>
31 public string Summary
32 {
33 get;
34 internal set;
35 }
36
37 /// <summary>
38 /// 發表時間。
39 /// </summary>
40 public DateTime Published
41 {
42 get;
43 internal set;
44 }
45
46 /// <summary>
47 /// 更新時間。
48 /// </summary>
49 public DateTime Updated
50 {
51 get;
52 internal set;
53 }
54
55 /// <summary>
56 /// 新聞連結。
57 /// </summary>
58 public Uri Link
59 {
60 get;
61 internal set;
62 }
63
64 /// <summary>
65 /// 推薦數。
66 /// </summary>
67 public int Diggs
68 {
69 get;
70 internal set;
71 }
72
73 /// <summary>
74 /// 檢視數。
75 /// </summary>
76 public int Views
77 {
78 get;
79 internal set;
80 }
81
82 /// <summary>
83 /// 評論數。
84 /// </summary>
85 public int Comments
86 {
87 get;
88 internal set;
89 }
90
91 /// <summary>
92 /// 主題。
93 /// </summary>
94 public string Topic
95 {
96 get;
97 internal set;
98 }
99
100 /// <summary>
101 /// 主題圖示。
102 /// </summary>
103 public Uri TopicIcon
104 {
105 get;
106 internal set;
107 }
108
109 /// <summary>
110 /// 轉載自。
111 /// </summary>
112 public string SourceName
113 {
114 get;
115 internal set;
116 }
117 }
118 }
View Code
PS:Q:為什麼set方法都寫為internal?A:為什麼外部要修改?Q:好吧,你赢了。
接下來就開始寫我們的 HotAsync 方法了。
HotAsync 方法有一個參數——itemCount,那麼當然要進行驗證。(不發送這些無謂的請求一方面可以不讓使用者等待、一方面減輕部落格園的壓力)
if (itemCount < 1)
{
throw new ArgumentOutOfRangeException(nameof(itemCount));
}
nameof,還沒跟上時代節奏的小夥伴就趕緊跟上了,這裡不解釋。
接下來當然是拼接 url。
var url = string.Format(CultureInfo.InvariantCulture, HotUrlTemplate, itemCount);
var uri = new Uri(url, UriKind.Absolute);
HotUrlTemplate 的定義:
private const string HotUrlTemplate = "http://wcf.open.cnblogs.com/news/hot/{0}";
常量最好不要出現在方法中這是好習慣哦。
準備WebRequest,并且調用GetResponse。
var request = WebRequest.Create(uri);
using (var response = await request.GetResponseAsync())
{
// TODO
}
由于傳回的是一個xml,是以直接使用 XDocument 加載(PS:我特讨厭XmlDocument那套API)
var document = XDocument.Load(response.GetResponseStream());
接下來就是将XDocument轉換為News實體類的清單,這裡我們寫到别的方法去,因為下面幾個服務接口可能也會用到。
建立NewsHelper類。
根據entry節點的結果,寫出以下代碼:
1 internal static class NewsHelper
2 {
3 internal static IEnumerable<News> Deserialize(XDocument document)
4 {
5 var root = document?.Root;
6 if (root == null)
7 {
8 return null;
9 }
10
11 var ns = root.GetDefaultNamespace();
12 var news = from entry in root.Elements(ns + "entry")
13 where entry.HasElements
14 let temp = Deserialize(entry)
15 where temp != null
16 select temp;
17 return news;
18 }
19
20 internal static News Deserialize(XElement element)
21 {
22 if (element == null)
23 {
24 return null;
25 }
26
27 var ns = element.GetDefaultNamespace();
28 var id = element.Element(ns + "id");
29 var title = element.Element(ns + "title");
30 var summary = element.Element(ns + "summary");
31 var published = element.Element(ns + "published");
32 var updated = element.Element(ns + "updated");
33 var href = element.Element(ns + "link")?.Attribute("href");
34 var diggs = element.Element(ns + "diggs");
35 var views = element.Element(ns + "views");
36 var comments = element.Element(ns + "comments");
37 var topic = element.Element(ns + "topic");
38 var topicIcon = element.Element(ns + "topicIcon");
39 var sourceName = element.Element(ns + "sourceName");
40
41 if (id == null
42 || title == null
43 || summary == null
44 || published == null
45 || updated == null
46 || href == null
47 || diggs == null
48 || views == null
49 || comments == null
50 || topic == null
51 || topicIcon == null
52 || sourceName == null)
53 {
54 return null;
55 }
56
57 return new News
58 {
59 Id = int.Parse(id.Value, CultureInfo.InvariantCulture),
60 Title = WebUtility.HtmlDecode(title.Value),
61 Summary = WebUtility.HtmlDecode(summary.Value),
62 Published = DateTime.Parse(published.Value, CultureInfo.InvariantCulture),
63 Updated = DateTime.Parse(updated.Value, CultureInfo.InvariantCulture),
64 Link = new Uri(href.Value, UriKind.Absolute),
65 Diggs = int.Parse(diggs.Value, CultureInfo.InvariantCulture),
66 Views = int.Parse(views.Value, CultureInfo.InvariantCulture),
67 Comments = int.Parse(comments.Value, CultureInfo.InvariantCulture),
68 Topic = topic.Value,
69 TopicIcon = topicIcon.IsEmpty ? null : new Uri(topicIcon.Value, UriKind.Absolute),
70 SourceName = sourceName.Value
71 };
72 }
73 }
由于我們需要的是IEnumerable<News>,是以直接傳回Linq的結果就行了,不用ToList或者啥的。
注意PCL中是沒有HtmlDecode、HtmlEncode的,nuget上有一個PCL用的,可惜人家作者沒更新了,版本過舊,引用不了,沒辦法,自己動手豐衣足食。
項目位址:https://github.com/h82258652/SoftwareKobo.Net.WebUtility
Nuget位址:https://www.nuget.org/packages/SoftwareKobo.Net.WebUtility/
也是自己随便寫的,反正能處理一下常見的字元就算了。(=_=)(巨硬的源碼我實在是看不懂。。還打算照抄的……)
接下來回到 HotAsync 補充最後一句:
return NewsHelper.Deserialize(document);
完事。
其他什麼 Recent(最新的)、Recommend(推薦)如法炮制。(Recent有兩個接口,選擇分頁那個好了,反正感覺部落格園内部實作也是重載)
接下來看新聞内容這個接口:
寫上 DetailAsync 方法:
public static async Task<NewsDetail> DetailAsync(int newsId)
當然也有建上NewsDetail類。
1 /// <summary>
2 /// 新聞内容。
3 /// </summary>
4 public class NewsDetail
5 {
6 /// <summary>
7 /// Id。
8 /// </summary>
9 public int Id
10 {
11 get;
12 internal set;
13 }
14
15 /// <summary>
16 /// 标題。
17 /// </summary>
18 public string Title
19 {
20 get;
21 internal set;
22 }
23
24 /// <summary>
25 /// 轉載自。
26 /// </summary>
27 public string SourceName
28 {
29 get;
30 internal set;
31 }
32
33 /// <summary>
34 /// 發表時間。
35 /// </summary>
36 public DateTime SubmitDate
37 {
38 get;
39 internal set;
40 }
41
42 /// <summary>
43 /// 内容。
44 /// </summary>
45 public string Content
46 {
47 get;
48 internal set;
49 }
50
51 /// <summary>
52 /// 新聞中用到的圖檔的路徑。
53 /// </summary>
54 public ReadOnlyCollection<Uri> ImageUrl
55 {
56 get;
57 internal set;
58 }
59
60 /// <summary>
61 /// 上一條新聞的 Id。
62 /// </summary>
63 public int? PrevNews
64 {
65 get;
66 internal set;
67 }
68
69 /// <summary>
70 /// 下一條新聞的 Id。
71 /// </summary>
72 public int? NextNews
73 {
74 get;
75 internal set;
76 }
77
78 /// <summary>
79 /// 評論數。
80 /// </summary>
81 public int CommentCount
82 {
83 get;
84 internal set;
85 }
86 }
注意:
1、ImageUrl 用了 ReadOnlyCollection,原因同上,外部沒必要修改。
2、PreNews 和 NextNews 使用可空類型,因為不一定有上一條或下一條。(都最新了,還能有下一條麼)
接下來也是寫個Helper,将Xml的内容轉換為對象清單。完事。
剩下來擷取評論也是差不多。
部落格方面的接口封裝基本上也是這麼搞,注意的是新聞的評論和部落格文章的評論可以用同一個模型來表達。
都寫完後,感覺擷取新聞擷取評論不友善啊,得先通路Id,再調NewsService。天生懶,沒辦法,寫個擴充方法。
1 /// <summary>
2 /// 新聞擴充。
3 /// </summary>
4 public static class NewsExtension
5 {
6 /// <summary>
7 /// 擷取新聞評論。
8 /// </summary>
9 /// <param name="news">新聞。</param>
10 /// <param name="pageIndex">第幾頁,從 1 開始。</param>
11 /// <param name="pageSize">每頁條數。</param>
12 /// <returns>新聞評論。</returns>
13 /// <exception cref="ArgumentNullException">新聞為 null。</exception>
14 public static async Task<IEnumerable<Comment>> CommentAsync(this News news, int pageIndex, int pageSize)
15 {
16 if (news == null)
17 {
18 throw new ArgumentNullException(nameof(news));
19 }
20 return await NewsService.CommentAsync(news.Id, pageIndex, pageSize);
21 }
22
23 /// <summary>
24 /// 擷取新聞内容。
25 /// </summary>
26 /// <param name="news">新聞。</param>
27 /// <returns>新聞内容。</returns>
28 /// <exception cref="ArgumentNullException">新聞為 null。</exception>
29 public static async Task<NewsDetail> DetailAsync(this News news)
30 {
31 if (news == null)
32 {
33 throw new ArgumentNullException(nameof(news));
34 }
35 return await NewsService.DetailAsync(news.Id);
36 }
37 }
Q:為什麼不在News類裡寫?A:沒什麼,保持模型純正而已。-_-|||也好管理。
完事了,Release編譯,弄到nuget包裡,釋出。測試添加引用,沒注釋!!!
回到項目,修改項目屬性,生成XML,勾上!!
把XML一同弄到nuget包裡,再釋出,測試,好了。
At The End:
項目位址:https://github.com/h82258652/SoftwareKobo.CnblogsAPI
Nuget位址:https://www.nuget.org/packages/SoftwareKobo.CnblogsAPI/
部落客我(h82258652)的話:有什麼建議歡迎在下面評論提,畢竟部落客我也是菜鳥。
↑重點當然要大隻字!