CP Vanity Lite是由Luc Pattyn开发的CP Vanity应用的简化版本。Luc Pattyn经常需要修改解析代码以适应网站不可避免的更新,因此他停止更新该应用。这促使编写了这个应用,它是一个更小、更轻量级的版本。与CP Vanity不同,CP Vanity Lite不会深入细节,它只会获取那些很可能拥有高声誉分数的成员的总声誉。采用了与Luc相同的方法,即获取基于发帖数量和文章数量的顶级成员,然后获取这些成员的声誉分数。获取每个类别的前125名,但由于某些成员同时出现在两个组中,会发现实际获取声誉分数的成员总数少于250人。除了总分数外,它还显示基于总分数和成员注册天数计算的日平均分数。
警告:该应用程序从硬编码的URL获取并解析HTML。如果URL或HTML内容发生变化,解析代码将会失效。在CP提供返回此信息的网络服务之前,该应用程序的前提将是不稳固的。这同样适用于和像Luc Pattyn这样的人编写的类似应用程序。
使用这个应用程序非常简单,只需要运行应用程序并等待用户界面填充。标题栏将显示“获取中”,当HTTP获取正在进行时,一旦所有数据都可用,它将显示“完成”。用户界面是一个简单的ListView,可以在“声誉分数”列上进行排序。每次点击标题,排序就会在升序和降序之间切换,这非常方便,如果厌倦了看到CG的名字总是排在最上面!数据是通过工作线程异步填充的,所以在获取期间应用程序将始终保持响应。即使在所有数据都被获取之前,也可以对数据进行排序,但请记住,新添加的行将不会被排序,尽管可以随意排序多少次。一旦所有数据都被获取,"导出为CSV"按钮将被启用,可以保存一个CSV文件,然后可以在像Excel这样的应用程序中打开它,进行各种复杂的数据操作和图表。看看图2,会发现CG在竞争中遥遥领先,他的得分超过了John和Pete的总和!如果定期运行应用程序并保存CSV文件,将很好地为基于日期的数据解析做好准备,以确定分数的变化以及某人追赶的速度(如果是统计学好奇的类型)。
HTML抓取是在名为RepScoreScraper的单个类中完成的。代码将会阻塞,因此调用代码需要使用工作线程或某种异步/任务模式,以避免阻塞主线程。通过查询Who's who页面的顶级消息发布者和顶级文章作者,获取顶级潜在得分成员的ID。使用正则表达式提取信息。
C# private Regex memberNumberRegex = new Regex("Member No. (\\d*)");
private Regex repScoreRegex = new Regex("([\\s\\S]*?)");
private Regex displayNameRegex = new Regex("([\\s\\S]*?)
");
private Regex memberSinceRegex = new Regex("Member since (.+)\n");
请注意,已经剪切了代码片段中的URL,以防止水平滚动。
C# public void StartScraping() {
string[] ml_obs = new[] {
"ArticleCount",
"MessageCount"
};
HashSet ids = new HashSet();
for (int j = 0; j < ml_obs.Length; j++) {
for (int page = 1; page <= 5; page++) {
string url = String.Format("**SNIPPED**?ml_ob={0}&mgtid=-1&mgm=False&pgnum={1}", ml_obs[j], page);
string html = GetHttpPage(url, timeOut);
var memberNumberMatches = memberNumberRegex.Matches(html);
var repScoreMatches = repScoreRegex.Matches(html);
var displayNameMatches = displayNameRegex.Matches(html);
var memberSinceMatches = memberSinceRegex.Matches(html);
if (memberNumberMatches.Count == repScoreMatches.Count && memberNumberMatches.Count == displayNameMatches.Count && memberNumberMatches.Count == memberSinceMatches.Count) {
for (int i = 0; i < memberNumberMatches.Count; i++) {
int id = -1;
double score = -1.0;
double scorePerDay = 0.0;
DateTime memberSince = ParseDateTime(memberSinceMatches[i].Value);
if (memberNumberMatches[i].Groups.Count == 2 && Int32.TryParse(memberNumberMatches[i].Groups[1].Value, out id) && repScoreMatches[i].Groups.Count == 2 && Double.TryParse(repScoreMatches[i].Groups[1].Value, out score) && displayNameMatches[i].Groups.Count == 2) {
if (!ids.Contains(id)) {
ids.Add(id);
if (memberSince != DateTime.MinValue) {
scorePerDay = Math.Round(score / (DateTime.Now - memberSince).TotalDays, 2);
}
var handler = MemberInfoScraped;
if (handler != null) {
handler(this, new RepScoreScraperEventArgs() { Id = id, DisplayName = StripOffHtml(displayNameMatches[i].Groups[1].Value.Trim()), ReputationScore = (int)score, DailyAverage = scorePerDay });
}
}
}
}
}
}
}
var finishedHandler = ScrapeFinished;
if (finishedHandler != null) {
finishedHandler(this, EventArgs.Empty);
}
}