C#简化工作之实现网页爬虫获取数据
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}");}}//构造每一页的URLpublic 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点气象预告";// 解析字符串为DateTimeDateTime 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#简化工作之实现网页爬虫获取数据
1、需求 想要获取网站上所有的气象信息,网站如下所示: 目前总共有67页,随便点开一个如下所示: 需要获取所有天气数据,如果靠一个个点开再一个个复制粘贴那么也不知道什么时候才能完成,这个时候就可以使用C…...
回顾过去的五年
回顾过去的五年 不知不觉,一晃就5年了。孩子也慢慢的长大了,都快和我一样高了。 2017-2019年依旧服务于原公司。后来公司停业了,得到了相应的赔偿。在家里呆了几个月,变成了无业游民。陪伴家人,也会收到家人的鞭策。…...
企业微信http协议接口调用,根据手机号搜索联系人
产品说明 一、 hook版本:企业微信hook接口是指将企业微信的功能封装成dll,并提供简易的接口给程序调用。通过hook技术,可以在不修改企业微信客户端源代码的情况下,实现对企业微信客户端的功能进行扩展和定制化。企业微信hook接口…...
第三方支付原理
1.什么是第三方支付 所谓第三方支付,就是一些和各大银行签约、并具备一定实力和信誉保障的第三方独立机构提供的交易支持平台。在通过第三方支付平台的交易中,买方选购商品后,使用第三方平台提供的账户进行货款支付,由第三方通知卖…...
logcat日志的使用——Qt For Android
前言 最近一直用qt开发安卓app,一直无法用真机调试,可能是缺什么东西。但是如果通过Qt Creator在真机上运行,可以在电脑控制台看打印(安卓本身的日志、qDebug之类的打印),所以我是通过打印猜测问题所在&am…...
软著项目推荐 深度学习的智能中文对话问答机器人
文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分:4.2 损失函数:4.3 搭建seq2seq框架:4.4 测试部分:4.5 评价NLP测试效果:4.6 梯度截断…...
灰度发布专题---3、Nginx+Lua灰度发布
上一章已经讲解了配置文件灰度发布、应用版本灰度发布、API网关灰度发布实现,但如果用户这时候在代理层如何做灰度发布呢? 代理层灰度发布分析 用户无论访问应用服务还是静态页,都要经过Nginx代理层,我们可以在Nginx这里做灰度发…...
冬天来了,波司登的高端化“春天”不远了?
最近,羽绒服频繁“贵”上热搜。 在众多热搜词条中,一条“国产羽绒服卖到7000元”的话题一度将波司登推上了舆论的风口浪尖。 对此,波司登在最新的业绩说明会上进行了回应,公司表示:“波司登旗下主品牌及子品牌将形成差…...
Vue3.0优点详解
相对于Vue2.0 3.0有了比较大的改进,优势主要有以下几点: 一、性能提升 1、Vue3.0的响应式系统使用了Proxy代理对象,取代了Vue2.0中的Object.defineProperty,使得Vue3.0的响应式系统更快、更灵活。 2、Vue3.0对TypeScript的支持更…...
Unity3D URP 自定义范围的特效热扭曲详解
前言 Unity3D URP(Universal Render Pipeline)是Unity官方推出的一款渲染管线,可以实现高效、高质量的图形渲染。在URP中,我们可以通过自定义特效来增强游戏的视觉效果。本文将详细解释如何使用URP实现一个自定义范围的特效热扭曲…...
Apache Flink(一):Apache Flink是什么?
🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录...
Wordpress自动定时发布怎么开通-Wordpress怎么自动发布原创文章
在当今数字化时代,博客已经成为许多人分享观点、经验和知识的重要平台。然而,对于博主们来说,每天按时发布一篇又一篇的文章可能是一项具有挑战性的任务。为了解决这个问题,一些创新的工具应运而生,其中包括WordPress的…...
VUE项目中问题学习总结(一)
文章目录 🍁自定义组件使用🍁clearInterval函数的使用🌿定时器的作用 🍁localStorage的使用🌿设置数据🌿获取数据🌿更新数据🌿删除数据 🍁VUE国际化配置🍁项目…...
使用K-means把人群分类
1.前言 K-mean 是无监督的聚类算法 算法分类: 2.实现步骤 1.数据加工:把数据转为全数字(比如性别男女,转换为0 和 1) 2.模型训练 fit 3.预测 3.代码 原数据类似这样(source:http:img-blog.csdnimg.cn…...
静态HTTP和动态HTTP有什么区别
静态HTTP是指网页内容在服务器上以静态文件的形式存在,每个页面都是固定的,不能根据用户的操作或输入进行改变。当用户请求一个静态页面时,服务器直接将页面的HTML代码返回给用户的浏览器进行显示。静态HTTP服务器的主要优点是速度快、简单易…...
分享66个在线客服JS特效,总有一款适合您
分享66个在线客服JS特效,总有一款适合您 66个在线客服JS特效下载 链接:https://pan.baidu.com/s/1VqM6ASgKRFdQ8RyzbsX4uA?pwd6666 提取码:6666 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气࿰…...
Backend - Django JsonResponse HttpResponse
目录 一、关系 二、使用 (一)data 字典传值 1. JsonResponse 2. HttpResponse 3. 例子 (二)JsonResponse 有一个 safe 参数 (三)前端接收 1. 接收 JsonResponse 回传的值 2. 接收 HttpResponse 回…...
第四阶|自在行草 暄桐教室,林曦书法 从书法之美到生活之美
我这有很多的课程,需要了可以取用 新一期(入门课),目前已经更新完毕。 新一期(第一阶),目前已经更新完毕。 新一期(第二阶),目前已经更新完毕。 新一期&#…...
kubernetes详解——从入门到入土(更新中~)
k8s简介 编排工具:系统层面ansible、saltstackdocker容器docker compose docker swarm docker machinedocker compose:实现单机容器编排docker swarm:实现多主机整合成为一个docker machine:初始化新主机mesos marathonmesos …...
VScode异常处理 (因为在此系统上禁止运行脚本)
在使用 VScode 自带程序终端的时候会报出"系统禁止脚本运行的错误" 这是由于 Windows PowerShell执行策略导致的 解决办法 管理员身份运行 Windows PowerShell执行:get-ExecutionPolicy1,显示Restricted2执行:Set-ExecutionPoli…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
