基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)

上一篇使用HtmlAgilityPack抓取壁纸数据成功将图片存入数据库,本篇继续来完成一个全网各大平台的热点新闻数据的抓取。

同样的,可以先预览一下我个人博客中的成品:https://meowv.com/hot ????????????,和抓取壁纸的套路一样,大同小异。

本次要抓取的源有18个,分别是博客园、V2EX、SegmentFault、掘金、微信热门、豆瓣精选、IT之家、36氪、百度贴吧、百度热搜、微博热搜、知乎热榜、知乎日报、网易新闻、GitHub、抖音热点、抖音视频、抖音正能量。

还是将数据存入数据库,按部就班先将实体类和自定义仓储创建好,实体取名HotNews。贴一下代码:

//HotNews.cs
using System;
using Volo.Abp.Domain.Entities;namespace Meowv.Blog.Domain.HotNews
{public class HotNews : Entity<Guid>{/// <summary>/// 标题/// </summary>public string Title { get; set; }/// <summary>/// 链接/// </summary>public string Url { get; set; }/// <summary>/// SourceId/// </summary>public int SourceId { get; set; }/// <summary>/// 创建时间/// </summary>public DateTime CreateTime { get; set; }}
}

剩下的大家自己完成,最终数据库生成一张空的数据表,meowv_hotnews 。

然后还是将我们各大平台放到一个枚举类HotNewsEnum.cs中。

//HotNewsEnum.cs
using System.ComponentModel;namespace Meowv.Blog.Domain.Shared.Enum
{public enum HotNewsEnum{[Description("博客园")]cnblogs = 1,[Description("V2EX")]v2ex = 2,[Description("SegmentFault")]segmentfault = 3,[Description("掘金")]juejin = 4,[Description("微信热门")]weixin = 5,[Description("豆瓣精选")]douban = 6,[Description("IT之家")]ithome = 7,[Description("36氪")]kr36 = 8,[Description("百度贴吧")]tieba = 9,[Description("百度热搜")]baidu = 10,[Description("微博热搜")]weibo = 11,[Description("知乎热榜")]zhihu = 12,[Description("知乎日报")]zhihudaily = 13,[Description("网易新闻")]news163 = 14,[Description("GitHub")]github = 15,[Description("抖音热点")]douyin_hot = 16,[Description("抖音视频")]douyin_video = 17,[Description("抖音正能量")]douyin_positive = 18}
}

和上一篇抓取壁纸一样,做一些准备工作。

.Application.Contracts层添加HotNewsJobItem<T>,在.BackgroundJobs层添加HotNewsJob用来处理爬虫逻辑,用构造函数方式注入仓储IHotNewsRepository

//HotNewsJobItem.cs
using Meowv.Blog.Domain.Shared.Enum;namespace Meowv.Blog.Application.Contracts.HotNews
{public class HotNewsJobItem<T>{/// <summary>/// <see cref="Result"/>/// </summary>public T Result { get; set; }/// <summary>/// 来源/// </summary>public HotNewsEnum Source { get; set; }}
}
//HotNewsJob.CS
using Meowv.Blog.Domain.HotNews.Repositories;
using System;
using System.Net.Http;
using System.Threading.Tasks;namespace Meowv.Blog.BackgroundJobs.Jobs.HotNews
{public class HotNewsJob : IBackgroundJob{private readonly IHttpClientFactory _httpClient;private readonly IHotNewsRepository _hotNewsRepository;public HotNewsJob(IHttpClientFactory httpClient,IHotNewsRepository hotNewsRepository){_httpClient = httpClient;_hotNewsRepository = hotNewsRepository;}public async Task ExecuteAsync(){throw new NotImplementedException();}}
}

接下来明确数据源地址,因为以上数据源有的返回是HTML,有的直接返回JSON数据。为了方便调用,我这里还注入了IHttpClientFactory

整理好的待抓取数据源列表是这样的。

...
var hotnewsUrls = new List<HotNewsJobItem<string>>
{new HotNewsJobItem<string> { Result = "https://www.cnblogs.com", Source = HotNewsEnum.cnblogs },new HotNewsJobItem<string> { Result = "https://www.v2ex.com/?tab=hot", Source = HotNewsEnum.v2ex },new HotNewsJobItem<string> { Result = "https://segmentfault.com/hottest", Source = HotNewsEnum.segmentfault },new HotNewsJobItem<string> { Result = "https://web-api.juejin.im/query", Source = HotNewsEnum.juejin },new HotNewsJobItem<string> { Result = "https://weixin.sogou.com", Source = HotNewsEnum.weixin },new HotNewsJobItem<string> { Result = "https://www.douban.com/group/explore", Source = HotNewsEnum.douban },new HotNewsJobItem<string> { Result = "https://www.ithome.com", Source = HotNewsEnum.ithome },new HotNewsJobItem<string> { Result = "https://36kr.com/newsflashes", Source = HotNewsEnum.kr36 },new HotNewsJobItem<string> { Result = "http://tieba.baidu.com/hottopic/browse/topicList", Source = HotNewsEnum.tieba },new HotNewsJobItem<string> { Result = "http://top.baidu.com/buzz?b=341", Source = HotNewsEnum.baidu },new HotNewsJobItem<string> { Result = "https://s.weibo.com/top/summary/summary", Source = HotNewsEnum.weibo },new HotNewsJobItem<string> { Result = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50&desktop=true", Source = HotNewsEnum.zhihu },new HotNewsJobItem<string> { Result = "https://daily.zhihu.com", Source = HotNewsEnum.zhihudaily },new HotNewsJobItem<string> { Result = "http://news.163.com/special/0001386F/rank_whole.html", Source = HotNewsEnum.news163 },new HotNewsJobItem<string> { Result = "https://github.com/trending", Source = HotNewsEnum.github },new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word", Source = HotNewsEnum.douyin_hot },new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme", Source = HotNewsEnum.douyin_video },new HotNewsJobItem<string> { Result = "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/aweme/?type=positive", Source = HotNewsEnum.douyin_positive },
};
...

其中有几个比较特殊的,掘金、百度热搜、网易新闻。

掘金需要发送Post请求,返回的是JSON数据,并且需要指定特有的请求头和请求数据,所以使用IHttpClientFactory创建了HttpClient对象。

百度热搜、网易新闻两个老大哥玩套路,网页编码是GB2312的,所以要专门为其指定编码方式,不然取到的数据都是乱码。

...
var web = new HtmlWeb();
var list_task = new List<Task<HotNewsJobItem<object>>>();hotnewsUrls.ForEach(item =>
{var task = Task.Run(async () =>{var obj = new object();if (item.Source == HotNewsEnum.juejin){using var client = _httpClient.CreateClient();client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/537.36 Edg/83.0.478.13");client.DefaultRequestHeaders.Add("X-Agent", "Juejin/Web");var data = "{\"extensions\":{\"query\":{ \"id\":\"21207e9ddb1de777adeaca7a2fb38030\"}},\"operationName\":\"\",\"query\":\"\",\"variables\":{ \"first\":20,\"after\":\"\",\"order\":\"THREE_DAYS_HOTTEST\"}}";var buffer = data.SerializeUtf8();var byteContent = new ByteArrayContent(buffer);byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");var httpResponse = await client.PostAsync(item.Result, byteContent);obj = await httpResponse.Content.ReadAsStringAsync();}else{Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);obj = await web.LoadFromWebAsync(item.Result, (item.Source == HotNewsEnum.baidu || item.Source == HotNewsEnum.news163) ? Encoding.GetEncoding("GB2312") : Encoding.UTF8);}return new HotNewsJobItem<object>{Result = obj,Source = item.Source};});list_task.Add(task);
});
Task.WaitAll(list_task.ToArray());

循环 hotnewsUrls ,可以看到HotNewsJobItem我们返回的是object类型,因为有JSON又有HtmlDocument对象。所以这里为了能够统一接收,就是用了object。

针对掘金做了单独处理,使用HttpClient发送Post请求,返回JSON字符串数据。

针对百度热搜和网易新闻,使用Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);注册编码提供程序,然后在web.LoadFromWebAsync(...)加载网页数据的时候指定网页编码,我使用了一个三元表达式来处理。

完成上面这一步,就可以循环 list_task,使用XPath语法,或者解析JSON数据,去拿到数据了。

...
var hotNews = new List<HotNews>();
foreach (var list in list_task)
{var item = await list;var sourceId = (int)item.Source;...if (hotNews.Any()){await _hotNewsRepository.DeleteAsync(x => true);await _hotNewsRepository.BulkInsertAsync(hotNews);}
}

这个爬虫同样很简单,只要拿到标题和链接即可,所以主要目标是寻找到页面上的a标签列表。这个我觉得也没必要一个个去分析了,直接上代码。

// 博客园if (item.Source == HotNewsEnum.cnblogs){var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='post_item_body']/h3/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});}
// V2EX
if (item.Source == HotNewsEnum.v2ex)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//span[@class='item_title']/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = $"https://www.v2ex.com{x.GetAttributeValue("href", "")}",SourceId = sourceId,CreateTime = DateTime.Now});});
}
 // SegmentFaultif (item.Source == HotNewsEnum.segmentfault){var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='news__item-info clearfix']/a").Where(x => x.InnerText.IsNotNullOrEmpty()).ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.SelectSingleNode(".//h4").InnerText,Url = $"https://segmentfault.com{x.GetAttributeValue("href", "")}",SourceId = sourceId,CreateTime = DateTime.Now});});}
// 掘金
if (item.Source == HotNewsEnum.juejin)
{var obj = JObject.Parse((string)item.Result);var nodes = obj["data"]["articleFeed"]["items"]["edges"];foreach (var node in nodes){hotNews.Add(new HotNews{Title = node["node"]["title"].ToString(),Url = node["node"]["originalUrl"].ToString(),SourceId = sourceId,CreateTime = DateTime.Now});}
}
// 微信热门
if (item.Source == HotNewsEnum.weixin)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//ul[@class='news-list']/li/div[@class='txt-box']/h3/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 豆瓣精选
if (item.Source == HotNewsEnum.douban)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='channel-item']/div[@class='bd']/h3/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});
}
// IT之家
if (item.Source == HotNewsEnum.ithome)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='lst lst-2 hot-list']/div[1]/ul/li/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 36氪
if (item.Source == HotNewsEnum.kr36)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='hotlist-main']/div[@class='hotlist-item-toptwo']/a[2]|//div[@class='hotlist-main']/div[@class='hotlist-item-other clearfloat']/div[@class='hotlist-item-other-info']/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = $"https://36kr.com{x.GetAttributeValue("href", "")}",SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 百度贴吧
if (item.Source == HotNewsEnum.tieba)
{var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);var nodes = obj["data"]["bang_topic"]["topic_list"];foreach (var node in nodes){hotNews.Add(new HotNews{Title = node["topic_name"].ToString(),Url = node["topic_url"].ToString().Replace("amp;", ""),SourceId = sourceId,CreateTime = DateTime.Now});}
}
// 百度热搜
if (item.Source == HotNewsEnum.baidu)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table[@class='list-table']//tr/td[@class='keyword']/a[@class='list-title']").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 微博热搜
if (item.Source == HotNewsEnum.weibo)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//table/tbody/tr/td[2]/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = $"https://s.weibo.com{x.GetAttributeValue("href", "").Replace("#", "%23")}",SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 知乎热榜
if (item.Source == HotNewsEnum.zhihu)
{var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);var nodes = obj["data"];foreach (var node in nodes){hotNews.Add(new HotNews{Title = node["target"]["title"].ToString(),Url = $"https://www.zhihu.com/question/{node["target"]["id"]}",SourceId = sourceId,CreateTime = DateTime.Now});}
}
// 知乎日报
if (item.Source == HotNewsEnum.zhihudaily)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='box']/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = $"https://daily.zhihu.com{x.GetAttributeValue("href", "")}",SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 网易新闻
if (item.Source == HotNewsEnum.news163)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//div[@class='area-half left']/div[@class='tabBox']/div[@class='tabContents active']/table//tr/td[1]/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText,Url = x.GetAttributeValue("href", ""),SourceId = sourceId,CreateTime = DateTime.Now});});
}
// GitHub
if (item.Source == HotNewsEnum.github)
{var nodes = ((HtmlDocument)item.Result).DocumentNode.SelectNodes("//article[@class='Box-row']/h1/a").ToList();nodes.ForEach(x =>{hotNews.Add(new HotNews{Title = x.InnerText.Trim().Replace("\n", "").Replace(" ", ""),Url = $"https://github.com{x.GetAttributeValue("href", "")}",SourceId = sourceId,CreateTime = DateTime.Now});});
}
// 抖音热点
if (item.Source == HotNewsEnum.douyin_hot)
{var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);var nodes = obj["word_list"];foreach (var node in nodes){hotNews.Add(new HotNews{Title = node["word"].ToString(),Url = $"#{node["hot_value"]}",SourceId = sourceId,CreateTime = DateTime.Now});}
}
// 抖音视频 & 抖音正能量
if (item.Source == HotNewsEnum.douyin_video || item.Source == HotNewsEnum.douyin_positive)
{var obj = JObject.Parse(((HtmlDocument)item.Result).ParsedText);var nodes = obj["aweme_list"];foreach (var node in nodes){hotNews.Add(new HotNews{Title = node["aweme_info"]["desc"].ToString(),Url = node["aweme_info"]["share_url"].ToString(),SourceId = sourceId,CreateTime = DateTime.Now});}
}

item.Result转换成指定类型,最终拿到数据后,我们先删除所有数据后再批量插入。

然后新建扩展方法UseHotNewsJob(),在模块类中调用。

//MeowvBlogBackgroundJobsExtensions.cs
.../// <summary>/// 每日热点数据抓取/// </summary>/// <param name="context"></param>public static void UseHotNewsJob(this IServiceProvider service){var job = service.GetService<HotNewsJob>();RecurringJob.AddOrUpdate("每日热点数据抓取", () => job.ExecuteAsync(), CronType.Hour(1, 2));}
...

指定定时任务为每2小时运行一次。

...public override void OnApplicationInitialization(ApplicationInitializationContext context){...var service = context.ServiceProvider;...service.UseHotNewsJob();}

编译运行,此时周期性作业就会出现我们的定时任务了。

默认时间没到是不会执行的,我们手动执行等待一会看看效果。

执行完成后,成功将所有热点数据保存在数据库中,说明我们的爬虫已经搞定了,并且Hangfire会按照给定的规则去循环执行,你学会了吗?????????????

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309528.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TechEmpower Web 框架性能第19轮测试结果正式发布,ASP.NET Core在主流框架中拔得头筹...

TechEmpower第19轮编程语言框架性能排行榜2020年5月28日正式发布,详见官方博客&#xff1a;https://www.techempower.com/blog/2020/05/28/framework-benchmarks-round-19/&#xff0c;TechEmpower基准测试有许多场景&#xff08;也称为测试类型&#xff09;&#xff0c;此次评…

[Java基础]JDK内置注解

示例代码如下: package annotation;SuppressWarnings("all") public class AnnoDemo2 {Overridepublic String toString(){return super.toString();}Deprecatedpublic void show(){//有缺陷}public void show1(){//替代show方法}public void demo(){show();}}

学了这么多年精益思想,居然不知道还有第八种浪费 | IDCF

价值和浪费“我是彻底的现场主义者。与其在领导办公室内冥思苦想&#xff0c;倒不如到生产现场的各个角落&#xff0c;直接获得第一手的生产信息和感受直接的刺激。”——大野耐一&#xff0c;丰田生产方式之父大野耐一提倡直接去现场工作&#xff0c;在那里才能看到价值与浪费…

41 sysfs 文件系统

前言 在 linux 中常见的文件系统 有很多, 如下 基于磁盘的文件系统, ext2, ext3, ext4, xfs, btrfs, jfs, ntfs 内存文件系统, procfs, sysfs, tmpfs, squashfs, debugfs 闪存文件系统, ubifs, jffs2, yaffs 文件系统这一套体系在 linux 有一层 vfs 抽象, 用户程序不用…

A Greeting from Qinhuangdao Gym - 102769A 2020ccpc秦皇岛分站赛

题意&#xff1a; 给你n个红球和m个蓝色球。然后以相等的概率随机选择了其中两个。选择两个红球的概率是多少&#xff1f; 题目&#xff1a; Welcome to the CCPC Qinhuangdao Site! Qinhuangdao is a beautiful coastal city full of charm, integrating historical herit…

Gartner:6个容器和Kubernetes策略的最佳实用技巧

导语采用容器和Kubernetes要求整个企业保持一致&#xff0c;不了解这些前期现实会导致一些非常严峻的后果。正文Gartner估计&#xff0c;到2022年&#xff0c;将有75&#xff05;的组织在生产中运行容器化应用程序。毫无疑问&#xff0c;Kubernetes已成为组织容器的流行方法。通…

测试人员未来的3条出路

大家好&#xff0c;我是Z哥。前两天有个做测试的小伙伴加我微信问我测试相关的一些事情。她自己是从学习毕业就开始进入到互联网行业做测试的&#xff0c;到现在三年工作经验。她现在都不太敢跳槽&#xff0c;因为觉得自己没有什么核心竞争力&#xff0c;平常就是点点鼠标&…

自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧

一&#xff1a;背景1. 讲故事曾今在项目中发现有同事自定义结构体的时候&#xff0c;居然没有重写Equals方法&#xff0c;比如下面这段代码&#xff1a;static void Main(string[] args){var list Enumerable.Range(0, 1000).Select(m > new Point(m, m)).ToList();var ite…

使用 Windows Terminal 连接远程主机

使用 Windows Terminal 连接远程主机IntroWindows Terminal 是微软新推出来的一个全新的、流行的、功能强大的命令行终端工具。包含很多来社区呼声很高的特性&#xff0c;例如&#xff1a;多 Tab 支持、富文本、多语言支持、可配置、主题和样式&#xff0c;支持 emoji 和基于 G…

[Java基础]自定义注解之属性定义

代码如下: package AnnoDemo01;public enum Person {p1,p2; }package AnnoDemo01;public interface MyAnno2 {}package AnnoDemo01;public interface MyAnno {int show1();String show2();Person per();MyAnno2 ann02();String[] strs(); }定义了属性&#xff0c;在使用时需要…

微软开源 Tye 项目,可简化微服务开发

微软近期开源了一款开发人员工具 Tye&#xff0c;能够用于简化微服务以及分布式应用程序的开发、测试以及部署过程。项目地址&#xff1a;https://github.com/dotnet/tye。该项目负责人 Amiee 表示&#xff0c;在构建由多个项目组成的应用程序时&#xff0c;开发者通常希望能够…

L1-046 整除光棍 (20分)(模拟除法竖式求商的位运算)

题目&#xff1a; 这里所谓的“光棍”&#xff0c;并不是指单身汪啦~ 说的是全部由1组成的数字&#xff0c;比如1、11、111、1111等。传说任何一个光棍都能被一个不以5结尾的奇数整除。比如&#xff0c;111111就可以被13整除。 现在&#xff0c;你的程序要读入一个整数x&#…

Sql Server之旅——第十站 简单说说sqlserver的执行计划

我们知道sql在底层的执行给我们上层人员开了一个窗口&#xff0c;那就是执行计划&#xff0c;有了执行计划之后&#xff0c;我们就清楚了那些烂sql是怎么执行的&#xff0c;这样 就可以方便的找到sql的缺陷和优化点。一&#xff1a;执行计划生成过程说到执行计划&#xff0c;首…

【半译】扩展shutdown超时设置以保证IHostedService正常关闭

我最近发现一个问题&#xff0c;当应用程序关闭时&#xff0c;我们的应用程序没有正确执行在IHostedService中的StopAsync方法。经过反复验证发现&#xff0c;这是由于某些服务对关闭信号做出响应所需的时间太长导致的。在这篇文章中&#xff0c;我将展示出现这个问题的一个示例…

[JavaWeb-MySQL]多表关系介绍

多表之间的关系 1. 分类&#xff1a;1. 一对一(了解)&#xff1a;* 如&#xff1a;人和身份证* 分析&#xff1a;一个人只有一个身份证&#xff0c;一个身份证只能对应一个人2. 一对多(多对一)&#xff1a;* 如&#xff1a;部门和员工* 分析&#xff1a;一个部门有多个员工&am…