• C#爬虫工具 如何使用HtmlAgilityPack解析Html


    HtmlAgilityPack 是一个开源的快速解析Html的C#类库。简单理解,它可以像解析Xml一样,将Html根据XPATH转化为一个个Node节点,并支持调整节点以及节点的各种属性。

    传送门:官网 | Github源码

    多种方式加载Html

    主要加载方式有3类:从网络链接加载、从字符串文本中加载、从文件加载

    var doc = new HtmlDocument();
    //直接通过url加载
    doc = new HtmlWeb().Load("https://www.baidu.com/");
    //通过字符串加载
    doc.LoadHtml(result);
    //通过html文件加载 可指定编码方式
    doc.Load(@"c://index.html",Encoding.UTF8)
    

    HtmlNode常用方法

    使用SelectNodes()和SelectSingleNode()方法(类似解析XML格式数据的XmlDocument)来获取的目标节点,分别对应HtmlNodeCollection和HtmlNode两个类。

    "//"表示从根节点开始查找,两个斜杠"//"表示查找所有childnodes;一个斜杠"/"表示只查找第一层的childnodes(即不查找grandchild);点斜杠"./"表示从当前结点而不是根结点开始查找(只在xpath最开始出现)

    注意:
    id class 属性匹配大小写敏感
    xpath匹配下标从1开始

    1. 通过属性和路径匹配来选择对应的节点

    var node =  doc.DocumentNode;
    
    //选择不包含class属性的div节点
    var result = node.SelectNodes(".//div[not(@class)]");
    
    //选择不包含class和id属性的div节点
    var result = node.SelectNodes(".//div[not(@class) and not(@id)]");
    
    //选择class中包含"expire"的span节点
    var result = node.SelectNodes(".//span[contains(@class,'expire')]");
    
    //选择class中不包含"expire"的span节点
    var result = node.SelectNodes(".//span[not(contains(@class,'expire'))]");
    
    //选择class="expire"的span节点
    var result = node.SelectNodes(".//span[@class='expire']");
    
    //选择id="expire"的div节点下第一个div节点
    var result = node.SelectSingleNode(".//div[@id='expire']/div[1]");
    

    2. 获取节点文本内容

    根据需求不同,通过不同的方式来获取相应的文本内容。
    OuterHtml:返回包含当前节点在内的所有Html
    InnerHtml:返回当前节点内所有子节点Html
    InnerText:返回当前节点内去除所有Html后的文本内容

    <div id="title">
       <p>
         <a class="MainTitle" href="https://www.cnblogs.com/cplemom/">傅小灰</a>
       </p>
    </div>
    

    以上面的Html为例

    var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p]");
    
    node.OuterHtml; //返回结果:<p><a class="MainTitle"href="https://www.cnblogs.com/cplemom/">傅小灰</a></p>
    
    node.InnerHtml; //返回结果:<a class="MainTitle"href="https://www.cnblogs.com/cplemom/">傅小灰</a>
    
    node.InnerText; //返回结果:傅小灰
    

    3. 获取/修改节点属性值

    以上面的Html举例,我们获取到了a标签为node节点。我们想要获取a标签指向的链接地址,并修改为我们设定的地址。这里以href属性举例,同样可以用在class/src/id等属性上。

    var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p/a]");
    
    //第二个参数为找不到对应属性时返回的默认值
    var url = node.GetAttributeValue("href", "");//返回结果:https://www.cnblogs.com/cplemom/
    
    //设置属性值
    node.SetAttributeValue("href", "http://www.cplemom.com/");
    
    //获取所有属性值
    var list = node.Attributes.ToList();
    
    

    4. 删除/替换节点

    继续以上面的Html举例,我们获取到了a标签为node节点。
    对于我们不需要的内容,我们只需要调用节点Remove方法即可。

    var node= doc.DocumentNode.SelectSingleNode("//div[@id='title'/p/a]");
    
    node.Remove();//删除节点
    

    一个很常见的场景就是我们需要移除a标签,但是要在html上下文中保留a标签的文本。
    PS : a标签内的文本在HtmlDocument中其实是一个text类型的node节点。所以我们可以通过删除a标签,保留text标签的方式来完成目标。

    node.ParentNode.RemoveChild(node,true); 
    

    true表示留下a标签的子节点只删除a标签,在这里就表示保留下“傅小灰”text节点; false表示将此结点连同所有子节点一起删除。

    换个角度思考下,当前node节点代表的是单个a标签,那么如果p标签下存在多个a标签需要处理,或者node节点指向的就是p标签呢?当然,我们可以通过获取所有a标签然后循环处理的方式来实现,但是还有没有别的更好的处理方式呢?

    这里提供一个思路,获取所有的文本内容,新建为一个text节点,然后替换掉当前节点。

    node.ParentNode.ReplaceChild(HtmlNode.CreateNode(item.InnerText), node);
    

    几个常见使用场景和解决方案

    1. 获取所有的img标签

    //通过Descendants获取所有的子后代节点中的img标签
    var list = node.Descendants("img");
    
    
    //通过Xpath匹配获取所有img标签
    var list = node.SelectNodes("//img");
    
    

    2. 通过url访问时需要携带cookie等验证信息

    有些页面需要携带验证信息才能访问,比如用户中心,订单列表等,这时候直接通过HtmlWeb类获取html会被拒绝。有个简单的方式就是通过HttpClient请求到对应的html内容,再使用HtmlDocument加载。其实HtmlWeb说白了也是封装的HttpWebRequest进行网络请求的,所以暴露一个委托给外部用以修改请求上下文。

    var web = new HtmlWeb();
    web.PreRequest = new HtmlWeb.PreRequestHandler(GetRequest);
    var node = web.Load("https://www.cplemom.com/");
    
    public static bool GetRequest(HttpWebRequest req)
    {
        req.Headers.Add("Host", "www.cplemom.com");
    	req.Headers.Add("Cookie", "xxxxxxxxxxxxx");
        return true;
    }
    

    总结

    用到现在,个人感觉上面的方法已经可以实现90%以上的的Html解析相关需求了,更多方便快捷的方法还是到官网的API文档进行了解吧。

  • 相关阅读:
    Notes of Daily Scrum Meeting(12.18)
    Notes of Daily Scrum Meeting(12.17)
    Notes of Daily Scrum Meeting(12.16)
    Notes of Daily Scrum Meeting(12.8)
    Notes of Daily Scrum Meeting(12.5)
    Notes of Daily Scrum Meeting(12.3)
    Notes of Daily Scrum Meeting(11.12)
    Linux中profile、bashrc、bash_profile之间的区别和联系
    Linux GCC编译
    mysql 5.7.16 远程连接
  • 原文地址:https://www.cnblogs.com/cplemom/p/13388613.html
Copyright © 2020-2023  润新知