使用C#解析HTML的HTMLAgilityPack问题

3
我只是想学习HTMLAgilityPack和XPath,试图从纳斯达克网站获取(HTML链接)公司列表;

http://www.nasdaq.com/quotes/nasdaq-100-stocks.aspx

我目前有以下代码;

HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();

        // Create a request for the URL.        
        WebRequest request = WebRequest.Create("http://www.nasdaq.com/quotes/nasdaq-100-stocks.aspx");
        // Get the response.
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        // Get the stream containing content returned by the server.
        Stream dataStream = response.GetResponseStream();
        // Open the stream using a StreamReader for easy access.
        StreamReader reader = new StreamReader(dataStream);
        // Read the content.
        string responseFromServer = reader.ReadToEnd();
        // Read into a HTML store read for HAP
        htmlDoc.LoadHtml(responseFromServer);

        HtmlNodeCollection tl = htmlDoc.DocumentNode.SelectNodes("//*[@id='indu_table']/tbody/tr[*]/td/b/a");
        foreach (HtmlAgilityPack.HtmlNode node in tl)
        {
            Debug.Write(node.InnerText);
        }            

        // Cleanup the streams and the response.
        reader.Close();
        dataStream.Close();
        response.Close();

我使用了Chrome的XPath插件来获取XPath;

//*table[@id='indu_table']/tbody/tr[*]/td/b/a

在运行我的项目时,我遇到了关于xpath无法处理的异常,提示它是一个无效的令牌。
我不太确定出了什么问题,我尝试在上面的tr [*]部分中放置一个数字,但仍然出现相同的错误。
我已经看了一个小时了,有什么简单的解决方法吗?
谢谢
3个回答

3

由于数据来自JavaScript,因此您需要解析JavaScript而不是HTML,因此Agility Pack并没有太大帮助,但它使事情变得更容易。以下是使用Agility Pack和Newtonsoft JSON.Net解析JavaScript的方法。

HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.Load(new WebClient().OpenRead("http://www.nasdaq.com/quotes/nasdaq-100-stocks.aspx"));
List<string> listStocks = new List<string>();
HtmlNode scriptNode = htmlDoc.DocumentNode.SelectSingleNode("//script[contains(text(),'var table_body =')]");
if (scriptNode != null)
{
  //Using Regex here to get just the array we're interested in...
  string stockArray = Regex.Match(scriptNode.InnerText, "table_body = (?<Array>\\[.+?\\]);").Groups["Array"].Value;
  JArray jArray = JArray.Parse(stockArray);
  foreach (JToken token in jArray.Children())
  {
    listStocks.Add("http://www.nasdaq.com/symbol/" + token.First.Value<string>().ToLower());
  }
}

更详细地解释一下,数据来自页面上一个名为var table_body = [...的大型JavaScript数组。 每个股票都是数组中的一个元素,并且本身也是一个数组。
例如:["ATVI", "Activision Blizzard, Inc", 11.75, 0.06, 0.51, 3058125, 0.06, "N", "N"] 因此,通过解析数组并获取第一个元素,然后附加修复URL,我们可以得到与JavaScript相同的结果。

0
如果您查看该URL的页面源代码,实际上并没有一个带有id=indu_table的元素。它似乎是动态生成的(即在JavaScript中);直接从服务器加载时获取的HTML将不反映任何由客户端脚本更改的内容。这可能就是为什么它无法正常工作的原因。

目前可用的工具,您可能需要在“WebBrowser”控件中运行它。理论上,可以使用类似Jurassic之类的工具来执行javascript,并使用jsdom针对DOM模拟进行操作,但我不知道是否有人已经这样做了(例如尝试完全在C#中用JavaScript仿真Web浏览器)。 - Jamie Treworgy
另一种可能性是反向工程他们的页面并找到提供数据的API,然后直接访问它。这样做会更加干净,但需要更多的工作。不过,这可能并不难,一个现代化设计良好的网页应该有一个不错的API,但也没有硬性规定。 - Jamie Treworgy
看起来第三个回答者已经为您找到了API :) - Jamie Treworgy

0
为什么不直接使用Descendants("a")方法呢?这样更简单,而且更面向对象。你只需要得到一堆对象,然后从这些对象中获取“href”属性即可。
示例代码:
htmlDoc.DocumentNode.Descendants("a").Attributes["href"].Value

如果您只需要从特定网页获取链接列表,那么这种方法就足够了。

这样做会获取整个页面的链接,而不是公司列表中的链接吗? - Nathan
是的,但这只是一个演示如何轻松获取链接的示例。您可以简单地应用Descendants(“html-tag-name-here”)方法来获取表格作为对象...同样的方式,您可以从此表格中获取链接列表。我最近一直在使用这个库,我不得不承认我用这种方式做了相当好的解析... P.S. 对我来说,XPath比简单对象更复杂 - GaaRa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接