当前位置: 首页 > news >正文

C#简化工作之实现网页爬虫获取数据

1、需求

想要获取网站上所有的气象信息,网站如下所示:

image-20231127193134632

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

image-20231127193254040

需要获取所有天气数据,如果靠一个个点开再一个个复制粘贴那么也不知道什么时候才能完成,这个时候就可以使用C#来实现网页爬虫获取这些数据。

2、效果

先来看下实现的效果,所有数据都已存入数据库中,如下所示:

image-20231127193726966

总共有4万多条数据。

3、具体实现

构建每一页的URL

第一页的网址如下所示:

image-20231127194211474

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

image-20231127195622290

可以发现是有规律的,那么就可以先尝试构建出每个页面的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

image-20231127195928840

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信息,如下所示:

image-20231127201037439

可以进一步构建每一天的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标签,如下所示:

image-20231127201558734

构建每一天的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标签,如下所示:

image-20231127201814284

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的效果如下所示:

image-20231127202711471

获取温度数据

需要获取的内容如下:

image-20231127202852536

设计对应的类如下:

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("&nbsp;", "");wd8.Weather = tds[1].InnerText.Trim().Replace("&nbsp;", "");wd8.Tem_Low = tds[2].InnerText.Trim().Replace("&nbsp;", "");wd8.Tem_High = tds[3].InnerText.Trim().Replace("&nbsp;", "");wd8.Wind = tds[4].InnerText.Trim().Replace("&nbsp;", "");wd8.Visibility_Low = tds[5].InnerText.Trim().Replace("&nbsp;", "");wd8.Visibility_High = tds[6].InnerText.Trim().Replace("&nbsp;", "");wd8.Fog = tds[7].InnerText.Trim().Replace("&nbsp;", "");wd8.Date = list[i].Date;weatherDataList.Add(wd8);break;case 9:var wd9 = new WeatherData();wd9.StationName = tds[0].InnerText.Trim().Replace("&nbsp;", "");wd9.Weather = tds[1].InnerText.Trim().Replace("&nbsp;", "");wd9.Tem_Low = tds[2].InnerText.Trim().Replace("&nbsp;", "");wd9.Tem_High = tds[3].InnerText.Trim().Replace("&nbsp;", "");wd9.Wind = tds[4].InnerText.Trim().Replace("&nbsp;", "");wd9.Visibility_Low = tds[5].InnerText.Trim().Replace("&nbsp;", "");wd9.Visibility_High = tds[6].InnerText.Trim().Replace("&nbsp;", "");wd9.Fog = tds[7].InnerText.Trim().Replace("&nbsp;", "");wd9.Haze = tds[8].InnerText.Trim().Replace("&nbsp;", "");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("&nbsp;", "");

这里对文本进行这样处理是因为原始的数据是“\n内容&nbsp\n”,C#中String.Trim()方法会删除字符串前后的空白,string.Replace(“a”,“b”)方法会将字符串中的a换成b。

效果如下所示:

image-20231127204509544

image-20231127204629667

将数据全部都存入数据库中了。

4、最后

通过这个实例说明了其实C#也是可以实现网页爬虫的,对于没有反爬的情况下是完全适用的,再配合linq做数据处理也是可以的。

相关文章:

C#简化工作之实现网页爬虫获取数据

1、需求 想要获取网站上所有的气象信息&#xff0c;网站如下所示&#xff1a; 目前总共有67页&#xff0c;随便点开一个如下所示&#xff1a; 需要获取所有天气数据&#xff0c;如果靠一个个点开再一个个复制粘贴那么也不知道什么时候才能完成&#xff0c;这个时候就可以使用C…...

回顾过去的五年

回顾过去的五年 不知不觉&#xff0c;一晃就5年了。孩子也慢慢的长大了&#xff0c;都快和我一样高了。 2017-2019年依旧服务于原公司。后来公司停业了&#xff0c;得到了相应的赔偿。在家里呆了几个月&#xff0c;变成了无业游民。陪伴家人&#xff0c;也会收到家人的鞭策。…...

企业微信http协议接口调用,根据手机号搜索联系人

产品说明 一、 hook版本&#xff1a;企业微信hook接口是指将企业微信的功能封装成dll&#xff0c;并提供简易的接口给程序调用。通过hook技术&#xff0c;可以在不修改企业微信客户端源代码的情况下&#xff0c;实现对企业微信客户端的功能进行扩展和定制化。企业微信hook接口…...

第三方支付原理

1.什么是第三方支付 所谓第三方支付&#xff0c;就是一些和各大银行签约、并具备一定实力和信誉保障的第三方独立机构提供的交易支持平台。在通过第三方支付平台的交易中&#xff0c;买方选购商品后&#xff0c;使用第三方平台提供的账户进行货款支付&#xff0c;由第三方通知卖…...

logcat日志的使用——Qt For Android

前言 最近一直用qt开发安卓app&#xff0c;一直无法用真机调试&#xff0c;可能是缺什么东西。但是如果通过Qt Creator在真机上运行&#xff0c;可以在电脑控制台看打印&#xff08;安卓本身的日志、qDebug之类的打印&#xff09;&#xff0c;所以我是通过打印猜测问题所在&am…...

软著项目推荐 深度学习的智能中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…...

灰度发布专题---3、Nginx+Lua灰度发布

上一章已经讲解了配置文件灰度发布、应用版本灰度发布、API网关灰度发布实现&#xff0c;但如果用户这时候在代理层如何做灰度发布呢&#xff1f; 代理层灰度发布分析 用户无论访问应用服务还是静态页&#xff0c;都要经过Nginx代理层&#xff0c;我们可以在Nginx这里做灰度发…...

冬天来了,波司登的高端化“春天”不远了?

最近&#xff0c;羽绒服频繁“贵”上热搜。 在众多热搜词条中&#xff0c;一条“国产羽绒服卖到7000元”的话题一度将波司登推上了舆论的风口浪尖。 对此&#xff0c;波司登在最新的业绩说明会上进行了回应&#xff0c;公司表示&#xff1a;“波司登旗下主品牌及子品牌将形成差…...

Vue3.0优点详解

相对于Vue2.0 3.0有了比较大的改进&#xff0c;优势主要有以下几点&#xff1a; 一、性能提升 1、Vue3.0的响应式系统使用了Proxy代理对象&#xff0c;取代了Vue2.0中的Object.defineProperty&#xff0c;使得Vue3.0的响应式系统更快、更灵活。 2、Vue3.0对TypeScript的支持更…...

Unity3D URP 自定义范围的特效热扭曲详解

前言 Unity3D URP&#xff08;Universal Render Pipeline&#xff09;是Unity官方推出的一款渲染管线&#xff0c;可以实现高效、高质量的图形渲染。在URP中&#xff0c;我们可以通过自定义特效来增强游戏的视觉效果。本文将详细解释如何使用URP实现一个自定义范围的特效热扭曲…...

Apache Flink(一):Apache Flink是什么?

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录...

Wordpress自动定时发布怎么开通-Wordpress怎么自动发布原创文章

在当今数字化时代&#xff0c;博客已经成为许多人分享观点、经验和知识的重要平台。然而&#xff0c;对于博主们来说&#xff0c;每天按时发布一篇又一篇的文章可能是一项具有挑战性的任务。为了解决这个问题&#xff0c;一些创新的工具应运而生&#xff0c;其中包括WordPress的…...

VUE项目中问题学习总结(一)

文章目录 &#x1f341;自定义组件使用&#x1f341;clearInterval函数的使用&#x1f33f;定时器的作用 &#x1f341;localStorage的使用&#x1f33f;设置数据&#x1f33f;获取数据&#x1f33f;更新数据&#x1f33f;删除数据 &#x1f341;VUE国际化配置&#x1f341;项目…...

使用K-means把人群分类

1.前言 K-mean 是无监督的聚类算法 算法分类&#xff1a; 2.实现步骤 1.数据加工&#xff1a;把数据转为全数字&#xff08;比如性别男女&#xff0c;转换为0 和 1&#xff09; 2.模型训练 fit 3.预测 3.代码 原数据类似这样(source&#xff1a;http:img-blog.csdnimg.cn…...

静态HTTP和动态HTTP有什么区别

静态HTTP是指网页内容在服务器上以静态文件的形式存在&#xff0c;每个页面都是固定的&#xff0c;不能根据用户的操作或输入进行改变。当用户请求一个静态页面时&#xff0c;服务器直接将页面的HTML代码返回给用户的浏览器进行显示。静态HTTP服务器的主要优点是速度快、简单易…...

分享66个在线客服JS特效,总有一款适合您

分享66个在线客服JS特效&#xff0c;总有一款适合您 66个在线客服JS特效下载 链接&#xff1a;https://pan.baidu.com/s/1VqM6ASgKRFdQ8RyzbsX4uA?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0…...

Backend - Django JsonResponse HttpResponse

目录 一、关系 二、使用 &#xff08;一&#xff09;data 字典传值 1. JsonResponse 2. HttpResponse 3. 例子 &#xff08;二&#xff09;JsonResponse 有一个 safe 参数 &#xff08;三&#xff09;前端接收 1. 接收 JsonResponse 回传的值 2. 接收 HttpResponse 回…...

第四阶|自在行草 暄桐教室,林曦书法 从书法之美到生活之美

我这有很多的课程&#xff0c;需要了可以取用 新一期&#xff08;入门课&#xff09;&#xff0c;目前已经更新完毕。 新一期&#xff08;第一阶&#xff09;&#xff0c;目前已经更新完毕。 新一期&#xff08;第二阶&#xff09;&#xff0c;目前已经更新完毕。 新一期&#…...

kubernetes详解——从入门到入土(更新中~)

k8s简介 编排工具&#xff1a;系统层面ansible、saltstackdocker容器docker compose docker swarm docker machinedocker compose&#xff1a;实现单机容器编排docker swarm&#xff1a;实现多主机整合成为一个docker machine&#xff1a;初始化新主机mesos marathonmesos …...

VScode异常处理 (因为在此系统上禁止运行脚本)

在使用 VScode 自带程序终端的时候会报出"系统禁止脚本运行的错误" 这是由于 Windows PowerShell执行策略导致的 解决办法 管理员身份运行 Windows PowerShell执行&#xff1a;get-ExecutionPolicy1&#xff0c;显示Restricted2执行&#xff1a;Set-ExecutionPoli…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...