前言

大家好,我是晓晨。许久没有更新博客了,今天给大家带来一篇干货型文章,一个每隔5分钟抓取博客园首页文章信息并在第二天的上午9点发送到你的邮箱的小工具。比如我在2018年2月14日,9点来到公司我就会收到一封邮件,是2018年2月13日的博客园首页的文章信息。写这个小工具的初衷是,一直有看博客的习惯,但是最近由于各种原因吧,可能几天都不会看一下博客,要是中途错过了什么好文可是十分心疼的哈哈。所以做了个工具,每天归档发到邮箱,妈妈再也不会担心我错过好的文章了。为什么只抓取首页?因为博客园首页文章的质量相对来说高一些。

准备

作为一个持续运行的工具,没有日志记录怎么行,我准备使用的是NLog来记录日志,它有个日志归档功能非常不错。在http请求中,由于网络问题吧可能会出现失败的情况,这里我使用Polly来进行Retry。使用HtmlAgilityPack来解析网页,需要对xpath有一定了解。下面是详细说明:

组件名 用途 github
NLog 记录日志 https://github.com/NLog/NLog
Polly 当http请求失败,进行重试 https://github.com/App-vNext/Polly
HtmlAgilityPack 网页解析 https://github.com/zzzprojects/html-agility-pack
MailKit 发送邮件 https://github.com/jstedfast/MailKit

有不了解的组件,可以通过访问github获取资料。

关于发送邮件感谢下面的园友提供的资料:
https://www.cnblogs.com/qulianqing/p/7413640.html
http://www.cnblogs.com/rocketRobin/p/8337055.html

获取&解析博客园首页数据

我是用的是HttpWebRequest来进行http请求,下面分享一下我简单封装的类库:

using System;using System.IO;using System.Net;using System.Text;namespace CnBlogSubscribeTool{    /// <summary>/// Simple Http Request Class/// .NET Framework >= 4.0/// Author:stulzq/// CreatedTime:2017-12-12 15:54:47/// </summary>public class HttpUtil{        static HttpUtil()        {            //Set connection limit ,Default limit is 2ServicePointManager.DefaultConnectionLimit = 1024;}        /// <summary>/// Default Timeout 20s/// </summary>public static int DefaultTimeout = 20000;        /// <summary>/// Is Auto Redirect/// </summary>public static bool DefalutAllowAutoRedirect = true;        /// <summary>/// Default Encoding/// </summary>public static Encoding DefaultEncoding = Encoding.UTF8;        /// <summary>/// Default UserAgent/// </summary>public static string DefaultUserAgent =                "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36";        /// <summary>/// Default Referer/// </summary>public static string DefaultReferer = "";        /// <summary>/// httpget request/// </summary>/// <param name="url">Internet Address</param>/// <returns>string</returns>public static string GetString(string url)        {            var stream = GetStream(url);            string result;            using (StreamReader sr = new StreamReader(stream)){result = sr.ReadToEnd();}            return result;}        /// <summary>/// httppost request/// </summary>/// <param name="url">Internet Address</param>/// <param name="postData">Post request data</param>/// <returns>string</returns>public static string PostString(string url, string postData)        {            var stream = PostStream(url, postData);            string result;            using (StreamReader sr = new StreamReader(stream)){result = sr.ReadToEnd();}            return result;}        /// <summary>/// Create Response/// </summary>/// <param name="url"></param>/// <param name="post">Is post Request</param>/// <param name="postData">Post request data</param>/// <returns></returns>public static WebResponse CreateResponse(string url, bool post, string postData = "")        {            var httpWebRequest = WebRequest.CreateHttp(url);httpWebRequest.Timeout = DefaultTimeout;httpWebRequest.AllowAutoRedirect = DefalutAllowAutoRedirect;httpWebRequest.UserAgent = DefaultUserAgent;httpWebRequest.Referer = DefaultReferer;            if (post){                var data = DefaultEncoding.GetBytes(postData);httpWebRequest.Method = "POST";httpWebRequest.ContentType = "application/x-www-form-urlencoded;charset=utf-8";httpWebRequest.ContentLength = data.Length;                using (var stream = httpWebRequest.GetRequestStream()){stream.Write(data, 0, data.Length);}}            try{                var response = httpWebRequest.GetResponse();                return response;}            catch (Exception e){                throw new Exception(string.Format("Request error,url:{0},IsPost:{1},Data:{2},Message:{3}", url, post, postData, e.Message), e);}}        /// <summary>/// http get request/// </summary>/// <param name="url"></param>/// <returns>Response Stream</returns>public static Stream GetStream(string url)        {            var stream = CreateResponse(url, false).GetResponseStream();                        if (stream == null){                throw new Exception("Response error,the response stream is null");}            else{                return stream;}}        /// <summary>/// http post request/// </summary>/// <param name="url"></param>/// <param name="postData">post data</param>/// <returns>Response Stream</returns>public static Stream PostStream(string url, string postData)        {            var stream = CreateResponse(url, true, postData).GetResponseStream();            if (stream == null){                throw new Exception("Response error,the response stream is null");}            else{                return stream;}}}
}

获取首页数据

string res = HttpUtil.GetString("https://www.cnblogs.com");

解析数据

我们成功获取到了html,但是怎么提取我们需要的信息(文章标题、地址、摘要、作者、发布时间)呢。这里就亮出了我们的利剑HtmlAgilityPack,他是一个可以根据xpath来解析网页的组件。

载入我们前面获取的html:

HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);

从上图中,我们可以看出,每条文章所有信息都在一个class为post_item的div里,我们先获取所有的class=post_item的div

//获取所有文章数据项var itemBodys = doc.DocumentNode.SelectNodes("//div[@class='post_item_body']");

我们继续分析,可以看出文章的标题在class=post_item_body的div下面的h3标签下的a标签,摘要信息在class=post_item_summary的p标签里面,发布时间和作者在class=post_item_foot的div里,分析完毕,我们可以取出我们想要的数据了:

foreach (var itemBody in itemBodys)
{    //标题元素var titleElem = itemBody.SelectSingleNode("h3/a");    //获取标题var title = titleElem?.InnerText;    //获取urlvar url = titleElem?.Attributes["href"]?.Value;    //摘要元素var summaryElem = itemBody.SelectSingleNode("p[@class='post_item_summary']");    //获取摘要var summary = summaryElem?.InnerText.Replace("\r\n", "").Trim();    //数据项底部元素var footElem = itemBody.SelectSingleNode("div[@class='post_item_foot']");    //获取作者var author = footElem?.SelectSingleNode("a")?.InnerText;    //获取文章发布时间var publishTime = Regex.Match(footElem?.InnerText, "\\d+-\\d+-\\d+ \\d+:\\d+").Value;Console.WriteLine($"标题:{title}");Console.WriteLine($"网址:{url}");Console.WriteLine($"摘要:{summary}");Console.WriteLine($"作者:{author}");Console.WriteLine($"发布时间:{publishTime}");Console.WriteLine("--------------华丽的分割线---------------");
}

运行一下:

我们成功的获取了我们想要的信息。现在我们定义一个Blog对象将它们装起来。

public class Blog{    /// <summary>/// 标题/// </summary>public string Title { get; set; }    /// <summary>/// 博文url/// </summary>public string Url { get; set; }    /// <summary>/// 摘要/// </summary>public string Summary { get; set; }    /// <summary>/// 作者/// </summary>public string Author { get; set; }    /// <summary>/// 发布时间/// </summary>public DateTime PublishTime { get; set; }
}

http请求失败重试

我们使用Polly在我们的http请求失败时进行重试,设置为重试3次。

//初始化重试器_retryTwoTimesPolicy =Policy.Handle<Exception>().Retry(3, (ex, count) =>{_logger.Error("Excuted Failed! Retry {0}", count);_logger.Error("Exeption from {0}", ex.GetType().Name);});

测试一下:

可以看到当遇到exception是Polly会帮我们重试三次,如果三次重试都失败了那么会放弃。

发送邮件

使用MailKit来进行邮件发送,它支持IMAP,POP3和SMTP协议,并且是跨平台的十分优秀。下面是根据前面园友的分享自己封装的一个类库:

using System.Collections.Generic;using CnBlogSubscribeTool.Config;using MailKit.Net.Smtp;using MimeKit;

namespace CnBlogSubscribeTool{        /// <summary>/// send email/// </summary>public class MailUtil{             private static bool SendMail(MimeMessage mailMessage,MailConfig config)   {                try{                               var smtpClient = new SmtpClient();smtpClient.Timeout = 10 * 1000;   //设置超时时间smtpClient.Connect(config.Host, config.Port, MailKit.Security.SecureSocketOptions.None);//连接到远程smtp服务器smtpClient.Authenticate(config.Address, config.Password);smtpClient.Send(mailMessage);//发送邮件smtpClient.Disconnect(true);                                  return true;}                      catch{                                    throw;}}        /// <summary>///发送邮件/// </summary>/// <param name="config">配置</param>/// <param name="receives">接收人</param>/// <param name="sender">发送人</param>/// <param name="subject">标题</param>/// <param name="body">内容</param>/// <param name="attachments">附件</param>/// <param name="fileName">附件名</param>/// <returns></returns>public static bool SendMail(MailConfig config,List<string> receives, string sender, string subject, string body, byte[] attachments = null,string fileName="")        {               var fromMailAddress = new MailboxAddress(config.Name, config.Address);            var mailMessage = new MimeMessage();mailMessage.From.Add(fromMailAddress);            foreach (var add in receives){                               var toMailAddress = new MailboxAddress(add);mailMessage.To.Add(toMailAddress);}                      if (!string.IsNullOrEmpty(sender)){                               var replyTo = new MailboxAddress(config.Name, sender);mailMessage.ReplyTo.Add(replyTo);}                       var bodyBuilder = new BodyBuilder() { HtmlBody = body };            //附件if (attachments != null){                              if (string.IsNullOrEmpty(fileName)){fileName = "未命名文件.txt";}                               var attachment = bodyBuilder.Attachments.Add(fileName, attachments);                //解决中文文件名乱码var charset = "GB18030";attachment.ContentType.Parameters.Clear();attachment.ContentDisposition.Parameters.Clear();attachment.ContentType.Parameters.Add(charset, "name", fileName);attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);                //解决文件名不能超过41字符foreach (var param in attachment.ContentDisposition.Parameters)param.EncodingMethod = ParameterEncodingMethod.Rfc2047;                foreach (var param in attachment.ContentType.Parameters)param.EncodingMethod = ParameterEncodingMethod.Rfc2047;}mailMessage.Body = bodyBuilder.ToMessageBody();mailMessage.Subject = subject;            return SendMail(mailMessage, config);}}
}

测试一下:

说明

关于抓取数据和发送邮件的调度,程序异常退出的数据处理等等,在此我就不详细说明了,有兴趣的看源码(文末有github地址)

抓取数据是增量更新的。不用RSS订阅的原因是RSS更新比较慢。

完整的程序运行截图:

每发送一次邮件,程序就会将记录时间调整到今天的9点,然后每次抓取数据之后就会判断当前时间减去记录时间是否大于等于24小时,如果符合就发送邮件并且更新记录时间。

收到的邮件截图:

截图中的邮件标题为13日但是邮件内容为14日,是因为我为了演示效果,将今天(14日)的数据copy到了13日的数据里面,不要被误导了。

还提供一个附件便于收集整理:

好了介绍完毕,我自己已经将这个小工具部署到服务器,想要享受这个服务的可以在评论留下邮箱(手动滑稽)。

github:https://github.com/stulzq/CnBlogSubscribeTool 如果你喜欢,欢迎来个star

原文地址:http://www.cnblogs.com/stulzq/p/8448183.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

.NET Core 实现定时抓取博客园首页文章信息并发送到邮箱相关推荐

  1. .net core 实现简单爬虫—抓取博客园的博文列表

    一.介绍一个Http请求框架HttpCode.Core HttpCode.Core 源自于HttpCode(传送门),不同的是 HttpCode.Core是基于.net standard 2.0实现的 ...

  2. 爬取博客园首页并定时发送到微信

    应女朋友要求,为了能及时掌握技术动向,特意写了这个爬虫,每天定时爬取博客园首页并发送至微信. 环境: Python3.4 第三方库 Requests:向服务器发送请求 BeautifulSoup4:解 ...

  3. webmagic 获取文本_webmagic爬取博客园所有文章

    最近学习了下webmagic,学webmagic是因为想折腾下爬虫,但是自己学java的,又不想太费功夫,所以webmagic是比较好的选择了. 写了几个demo,源码流程大致看了一遍.想着把博客园的 ...

  4. 【Python3 爬虫】13_爬取博客园首页所有文章

    首先,我们确定博客园首页地址为:https://www.cnblogs.com/ 我们打开可以看到有各种各样的文章在首页,如下图: 我们以上图标记的文章为例子吧!打开网页源码,搜索Docker,搜索结 ...

  5. java爬虫之爬取博客园推荐文章列表

    这几天学习了一下Java爬虫的知识,分享并记录一下: 写一个可以爬取博客园十天推荐排行的文章列表 通过浏览器查看下一页点击请求,可以发现 在点击下一页的时候是执行的 post请求,请求地址为 http ...

  6. (转)利用快速开发框架,快速搭建微信浏览博客园首页文章

    原文地址:http://www.cnblogs.com/inday/p/weixin-publicf-platform-cnblogs.html 这几天接连发布了<快速开发微信公众平台框架--- ...

  7. 微信快速开发框架(五)-- 利用快速开发框架,快速搭建微信浏览博客园首页文章...

    这几天接连发布了<快速开发微信公众平台框架---简介>和<体验微信公众平台快速开发框架>几篇关于微信平台的文章,不过反响一般,可能需求不是很多吧.闲来无事,还是继续改造一下这个 ...

  8. Python爬虫-博客园首页推荐博客排行(整合词云+邮件发送)

    1.前提: 总体思路,利用多线程(mutiSpider)爬取博客园首页推荐博客,根据用户名爬取该用户的阅读排行榜(TopViewPosts),评论排行榜(TopFeedbackPosts),推荐排行榜 ...

  9. 利用urllib3 抓取博客列表

    利用urllib3 抓取博客列表 分析页面代码 分析正则表达式 完整代码 分析页面代码 从这段代码中可以找到很多规律,例如,每条博客的所有信息都包含在一个 <div> 节点中,这个< ...

最新文章

  1. 资金只够支撑10个月,自动驾驶致命事故重演:特斯拉陷入困境
  2. echarts - 使用echarts过程中遇到的问题(pending...)
  3. SQL Server 索引结构及其使用(一)[转]
  4. 1713. 得到子序列的最少操作次数
  5. idea terminal终端修改为git bash设置
  6. JavaScript原生对象属性和方法详解——Array对象
  7. 何必!放着985双一流专业不读,非要当程序员去内卷!
  8. java框架注入是创建对象吗_spring之IOC容器创建对象
  9. emacs文件/目录比较工具
  10. mybait-plus实现动态自定义查询条件
  11. 【mmdetection3d】——3D 目标检测 KITTI 数据集
  12. 春季养肝注意三个行为,做对了一年少生病
  13. android m3u8 合并,M3u8合并APP
  14. 用墨刀设计原型,易被忽略的8种玩法。
  15. 计算机专业的励志人物,北京科技大学计算机与通信工程学院-【毕业学子未来路】王禹:保入中科院的全国大学生励志人物...
  16. 【FTP】FTP主动模式与被动模式
  17. 24核48线虚拟化服务器,24核48线程的威力:戴尔PowerEdge R910服务器评测
  18. MATLAB身份证件号码定位识别
  19. Linux时间延迟平滑对时方案的分析-Ntp和Chrony的不同表现
  20. 1037.有效的回旋镖

热门文章

  1. app接口开发(php)
  2. webpake-node-sass 报错
  3. 《CMake实践》笔记二:INSTALL/CMAKE_INSTALL_PREFIX
  4. jquery验证手机号码和邮箱地址例子
  5. Openfire3.10beta版源码在eclipse上部署编译
  6. nagios监控mysql主机,nginx,cpu,网卡流量
  7. .NET 6 中 gRPC 的新功能
  8. 手把手教你学Dapr - 2. 必须知道的概念
  9. Quartz - 作业调度框架-插件化开发
  10. 轻量易用的微信Sdk发布——Magicodes.Wx.Sdk