65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.55/5 (8投票s)

2007年3月21日

CPOL

5分钟阅读

viewsIcon

42397

downloadIcon

445

演示网页内容的聚合和模式匹配。

Screenshot - stockScout.jpg

引言

随着技术的不断发展和新的内容分发方法的出现,仍然需要能够以原始内容分发者可能从未设想过的方式处理、解析、操作和聚合数据。通过 RSS、XML、Web 浏览器等,获取数据从未如此容易;然而,提炼这些方式传递的数据,使其只包含我们想要的内容,有时会带来相当大的挑战。

例如,假设我们想从一个您经常访问的网站聚合 10 个营业地点的本地天气。为了完成这样的任务,我们将不得不打开一个 Web 浏览器,连接到该网站,并通过该网站的前端运行 10 个查询才能获得所需的数据。但是,使用各种 .NET 类和正则表达式,这样的任务可以用很少的代码以显著更快的速度完成。

或者,我们也可以应用这些技术来从一个源(如网站)聚合数据,并将其格式化以供多个设备(如掌上电脑或媒体中心应用程序)使用。这种概念在各种平台上有过一些引人入胜的实现,例如 XBOX Media Center 和 MythTV,它们已将这些原则应用于聚合音乐视频、电影放映时间、RSS feed 和网络广播流。

法律问题

当然,在消费您可能不拥有的内容并将其修改为适合您需求时,会产生法律和道德问题。本文假定您拥有要聚合的内容,或者您已获得内容所有者的许可,可以在其预期用途之外消费该数据。

示例应用程序 - 股票评级聚合器

问题

John Q. Public 奉行一种激进的投资策略,他在收到 ACME 公司的薪水后,每两周就会重复一遍同样的过程。

  1. 他登录 MSN 的 Money 网站并查看他的投资组合。
  2. 他已经有了一个他经常投资的 15 家公司池,所以他会快速浏览它们的表现。
  3. 他的经纪人每周只允许他交易一次,所以他每周都会更换股票(从他的 15 家公司列表中选择)。
  4. 由于他想明智地选择本周投资哪只股票,他登录 MSN 的 MoneyCentral 网站,逐一查看他的列表,输入每只股票的代码,以查看备受尊敬的 Stock Scouter 评级。
  5. 在被 SIRI(Sirius Satellite Radio)坑过之后,John 决定不投资任何评级低于 8(满分 10 分)的股票。

John 渴望有一天 MSN 能提供某种 RSS feed 来为他聚合股票评级,但由于这不可用,他接受了每两周(按 ACME 的时间计算)将花费多达一小时手动完成这项工作的现实。如果 John Q. Public 是一位 .NET 程序员,他就会知道像这样的任务自动化起来有多么容易!

解决方案

John 是初学者,所以我们想让他觉得简单,因此我们希望确保源代码中不会硬编码任何内容。(在本例中,我们将打破“始终面向接口编程而非实现”的 OOP 规则,但 John 不知道这些!)为了存储变量,我们将使用一个 XML 文件。然而,XML 文件中有太多标签无法在此处显示,您可以在项目文件中查看。XML 文件存储了 John 的所有股票代码、他将用于解析网页流的正则表达式,以及他想要解析的网站的 URL。

我们只需要三个类和一个主入口点(非常简单)。这些类是

  1. Stock - 解析 HTML 以查找股票评级。
  2. HTTPParser - 获取流(原始 HTML)。
  3. 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.NetSystem.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.HttpWebRequestNet.HttpResponseIO.StreamIO.StreamReaderRegularExpressions.Regex 类似乎是天作之合,当它们结合使用时。

负责任地编码!

© . All rights reserved.