上一篇(https://www.cnblogs.com/meowv/p/12966092.html)文章使用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语法这里也不做介绍了,对于不会的这里有一篇快速入门的文章:https://www.cnblogs.com/meowv/p/11310538.html

利用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

基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)的更多相关文章

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

    上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操作做一个提醒.当每次抓取完数据后, ...

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

    上一篇(https://www.cnblogs.com/meowv/p/12971041.html)使用HtmlAgilityPack抓取壁纸数据成功将图片存入数据库,本篇继续来完成一个全网各大平台的 ...

  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  8. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  9. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

随机推荐

  1. 1 ThinkPHP框架初识

    一.PHP主流框架介绍 主流的框架有laravel.symfony.thinkphp MVC和三层结构 MVC可以说是一种开发模式,三层结构是一种开发习惯,严格来讲,他们两者是完全不同的概念,但是在实 ...

  2. spark系列-7、spark调优

    官网说明:http://spark.apache.org/docs/2.1.1/tuning.html#data-serialization 一.JVM调优 1.1.Java虚拟机垃圾回收调优的背景 ...

  3. turtle库应用实例2-六芒星的绘制

    六芒星的绘制 ‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪ ...

  4. java运行时跟编译时的区别,欢迎大家指正

    个人博客地址:https://blog.csdn.net/qq_41907991 关于java运行时及编译时期的区别: 首先我们要了解编译以及运行的概念: 编译就是指,编译器帮你把源码翻译成机器能识别 ...

  5. 如何发挥Visual Studio 2019强大的编辑功能轻松编辑Keil项目

    本文地址:https://www.cnblogs.com/jqdy/p/12565161.html 习惯了VS的强大编辑功能,对Keil 5越来越深恶痛绝.查阅网络文章后按图索骥初步实现了VS编辑Ke ...

  6. 将csv文件导入sql数据库

    有一个csv文件需要导入到Sql数据库中,其格式为 “adb”,"dds","sdf" “adb”,"dds","sdf" ...

  7. 【HBase】快速了解上手rowKey的设计技巧

    目录 为什么要设计rowKey 三大原则 长度原则 散列原则 唯一原则 热点问题的解决 加盐 哈希 反转 时间戳反转 为什么要设计rowKey 首先要弄明白一点,Regions的分区就是根据数据的ro ...

  8. 一文带你了解Spring核心接口Ordered的实现及应用

    前言 最近在看框架的时候,发现了这个接口,在此进行总结,希望能够给大家帮助,同时提升自己. order接口的大体介绍 Spring框架中有这个一个接口,名字叫Ordered,联想我们在数据库中应用的O ...

  9. LiteAI四大技术"杀手锏",解锁物联网智能设备AI开发难关

    你知道我们生活中常见的物联网智能设备融合AI技术后,会给我们带来什么样的智能交互体验?在我们指尖触碰的那一刹那背后隐藏的代码世界又是怎么样的呢? 今天就来和大家说说IoT智能设备轻松实现AI的奥秘! ...

  10. 初识spring boot maven管理--属性文件配置

    在使用springboot的时候可以使用属性文件配置对属性值进行动态配置,官方文档原文如下: Spring Boot uses a very particular PropertySource ord ...