2014年02月05日 · 151 次访问

配合HtmlAgilityPack获取页面中所有图片

某一个时刻,我们会有将页面中所有美图都下载到本地的冲动。本文提供一种C#的实现,但是只到提供图片URL的地步,毕竟有了URL,批量下载图片实在是太简单了。

解析HTML的DOM结构乍一看并不是多难的事情,获得源代码之后类似于XML的解析即可。可真正做过XML解析的人就会知道另一个事实:解析XML从来都不容易啊,各种兼容性都要考虑到,少了个引号,多了个右破折号之类的会让人瞬间抓狂。针对HTML还有更多的问题,你得兼容各种奇怪的写法,也许你还得解析上个世纪的一些老网页,那种你看到源代码就会撞墙的事情相信你也不想尝试。

所以,HtmlAgilityPack的问世,就会让你瞬间觉得这个时间还是很美好,很可爱的。

首先,毫无疑问用Nuget Package下载最新的release版本。

PM> Install-Package HtmlAgilityPack

接下来,我们来获取页面的源代码。需要注意的是HtmlAgilityPack是不支持直接从网址中获得源码并解析的,换言之你必须先把源码用自己的方式下载下来,然后以字符串的形式进行解析。不知道作者是出于什么考虑才这样设计的,但不得不得说这样灵活性很大。

static async Task<HttpResponseMessage> RetrievePage(string url)
{
    var client = new HttpClient();
    var result = await client.GetAsync(url);
    return result;
}

上面使用了C# 5.0的一些异步语法,除了asyncawait这两个新面孔外与同步写法没有太大差别。当然上面是获得了HttpResponseMessage对象而已,要获得源代码还得进一步解析。

static async Task<HtmlDocument> GetDocument(HttpResponseMessage responeMessage)
{
    var content = await responeMessage.Content.ReadAsStringAsync();
    if (!responeMessage.IsSuccessStatusCode)
    {
        throw new FileNotFoundException("Unable to get doucment.");
    }

    var doc = new HtmlDocument();
    doc.LoadHtml(content);
    return doc;
}

我们首先检查返回的状态码,如果不是200就抛出异常,当然在实际情况下肯定会用户友好的以另一种方式“抛出”。紧接着便是HtmlAligityPack加载Html源码的方式,这样最终我们会获得一个Task<HtmlDocument>类型的对象。从此以后,就算是正式开始解析DOM结构了。

static void Main(string[] args)
{
    var page = RetrievePage("http://www.microsoft.com/en-us/default.aspx");
    var doc = GetDocument(page.Result);
    var html = doc.Result;

    var images = html.DocumentNode.SelectNodes("//img");
    foreach (var img in images)
    {
        Console.WriteLine(img.Attributes["src"].Value);
    }
}

利用SelectNodes这个API,我们可以获得页面中所有img标签的HtmlNode对象,前面的“//”是XPATH的一种语法,如果它位于模式的最前面,比如代码中的“//img”,那么它便可匹配以根元素为相对位置的任意img节点。换言之,从源代码中以最省力的方式获得某类节点,直接以“//”开头便可。

接下来,遍历打印每个节点的src属性。由此我们便得到了所有img节点的图片地址,接下来只要使用我们最喜欢的方式将它们异步下载下来即可。

所以,总的来说HtmlAgilityPack已经帮我们把最恶心的工作做完了,从此以后我们看到页面中有任何美图都无所畏惧了。