在 C# 中聚合 Web 内容 (股票评级示例)






3.55/5 (8投票s)
演示网页内容的聚合和模式匹配。
引言
随着技术的不断发展和新的内容分发方法的出现,仍然需要能够以原始内容分发者可能从未设想过的方式处理、解析、操作和聚合数据。通过 RSS、XML、Web 浏览器等,获取数据从未如此容易;然而,提炼这些方式传递的数据,使其只包含我们想要的内容,有时会带来相当大的挑战。
例如,假设我们想从一个您经常访问的网站聚合 10 个营业地点的本地天气。为了完成这样的任务,我们将不得不打开一个 Web 浏览器,连接到该网站,并通过该网站的前端运行 10 个查询才能获得所需的数据。但是,使用各种 .NET 类和正则表达式,这样的任务可以用很少的代码以显著更快的速度完成。
或者,我们也可以应用这些技术来从一个源(如网站)聚合数据,并将其格式化以供多个设备(如掌上电脑或媒体中心应用程序)使用。这种概念在各种平台上有过一些引人入胜的实现,例如 XBOX Media Center 和 MythTV,它们已将这些原则应用于聚合音乐视频、电影放映时间、RSS feed 和网络广播流。
法律问题
当然,在消费您可能不拥有的内容并将其修改为适合您需求时,会产生法律和道德问题。本文假定您拥有要聚合的内容,或者您已获得内容所有者的许可,可以在其预期用途之外消费该数据。
示例应用程序 - 股票评级聚合器
问题
John Q. Public 奉行一种激进的投资策略,他在收到 ACME 公司的薪水后,每两周就会重复一遍同样的过程。
- 他登录 MSN 的 Money 网站并查看他的投资组合。
- 他已经有了一个他经常投资的 15 家公司池,所以他会快速浏览它们的表现。
- 他的经纪人每周只允许他交易一次,所以他每周都会更换股票(从他的 15 家公司列表中选择)。
- 由于他想明智地选择本周投资哪只股票,他登录 MSN 的 MoneyCentral 网站,逐一查看他的列表,输入每只股票的代码,以查看备受尊敬的 Stock Scouter 评级。
- 在被 SIRI(Sirius Satellite Radio)坑过之后,John 决定不投资任何评级低于 8(满分 10 分)的股票。
John 渴望有一天 MSN 能提供某种 RSS feed 来为他聚合股票评级,但由于这不可用,他接受了每两周(按 ACME 的时间计算)将花费多达一小时手动完成这项工作的现实。如果 John Q. Public 是一位 .NET 程序员,他就会知道像这样的任务自动化起来有多么容易!
解决方案
John 是初学者,所以我们想让他觉得简单,因此我们希望确保源代码中不会硬编码任何内容。(在本例中,我们将打破“始终面向接口编程而非实现”的 OOP 规则,但 John 不知道这些!)为了存储变量,我们将使用一个 XML 文件。然而,XML 文件中有太多标签无法在此处显示,您可以在项目文件中查看。XML 文件存储了 John 的所有股票代码、他将用于解析网页流的正则表达式,以及他想要解析的网站的 URL。
我们只需要三个类和一个主入口点(非常简单)。这些类是
Stock
- 解析 HTML 以查找股票评级。HTTPParser
- 获取流(原始 HTML)。Settings
- 封装 XML 设置文件中的值。
应用程序入口点
static void Main(string[] args)
{
Program stockScouter = new Program();
stockScouter.init();
Console.ReadLine();
}
private void init()
{
//Load the Application settings from the xml file
Settings xmlConfiguration = new Settings();
//Enumerate the stock symbols
foreach (string stockSymbol in xmlConfiguration.GetStocks)
{
//Parse the searchUrl and display the rating
string stockSearchUrl = xmlConfiguration.BaseUrl + stockSymbol;
Stock getStock = new Stock(stockSearchUrl,
xmlConfiguration.Pattern);
Console.WriteLine(stockSymbol + " " + getStock.GetRating);
}
}
HTTP 解析器(StreamReader)
这个类中有几个属性,但我将介绍核心方法。这个类需要引用 System.Net
和 System.IO
命名空间。它非常简单,我们在实例化时只请求一个搜索 URL,并将其设置为本地字段/公共属性。Parse
方法创建一个新的 Web 请求,并将整个页面的 HTML 代码保存在本地字段/公共属性中。
public HTTPParser(string url)
{
this.url = url;
}
public void Parse()
{
string cachedStream = string.Empty;
HttpWebRequest myWebRequest = (HttpWebRequest)WebRequest.Create(Url);
HttpWebResponse siteResponse = (HttpWebResponse)myWebRequest.GetResponse();
Stream streamResponse = siteResponse.GetResponseStream();
StreamReader reader = new StreamReader(streamResponse, Encoding.UTF8);
cachedStream = reader.ReadToEnd();
reader.Close();
siteResponse.Close();
streamResponse.Close();
Html = cachedStream;
}
Stock 类
理想情况下,这个类应该包含股票名称、代码、价格、评级等,但我们保持简单,所以这个类只作为股票的物理表示。它的目的是从字符串(存储在 HTTP 解析器中)中提取股票的评级。
class Stock
{
HTTPParser msnStockStream;
private string cachedMsnStream = string.Empty;
private string rating = string.Empty;
private string RegExPattern = string.Empty;
public string GetRating
{
get{ return rating; }
}
public Stock(string url, string RegExPattern)
{
this.RegExPattern = RegExPattern;
//Create the instance of the Parser
msnStockStream = new HTTPParser(url);
//kick off the HTTPParser to get the page
msnStockStream.Parse();
//Save the HTML in a local variable
cachedMsnStream = msnStockStream.GetHtml;
getRating();
}
private void getRating()
{
//Match our Pattern
rating = Regex.Match(cachedMsnStream,
RegExPattern).Value;
rating = cleanUnwantedChars(rating);
}
private string cleanUnwantedChars(string matchedString)
{
//custom function to remove the extra quotes
return matchedString.Replace("\"", "");
}
}
Settings 类
Settings
类封装了我们的 settings.xml 文件,因此我们只需要 System.Xml
的一个实例来访问我们所有的设置。这个类只有一个方法,其余的代码(在发布的源代码中)仅用于提供友好的 getter 方法。
private void Read()
{
symbols = new ArrayList();
string xmlFilePath = System.IO.Directory.GetCurrentDirectory() +
"\\settings.xml";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFilePath);
XmlNode stockSettings = xmlDoc.SelectSingleNode("//Stocks");
for (int i = 0; i < stockSettings.ChildNodes.Count; i++)
symbols.Add(stockSettings.ChildNodes[i].InnerText);
XmlNode RegExSettings = xmlDoc.SelectSingleNode("//RegEx");
pattern = RegExSettings.ChildNodes[0].InnerText;
baseUri = RegExSettings.ChildNodes[1].InnerText;
}
结论
在五分钟或更短的时间内,John Q. Public 就能够编写几个简单的 C# 类,并自动化一个消耗了他生命中许多时间的流程!由于效率的提高,他现在有更多的时间可以忽略工作时响个不停的电话,并假装很忙!
Net.HttpWebRequest
、Net.HttpResponse
、IO.Stream
、IO.StreamReader
和 RegularExpressions.Regex
类似乎是天作之合,当它们结合使用时。
负责任地编码!