csdn论坛公开了一些常用api,不过内部测试阶段,地址是http://forum.csdn.net/OpenApi/forumapi.asmx还有一个使用的demo,http://forum.csdn.net/OpenApi/ForumOpenAPIDemo.rar,源码在这里下载demo源码
总体概述:
公开的方法如下:
- CheckOutTopic :结贴
- GetForums :获得论坛列表
- GetTopicsOfUser :获得我的帖子,我参与的帖子,我得分的帖子,别人问我的帖子
- GetUserProfile :获得用户资料
- PointDonate :积分捐赠
- Post :发帖
- Reply :回帖
至于获得帖子和获得帖子列表的方法,虽然没有提供独立的api,但是都可以借助论坛现有的资源,待会会单独讲到
上面的API除了GetForums外,都需要输入一个identity实体,表明你的身份,返回了一个bool型变量,表示操作是否完成,结果会以out变量的形式输出,同时输出的一般还有错误信息Error
identity的参考定义如下
/// 用户身份信息
/// </summary>
public struct Identity{
/// <summary>
/// 用户名
/// </summary>
public string username;
/// <summary>
/// 密码
/// </summary>
public string password;
}
他包含用于身份验证的用户名和密码,除获得论坛列表以外,其他的操作均要求此参数,调用者可以将用户信息加密存与本地,详细请参考账户管理
而错误信息的参考定义如下
/// 错误信息
/// </summary>
public struct Error
{
/// <summary>
/// 错误id
/// </summary>
public int errId;
private string _errInfo;
/// <summary>
/// 错误信息
/// </summary>
public string errInfo;
/// <summary>
/// 描述
/// </summary>
public string description;
}
这个实体存放了调用过程中返回的错误,如果返回结果为false,我们就可以查看或者错误信息:
比如下面的积分捐赠的代码段
Error error;
if ( ! openApiService.PointDonate(dp.GetDefaultAccount(), tbUserName.Text, point, " abc " , out error))
ErrorForm.ShowDialog(error);
else
MessageBox.Show( " 捐赠成功 " );
获得论坛GetForums
GetForums 非常的简单,没有传入参数,方法的返回值是一个Forum实体数组
Forum的参考定义和具体字段含义如下
/// 论坛信息
/// </summary>
public struct Forum
{
/// <summary>
/// 论坛id
/// </summary>
public Guid forumId;
/// <summary>
/// 父论坛id
/// </summary>
public Guid parentForumId;
/// <summary>
/// 论坛名称
/// </summary>
public string name;
/// <summary>
/// 别名
/// </summary>
public string alias;
/// <summary>
/// 是否技术论坛
/// </summary>
public bool IsTech;
/// <summary>
/// 版主
/// </summary>
public string [] morderators;
/// <summary>
/// 积分归属论坛
/// </summary>
public Guid pointBelongsToForumId;
}
获得用户信息GetUserProfile :
方法定义如下:
/// 获得用户信息
/// </summary>
/// <param name="identity"> 用户身份信息 </param>
/// <param name="profile"> 用户信息 </param>
/// <param name="error"> 错误信息 </param>
/// <param name="username"> 需要获得用户信息的用户名 </param>
/// <returns> 操作是否成功 </returns>
public bool GetUserProfile(Identity identity, string username, out UserProfile profile, out Error error)
此方法用于查询某用户的用户信息,包括用户昵称,可用分,用户技术专家分,非技术专家分,以及他在各个论坛的得分和级别(只展示用户在其有得分的论坛信息)
用户信息UserProfile的参考定义和字段含义如下
{
/// <summary>
/// 可用分
/// </summary>
public int point;
/// <summary>
/// 技术专家分
/// </summary>
public int techExpertPoint;
/// <summary>
/// 用户在各个论坛的积分和级别信息
/// </summary>
public List < TopForum > topForums;
/// <summary>
/// 非技术专家分
/// </summary>
public int nonTechExpertPoint;
/// <summary>
/// 昵称
/// </summary>
public string nickName;
/// <summary>
/// 用户名
/// </summary>
public string username;
}
/// <summary>
/// 用户在各个论坛的积分和级别
/// </summary>
public struct TopForum{
/// <summary>
/// 论坛
/// </summary>
public Guid forumId;
/// <summary>
/// 专家分
/// </summary>
public int expertPoint;
/// <summary>
/// 星级
/// </summary>
public string rank;
}
发帖Post :
发帖方法定义如下
/// 发帖
/// </summary>
/// <param name="identity">用户身份证</param>
/// <param name="post">帖子</param>
/// <param name="error">错误信息</param>
/// <param name="topicUrl">帖子链接</param>
/// <returns>发帖是否成功</returns>
public bool Post(Identity identity, Post post, out Error error, out string topicUrl)
Post结构参考定义
/// 帖子
/// </summary>
public struct Post
{
/// <summary>
/// 论坛id(发帖时必须)
/// </summary>
public Guid forumId;
/// <summary>
/// 标题(发帖时必须)
/// </summary>
public string subject;
/// <summary>
/// 帖子内容(发帖时必须)
/// </summary>
public string body;
/// <summary>
/// 标签
/// </summary>
public string tag;
/// <summary>
/// 给分
/// </summary>
public int point;
/// <summary>
/// 是否问专家贴(发帖时必须)
/// </summary>
public bool isAskExpert;
/// <summary>
/// 专家用户名称(若isAskExpert,必须)
/// </summary>
public string expertUserName;
/// <summary>
/// 编辑器类型(发帖时必须),现只支持ubb类型
/// </summary>
public EditorType editor;
/// <summary>
/// 帖子链接
/// </summary>
public string url;
}
回帖Reply :
/// 回复帖子
/// </summary>
/// <param name="identity">用户身份证</param>
/// <param name="reply">回复</param>
/// <param name="error">错误信息</param>
/// <param name="replyId">回复id</param>
/// <param name="layer">楼层</param>
/// <returns>回复是否成功</returns>
public bool Reply(Identity identity, Reply reply, out Error error, out long replyId, out int layer)
回复实体参考定义如下
/// 回复
/// </summary>
public struct Reply
{
/// <summary>
/// 论坛id(必须)
/// </summary>
public Guid forumId;
/// <summary>
/// 帖子url(必须)
/// </summary>
public string topicUrl;
/// <summary>
/// 回复内容(必须)
/// </summary>
public string body;
/// <summary>
/// 是否需要ubb(必须)
/// </summary>
public EditorType editor;
}
结帖CheckOutTopic:
/// 结贴
/// </summary>
/// <param name="identity"> 用户身份证 </param>
/// <param name="topicUrl"> 帖子链接 </param>
/// <param name="forumId"> 论坛id </param>
/// <param name="replyPoints"> 回复给分列表 </param>
/// <param name="error"> 错误 </param>
/// <returns> 结贴是否成功 </returns>
public bool CheckOutTopic(Identity identity, string topicUrl, Guid forumId, List < ReplyPoint > replyPoints, out Error error)
List<ReplyPoint> replyPoints为回复id和给分数组
/// 回帖得分
/// </summary>
public struct ReplyPoint
{
/// <summary>
/// 回复id
/// </summary>
public long replyId;
/// <summary>
/// 得分
/// </summary>
public int point;
}
积分捐赠PointDonate
/// 可用分捐赠
/// </summary>
/// <param name="identity"> 用户身份证 </param>
/// <param name="toUser"> 捐赠对象 </param>
/// <param name="point"> 捐赠积分 </param>
/// <param name="reason"> 原因 </param>
/// <param name="error"> 错误 </param>
/// <returns> 捐赠是否成功 </returns>
public bool PointDonate(Identity identity, string toUser, int point, string reason, out Error error)
获得我的帖子,我参与的帖子,我得分的帖子,别人问我的帖子 GetTopicsOfUser
/// 获得我发表的帖子,我回复过的帖子,我得分的帖子
/// </summary>
/// <param name="listType"> 列表类型 </param>
/// <param name="forumId"> 论坛id </param>
/// <param name="posts"> 帖子列表 </param>
/// <param name="error"> 错误信息 </param>
/// <param name="identity"> 身份信息 </param>
/// <returns> 是否成功 </returns>
[WebMethod]
public bool GetTopicsOfUser(Identity identity, UserTopicListType listType, Guid forumId, out List < Post > posts, out Error error)
列表类型定义如下
/// 用户帖子列表类型
/// </summary>
public enum UserTopicListType
{
/// <summary>
/// 用户的帖子
/// </summary>
TopicOfUser,
/// <summary>
/// 用户回复过的帖子
/// </summary>
TopicUserJoined,
/// <summary>
/// 用户得分的帖子
/// </summary>
TopicUserRewarded,
/// <summary>
/// 所有问专家
/// </summary>
AllAskExpert
}
获得帖子列表
获得帖子列表,包括
- 待解决
- 抢分区
- 零回复
- 热点区
- 已解决
- 精华区
没有提供独立的Webservice,原因是这些帖子列表均提供了Rss,调用者通过Rss获得需要的信息
一个列子列表的rss链接由论坛别名和列表类型两部分组成
比如灌水乐园抢分区的Rss链接为http://forum.csdn.net/Rss/FreeZone/RobPointList/
其中黄色部分(FreeZone)为论坛别名,红色部分(RobPointList)表明列表类型为抢分区,我们可以通过如下代码简单实现取得rss并转为dataset
public DataTable GetTopicListRss()
{
string url = " http://forum.csdn.net/Rss/FreeZone/RobPointList/ " ;
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
WebResponse response = request.GetResponse();
DataSet result = new DataSet();
Stream rssStream = response.GetResponseStream();
StreamReader sr = new StreamReader(rssStream, Utility.GetEncoding());
result.ReadXml(sr);
return result.Tables[ 2 ];
}获得与解析帖子
公开的API也没专门获得帖子的方法,主要是处于性能的考虑,想要获得帖子,就直接获取帖子html文件,如果需要帖子的信息,比如发帖人,分数,就必须解析帖子文件,文件中提供了一系列标识(csdnid),让解析者可以通过其找到对应的内容,并且在所附demo中,也提供了一个经过改造的解析模块,调用者可以使用这个模块,通过csdnid来找到帖子文件中具体的内容
什么是csdnid?
打开任意一个帖子文件,里面都会看到一些由csdnid标识的元素,这些元素的属性和内容一般都具有特殊的意义,比如帖子源文件中的下面html代码
< meta id ="topicViewUrl" csdnid ="topicViewUrl" content ="http://topic.csdn.net/u/20080328/15/ce3f9a96-7f91-4dea-83fb-23beffe36cb8.html" >
< meta csdnid ="sectionId" content ="a3049f56-b572-48f5-89be-4797b70d71cd" >csdnid="topicViewUrl" 的meta元素的content属性,说明了帖子的url,为:http://topic.csdn.net/u/20080328/15/ce3f9a96-7f91-4dea-83fb-23beffe36cb8.html
而csdnid="sectionId"的meta元素的content属性,说明了帖子的论坛id为:
a3049f56-b572-48f5-89be-4797b70d71cd而<var csdnid="topicUsername" id="topicUserName">Orange1997</var>中,此元素的innerHTML为发帖用户名
如何解析帖子文件并得到我们想要的信息
解析html文件有很多方法,这里使用使用经过改进的开源html解析其HtmlAgilityPack,Demo中有此模块,
-
基本使用方法
加载Html文件
下面方法可以把某个html加载进来
HtmlDocument d = new HtmlDocument();
d.Load("C:/test.html");Load方法还有多个重载,可以从Stream,StreamReader等对象中加载html文档
加载后使用GetElementsbyCsdnId来获得指定csdnid标识的元素,比如
d.GetElementsbyCsdnId("topicBody"),获得所有用csdnid="topicBody"标识的元素
注意这里的返回值是一个元素数组,因为csdnid和id属性不同,是可以重复的;
下面的代码是demo中用于解析帖子文件的方法,详细使用请看demo源码
private InternalTopic ParseFile(StreamReader reader){
InternalTopic post = new InternalTopic();
HtmlDocument d = new HtmlDocument();
d.Load(reader);
post.body = ((HtmlNode)d.GetElementsbyCsdnId( " topicBody " )[ 0 ]).InnerHtml;
post.forumId = new Guid(((HtmlNode)d.GetElementsbyCsdnId( " sectionId " )[ 0 ]).Attributes[ " content " ].Value);
post.subject = ((HtmlNode)d.GetElementsbyCsdnId( " topicSubject " )[ 0 ]).InnerHtml;
post.point = int .Parse(((HtmlNode)d.GetElementsbyCsdnId( " topicPoint " )[ 0 ]).InnerHtml);
post.tags = ((HtmlNode)d.GetElementsbyCsdnId( " keywords " )[ 0 ]).Attributes[ " content " ].Value;
post.username = ((HtmlNode)d.GetElementsbyCsdnId( " topicUsername " )[ 0 ]).InnerHtml;
post.postDate = DateTime.Parse(((HtmlNode)d.GetElementsbyCsdnId( " topicPostDate " )[ 0 ]).InnerHtml);
post.topicUrl = ((HtmlNode)d.GetElementsbyCsdnId( " topicViewUrl " )[ 0 ]).Attributes[ " content " ].Value;
Guid topicId;
DateTime postDate;
if ( ! Utility.TryParseTopicUrl(post.topicUrl, out postDate, out topicId))
{
throw new ArgumentException( " 错误的帖子链接 " );
}
ArrayList replylist = d.GetElementsbyCsdnId( " replyId " );
if (replylist != null )
{
foreach (HtmlNode n in replylist)
{
long rid = long .Parse(n.Attributes[ " name " ].Value);
post.replies.Add(ParseReply(d, rid));
}
}
string dataPath = Utility.WriteData(topicId.ToString() + " .xml " , typeof (InternalTopic), post);
return post;
}
/// <summary>
/// 解析回复
/// </summary>
/// <param name="d"> html文件 </param>
/// <param name="rid"> 回复id </param>
/// <returns></returns>
private InternalReply ParseReply(HtmlDocument d, long rid)
{
HtmlNode replyTable = d.GetElementsbyCsdnId( " reply_ " + rid.ToString())[ 0 ] as HtmlNode;
HtmlDocument replyHtml = new HtmlDocument();
replyHtml.LoadHtml(replyTable.OuterHtml);
InternalReply reply = new InternalReply();
reply.replyId = rid;
reply.username = ((HtmlNode)replyHtml.GetElementsbyCsdnId( " replyUsername " )[ 0 ]).InnerHtml;
reply.replyDate = DateTime.Parse(((HtmlNode)replyHtml.GetElementsbyCsdnId( " replyDate " )[ 0 ]).InnerHtml);
reply.body = ((HtmlNode)replyHtml.GetElementsbyCsdnId( " replyBody " )[ 0 ]).InnerHtml;
reply.point = int .Parse(((HtmlNode)replyHtml.GetElementsbyCsdnId( " replyPoint " )[ 0 ]).InnerHtml);
reply.layer = int .Parse(((HtmlNode)replyHtml.GetElementsbyCsdnId( " replyLayer " )[ 0 ]).InnerHtml);
return reply;
} -
常用csdnid参考
csdnid 属性 描述 topicPostDate InnerHTML 发帖时间 topicBody InnerHTML 帖子内容 sectionId content 论坛id isPrime class 是否精华,为空则不是精华 isCheckOut InnerHTML 是否已结,为空则未结 topicPoint InnerHTML 给分 topicUsername InnerHTML 发帖用户 description content 标题 topicViewUrl content 帖子Url replyId name 回复id replyCount InnerHTML 回复数 reply_{id} OuterHTML 指定id的回复区域 replyUsername InnerHTML 回复用户名 replyNickname InnerHTML 回复用户昵称 replyDate InnerHTML 回复日期 replyLayer InnerHTML 回复楼层 replyPoint InnerHTML 回复得分 replyBody InnerHTML 回复内容 错误ID
错误id由四位二进制数表示,前两位为功能号,后两位为具体错误号,比如错误0301,表明为回复功能的内容为空错误
功能号列表
00:通用
01:结贴
02:发帖
03:回复
04:积分捐赠
每个功能号的具体错误,将另行文档说明