一、功能详解
我们先来规划一下程序。这里我是想每隔一段时间,自动去网站抓取一波数据。
在抓取的过程中可能会抓取到重复的博客,这里我有两种想法:
1. 如果碰到相同博客,更新博客;如果不相同,就去新增;
2. 如果碰到一个重复的,就认为后面的都是重复的,直接停止任务;
经过琢磨,第一种方式绝对不可取,因为我们抓取的上限是 200 页,这要是每次都抓 200 也数据,那不得疯了;所以我们使用第二种;
废话不多说,直接上代码~
二、代码实现
这里我们用到了定时任务,因为 Spring 自己就带了这个东西,所以我也就没有引用 jdk 或者是 Quartz。当然,这里你会那种定时任务就用那种。
1. 启动对定时任务的支持
我们在启动类上增加注解:@EnableScheduling,让 Spring 启动对计划任务的支持。
2. 创建定时任务
类名:CrawlCnblogTask
@Component
public class CrawlCnblogTask {
private Logger logger = LoggerFactory.getLogger(CrawlCnblogTask.class);
@Scheduled(cron = "0 0/20 * * * ?")
public void task() {
}
}
这里我们弄一个 task 任务,然后通过注解:@Scheduled 设置间隔时间,我这里设置的是每 20 分钟去定时循环一次,要是看不懂 cron,可以去找一下我的 SpringBoot 相关博客学习一下。
3. 配置要抓取的网页
这里我们直接将我们要爬的页面 url 配置到 yml 文件中
...之前的 yml 配置文件
# 要爬行的页面
crawl:
cnblogs: https://xxx/xxx/x
因为我们一个 task 任务只爬一个页面,所以我这里直接用的是网页的域名作为配置名。
然后这里的域名是不对的,如果需要知道具体的域名,自己去 clone 代码(gitee:https://gitee.com/soul-sys/lemon1234_scraper)
4. 修改 CrawlCnblogTask 类
我们将刚刚配置的 crawl.cnblogs 配置到类里面
@Value("${crawl.cnblogs}")
private String taskUrl;
5. 域名可用性检测
当然,这个域名虽然配置进去了,但是我们为了安全起见,增加一个小功能,就是对 url 检测,看当前 url 能否可以请求的通。
public static boolean checkUrl(String url) {
CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet(url);
try {
CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
if(closeableHttpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return true;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
closeableHttpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
很简单哈,如果能请求的通,就返回 true,请求不通,就返回 false。
6. 创建 htmlunit 工具类
从现在开始我们就要用到 htmlunit 这个东西了,我个人建议大家去看一下它官网上 Get Start,连接地址:HtmlUnit – Getting Started with HtmlUnit
我们根据它官网的提示,htmlunit 想要去爬页面数据,首先是需要用到 WebClient,所以这里我们弄个简单的工具类,用来管理它。
public class HtmlunitUtil {
public static WebClient getWebClient() {
WebClient webClient = new WebClient(BrowserVersion.CHROME);
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(false);
webClient.waitForBackgroundJavaScript(2000L);
webClient.getOptions().setTimeout(5000);
webClient.getOptions().setUseInsecureSSL(true);
return webClient;
}
public static void closeWebClient(WebClient webClient) {
if(webClient != null) {
webClient.close();
}
}
}
当然,上面除去实例化 WebClient 之外,还给 WebClient 设置了很多属性,具体是什么意思,可以去看 API:HtmlUnit 2.60.0 API
7. 将 WebClient 接入到 task 中
CrawlCnblogTask 完整代码如下:
@Component
public class CrawlCnblogTask {
private Logger logger = LoggerFactory.getLogger(CrawlCnblogTask.class);
@Value("${crawl.cnblogs}")
private String taskUrl;
@Scheduled(cron = "0 0/20 * * * ?")
public void task() {
if(StrUtil.isEmpty(taskUrl)) {
taskUrl = "https://xxx/xxx/x";
}
logger.info("========== {} 爬取 cnblog 开始 ======================================== ", DateUtil.now());
try {
crawlData(taskUrl);
} catch (Exception e) {
logger.info("{} 爬取 cnblog 出现异常,错误原因:{}", DateUtil.now(), e.getMessage());
e.printStackTrace();
}
}
public void crawlData(String taskUrl) throws Exception {
if(!UrlUtil.checkUrl(taskUrl + "1")) {
logger.info("{} 爬取 cnblog 失败,错误原因:连接地址错误,无法请求:{}", DateUtil.now(), taskUrl);
throw new RuntimeException("连接地址错误,无法请求");
}
WebClient webClient = HtmlunitUtil.getWebClient();
for(int i = 1; i <= 1; i++) {
HtmlPage page = webClient.getPage(taskUrl + i);
System.out.println(page.asXml());
}
HtmlunitUtil.closeWebClient(webClient);
}
}
这里有几个注意点要说的:
- taskUrl 是错误的,没有 https://xxx 这种网站,想要获取正确的地址,去 clone 我的项目,项目的 yml 配置文件里有
- crawlData 方法中为啥 taskUrl 要拼接一个数字 1?这里我们其实在上一个博客就说了,他们的请求是一个 Restful 请求,1 就是代表第一页,2 就是第二页,以此类推
- 下面 for 循环只循环一次,因为我们只是用来测试,后期我们需要将条件改成 i <= 200
8. 测试
测试的话比较简单,学过 maven 的都会知道 maven 是有专门的测试类的,而且 SpringBoot 也是可以支持测试,所以直接上代码
@SpringBootTest
class Lemon1234ScraperApplicationTests {
@Autowired
private CrawlCnblogTask syncCnblogTask;
@Test
void contextLoads() {
syncCnblogTask.task();
}
}
来看看运行结果
可以看到,整个 html 页面就直接拿下了,下一讲我们来说对这段 html 的解析。
这一讲就讲到这里,有问题可以联系我:QQ 2100363119。