公众号「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("&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。

效果如下所示:

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

4、最后

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

C#简化工作之实现网页爬虫获取数据的更多相关文章

  1. 使用Xpath从网页中获取数据

    /// <summary> /// 从官方网站中抓取产品信息存放在本地数据库中 /// </summary> /// <returns></returns&g ...

  2. Python开发实战教程(8)-向网页提交获取数据

    来这里找志同道合的小伙伴!↑↑↑ Python应用现在如火如荼,应用范围很广.因其效率高开发迅速的优势,快速进入编程语言排行榜前几名.本系列文章致力于可以全面系统的介绍Python语言开发知识和相关知 ...

  3. 利用Jsoup模拟跳过登录爬虫获取数据

    今天在学习爬虫的时候想着学习一下利用jsoup模拟登录.下面分为有验证码和无验证码的情况进行讨论. ---------------------------无验证码的情况---------------- ...

  4. HttpURLConnection连接网页和获取数据的使用实例

    HttpURLConnection是java.net 里面自带的一个类,非常好用.虽然现在很多人用阿帕奇的HttpClient,但HttpURLConnection也是个不错的选择. 其实使用方法非常 ...

  5. nodeJs爬虫获取数据

    var http=require('http'); var cheerio=require('cheerio');//页面获取到的数据模块 var url='http://www.jcpeixun.c ...

  6. 在我的新书里,尝试着用股票案例讲述Python爬虫大数据可视化等知识

    我的新书,<基于股票大数据分析的Python入门实战>,预计将于2019年底在清华出版社出版. 如果大家对大数据分析有兴趣,又想学习Python,这本书是一本不错的选择.从知识体系上来看, ...

  7. JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

    JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...

  8. Java 网络爬虫获取网页源代码原理及实现

    Java 网络爬虫获取网页源代码原理及实现 1.网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成.传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL ...

  9. 爬虫 Http请求,urllib2获取数据,第三方库requests获取数据,BeautifulSoup处理数据,使用Chrome浏览器开发者工具显示检查网页源代码,json模块的dumps,loads,dump,load方法介绍

    爬虫 Http请求,urllib2获取数据,第三方库requests获取数据,BeautifulSoup处理数据,使用Chrome浏览器开发者工具显示检查网页源代码,json模块的dumps,load ...

  10. 网页爬虫的设计与实现(Java版)

    网页爬虫的设计与实现(Java版)     最近为了练手而且对网页爬虫也挺感兴趣,决定自己写一个网页爬虫程序. 首先看看爬虫都应该有哪些功能. 内容来自(http://www.ibm.com/deve ...

随机推荐

  1. NOIP2022 题解

    终于有机会补NOIP的题了 T1 考虑枚举 C 与 F 的纵列 考虑预处理出每个点最左边和最下边可以延伸到哪 之后枚举列,然后对行做类似于扫描线的操作,统计有多少可行的 "第一横行" ...

  2. 基于CUBEMX的STM32F4 Hal库,配置LVGL(无操作系统版)

    本篇文章移植思路适用于所有嵌入式MCU,包括Arm,STM32,NXP,乐鑫,Nuvoton,Arduino,RT-Thread,Zephyr,NuttX,Adafruit等等. 为什么要写这一篇移植 ...

  3. cs50ai1

    cs50ai1-------Knowledge cs50ai1-------Knowledge 基础知识 课后题目 代码实践 学习链接 总结 基础知识 对我们来说,一些基本的logic是自然而然的,我 ...

  4. 使用 Rancher 安装 K8s 集群

    舞台环境 Ubuntu 22.04.2 LTS Docker 24.0.2 2GB RAM或者更多 CPU 2核心或者更多 Rancher 2.6.9 测试环境中,我准备了两台 Ubuntu 服务器, ...

  5. KRPANO资源分析工具下载VR-FACTORY全景图

    示:目前分析工具中的全景图下载功能将被极速全景图下载大师替代,相比分析工具,极速全景图下载大师支持更多的网站(包括各类KRPano全景网站,和百度街景) 详细可以查看如下的链接: 极速全景图下载大师官 ...

  6. 一篇文章让你弄懂分布式一致性协议Paxos

    一.Paxos协议简介 Paxos算法由Leslie Lamport在1990年提出,它是少数在工程实践中被证实的强一致性.高可用.去中心的分布式协议.Paxos协议用于在多个副本之间在有限时间内对某 ...

  7. (数据科学学习手札154)geopandas 0.14版本新特性一览

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,就在前两天,Python生态中 ...

  8. Record -「Tricks」记录

    曼哈顿距离 \(\text{dist}(A,B)=|x_{A}-x_{B}|+|y_{A}-y_{B}|\) 可以拆成 \(\max\{x_{A}-x_{B}+y_{A}-y_{B},x_{A}-x_ ...

  9. mpi转以太网连接300PLC在气动系统中的应用

    mpi转以太网连接300PLC在气动系统中的应用 某企业装备有限公司 摘要 工业通讯迅速发展的今天,MPI转以太网通讯已经发展为成熟,稳定,高效通讯 方式,兴达易控自主研发的MPI转以太网模块MPI- ...

  10. Harry Potter RPG_1

    RPG--Harry Potter 博主最近迷上了<Harry Potter> So 我制作了一款RPG对话模拟游戏, 目前主线以进行到了分院以后: 有兴趣的小伙伴可以看看,能点个关注就更 ...