.NetCore实践爬虫系统(一)解析网页内容
爬虫系统的意义
爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情。谷歌,百度,今日头条,天眼查都离不开爬虫。
爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情。谷歌,百度,今日头条,天眼查都离不开爬虫。
今日目标
今天我们来实践一个最简单的爬虫系统。根据Url来识别网页内容。
今天我们来实践一个最简单的爬虫系统。根据Url来识别网页内容。
网页内容识别利器:HtmlAgilityPack
HtmlAgilityPack的stackoverflow地址
至今Nuget已有超过900多万的下载量,应用量十分庞大。它提供的文档教程也十分简单易用。
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);
}
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
选择器允许您从HtmlDocument中选择HTML节点。它提供了两个方法,可以用XPath表达式筛选节点。XPath教程
SelectNodes() 返回多个节点
SelectSingleNode(String) 返回单个节点
简介到此为止,更全的用法参考 http://html-agility-pack.net
查看网页结构
我们以博客园首页为示例。用chrome分析下网页结构,可采集出推荐数,标题,内容Url,内容简要,作者,评论数,阅读数。

我们以博客园首页为示例。用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;
}
建立一个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语法
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;
}
感谢apgk也提供了一种办法,也是ok的。
var titlenodes = post_item_bodynode.SelectNodes(post_item_bodynode.XPath+"//a[@class='titlelnk']");
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;
}
感谢apgk也提供了一种办法,也是ok的。
var titlenodes = post_item_bodynode.SelectNodes(post_item_bodynode.XPath+"//a[@class='titlelnk']");
源码
点击 推荐
查看源码。
查看源码。
总结
demo到此结束。谢谢观看!
下篇继续构思如何构建自定义规则,让用户可以在页面自己填写规则去识别。
.NetCore实践爬虫系统(一)解析网页内容的更多相关文章
- .NetCore实践爬虫系统(二)自定义规则
回顾 上篇文章NetCore实践爬虫系统(一)解析网页内容 我们讲了利用HtmlAgilityPack,输入XPath路径,识别网页节点,获取我们需要的内容.评论中也得到了大家的一些支持与建议.下面继 ...
- NodeJS爬虫系统初探
NodeJS爬虫系统 NodeJS爬虫系统 0. 概论 爬虫是一种自动获取网页内容的程序.是搜索引擎的重要组成部分,因此搜索引擎优化很大程度上是针对爬虫而做出的优化. robots.txt是一个文本文 ...
- 爬虫系统-日志、初始化url
1.日志log4j 1.1.DEBUG:debug级别 1.2.stdout:输出到控制台 1.3.D:输出到文件 log4j.rootLogger=DEBUG, stdout,D #Console ...
- 基于golang分布式爬虫系统的架构体系v1.0
基于golang分布式爬虫系统的架构体系v1.0 一.什么是分布式系统 分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统.简单来说就是一群独立计算机 ...
- 用python3.x与mysql数据库构建简单的爬虫系统(转)
这是在博客园的第一篇文章,由于本人还是一个编程菜鸟,也写不出那些高大上的牛逼文章,这篇文章就是对自己这段时间学习python的一个总结吧. 众所周知python是一门对初学编程的人相当友好的编程语言, ...
- 干货|爱奇艺CDN巡检系统技术解析
小结: 1. 中心处理系统 /1/将定制后的巡检任务拆分,通过配置与任务分发系统.CMDB*( configuration management database)将派发到边缘拨测系统/2/处理边缘拨 ...
- PySpider 爬虫系统
PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI.采用Python语言编写,分布式架构,支持多种数据库后端,强大的WebUI支持脚本编辑器,任务监视器,项目管理器以及结果查看器 ...
- python爬虫之网页解析
CSS Selector 与Xpath path = ‘D:\\Postgraduate\\Python\\python_projects\\Python视频 分布式 爬虫Scrapy入门到精通\\第 ...
- 从程序员到CTO的Java技术路线图 JAVA职业规划 JAVA职业发展路线图 系统后台框架图、前端工程师技能图 B2C电子商务基础系统架构解析
http://zz563143188.iteye.com/blog/1877266在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样 ...
随机推荐
- [Python][小知识][NO.1] Python字符串前 加 u、r、b 的含义
1.字符串前加 u 例:u"我是含有中文字符组成的字符串." 作用:后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止因为源码储存格式问题,导致再次使用时出 ...
- Java 一些知识点总结
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺(阅读本文需要有一定的Java基础).本文的 ...
- zabbix监控tomcat(使用jmx监控,但不使用系统自带模版)
一,zabbx使用jmx监控tomcat的原理分析 1.Zabbix-Server找Zabbix-Java-Gateway获取Java数据 2.Zabbix-Java-Gateway找Java程序(j ...
- MySQL Host is blocked because of many connection errors 解决方法
应用日志提示错误:create connection error, url: jdbc:mysql://10.45.236.235:3306/db_wang?useUnicode=true&c ...
- 为什么会出现Notice: Undefined index: submit in D:\xampp\htdocs\test.php on line 19
事例如下": <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:/ ...
- ELK-logstash-6.3.2-常用配置
1. input-file收集日志信息 [yun@mini04 config]$ pwd /app/logstash/config [yun@mini04 config]$ cat file.conf ...
- LeetCode算法题-Maximum Depth of Binary Tree
这是悦乐书的第164次更新,第166篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第23题(顺位题号是104).给定二叉树,找到它的最大深度.最大深度是从根节点到最远叶节 ...
- C语言 求两数的最大公约数和最小公倍数
//作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ #include<stdio.h> //最大公约数 int gys(int x,int ...
- ubuntu下安裝sogou拼音
方法/步骤 打开搜狗输入法Linux版的官网http://pinyin.sogou.com/linux/?r=pinyin,并下载你需要的版本,这里选择64位版. 在Ubuntu14.01下可以直接点 ...
- vue源码分析—模板解析
福建省啦剑飞傻了剑飞撒到了看风景啊撒: