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…...
(5h)Unity3D快速入门之Roll-A-Ball游戏开发
DAY1:Unity3D安装 链接 DAY2:构建场景,编写代码 链接 内容:WASD前后左右移动、摄像机跟随 DAY3:待更新 DAY4:待更新 DAY5:待更新...
分享86个选项卡TABJS特效,总有一款适合您
分享86个选项卡TABJS特效,总有一款适合您 86个选项卡TABJS特效下载链接:https://pan.baidu.com/s/1NBtPP2tT5YQqi6c744tCqg?pwd6666 提取码:6666 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气࿰…...
【Linux】Linux基础
文章目录 学习目标操作系统不同应用领域的主流操作系统虚拟机 Linux系统的发展史Linux内核版和发行版 Linux系统下的文件和目录结构单用户操作系统vs多用户操作系统Windows和Linux文件系统区别 Linux终端命令格式终端命令格式查阅命令帮助信息 常用命令显示文件和目录切换工作目…...
动态规划求解 fibonacci 数列
动态规划: 动态规划的基本思想是:将原问题拆分为若干子问题,自底向上的求解。是自底向上的求解,即是先计算子问题的解,再得出原问题的解。 思路: 创建一个数组,大小为n1,用于存储斐波那契数列的值。数组的…...
js最大公约数的实现有哪些办法
在JavaScript中,有几种常见的方法可以实现最大公约数(GCD)的计算。以下是其中一些方法: 辗转相除法(欧几里德算法): 辗转相除法是一种基于递归的算法,用于计算两个数的最大公约数。它…...
盘后股价狂飙16% — GitLab的DevOps产品在AI时代展现强劲财务业绩
12月4日(周一)在美股收盘后,GitLab的股价狂飙16%!人工智能驱动的DevOps产品继续凸显其平台能力的优势。 GitLab 12 月 4 日股价图 GitLab报告第三季度收入同比增长32%!根据粗略统计,全球已经有接近1万家企…...
unity UI特效遮罩
using System.Collections; using System.Collections.Generic; using UnityEngine;/**UI特效遮罩 1.需要将ScrollRect 的遮罩Mask 换为 2D Mask2.将特效的Render里面的 Masking 设置为*/ public class UIParticleMaskControll : MonoBehaviour {// Start is called before …...
编程模拟支付宝能量产生过程--数据控制流
#模拟支付宝蚂蚁森林的能量产生过程 behavior_points { # 定义行为对应的积分"步行": 2,"生活缴费": 10,"线下支付": 5,"网络购票": 5,"共享单车": 10 }total_points 0 # 初始化总积分while True: # 开…...
SQL Sever 基础知识 - 数据筛选(1)
SQL Sever 基础知识 - 四、数据筛选 四、筛选数据第1节 DISTINCT - 去除重复值1.1 SELECT DISTINCT 子句简介1.2 SELECT DISTINCT 示例1.2.1 DISTINCT 一列示例1.2.2 DISTINCT 多列示例 1.2.3 DISTINCT 具有 null 值示例1.2.4 DISTINCT 与 GROUP BY 对比 第2节 WHERE - 过滤查询…...
2024 Move 中文开发者大会将于1月13–14日在上海举办
*以下文章来源于MoveFuns ,作者MoveFunsDAO 2024 Move 中文开发者大会将于1月13日-1月14日在上海举办。本届 Move 开发者大会以 “Move 生态关键的一年” 为主题。 由 MoveFuns 、OpenBuild 和 MoveBit 主办,Rooch、AptosGlobal、alcove、zkMove 和 Ti…...
延安有哪些做网站的公司/应用商店关键词优化
radio组件的样式大小太大了,和我的文字大小不匹配,看起来很难看 于是给radio设置了width和 height ,然而并不起作用 最后是使用transform:scale(0.7); 完美解决问题...
公司网站建设工作重点/提高网站搜索排名
问题描述 今天在设计python串口代码时遇见一个问题,接收到的数据打印出来,一直都是两三组连在一起,即我的目标数据是: 7e307d02087d01557e 但是我把接收到的数据经过处理打印出来后是:7e307d02087d01557e7e307d02087d…...
快站微信网站制作/免费b站推广软件
最近准备玩一下Oracle Golden Gate,需要搭个新环境,安装过程记录一下。操作系统版本:Oracle Linux Server release 5.7Oracle版本:Oracle Database 11g Release 2 (11.2.0.1.0) for Linux x86一、安装前的环境配置1. 检查官方文档…...
wordpress编程主题/最新app推广项目平台
使用Property管理属性python提供了一种友好的getter、setter、deleter类方法的属性管理工具:property。property()是一个内置函数,它返回一个Property对象,它的用法很简单,将getter、setter、deleter三个方法作为它的参数即可&…...
做网站郑州汉狮/上海城市分站seo
Python3中有六种基本数据类型: Number(数字) String(字符串) List(列表) Tuple(元组) Set(集合) Dict(字典) 他们之前有什么区别?联系?直接上图:按照图示的分类,开始学习吧。(python3) Number(数字类型) Python3 支持 int、float、bool、complex(复数)…...
扬中网站建设/登封搜索引擎优化
2019独角兽企业重金招聘Python工程师标准>>> 当你无法迅速的理解一个方法的逻辑时,把方法的逻辑转换成几个同一层面上的、能够说明意图的步骤。 动机: Composed Method由对其他方法的调用组成,好的Composed Method的代码都在细节的…...