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

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

爬虫系统的意义

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

今日目标

今天我们来实践一个最简单的爬虫系统。根据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到此结束。谢谢观看!

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

相关文章
|
1月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
7天前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
37 3
|
7天前
|
数据采集 前端开发 JavaScript
除了网页标题,还能用爬虫抓取哪些信息?
爬虫技术可以抓取网页上的各种信息,包括文本、图片、视频、链接、结构化数据、用户信息、价格和库存、导航菜单、CSS和JavaScript、元数据、社交媒体信息、地图和位置信息、广告信息、日历和事件信息、评论和评分、API数据等。通过Python和BeautifulSoup等工具,可以轻松实现数据抓取。但在使用爬虫时,需遵守相关法律法规,尊重网站的版权和隐私政策,合理控制请求频率,确保数据的合法性和有效性。
|
17天前
|
数据采集 存储 XML
Python实现网络爬虫自动化:从基础到实践
本文将介绍如何使用Python编写网络爬虫,从最基础的请求与解析,到自动化爬取并处理复杂数据。我们将通过实例展示如何抓取网页内容、解析数据、处理图片文件等常用爬虫任务。
107 1
|
27天前
|
关系型数据库 C# 数据库
.NET 8.0 开源在线考试系统(支持移动端)
【10月更文挑战第27天】以下是适用于 .NET 8.0 的开源在线考试系统(支持移动端)的简介: 1. **基于 .NET Core**:跨平台,支持多种数据库,前后端分离,适用于多操作系统。 2. **结合 Blazor**:使用 C# 开发 Web 应用,支持响应式设计,优化移动端体验。 3. **基于 .NET MAUI**:跨平台移动应用开发,一套代码多平台运行,提高开发效率。 开发时需关注界面设计、安全性与稳定性。
|
1月前
|
消息中间件 中间件 数据库
NServiceBus:打造企业级服务总线的利器——深度解析这一面向消息中间件如何革新分布式应用开发与提升系统可靠性
【10月更文挑战第9天】NServiceBus 是一个面向消息的中间件,专为构建分布式应用程序设计,特别适用于企业级服务总线(ESB)。它通过消息队列实现服务间的解耦,提高系统的可扩展性和容错性。在 .NET 生态中,NServiceBus 提供了强大的功能,支持多种传输方式如 RabbitMQ 和 Azure Service Bus。通过异步消息传递模式,各组件可以独立运作,即使某部分出现故障也不会影响整体系统。 示例代码展示了如何使用 NServiceBus 发送和接收消息,简化了系统的设计和维护。
48 3
|
20天前
|
机器学习/深度学习 Android开发 UED
移动应用与系统:从开发到优化的全面解析
【10月更文挑战第25天】 在数字化时代,移动应用已成为我们生活的重要组成部分。本文将深入探讨移动应用的开发过程、移动操作系统的角色,以及如何对移动应用进行优化以提高用户体验和性能。我们将通过分析具体案例,揭示移动应用成功的关键因素,并提供实用的开发和优化策略。
|
1月前
|
域名解析 缓存 网络协议
【网络】DNS,域名解析系统
【网络】DNS,域名解析系统
97 1
|
2月前
|
移动开发 Android开发 数据安全/隐私保护
移动应用与系统的技术演进:从开发到操作系统的全景解析随着智能手机和平板电脑的普及,移动应用(App)已成为人们日常生活中不可或缺的一部分。无论是社交、娱乐、购物还是办公,移动应用都扮演着重要的角色。而支撑这些应用运行的,正是功能强大且复杂的移动操作系统。本文将深入探讨移动应用的开发过程及其背后的操作系统机制,揭示这一领域的技术演进。
本文旨在提供关于移动应用与系统技术的全面概述,涵盖移动应用的开发生命周期、主要移动操作系统的特点以及它们之间的竞争关系。我们将探讨如何高效地开发移动应用,并分析iOS和Android两大主流操作系统的技术优势与局限。同时,本文还将讨论跨平台解决方案的兴起及其对移动开发领域的影响。通过这篇技术性文章,读者将获得对移动应用开发及操作系统深层理解的钥匙。
|
1月前
|
域名解析 运维 网络协议
推荐一款专业级的动态域名解析系统 - bind webadmin
`bind webadmin`是一款基于Bind9打造的高效DNS管理系统,简化了DNS配置与管理流程,适用于动态IP环境下的远程访问需求。此系统不仅便于维护,还支持API接口,方便自动化操作与第三方应用集成,特别适合远程办公、智能家居及各类物联网应用场景。其自托管特性保障了数据的安全与可控性,同时提供了详尽的中文安装教程,易于部署。项目地址:[bindwebadmin](https://github.com/guofusheng007/bindwebadmin.git)。建议使用阿里云主机以获得最佳性能。

推荐镜像

更多
下一篇
无影云桌面