.NetCore实践爬虫系统(一)解析网页内容

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介:

爬虫系统的意义

爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情。谷歌,百度,今日头条,天眼查都离不开爬虫。

今日目标

今天我们来实践一个最简单的爬虫系统。根据Url来识别网页内容。

网页内容识别利器:HtmlAgilityPack

GitHub地址

HtmlAgilityPack官网

HtmlAgilityPack的stackoverflow地址

至今Nuget已有超过900多万的下载量,应用量十分庞大。它提供的文档教程也十分简单易用。

Parser解析器

HtmlParse可以让你解析HTML并返回HtmlDocument

  • FromFile从文件读取
/// <summary>
/// 从文件读取
/// </summary>
public void FromFile() {          
    var path = @"test.html";
    var doc = new HtmlDocument();
    doc.Load(path);
    var node = doc.DocumentNode.SelectSingleNode("//body");
    Console.WriteLine(node.OuterHtml);
}
  • 从字符串加载
/// <summary>
/// 从字符串读取
/// </summary>
public void FromString()
{
    var html = @"<!DOCTYPE html>
    <html>
    <body>
        <h1>This is <b>bold</b> heading</h1>
        <p>This is <u>underlined</u> paragraph</p>
        <h2>This is <i>italic</i> heading</h2>
    </body>
    </html> ";

    var htmlDoc = new HtmlDocument();
    htmlDoc.LoadHtml(html);

    var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body");

    Console.WriteLine(htmlBody.OuterHtml);
}
  • 从网络加载
/// <summary>
/// 从网络地址加载
/// </summary>
public void FromWeb() {
    var html = @"https://www.cnblogs.com/";

    HtmlWeb web = new HtmlWeb();

    var htmlDoc = web.Load(html);

    var node = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']");

    Console.WriteLine("Node Name: " + node.Name + "\n" + node.OuterHtml);
}

Selectors选择器

选择器允许您从HtmlDocument中选择HTML节点。它提供了两个方法,可以用XPath表达式筛选节点。XPath教程

SelectNodes() 返回多个节点

SelectSingleNode(String) 返回单个节点

简介到此为止,更全的用法参考 http://html-agility-pack.net

查看网页结构

我们以博客园首页为示例。用chrome分析下网页结构,可采集出推荐数,标题,内容Url,内容简要,作者,评论数,阅读数。

博客园主页内容结构图

编码实现

建立一个Article用来接收文章信息。


public class Article
    {
        /// <summary>
        /// 
        /// </summary>
        public string Id { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 概要
        /// </summary>
        public string Summary { get; set; }
        /// <summary>
        /// 文章链接
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 推荐数
        /// </summary>
        public long Diggit { get; set; }
        /// <summary>
        /// 评论数
        /// </summary>
        public long Comment { get; set; }
        /// <summary>
        /// 阅读数
        /// </summary>
        public long View { get; set; }
        /// <summary>
        ///明细
        /// </summary>
        public string Detail { get; set; }
        /// <summary>
        ///作者
        /// </summary>
        public string Author { get; set; }
        /// <summary>
        /// 作者链接
        /// </summary>
        public string AuthorUrl { get; set; }
    }

然后根据网页结构,查看XPath路径,采集内容

/// <summary>
        /// 解析
        /// </summary>
        /// <returns></returns>
        public List<Article> ParseCnBlogs()
        {
            var url = "https://www.cnblogs.com";
            HtmlWeb web = new HtmlWeb();
            //1.支持从web或本地path加载html
            var htmlDoc = web.Load(url);
            var post_listnode = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']");
            Console.WriteLine("Node Name: " + post_listnode.Name + "\n" + post_listnode.OuterHtml);

            var postitemsNodes = post_listnode.SelectNodes("//div[@class='post_item']");
            var articles = new List<Article>();
            var digitRegex = @"[^0-9]+";
            foreach (var item in postitemsNodes)
            {
                var article = new Article();
                var diggnumnode = item.SelectSingleNode("//span[@class='diggnum']");
                //body
                var post_item_bodynode = item.SelectSingleNode("//div[@class='post_item_body']");

                var titlenode = post_item_bodynode.SelectSingleNode("//a[@class='titlelnk']");

                var summarynode = post_item_bodynode.SelectSingleNode("//p[@class='post_item_summary']");
                //foot
                var footnode = item.SelectSingleNode("//div[@class='post_item_foot']");
                var authornode = footnode.ChildNodes[1];
                var commentnode = item.SelectSingleNode("//span[@class='article_comment']");
                var viewnode = item.SelectSingleNode("//span[@class='article_view']");


                article.Diggit = int.Parse(diggnumnode.InnerText);
                article.Title = titlenode.InnerText;
                article.Url = titlenode.Attributes["href"].Value;
                article.Summary = titlenode.InnerHtml;
                article.Author = authornode.InnerText;
                article.AuthorUrl = authornode.Attributes["href"].Value;

                article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText, digitRegex, ""));
                article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, ""));

                articles.Add(article);
            }
            return articles;
        }

查看采集结果

看到结果就惊呆了,竟然全是重复的。难道是Xpath语法理解不对么?
采集结果

重温下XPath语法

XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的

表达式        描述
nodename    选取此节点的所有子节点。
/            从根节点选取。
//            从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
.            选取当前节点。
..            选取当前节点的父节点。
@            选取属性。

XPath 通配符可用来选取未知的 XML 元素

通配符       描述
*            匹配任何元素节点。
@*            匹配任何属性节点。
node()        匹配任何类型的节点。

我测试了几个语法如:

//例1,会返回20个
var titlenodes = post_item_bodynode.SelectNodes("//a[@class='titlelnk']");
//会报错,因为这个a并不直接在bodynode下面,而是在子级h3元素的子级。
var titlenodes = post_item_bodynode.SelectNodes("a[@class='titlelnk']");

然后又实验了一种:

//Bingo,这个可以,但是强烈指定了下级h3,这就稍微麻烦了点。
var titlenodes = post_item_bodynode.SelectNodes("h3//a[@class='titlelnk']");

这里就引申出了一个小问题:如何定位子级的子级?用通配符*可以么?

//返回1个。
var titlenodes= post_item_bodynode.SelectNodes("*//a[@class='titlelnk']")

能正确返回1,应该是可以了,我们改下代码看下效果。
运行结果
然后和博客园首页数据对比,结果吻合。
所以我们可以得出结论:

XPath搜索以//开头时,会匹配所有的项,并不是子项。

直属子级可以直接跟上 node名称。

只想查子级的子级,可以用*代替子级,实现模糊搜索。

改过后代码如下:

public List<Article> ParseCnBlogs()
        {
            var url = "https://www.cnblogs.com";
            HtmlWeb web = new HtmlWeb();
            //1.支持从web或本地path加载html
            var htmlDoc = web.Load(url);
            var post_listnode = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']");
            //Console.WriteLine("Node Name: " + post_listnode.Name + "\n" + post_listnode.OuterHtml);

            var postitemsNodes = post_listnode.SelectNodes("div[@class='post_item']");
            var articles = new List<Article>();
            var digitRegex = @"[^0-9]+";
            foreach (var item in postitemsNodes)
            {
                var article = new Article();
                var diggnumnode = item.SelectSingleNode("*//span[@class='diggnum']");
                //body
                var post_item_bodynode = item.SelectSingleNode("div[@class='post_item_body']");

                var titlenode = post_item_bodynode.SelectSingleNode("*//a[@class='titlelnk']");

                var summarynode = post_item_bodynode.SelectSingleNode("p[@class='post_item_summary']");
                //foot
                var footnode = post_item_bodynode.SelectSingleNode("div[@class='post_item_foot']");
                var authornode = footnode.ChildNodes[1];
                var commentnode = footnode.SelectSingleNode("span[@class='article_comment']");
                var viewnode = footnode.SelectSingleNode("span[@class='article_view']");


                article.Diggit = int.Parse(diggnumnode.InnerText);
                article.Title = titlenode.InnerText;
                article.Url = titlenode.Attributes["href"].Value;
                article.Summary = titlenode.InnerHtml;
                article.Author = authornode.InnerText;
                article.AuthorUrl = authornode.Attributes["href"].Value;

                article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText, digitRegex, ""));
                article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, ""));

                articles.Add(article);
            }
            return articles;
        }

源码

代码已上传至 GitHub

总结

demo到此结束。谢谢观看!

下篇继续构思如何构建自定义规则,让用户可以在页面自己填写规则去识别。

相关文章
|
2天前
|
SQL 运维 监控
MSSQL性能调优深度解析:索引优化策略、SQL查询优化技巧与高效并发管理实践
在Microsoft SQL Server(MSSQL)的运维与优化领域,性能调优是确保数据库高效运行、满足业务需求的关键环节
|
2天前
|
机器学习/深度学习 搜索推荐 算法
深度学习在推荐系统中的应用:技术解析与实践
【7月更文挑战第6天】深度学习在推荐系统中的应用为推荐算法的发展带来了新的机遇和挑战。通过深入理解深度学习的技术原理和应用场景,并结合具体的实践案例,我们可以更好地构建高效、准确的推荐系统,为用户提供更加个性化的推荐服务。
|
17小时前
|
数据挖掘 Shell 测试技术
怎么用Python解析HTML轻松搞定网页数据
**Python解析HTML摘要** 本文介绍了使用Python处理HTML的常见需求,如数据提取、网络爬虫和分析,并讨论了三种解析方法。正则表达式适用于简单匹配,但对复杂HTML不理想;BeautifulSoup提供简单API,适合多数情况;lxml结合XPath,适合处理大型复杂文档。示例展示了如何用这些方法提取链接。
|
8天前
|
设计模式 安全 PHP
PHP 7新特性深度解析与应用实践
【6月更文挑战第29天】在PHP 7的发布中,开发者社区迎来了一场性能与功能的盛宴。本文将深入挖掘PHP 7的新特性,从类型声明的强化到异常处理的改进,再到匿名类的引入,我们将一一探讨这些变化如何影响日常编码实践。通过实际代码示例,我们将展示如何有效利用这些新特性来编写更加清晰、高效和安全的PHP代码。
24 11
|
1天前
|
数据采集 存储 搜索推荐
CRM客户系统全解析:定义与企业应用
Zoho CRM是一个集成客户数据、销售流程、市场活动和客户服务的平台,助力企业提升效率和竞争力。它支持销售管理、客户细分及个性化营销,改善客户体验。系统在销售预测、客户档案管理和市场营销策略制定上发挥作用。然而,挑战包括系统集成、数据质量、员工培训和用户接纳。企业需克服这些挑战以确保CRM的有效运营。
11 2
|
6天前
|
数据可视化 安全 Linux
探索Linux命令repo-graph:深入解析与应用实践
`repo-graph`是Linux的Yum-utils工具,用于可视化仓库中软件包的依赖关系,简化复杂网络管理。它通过分析元数据生成图形,支持自定义输出格式和特定包分析。例如,`repo-graph --repoid=updates`显示更新仓库的依赖,而`--packages=httpd`则专注httpd包。注意权限、复杂性和选择合适输出格式。定期分析和图形化展示是最佳实践。
|
6天前
|
缓存 安全 编译器
PHP 8新特性解析与性能优化实践
PHP 8的发布带来了一系列新特性和性能改进,本文将深入探讨PHP 8的关键特性,如JIT编译器、类型系统的增强和语言语法的优化,以及如何利用这些特性优化现有代码和提升应用性能。【7月更文挑战第2天】
10 1
|
6天前
|
安全 算法 编译器
PHP 8新特性深度解析与实践应用
【7月更文挑战第2天】本文深入探讨了PHP 8带来的革新性特性,包括JIT编译器的引入、联合类型和属性的声明等。文章不仅剖析了这些新特性背后的技术原理,还通过实例展示了如何在现实项目中有效利用它们来提升代码质量和执行效率。读者将获得对PHP 8新特性的全面认识以及如何在实际开发中灵活运用它们的实用指南。
10 1
|
9天前
|
Java 程序员 编译器
Java内存模型深度解析与实践优化策略
在多线程编程领域,Java内存模型(Java Memory Model, JMM)是确保并发程序正确性的基石。本文深入探讨JMM的工作原理,结合最新研究成果和实际案例,揭示高效同步策略和避免常见并发缺陷的方法。文章不仅阐述理论,更注重实践,旨在为Java开发者提供全面的内存模型应用指南。
|
6天前
|
数据可视化 前端开发 大数据
商场智能导视系统深度解析,AR与大数据融合创新商业运营模式
**商场智能导视系统提升购物体验:** 通过三维电子地图、AR导航、AR营销、VR全景导购及可视化数据,解决顾客寻路困扰,增强店铺曝光,简化招商流程,优化商场管理,借助科技创新驱动顾客满意度、品牌曝光度及运营效率的全面提升。
20 0
商场智能导视系统深度解析,AR与大数据融合创新商业运营模式

推荐镜像

更多