SpringBoot+Webmagic+Redis+Docker定时爬取数据

项目说明:

   本demo主要是基于springboot基础框架,在指定时间优先判断是否已存在指定Key值的数据,存在则删除,再通过webmagic技术爬取官网上的新闻数据,通过Redis的set(集合)数据类型进行去重处理存入Redis中。最后打包成镜像发布至Docker;

技术栈:

技术 说明 链接
SpringBoot 基础框架 https://spring.io/projects/spring-boot
Webmagic 基于Java开发的爬虫框架 http://webmagic.io/
Reids 基于内存的Key-Value数据库 https://redis.io/
Lombok 自动接通你的编辑器和构建工具的Java库 https://projectlombok.org/
Docker 开源应用容器引擎 https://www.docker.com/

前期准备:

  安装Docker,运行Redis容器。对Docker不熟悉的可以查看Docker基本命令(一)Docker基本命令(二)

相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.3</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

</dependencies>

搭建流程:

  1.编辑配置文件application.yml

1
2
3
4
spring:
redis:
host: 127.0.0.1
port: 6379

  2.新建爬虫模块实体类,并实现序列化。如果不实现序列化,是无法存入Redis中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Data
public class News implements Serializable {
/**
* url地址
*/
private String url;

/**
* 题目
*/
private String title;

/**
* 来源
*/
private String source;

/**
* 作者
*/
private String author;

/**
* 发布日期
*/
private String releaseDate;

/**
* 浏览量
*/
private String pageViews;

/**
* 内容
*/
private String content;
}

  3.编写爬虫执行类,这里先看一下Webmagic的总体架构图。

Yoyou

  开始编写执行类,这里需要实现PageProcesser接口的process方法和getSite方法。process方法主要是做一些页面的收集,在此你需要先找到详情类地址和列表页地址的区别,建议使用正则表达式匹配,详情页获取具体的数据可以使用css、xpath、正则等表达式进行匹配,这里建议每写一条语句都进行一次Debug,以确保数据的准确性。最后通过page属性的putField(Key, Value);方法暂存。执行方法中配置线程、数据处理类型等信息。这里注意类上记得加上@Component等注解将该类添加进Spring容器!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Component
public class NewsProcesser implements PageProcessor {

private static final String URL = "https://www.jhc.cn/";

@Autowired
private RedisPipeline redisPipeline;
@Autowired
private RedisTemplate redisTemplate;

@Override
public void process(Page page) {
try {
if (!page.getUrl().toString().contains("page.htm")) {
page.addTargetRequests(page.getHtml().xpath("//li[@class='news-item']").links().all());
} else {
News newInfo = new News();
String url = page.getUrl().toString();
newInfo.setUrl(url);

String title = page.getHtml().xpath("//h1[@class='arti-title']/text()").toString();
newInfo.setTitle(title);

String[] source = page.getHtml().xpath("//p[@class='arti-metas']/span[1]/text()").toString().split(":");
newInfo.setSource(source[1]);

String[] auther = page.getHtml().xpath("//p[@class='arti-metas']/span[2]/text()").toString().split(":");
if (auther.length > 2) {
newInfo.setAuthor(auther[1]);
} else {
newInfo.setAuthor("");
}


String[] date = page.getHtml().xpath("//p[@class='arti-metas']/span[3]/text()").toString().split(":");
newInfo.setReleaseDate(date[1]);

String view = page.getHtml().xpath("//p[@class='arti-metas']/span[4]/span/text()").toString();
newInfo.setPageViews(view);

String content = page.getHtml().xpath("//div[@class='wp_articlecontent']").toString();
//剔除带class属性div
String replace = content.replace("<div class=\"wp_articlecontent\">", "");
String replace2 = replace.replace("</div>", "");
newInfo.setContent(replace2);

System.out.println("爬取:" + url);
page.putField("newInfo", newInfo);
}
} catch (Exception e) {
e.printStackTrace();
}
}

private Site site = Site.me()
.setCharset("utf8")
//超时时间
.setTimeOut(10 * 1000)
//重试间隔时间
.setRetrySleepTime(3000)
//重试次数
.setRetryTimes(3);

@Override
public Site getSite() {
return site;
}

public void runCrawler() {
if (redisTemplate.hasKey("news")) {
redisTemplate.delete("news");
}
Spider.create(new NewsProcesser())
.addUrl(URL)
.thread(5)
.addPipeline(redisPipeline)
.run();
}
}

  4.编写爬虫数据处理类,首先实现Pipeline接口的process方法,通过resultItems的get方法取出执行类中process方法通过putField方法放入的数据,并使用RedisTemplate类进行Redis的插入处理,使用他的opsForSet().add(key, value)方法进行集合方式数据添加。Mysql存储同理。同样记得添加@Component等注解,否则无法自动注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class RedisPipeline implements Pipeline {

@Autowired
private RedisTemplate redisTemplate;

@Override
public void process(ResultItems resultItems, Task task) {
News newInfo = resultItems.get("newInfo");
// System.out.println("数据:"+newInfo);
if (newInfo != null) {
redisTemplate.opsForSet().add("news", newInfo);
System.out.println("保存成功");
}

}
}

  5.使用注解开启定时任务,在启动类上添加@EnableScheduling注解,爬虫执行方法上添加@Scheduled注解。如对springboot的定时任务仍存在疑虑可以查看我的另一篇博客SpringBoot定时任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SpringBootApplication
@EnableScheduling
public class CrawlerApplication {

public static void main(String[] args) {
SpringApplication.run(CrawlerApplication.class, args);
}

}

@Component
public class NewsProcesser implements PageProcessor {
……

@Scheduled(initialDelay = 1000, fixedDelay = 100 * 1000)
public void runCrawler() {
if (redisTemplate.hasKey("news")) {
redisTemplate.delete("news");
}
Spider.create(new NewsProcesser())
.addUrl(URL)
.thread(5)
.addPipeline(redisPipeline)
.run();
}

}

  执行效果

Yoyou
Yoyou

  6.将项目打成jar包,编写DockerFile。

1
2
3
4
5
FROM java:8
COPY *.jar /app.jar
CMD ["--server.port=8080"]
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

  然后将其上传到服务器上。

Yoyou

  执行以下命令,将其打成镜像

1
docker build -t 镜像名 .

Yoyou

  查看所有镜像

Yoyou

  运行springboot容器

1
docker run -d --name 容器名 -p 暴露端口号:映射端口号 boot

Yoyou

  查看效果

Yoyou

  至此,就全部完成了。最后,附上Github代码仓库地址

如果你觉得有帮助,慷慨如你,可以扫描下面的二维码赞赏一下