• [项目记录]一个.net下使用HAP实现的吉大校园通知网爬虫工具:OAWebScraping


     

    本文为大大维原创,最早于博客园发表,转载请注明出处!!!

     

    第一章 简介

    本文主要介绍了在.NET下利用优秀的HTML解析组件HtmlAgilityPack开发的一个吉林大学校内通知oa.jlu.edu.cn的爬取器。尽管.Net下解析HTML文件有很多种选择,包括微软自己也提供MSHTML用于manipulate HTML文件。但是,经过我多方查阅资料和自己的尝试,Html Agility Pack逐步脱颖而出:它是Stackoverflow网站上推荐最多的C# HTML解析器。HAP开源,易用,解析速度快。因此,本人最终选择使用HAP作为爬虫的开发的HTML解析组件。

    笔者实现的爬虫OAWebScraping可以实现由使用者指定时间的(从当前时间往前n天)的,对oa.jlu.edu.cn上的所有所有通知和新闻,包括时间、标题、发表部门、正文全文和附件的爬取,并且按照时间->发表部门->标题->正文及附件的树形结构将爬取的文件保存在硬盘中。以下是使用截图:

    开始爬虫,输入爬取的时间范围:

     

    爬取成功,对于没有发放通知的日期给予提示:

     

    文件的组织结构:

     

    爬取的文件按照树形结构

    时间->发表部门->标题->正文及附件

    保存在硬盘:

    按时间:

     

    按发表部门:

    按标题:

    正文及附件:

    正文:

     

    附件:

     

    第二章    研究方法

    (1) How to use HAP?1

    1. 下载http://htmlagilitypack.codeplex.com/

    2. 解压

    3. 在Visual Studio Solution里,右击project -> add reference -> 选择解压文件夹里的HTMLAgilityPack.dll -> 确定

    4. 代码头部加入 using HtmlAgilityPack;

    Done!

    (2)HAP 概述:2

          在HtmlAgilityPack中常用到的类有HtmlDocument、HtmlNodeCollection、            HtmlNode和HtmlWeb等.其流程一般是先获取HTML,这个可以通过HtmlDocument的Load()或LoadHtml()来加载静态内容,或者也可以HtmlWeb的Get()或Load()方法来加载网络上的URL对应的HTML。

          得到了HtmlDocument的实例之后,就可以用HtmlDocument的DocumentNode属性,这是整个HTML文档的根节点,它本身也是一个HtmlNode,然后就可以利用HtmlNode的SelectNodes()方法返回多个HtmlNode的集合对象HtmlNodeCollection,也可以利用HtmlNode的SelectSingleNode()方法返回单个HtmlNode。

    (3)XPath 概述:[2]

          HtmlAgilityPack是一个支持用XPath来解析HTML的类库,避免了采用正则表达式一步步将无关的HTML注释及JS代码部分删除掉,然后再用正则表达式找出需要提取的部分,可以说使用正则表达式来做是一个比较繁琐的过程,以下是一个简单的XPath介绍:XPath 使用路径表达式来选取 XML 文档中的节点或节点集。节点是通过沿着路径 (path) 或者步 (steps) 来选取的。
     下面列出了最有用的路径表达式:
     nodename:选取此节点的所有子节点。 
     /:从根节点选取。 
     //:从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 
     .:选取当前节点。 
     ..:选取当前节点的父节点。

    (4)关键代码分析:

     

    Main函数逻辑:通过GetItemWebs(range)找出需要爬取的url簇,然后用etItemWeb(itemWebUrl)爬取文件并下载。

     

    拼接yyyyMMdd天的oa通知界面url。

     

    加载Html并查找当前页,由于oa中通知跳转连接均在<div> class“li rel”下 的<a>

    Class “font14” 中,爬取出其href并拼接成相关具体通知的url,压入一个List(itemWebs)中,由于有些时段没有发通知,这样会导致nodes为空,会使得nodes.Select()发生空指针异常,因此引入异常机制,保证程序正常运行,并对没有发通知的日期进行提示。

    以上是GetItemWebs(int range)的主要逻辑。该函数返回一个所有需要查找的通知的url的string s数组。

     

    在GetItemWeb(string itemWebUrl)函数中,通过url初始化uri,解析uri获得title和orgname。

     

     解析HtmlDocument并通过<div> class ‘content_time’ 获得通知发布时间。

     

    解析HtmlDocument中<div> class ‘content_font fontsize immmge’中的p标签,来拼接通知正文。

    按照“时间->发表部门->标题->正文”树形结构将正文存储在硬盘

     

    下载附件,并将附件和对应的正文存在一个文件中。

    以上是GetItemWeb(string itemWebUrl)的主要逻辑。

     第三章 数据分析和结果

          由于时间限制,笔者爬取了2017/6/15-2017/6/20之间6天的所有数据,其中2017/6/18没有通知,没有数据。另外17号只有研究院发放的一条通知,其他时段基本上每天都有10个左右的部门发放15条左右的通知。查日历可知,17,18号为周末。

          可见在工作日中,我校的教务工作都在积极进行,另外党委组织部和宣传部是通知发送的大头。具体可见ScrapingFiles文件夹(爬取数据的存储文件夹)。

    第四章 结论

          笔者在.NET下利用优秀的HTML解析组件HtmlAgilityPack开发的一个吉林大学校内通知oa.jlu.edu.cn的爬取器,测试好用并且挺实用。

    第五章 参考文献

    【1】Brian网络畅游的记录的博客《开源项目Html Agility Pack实现快速解析Html

    http://www.cnblogs.com/GmrBrian/p/6201237.html

    【2】51Ct0博主周公的博客《HTML解析利器HtmlAgilityPack》:

    http://zhoufoxcn.blog.51cto.com/792419/595344/

    【3】CSDN无极世界博主的博客《HtmlAgilityPack——解析html和采集网页的神兵利器http://blog.csdn.net/dalmeeme/article/details/7191793

    【4】HAP官网的开发文档

    http://htmlagilitypack.codeplex.com/

    第六章 代码

      1 using System;
      2 using System.Collections.Generic;
      3 using System.IO;
      4 using System.Linq;
      5 using System.Net;
      6 using System.Web;
      7 using HtmlAgilityPack;
      8 
      9 //作者:大大维
     10 namespace OA
     11 {
     12     class Program
     13     {
     14         static void Main()
     15         {
     16             Console.WriteLine("用于查询距今X天的oa通知!!!欢迎使用!!!");
     17             Console.WriteLine("请输入查询的范围(距现在多少天,0表示当天,1表示昨天+今天,以此类推):");
     18             var range = int.Parse(Console.ReadLine());
     19             Console.WriteLine("开始爬取!!!");
     20             var itemWebUrls = GetItemWebs(range);
     21             foreach (var itemWebUrl in itemWebUrls)
     22             {
     23                 GetItemWeb(itemWebUrl);
     24             }
     25             Console.WriteLine("爬取完毕!!!");
     26             Console.ReadLine();         
     27         }
     28 
     29         private static string[] GetItemWebs(int range)
     30         {
     31             /*在HtmlAgilityPack中常用到的类有HtmlDocument、HtmlNodeCollection、 
     32             HtmlNode和HtmlWeb等.其流程一般是先获取HTML,这个可以通过HtmlDocument的Load()或LoadHtml()来加载静态内容,
     33             或者也可以HtmlWeb的Get()或Load()方法来加载网络上的URL对应的HTML。 
     34             得到了HtmlDocument的实例之后,就可以用HtmlDocument的DocumentNode属性,
     35             这是整个HTML文档的根节点,它本身也是一个HtmlNode,
     36             然后就可以利用HtmlNode的SelectNodes()方法返回多个HtmlNode的集合对象HtmlNodeCollection,
     37             也可以利用HtmlNode的SelectSingleNode()方法返回单个HtmlNode。 */
     38             var itemWebs = new List<string>();//由于每页网站的通知内容数目不定,采用List较好
     39             //@忽略转义字符
     40             var baseUrl = @"https://oa.jlu.edu.cn/defaultroot/PortalInformation!jldxList.action?channelId=179577&searchnr=";
     41             var web = new HtmlWeb();
     42             for (var i = range; i >= 0; --i)
     43             {
     44                 //拼接yyyyMMdd天的oa通知界面
     45                 DateTime dt = DateTime.Now.AddDays(-i);
     46                 var dtStr = string.Format("{0:yyyyMMdd}", dt);
     47                 var url = baseUrl + dtStr + "&searchlx=3";
     48                 //加载HTML静态内容
     49                 var htmlDoc = web.Load(url);
     50                 //获取每个子页面的url并存入items中
     51                 var nodes = htmlDoc.DocumentNode.SelectNodes("//div[@class='li rel']/a[@class='font14']");
     52                 try
     53                 {
     54                     var pageItemUrl = nodes.Select(node => "https://oa.jlu.edu.cn/defaultroot/"
     55                     + node.GetAttributeValue("href", "")).ToList();
     56                     itemWebs.AddRange(pageItemUrl);
     57                 }
     58                 catch(Exception)//当当天没有通知下发时,nodes为空,在nodes.Select()中抛出空指针异常
     59                 {
     60                     Console.WriteLine(dtStr+"没有通知下发!!!");                    
     61                 }               
     62             }
     63             return itemWebs.ToArray();
     64         }
     65 
     66         private static void GetItemWeb(string itemWebUrl)
     67         {
     68             //uri解析
     69             var uri = new Uri(itemWebUrl);
     70             var title = HttpUtility.ParseQueryString(uri.Query).Get("title");//title即url的title部分
     71             var orgname = HttpUtility.ParseQueryString(uri.Query).Get("orgname");//orgname即url的orgname部分
     72 
     73             var web = new HtmlWeb();
     74             var htmlDoc = web.Load(itemWebUrl);
     75 
     76             var contentTime = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='content_time']").InnerText;
     77             var indexOfBlank = contentTime.IndexOf(' ');
     78             var time = contentTime.Substring(0, indexOfBlank);
     79 
     80             var contentNode = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='content_font fontsize immmge']");
     81             var content = string.Empty;
     82             var ps = contentNode.SelectNodes("p");
     83             if (ps != null)
     84             {
     85                 foreach (var p in ps)
     86                 {
     87                     content += p.InnerText.Replace("&nbsp;", " ").Replace('', ' ') + "
    ";
     88                 }
     89             }
     90             else
     91             {
     92                 content = contentNode.InnerHtml.TrimStart().Replace("&nbsp;", " ").Replace('', ' ').Replace("<br>", "
    ");
     93             }
     94 
     95             //创建ScrapingFiles文件夹保存文件
     96             Directory.CreateDirectory($"../../../ScrapingFiles/{time}/{orgname}/{title}");
     97             File.WriteAllText($"../../../ScrapingFiles/{time}/{orgname}/{title}/"+title + ".txt", content);
     98 
     99             //下载相关附件
    100             var fileNodes = htmlDoc.DocumentNode.SelectNodes("//td[@width='93%']/span");
    101             if (fileNodes != null)
    102             {
    103                 var webClient = new WebClient();
    104                 foreach (var node in fileNodes)
    105                 {
    106                     var fileId = node.GetAttributeValue("id", "");//寻找文件ID
    107                     var fileTitle = node.GetAttributeValue("title", "");//寻找文件title
    108                     var articleId = HttpUtility.ParseQueryString(uri.Query).Get("id");
    109 
    110                     var res = webClient.DownloadString(
    111                         "https://oa.jlu.edu.cn/defaultroot/rd/jldx/BASEEncoderAjax.jsp?" +
    112                         $"res={fileId}@{fileTitle}@{articleId}");
    113                     res = res.Trim().Replace("
    ", "");
    114                     webClient.DownloadFile("https://oa.jlu.edu.cn/defaultroot/rd/attachdownload.jsp?res=" + res,
    115                         $"../../../ScrapingFiles/{time}/{orgname}/{title}/" + fileTitle);
    116                 }
    117             }
    118         }
    119     }
    120 }
    View Code

     

  • 相关阅读:
    jquery $.fn $.fx 的意思以及使用
    jQuery树形控件zTree使用
    myeclipse9.0安装svn插件
    读取properties和xml中配置文件的值
    Jquery之ShowLoading遮罩组件
    程序员需谨记的8条团队开发原则(转)
    决策树算法
    第N个丑数
    数组反转
    倒数第K个结点
  • 原文地址:https://www.cnblogs.com/liujw2114/p/7056062.html
Copyright © 2020-2023  润新知