C#简化工作之实现网页爬虫获取数据
公众号「DotNet学习交流」,分享学习DotNet的点滴。
1、需求
想要获取网站上所有的气象信息,网站如下所示:

目前总共有67页,随便点开一个如下所示:

需要获取所有天气数据,如果靠一个个点开再一个个复制粘贴那么也不知道什么时候才能完成,这个时候就可以使用C#来实现网页爬虫获取这些数据。
2、效果
先来看下实现的效果,所有数据都已存入数据库中,如下所示:

总共有4万多条数据。
3、具体实现
构建每一页的URL
第一页的网址如下所示:

最后一页的网址如下所示:

可以发现是有规律的,那么就可以先尝试构建出每个页面的URL
// 发送 GET 请求
string url = "https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/";
HttpResponseMessage response = await httpClient.GetAsync(url);
// 处理响应
if (response.IsSuccessStatusCode)
{
string responseBody = await response.Content.ReadAsStringAsync();
doc.LoadHtml(responseBody);
//获取需要的数据所在的节点
var node = doc.DocumentNode.SelectSingleNode("//div[@class=\"page\"]/script");
string rawText = node.InnerText.Trim();
// 使用正则表达式来匹配页数数据
Regex regex = new Regex(@"\b(\d+)\b");
Match match = regex.Match(rawText);
if (match.Success)
{
string pageNumber = match.Groups[1].Value;
Urls = GetUrls(Convert.ToInt32(pageNumber));
MessageBox.Show($"获取每个页面的URL成功,总页面数为:{Urls.Length}");
}
}
//构造每一页的URL
public string[] GetUrls(int pageNumber)
{
string[] urls = new string[pageNumber];
for (int i = 0; i < urls.Length; i++)
{
if (i == 0)
{
urls[i] = "https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/index.shtml";
}
else
{
urls[i] = $"https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/index_{i}.shtml";
}
}
return urls;
}
这里使用了HtmlAgilityPack

HtmlAgilityPack(HAP)是一个用于处理HTML文档的.NET库。它允许你方便地从HTML文档中提取信息,修改HTML结构,并执行其他HTML文档相关的操作。HtmlAgilityPack 提供了一种灵活而强大的方式来解析和处理HTML,使得在.NET应用程序中进行网页数据提取和处理变得更加容易。
// 使用HtmlAgilityPack解析网页内容
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml("需要解析的Html");
//获取需要的数据所在的节点
var node = doc.DocumentNode.SelectSingleNode("XPath");
那么XPath是什么呢?
XPath(XML Path Language)是一种用于在XML文档中定位和选择节点的语言。它是W3C(World Wide Web Consortium)的标准,通常用于在XML文档中执行查询操作。XPath提供了一种简洁而强大的方式来导航和操作XML文档的内容。
构建每一天的URL
获取到了每一页的URL之后,我们发现在每一页的URL都可以获取关于每一天的URL信息,如下所示:

可以进一步构建每一天的URL,同时可以根据a的文本获取时间,当然也可以通过其他方式获取时间,但是这种可以获取到11点或者17点。
代码如下所示:
for (int i = 0; i < Urls.Length; i++)
{
// 发送 GET 请求
string url2 = Urls[i];
HttpResponseMessage response2 = await httpClient.GetAsync(url2);
// 处理响应
if (response2.IsSuccessStatusCode)
{
string responseBody2 = await response2.Content.ReadAsStringAsync();
doc.LoadHtml(responseBody2);
var nodes = doc.DocumentNode.SelectNodes("//div[@class=\"lie\"]/ul/li");
for (int j = 0; j < nodes.Count; j++)
{
var name = nodes[j].ChildNodes[3].InnerText;
//只有name符合下面的格式才能成功转换为时间,所以这里需要有一个判断
if (name != "" && name.Contains("气象预告"))
{
var dayUrl = new DayUrl();
//string format;
//DateTime date;
// 定义日期时间格式
string format = "yyyy年M月d日H点气象预告";
// 解析字符串为DateTime
DateTime date = DateTime.ParseExact(name, format, null);
var a = nodes[j].ChildNodes[3];
string urlText = a.GetAttributeValue("href", "");
string newValue = "https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/";
string realUrl = "";
realUrl = newValue + urlText.Substring(1);
dayUrl.Date = date;
dayUrl.Url = realUrl;
dayUrlList.Add(dayUrl);
}
else
{
Debug.WriteLine($"在{name}处,判断不符合要求");
}
}
}
}
// 将数据存入SQLite数据库
db.Insertable(dayUrlList.OrderBy(x => x.Date).ToList()).ExecuteCommand();
MessageBox.Show($"获取每天的URL成功,共有{dayUrlList.Count}条");
}
在这一步骤需要注意的是XPath的书写,以及每一天URL的构建,以及时间的获取。
XPath的书写:
var nodes = doc.DocumentNode.SelectNodes("//div[@class=\"lie\"]/ul/li");
表示一个类名为"lie"的div下的ul标签下的所有li标签,如下所示:

构建每一天的URL:
var a = nodes[j].ChildNodes[3];
string urlText = a.GetAttributeValue("href", "");
string newValue = "https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/";
string realUrl = "";
realUrl = newValue + urlText.Substring(1);
这里获取li标签下的a标签,如下所示:

string urlText = a.GetAttributeValue("href", "");
这段代码获取a标签中href属性的值,这里是./202311/t20231127_3103490.shtml。
string urlText = a.GetAttributeValue("href", "");
string newValue = "https://cj.msa.gov.cn/xxgk/xxgkml/aqxx/qxyg/";
string realUrl = newValue + urlText.Substring(1);
这里是在拼接每一天的URL。
var name = nodes[j].ChildNodes[3].InnerText;
// 定义日期时间格式
string format = "yyyy年M月d日H点气象预告";
// 解析字符串为DateTime
DateTime date = DateTime.ParseExact(name, format, null);
这里是从文本中获取时间,比如文本的值也就是name的值为:“2023年7月15日17点气象预告”,name获得的date就是2023-7-15 17:00。
// 将数据存入SQLite数据库
db.Insertable(dayUrlList.OrderBy(x => x.Date).ToList()).ExecuteCommand();
MessageBox.Show($"获取每天的URL成功,共有{dayUrlList.Count}条");
这里是将数据存入数据库中,ORM使用的是SQLSugar,类DayUrl如下:
internal class DayUrl
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
public DateTime Date { get; set; }
public string Url { get; set; }
}
最后获取每一天URL的效果如下所示:

获取温度数据
需要获取的内容如下:

设计对应的类如下:
internal class WeatherData
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
public string? StationName { get; set; }
public string? Weather { get; set; }
public string? Tem_Low { get; set; }
public string? Tem_High { get; set; }
public string? Wind { get; set; }
public string? Visibility_Low { get; set; }
public string? Visibility_High { get; set; }
public string? Fog { get; set; }
public string? Haze { get; set; }
public DateTime Date { get; set; }
}
增加了一个时间,方便以后根据时间获取。
获取温度数据的代码如下:
var list = db.Queryable<DayUrl>().ToList();
for (int i = 0; i < list.Count; i++)
{
HttpResponseMessage response = await httpClient.GetAsync(list[i].Url);
// 处理响应
if (response.IsSuccessStatusCode)
{
string responseBody2 = await response.Content.ReadAsStringAsync();
doc.LoadHtml(responseBody2);
var nodes = doc.DocumentNode.SelectNodes("//table");
if (nodes != null)
{
var table = nodes[5];
var trs = table.SelectNodes("tbody/tr");
for (int j = 1; j < trs.Count; j++)
{
var tds = trs[j].SelectNodes("td");
switch (tds.Count)
{
case 8:
var wd8 = new WeatherData();
wd8.StationName = tds[0].InnerText.Trim().Replace(" ", "");
wd8.Weather = tds[1].InnerText.Trim().Replace(" ", "");
wd8.Tem_Low = tds[2].InnerText.Trim().Replace(" ", "");
wd8.Tem_High = tds[3].InnerText.Trim().Replace(" ", "");
wd8.Wind = tds[4].InnerText.Trim().Replace(" ", "");
wd8.Visibility_Low = tds[5].InnerText.Trim().Replace(" ", "");
wd8.Visibility_High = tds[6].InnerText.Trim().Replace(" ", "");
wd8.Fog = tds[7].InnerText.Trim().Replace(" ", "");
wd8.Date = list[i].Date;
weatherDataList.Add(wd8);
break;
case 9:
var wd9 = new WeatherData();
wd9.StationName = tds[0].InnerText.Trim().Replace(" ", "");
wd9.Weather = tds[1].InnerText.Trim().Replace(" ", "");
wd9.Tem_Low = tds[2].InnerText.Trim().Replace(" ", "");
wd9.Tem_High = tds[3].InnerText.Trim().Replace(" ", "");
wd9.Wind = tds[4].InnerText.Trim().Replace(" ", "");
wd9.Visibility_Low = tds[5].InnerText.Trim().Replace(" ", "");
wd9.Visibility_High = tds[6].InnerText.Trim().Replace(" ", "");
wd9.Fog = tds[7].InnerText.Trim().Replace(" ", "");
wd9.Haze = tds[8].InnerText.Trim().Replace(" ", "");
wd9.Date = list[i].Date;
weatherDataList.Add(wd9);
break;
default:
break;
}
}
}
else
{
}
}
// 输出进度提示
Debug.WriteLine($"已处理完成第{i}个URL");
}
// 将数据存入SQLite数据库
db.Insertable(weatherDataList.OrderBy(x => x.Date).ToList()).ExecuteCommand();
MessageBox.Show($"获取天气数据成功,共有{weatherDataList.Count}条");
}
这里使用swith case是因为网页的格式并不是一层不变的,有时候少了一列,没有霾的数据。
wd9.StationName = tds[0].InnerText.Trim().Replace(" ", "");
这里对文本进行这样处理是因为原始的数据是“\n内容 \n”,C#中String.Trim()方法会删除字符串前后的空白,string.Replace("a","b")方法会将字符串中的a换成b。
效果如下所示:


将数据全部都存入数据库中了。
4、最后
通过这个实例说明了其实C#也是可以实现网页爬虫的,对于没有反爬的情况下是完全适用的,再配合linq做数据处理也是可以的。
C#简化工作之实现网页爬虫获取数据的更多相关文章
- 使用Xpath从网页中获取数据
/// <summary> /// 从官方网站中抓取产品信息存放在本地数据库中 /// </summary> /// <returns></returns&g ...
- Python开发实战教程(8)-向网页提交获取数据
来这里找志同道合的小伙伴!↑↑↑ Python应用现在如火如荼,应用范围很广.因其效率高开发迅速的优势,快速进入编程语言排行榜前几名.本系列文章致力于可以全面系统的介绍Python语言开发知识和相关知 ...
- 利用Jsoup模拟跳过登录爬虫获取数据
今天在学习爬虫的时候想着学习一下利用jsoup模拟登录.下面分为有验证码和无验证码的情况进行讨论. ---------------------------无验证码的情况---------------- ...
- HttpURLConnection连接网页和获取数据的使用实例
HttpURLConnection是java.net 里面自带的一个类,非常好用.虽然现在很多人用阿帕奇的HttpClient,但HttpURLConnection也是个不错的选择. 其实使用方法非常 ...
- nodeJs爬虫获取数据
var http=require('http'); var cheerio=require('cheerio');//页面获取到的数据模块 var url='http://www.jcpeixun.c ...
- 在我的新书里,尝试着用股票案例讲述Python爬虫大数据可视化等知识
我的新书,<基于股票大数据分析的Python入门实战>,预计将于2019年底在清华出版社出版. 如果大家对大数据分析有兴趣,又想学习Python,这本书是一本不错的选择.从知识体系上来看, ...
- JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫
JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...
- Java 网络爬虫获取网页源代码原理及实现
Java 网络爬虫获取网页源代码原理及实现 1.网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成.传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL ...
- 爬虫 Http请求,urllib2获取数据,第三方库requests获取数据,BeautifulSoup处理数据,使用Chrome浏览器开发者工具显示检查网页源代码,json模块的dumps,loads,dump,load方法介绍
爬虫 Http请求,urllib2获取数据,第三方库requests获取数据,BeautifulSoup处理数据,使用Chrome浏览器开发者工具显示检查网页源代码,json模块的dumps,load ...
- 网页爬虫的设计与实现(Java版)
网页爬虫的设计与实现(Java版) 最近为了练手而且对网页爬虫也挺感兴趣,决定自己写一个网页爬虫程序. 首先看看爬虫都应该有哪些功能. 内容来自(http://www.ibm.com/deve ...
随机推荐
- [redis]定制封装redis的docker镜像
前言 应开发需求,定制封装redis的docker镜像,需要通过环境变量修改redis的密码. redis.conf port 6379 requirepass REDIS_PASSWD daemon ...
- 如何在工作中利用Prompt高效使用ChatGPT?
导读 AI 不是来替代你的,是来帮助你更好工作.用better prompt使用chatgpt,替换搜索引擎,让你了解如何在工作中利用Prompt高效使用ChatGPT. 01背景 现在 GPT 已经 ...
- 6、Mybatis之高级查询
6.1.创建接口.映射文件和测试类 ++++++++++++++++++++++++++分割线++++++++++++++++++++++++++ 注意namespace属性值为对应接口的全限定类名 ...
- FastJson不成想还有个版本2啊:序列化大字符串报错
背景 发现陷入了一个怪圈,写文章的话,感觉只有大bug或比较值得写的内容才会写,每次一写就是几千字,争取写得透彻一些,但这样,我也挺费时间,读者也未必有这么多时间看. 我想着,日常遇到的小bug.平时 ...
- Echarts--x轴文本过长,设置超出隐藏显示省略号,鼠标悬浮上显示全部
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8&q ...
- vue3探索——组件通信之依赖注入
背景 通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props.想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据.在 ...
- wget 命令的使用:HTTP文件下载、FTP文件下载--九五小庞
1. wget 命令简介与安装wget是用于在命令行终端下载网络文件的开源免费的命令工具,支持 HTTP/HTTPS.FTP/FTPS 协议的下载.wget 与 curl 相似,curl 可以理解为是 ...
- 蚂蚁集团混沌工程 ChaosMeta V0.5 版本发布
混沌工程 ChaosMeta 的全新版本 V0.5 现已正式发布!该版本包含了许多新特性和增强功能,为用户提供了支撑混沌工程各个阶段的平台能力,以及降低使用门槛的用户界面. ChaosMeta V0. ...
- 接口自动化测试项目 | IHRM登录接口自动化测试
项目内容如下: ### 需求- 地址:http://ihrm-java.itheima.net/#/login- 测试接口: - 登录接口:针对登录的13个cases### 技术 - V1:pytho ...
- Go 语言内置类型全解析:从布尔到字符串的全维度探究
关注微信公众号[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证 ...