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

上一篇文章使用AutoMapper来处理对象与对象之间的映射关系,本篇主要围绕定时任务和数据抓取相关的知识点并结合实际应用,在定时任务中循环处理爬虫任务抓取数据。

开始之前可以删掉之前测试用的几个HelloWorld,没有什么实际意义,直接干掉吧。抓取数据我主要用到了,HtmlAgilityPackPuppeteerSharp,一般情况下HtmlAgilityPack就可以完成大部分的数据抓取需求了,当在抓取动态网页的时候可以用到PuppeteerSharp,同时PuppeteerSharp还支持将图片保存为图片和PDF等牛逼的功能。

关于这两个库就不多介绍了,不了解的请自行去学习。

先在.BackgroundJobs层安装两大神器:Install-Package HtmlAgilityPackInstall-Package PuppeteerSharp。我在使用Package Manager安装包的时候一般都不喜欢指定版本号,因为这样默认是给我安装最新的版本。

之前无意中发现爱思助手的网页版有很多手机壁纸(https://www.i4.cn/wper_4_0_1_1.html),于是我就动了小心思,把所有手机壁纸全部抓取过来自嗨,可以看看我个人博客中的成品吧:https://meowv.com/wallpaper ????????????

最开始我是用Python实现的,现在我们在.NET中抓它。

我数了一下,一共有20个分类,直接在.Domain.Shared层添加一个壁纸分类的枚举WallpaperEnum.cs

//WallpaperEnum.cs
using System.ComponentModel;namespace Meowv.Blog.Domain.Shared.Enum
{public enum WallpaperEnum{[Description("美女")]Beauty = 1,[Description("型男")]Sportsman = 2,[Description("萌娃")]CuteBaby = 3,[Description("情感")]Emotion = 4,[Description("风景")]Landscape = 5,[Description("动物")]Animal = 6,[Description("植物")]Plant = 7,[Description("美食")]Food = 8,[Description("影视")]Movie = 9,[Description("动漫")]Anime = 10,[Description("手绘")]HandPainted = 11,[Description("文字")]Text = 12,[Description("创意")]Creative = 13,[Description("名车")]Car = 14,[Description("体育")]PhysicalEducation = 15,[Description("军事")]Military = 16,[Description("节日")]Festival = 17,[Description("游戏")]Game = 18,[Description("苹果")]Apple = 19,[Description("其它")]Other = 20,}
}

查看原网页可以很清晰的看到,每一个分类对应了一个不同的URL,于是手动创建一个抓取的列表,列表内容包括URL和分类,然后我又想用多线程来访问URL,返回结果。新建一个通用的待抓项的类,起名为:WallpaperJobItem.cs,为了规范和后续的壁纸查询接口,我们放在.Application.Contracts层中。

//WallpaperJobItem.cs
using Meowv.Blog.Domain.Shared.Enum;namespace Meowv.Blog.Application.Contracts.Wallpaper
{public class WallpaperJobItem<T>{/// <summary>/// <see cref="Result"/>/// </summary>public T Result { get; set; }/// <summary>/// 类型/// </summary>public WallpaperEnum Type { get; set; }}
}

WallpaperJobItem<T>接受一个参数T,Result的类型由T决定,在.BackgroundJobs层Jobs文件夹中新建一个任务,起名叫做:WallpaperJob.cs吧。老样子,继承IBackgroundJob

//WallpaperJob.cs
using Meowv.Blog.Application.Contracts.Wallpaper;
using Meowv.Blog.Domain.Shared.Enum;
using System.Collections.Generic;
using System.Threading.Tasks;namespace Meowv.Blog.BackgroundJobs.Jobs.Wallpaper
{public class WallpaperJob : IBackgroundJob{public async Task ExecuteAsync(){var wallpaperUrls = new List<WallpaperJobItem<string>>{new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_1_1.html", Type = WallpaperEnum.Beauty },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_58_1.html", Type = WallpaperEnum.Sportsman },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_66_1.html", Type = WallpaperEnum.CuteBaby },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_4_1.html", Type = WallpaperEnum.Emotion },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_3_1.html", Type = WallpaperEnum.Landscape },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_9_1.html", Type = WallpaperEnum.Animal },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_13_1.html", Type = WallpaperEnum.Plant },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_64_1.html", Type = WallpaperEnum.Food },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_11_1.html", Type = WallpaperEnum.Movie },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_5_1.html", Type = WallpaperEnum.Anime },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_34_1.html", Type = WallpaperEnum.HandPainted },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_65_1.html", Type = WallpaperEnum.Text },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_2_1.html",  Type = WallpaperEnum.Creative },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_10_1.html", Type = WallpaperEnum.Car },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_14_1.html", Type = WallpaperEnum.PhysicalEducation },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_63_1.html", Type = WallpaperEnum.Military },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_17_1.html", Type = WallpaperEnum.Festival },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_15_1.html", Type = WallpaperEnum.Game },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_12_1.html", Type = WallpaperEnum.Apple },new WallpaperJobItem<string> { Result = "https://www.i4.cn/wper_4_19_7_1.html", Type = WallpaperEnum.Other }};}}
}

先构建一个要抓取的列表 wallpaperUrls,这里准备用 HtmlAgilityPack,默认只抓取第一页最新的数据。

public async Task RunAsync()
{...var web = new HtmlWeb();var list_task = new List<Task<WallpaperJobItem<HtmlDocument>>>();wallpaperUrls.ForEach(item =>{var task = Task.Run(async () =>{var htmlDocument = await web.LoadFromWebAsync(item.Result);return new WallpaperJobItem<HtmlDocument>{Result = htmlDocument,Type = item.Type};});list_task.Add(task);});Task.WaitAll(list_task.ToArray());
}

上面这段代码,先new了一个HtmlWeb对象,我们主要用这个对象去加载我们的URL。

web.LoadFromWebAsync(...),它会返回一个HtmlDocument对象,这样就和上面的list_task对应起来,从而也应证了前面添加的WallpaperJobItem是通用的一个待抓项的类。

循环处理 wallpaperUrls,等待所有请求完成。这样就拿到了20个HtmlDocument,和它的分类,接下来就可以去处理list_task就行了。

在开始处理之前,要想好抓到的图片数据存放在哪里?我这里还是选择存在数据库中,因为有了之前的自定义仓储之增删改查的经验,可以很快的处理这件事情。

添加实体类、自定义仓储、DbSet、Code-First等一些列操作,就不一一介绍了,我相信看过之前文章的人都能完成这一步。

Wallpaper实体类包含主键Guid,标题Title,图片地址Url,类型Type,和一个创建时间CreateTime。

自定义仓储包含一个批量插入的方法:BulkInsertAsync(...)

贴一下完成后的图片,就不上代码了,如果需要可以去GitHub获取。

回到WallpaperJob,因为我们要抓取的是图片,所以获取到HTML中的img标签就可以了。

查看源代码发现图片是一个列表呈现的,并且被包裹在//article[@id='wper']/div[@class='jbox']/div[@class='kbox']下面,学过XPath语法的就很容易了,关于XPath语法这里也不做介绍了,对于不会的这里有一篇快速入门的文章:XPath语法 。

利用XPath Helper工具我们在浏览器上模拟一下选择的节点是否正确。

使用//article[@id='wper']/div[@class='jbox']/div[@class='kbox']/div/a/img可以成功将图片高亮,说明我们的语法是正确的。

public async Task RunAsync()
{...var wallpapers = new List<Wallpaper>();foreach (var list in list_task){var item = await list;var imgs = item.Result.DocumentNode.SelectNodes("//article[@id='wper']/div[@class='jbox']/div[@class='kbox']/div/a/img[1]").ToList();imgs.ForEach(x =>{wallpapers.Add(new Wallpaper{Url = x.GetAttributeValue("data-big", ""),Title = x.GetAttributeValue("title", ""),Type = (int)item.Type,CreateTime = x.Attributes["data-big"].Value.Split("/").Last().Split("_").First().TryToDateTime()});});}...
}

在 foreach 循环中先拿到当前循环的Item对象,即WallpaperJobItem<HtmlDocument>

通过.DocumentNode.SelectNodes()语法获取到图片列表,因为在a标签下面有两个img标签,取第一个即可。

GetAttributeValue()HtmlAgilityPack的扩展方法,用于直接获取属性值。

在看图片的时候,发现图片地址的规则是根据时间戳生成的,于是用TryToDateTime()扩展方法将其处理转换成时间格式。

这样我们就将所有图片按分类存进了列表当中,接下来调用批量插入方法。

在构造函数中注入自定义仓储IWallpaperRepository

...private readonly IWallpaperRepository _wallpaperRepository;public WallpaperJob(IWallpaperRepository wallpaperRepository){_wallpaperRepository = wallpaperRepository;}
...
...var urls = (await _wallpaperRepository.GetListAsync()).Select(x => x.Url);wallpapers = wallpapers.Where(x => !urls.Contains(x.Url)).ToList();if (wallpapers.Any()){await _wallpaperRepository.BulkInsertAsync(wallpapers);}

因为抓取的图片可能存在重复的情况,我们需要做一个去重处理,先查询到数据库中的所有的URL列表,然后在判断抓取到的url是否存在,最后调用BulkInsertAsync(...)批量插入方法。

这样就完成了数据抓取的全部逻辑,在保存数据到数据库之后我们可以进一步操作,比如:写日志、发送邮件通知等等,这里大家自由发挥吧。

写一个扩展方法每隔3小时执行一次。

...public static void UseWallpaperJob(this IServiceProvider service){var job = service.GetService<WallpaperJob>();RecurringJob.AddOrUpdate("壁纸数据抓取", () => job.ExecuteAsync(), CronType.Hour(1, 3));}
...

最后在模块内中调用。

...public override void OnApplicationInitialization(ApplicationInitializationContext context){...service.UseWallpaperJob();}

编译运行,打开Hangfire界面手动执行看看效果。

完美,数据库已经存入了不少数据了,还是要提醒一下:爬虫有风险,抓数需谨慎。

Hangfire定时处理爬虫任务,用HtmlAgilityPack抓取数据后存入数据库,你学会了吗?????????????

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

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

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

相关文章

题目 2285: [蓝桥杯][2018年第九届真题]螺旋折线(数论+思维)

题目&#xff1a; 题目描述 如图所示的螺旋折线经过平面上所有整点恰好一次。 对于整点(X, Y)&#xff0c;我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。 例如dis(0, 1)3, dis(-2, -1)9 给出整点坐标(X, Y)&#xff0c;你能计算出dis(X, Y)吗&…

[Java基础]反射获取成员方法并使用练习

代码如下: package ClassObjectPack;public class Student {private String name;int age;public String address;public Student(String name, int age, String address) {this.name name;this.age age;this.address address;}public Student() {}private Student(String …

读懂操作系统之虚拟内存(一)

由于个人对虚拟内存这块特别感兴趣&#xff0c;所以就直接暂且跳过其他&#xff0c;接下来将通过几篇文章进行详细讲解&#xff0c;当然其他基础内容后续在我进行相应整体学习后也会同步输出文章&#xff0c;比如操作系统概念、程序链接、进程管理、页面置换算法、流水线、浮点…

[Java基础]反射练习之越过泛型检查,运行配置文件制定内容

代码如下: package ReflectTest01;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList;public class ReflectTest01 {public static void main(String[] args) throws NoSuchMethodException, InvocationTarg…

【Ids4实战】深究配置——用户信息操作篇

&#xff08;此花无日不春风&#xff09;其实IdentityServer4的小项目已经基本完结了&#xff0c;但是我总感觉还是有很多东西没有深入挖掘和研究的&#xff0c;这不&#xff0c;二群里有小伙伴问到了一个常见的问题&#xff0c;因为我去年都见到了&#xff0c;一直没有想过去解…

Sql Server之旅——第九站 看看DML操作对索引的影响

我们都知道建索引是需要谨慎的&#xff0c;当只有利大于弊的时候才适合建&#xff0c;同时也知道建索引是需要维护成本的&#xff0c;这个维护也就在于DML操作&#xff0c;下面具体看看到底DML对索引都有哪些内幕。。。。一&#xff1a;delete操作现在大家都已经知道索引是以B树…

[Java基础]反射获取成员方法并使用

代码如下: package ClassObjectPack01;import ClassObjectPack.Student;import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class ReflectDemo05 {public static void main(String[] args)…

副业刚需? 恐怕并不靠谱!

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份上一篇文章推了我的星球&#xff0c;两天时间就有接近200位读者加入。有「热心朋友」帮我计算了下&#xff1a;你这两天收入快2万啊&#xff0c;你这副业做的挺好啊。很不客气的说&#xff0c;如果写写公号&a…

[Java基础]Junit测试

Junit测试: 代码如下: package CalculatorPack;public class Calculator {public int add(int a,int b){return ab;}public int sub(int a,int b){return a-b;}}package CalculatorPack;import org.junit.Assert; import org.junit.Test;public class CalculatorTest {Testp…

15分钟为自己架设优雅如Github的代码仓库

前言Github大家都熟悉。除了开源的项目外&#xff0c;有时候&#xff0c;大家也会把自己或团队、公司的项目传到Github的私有仓库里&#xff0c;把Github当成自己的私人Git Server。但是&#xff0c;用Github会有一些问题&#xff1a;Github从国内访问不是很稳定&#xff0c;有…

Pseudoprime numbers POJ - 3641(快速幂+判素数)

题意&#xff1a; 给你两个数&#xff0c;p和a&#xff1b;满足两个条件&#xff1a; 1.p不是素数&#xff1b; 2.apa^{p}ap %pa; 满足则输出yes&#xff0c;反之输出no。 题目&#xff1a; Fermat’s theorem states that for any prime number p and for any integer a &g…

[Java基础]反射案列

pro.properties文件(该文件与ReflectTest01同处在同一个文件夹)&#xff1b; className domain.Person methodName eat代码如下: package domain;public class Student {public void sleep(){System.out.println("sleep...");} }package domain;public class Per…

[推荐]大量 Blazor 学习资源(三)

大量 Blazor 学习资源系列文章&#xff1a;[推荐]大量 Blazor 学习资源&#xff08;一&#xff09;[推荐]大量 Blazor 学习资源&#xff08;二&#xff09;这次主要内容有 Blazor 相关视频&#xff0c;因为本身视频是英文的&#xff0c;所以就保持原样了&#xff0c;描述没有翻…

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

上一篇使用HtmlAgilityPack抓取壁纸数据成功将图片存入数据库&#xff0c;本篇继续来完成一个全网各大平台的热点新闻数据的抓取。同样的&#xff0c;可以先预览一下我个人博客中的成品&#xff1a;https://meowv.com/hot ????????????&#xff0c;和抓取壁纸的套路…

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;在那里才能看到价值与浪费…