基于Spring Boot+jsoup+redis抓取CSDN每周干货的RESTFul爬虫

标签: SpringBoot Redis Jsoup 爬虫
2864人阅读 评论(0) 收藏 举报
分类:

    一个简单的爬虫,用于抓取csdn上的每周干货推荐。

    使用到的相关技术:SpringBoot、Redis、Jsoup、JQuery、Bootstrap等。

示例地址:

    http://tinyspider.anxpp.com/

效果图:

 

1、写在前面

    准备熟悉下Spring Boot + Redis的使用,所以就想到爬点东西出来,于是用上了号称Java版JQuery的Jsoup,实现的功能是获取每周的CSDN推荐文章,并缓存到Redis中(当然也可以持久化到数据库,相关配置已添加,只是没有实现),网页解析部分已抽象为接口,根据要抓取的不同网页,可以自定义对应的实现,也就是可以爬取任何网页了。

    解析网页的方法返回的数据为List<Map>,再定义对应的实体,可以直接反射为实体(已实现),具体见后文的代码介绍。

    下面介绍具体实现的步骤。

2、搭建Spring Boot并集成Redis

    Spring Boot工程的搭建不用多说了,不管是Eclipse还是Idea,Spring都提供了懒人工具,可根据要使用的组件一键生成项目。

    下面是Redis,首先是引入依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

    然后添加配置文件:

  1. #Redis
  2. spring.redis.database=0
  3. spring.redis.host=****
  4. spring.redis.password=a****
  5. spring.redis.pool.max-active=8
  6. spring.redis.pool.max-idle=8
  7. spring.redis.pool.max-wait=-1
  8. spring.redis.pool.min-idle=0
  9. spring.redis.port=****
  10. #spring.redis.sentinel.master= # Name of Redis server.
  11. #spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs.
  12. spring.redis.timeout=0

    ip和端口请自行根据实际情况填写。

    然后是配置Redis,此处使用JavaConfig的方式:

  1. package com.anxpp.tinysoft.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.cache.CacheManager;
  7. import org.springframework.cache.annotation.EnableCaching;
  8. import org.springframework.cache.interceptor.KeyGenerator;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import org.springframework.data.redis.cache.RedisCacheManager;
  12. import org.springframework.data.redis.connection.RedisConnectionFactory;
  13. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
  14. import org.springframework.data.redis.core.RedisTemplate;
  15. import org.springframework.data.redis.core.StringRedisTemplate;
  16. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  17. /**
  18. * Redis缓存配置
  19. * Created by anxpp.com on 2017/3/11.
  20. */
  21. @Configuration
  22. @EnableCaching
  23. public class RedisCacheConfig {
  24. @Value("${spring.redis.host}")
  25. private String host;
  26. @Value("${spring.redis.port}")
  27. private int port;
  28. @Value("${spring.redis.timeout}")
  29. private int timeout;
  30. @Value("${spring.redis.password}")
  31. private String password;
  32. @Bean
  33. public KeyGenerator csdnKeyGenerator() {
  34. return (target, method, params) -> {
  35. StringBuilder sb = new StringBuilder();
  36. sb.append(target.getClass().getName());
  37. sb.append(method.getName());
  38. for (Object obj : params) {
  39. sb.append(obj.toString());
  40. }
  41. return sb.toString();
  42. };
  43. }
  44. @Bean
  45. public JedisConnectionFactory redisConnectionFactory() {
  46. JedisConnectionFactory factory = new JedisConnectionFactory();
  47. factory.setHostName(host);
  48. factory.setPort(port);
  49. factory.setPassword(password);
  50. factory.setTimeout(timeout); //设置连接超时时间
  51. return factory;
  52. }
  53. @Bean
  54. public CacheManager cacheManager(RedisTemplate redisTemplate) {
  55. RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
  56. // Number of seconds before expiration. Defaults to unlimited (0)
  57. cacheManager.setDefaultExpiration(10); //设置key-value超时时间
  58. return cacheManager;
  59. }
  60. @Bean
  61. public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
  62. StringRedisTemplate template = new StringRedisTemplate(factory);
  63. setSerializer(template); //设置序列化工具,这样ReportBean不需要实现Serializable接口
  64. template.afterPropertiesSet();
  65. return template;
  66. }
  67. private void setSerializer(StringRedisTemplate template) {
  68. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  69. ObjectMapper om = new ObjectMapper();
  70. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  71. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  72. jackson2JsonRedisSerializer.setObjectMapper(om);
  73. template.setValueSerializer(jackson2JsonRedisSerializer);
  74. }
  75. }

    如果我们有多个程序(甚至是不同语言编写的的),需要注意Redis的key和value的序列化机制,比如PHP和Java中使用Redis的默认序列化机制是不同的,如果不做配置,可能会导致两边存的数据互相取不出来。

    这样一来,就配置好了,后面直接使用就好。

3、网页解析抽象和csdnweekly实现

    首先定义网页解析接口:

  1. package com.anxpp.tinysoft.Utils.analyzer;
  2. import org.jsoup.nodes.Document;
  3. import java.util.List;
  4. import java.util.Map;
  5. /**
  6. * 解析html文档抽象
  7. * Created by anxpp.com on 2017/3/11.
  8. */
  9. public interface DocumentAnalyzer {
  10. /**
  11. * 根据html文档对象获取List<Map>
  12. * @param document html文档对象
  13. * @return 结果
  14. */
  15. List<Map<String,Object>> forListMap(Document document);
  16. }

    针对csdn的每周干货推荐,编写具体实现:

  1. package com.anxpp.tinysoft.Utils.analyzer.impl;
  2. import com.anxpp.tinysoft.Utils.analyzer.DocumentAnalyzer;
  3. import org.jsoup.nodes.Document;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.util.ObjectUtils;
  6. import java.util.ArrayList;
  7. import java.util.HashMap;
  8. import java.util.List;
  9. import java.util.Map;
  10. /**
  11. * 解析CSDN每周知识干货html文档具体实现
  12. * Created by anxpp.com on 2017/3/11.
  13. */
  14. @Component
  15. public class CsdnWeeklyDocumentAnalyzer implements DocumentAnalyzer {
  16. /**
  17. * 根据html文档对象获取List<Map>
  18. * @param document html文档对象
  19. * @return 结果
  20. */
  21. @Override
  22. public List<Map<String,Object>> forListMap(Document document) {
  23. List<Map<String,Object>> results = new ArrayList<>();
  24. if(ObjectUtils.isEmpty(document))
  25. return results;
  26. document.body().getElementsByClass("pclist").get(0).children().forEach(ele -> {
  27. Map<String,Object> result = new HashMap<>();
  28. result.put("type",ele.getElementsByTag("span").get(0).getElementsByTag("a").get(0).attr("href"));
  29. result.put("img",ele.getElementsByTag("span").get(0).getElementsByTag("a").get(0).getElementsByTag("img").get(0).attr("src"));
  30. result.put("url",ele.getElementsByTag("span").get(1).getElementsByTag("a").get(0).attr("href"));
  31. result.put("name",ele.getElementsByTag("span").get(1).getElementsByTag("a").get(0).text());
  32. result.put("views",Integer.valueOf(ele.getElementsByTag("span").get(1).getElementsByTag("span").get(0).getElementsByTag("em").get(0).text().replaceAll("\\D+","")));
  33. result.put("collections",Integer.valueOf(ele.getElementsByTag("span").get(1).getElementsByTag("span").get(1).getElementsByTag("em").get(0).text().replaceAll("\\D+","")));
  34. results.add(result);
  35. });
  36. return results;
  37. }
  38. }

    当然,如果需要解析其他网页,实现DocumentAnalyzer接口,完成对应的解析方式也是完全可以的。

    然后,我们需要一个工具将Map转换为实体对象:

  1. package com.anxpp.tinysoft.Utils;
  2. import java.lang.reflect.Method;
  3. import java.util.Map;
  4. import java.util.Set;
  5. /**
  6. * 简单工具集合
  7. * Created by anxpp.com on 2017/3/11.
  8. */
  9. class TinyUtil {
  10. /**
  11. * map转对象
  12. *
  13. * @param map map
  14. * @param type 类型
  15. * @param <T> 泛型
  16. * @return 对象
  17. * @throws Exception 反射异常
  18. */
  19. static <T> T mapToBean(Map<String, Object> map, Class<T> type) throws Exception {
  20. if (map == null) {
  21. return null;
  22. }
  23. Set<Map.Entry<String, Object>> sets = map.entrySet();
  24. T entity = type.newInstance();
  25. Method[] methods = type.getDeclaredMethods();
  26. for (Map.Entry<String, Object> entry : sets) {
  27. String str = entry.getKey();
  28. String setMethod = "set" + str.substring(0, 1).toUpperCase() + str.substring(1);
  29. for (Method method : methods) {
  30. if (method.getName().equals(setMethod)) {
  31. method.invoke(entity, entry.getValue());
  32. }
  33. }
  34. }
  35. return entity;
  36. }
  37. }

    下面就是具体的数据获取逻辑了。

4、提供API以及数据获取逻辑

    首先我们需要定义一个实体:

  1. package com.anxpp.tinysoft.core.entity;
  2. import javax.persistence.*;
  3. import java.util.Date;
  4. /**
  5. * 文章信息
  6. * Created by anxpp.com on 2017/3/11.
  7. */
  8. @Entity
  9. @Table(name = "t_csdn_weekly_article")
  10. public class Article extends BaseEntity{
  11. /**
  12. * 文章名称
  13. */
  14. private String name;
  15. /**
  16. * 文章名称
  17. */
  18. private String url;
  19. /**
  20. * 属于哪一期
  21. */
  22. private Integer stage;
  23. /**
  24. * 浏览量
  25. */
  26. private Integer views;
  27. /**
  28. * 收藏数
  29. */
  30. private Integer collections;
  31. /**
  32. * 所属知识库类别
  33. */
  34. private String type;
  35. /**
  36. * 类别图片地址
  37. */
  38. private String img;
  39. /**
  40. * 更新时间
  41. */
  42. @Column(name = "update_at", nullable = false)
  43. @Temporal(TemporalType.TIMESTAMP)
  44. private Date updateAt;
  45. //省略get set 方法
  46. }

    如果要持久化数据到数据库,也可以添加Repo层,使用Spring Data JPA也是超级方便的,博客中已提供相关文章参考,此处直接使用Redis,跳过此层。

    Service接口定义:

  1. package com.anxpp.tinysoft.core.service;
  2. import com.anxpp.tinysoft.core.entity.Article;
  3. import java.util.List;
  4. /**
  5. * 文章数据service
  6. * Created by anxpp.com on 2017/3/11.
  7. */
  8. public interface ArticleService {
  9. /**
  10. * 根据期号获取文章列表
  11. * @param stage 期号
  12. * @return 文章列表
  13. */
  14. List<Article> forWeekly(Integer stage) throws Exception;
  15. }

    Service实现:

  1. package com.anxpp.tinysoft.core.service.impl;
  2. import com.anxpp.tinysoft.Utils.ArticleSpider;
  3. import com.anxpp.tinysoft.Utils.analyzer.impl.CsdnWeeklyDocumentAnalyzer;
  4. import com.anxpp.tinysoft.core.entity.Article;
  5. import com.anxpp.tinysoft.core.service.ArticleService;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.cache.annotation.Cacheable;
  8. import org.springframework.stereotype.Service;
  9. import javax.annotation.Resource;
  10. import java.util.List;
  11. /**
  12. * 文章service实现
  13. * Created by anxpp.com on 2017/3/11.
  14. */
  15. @Service
  16. public class ArticleServiceImpl implements ArticleService {
  17. @Value("${csdn.weekly.preurl}")
  18. private String preUrl;
  19. @Resource
  20. private CsdnWeeklyDocumentAnalyzer csdnWeeklyDocumentAnalyzer;
  21. /**
  22. * 根据期号获取文章列表
  23. *
  24. * @param stage 期号
  25. * @return 文章列表
  26. */
  27. @Override
  28. @Cacheable(value = "reportcache", keyGenerator = "csdnKeyGenerator")
  29. public List<Article> forWeekly(Integer stage) throws Exception {
  30. List<Article> articleList = ArticleSpider.forEntityList(preUrl + stage, csdnWeeklyDocumentAnalyzer, Article.class);
  31. articleList.forEach(article -> article.setStage(stage));
  32. return articleList;
  33. }
  34. }

    csdn.weekly.preurl为配置文件中配置的url前缀,后面会放出完整的配置文件。

    最后就是提供对外的接口,本文只添加了一个,也可以按需添加其他API:

  1. package com.anxpp.tinysoft.controller;
  2. import com.anxpp.tinysoft.core.entity.Article;
  3. import com.anxpp.tinysoft.core.service.ArticleService;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.ResponseBody;
  9. import javax.annotation.Resource;
  10. import java.util.List;
  11. /**
  12. * 默认页面
  13. * Created by anxpp.com on 2017/3/11.
  14. */
  15. @Controller
  16. @RequestMapping("/article")
  17. public class ArticleController {
  18. @Resource
  19. private ArticleService articleService;
  20. @ResponseBody
  21. @GetMapping("/get/stage/{stage}")
  22. public List<Article> getArticleByStage(@PathVariable("stage") Integer stage) throws Exception {
  23. return articleService.forWeekly(stage);
  24. }
  25. }

    完整的配置文件:

  1. server.port=****
  2. #DataSource
  3. spring.datasource.url=jdbc:mysql://****.***:****/****?createDatabaseIfNotExist=true
  4. spring.datasource.username=****
  5. spring.datasource.password=****
  6. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  7. #multiple Setting
  8. spring.jpa.hibernate.ddl-auto=update
  9. spring.jpa.show-sql=true
  10. #Redis
  11. spring.redis.database=0
  12. spring.redis.host=****
  13. spring.redis.password=a****
  14. spring.redis.pool.max-active=8
  15. spring.redis.pool.max-idle=8
  16. spring.redis.pool.max-wait=-1
  17. spring.redis.pool.min-idle=0
  18. spring.redis.port=****
  19. #spring.redis.sentinel.master= # Name of Redis server.
  20. #spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs.
  21. spring.redis.timeout=0
  22. #csdn setting
  23. csdn.weekly.preurl=http://lib.csdn.net/weekly/

    数据库等的配置请根据实际情况配置。

    现在启动程序即可访问。

    

    源码已提交到GitHub:https://github.com/anxpp/csdnweeklySpider

    后续有时间会继续完善本程序添加更多网站内容的抓取。

查看评论

利用spring boot 写一个稳定的爬虫

1、前言这篇文章是利用spring boot 写一个稳定的爬虫,爬取的网页数据包含未执行js的网页数据、http/https接口的请求数据、和经过网页渲染的js数据(需要chorme浏览器),数据库使...
  • q56231293811
  • q56231293811
  • 2017-11-06 11:45:04
  • 1198

爬虫框架webmagic与spring boot的结合使用

1. 爬虫框架webmagic WebMagic是一个简单灵活的爬虫框架。基于WebMagic,你可以快速开发出一个高效、易维护的爬虫。 1.1 官网地址 官网文档写的比较清楚,建议...
  • F1576813783
  • F1576813783
  • 2017-08-04 11:27:30
  • 1243

Springboot通过集成Webmagic实现数据抓取功能。

一、什么是Webmagic. 要使用Webmagic首先需要了解什么是Webmagic. webmagic是一个开源的Java垂直爬虫框架,目标是简化爬虫的开发流程,让开发者专注于逻辑功能的开发。...
  • yueaini10000
  • yueaini10000
  • 2017-09-22 13:56:40
  • 932

Spring Boot 菜鸟教程 11 html页面解析-jsoup

需求 需要对一个页面进行数据抓取,并导出doc文档 html解析器 jsoup 可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操...
  • JE_GE
  • JE_GE
  • 2016-11-30 22:19:56
  • 2718

使用springboot + lucence + ik +angularjs+jsoup 搜索自己博客

一。 爬取博客信息       使用jsoup可以下载并且使用jquery语法来解析xml  这里先通过http://blog.csdn.net/liaomin416100569 右键查看源代码可以看...
  • liaomin416100569
  • liaomin416100569
  • 2017-07-04 09:06:14
  • 1127

spring boot实战之XSS过滤

XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意...
  • u014411966
  • u014411966
  • 2017-10-06 12:43:55
  • 1297

Java爬虫初体验:简单抓取IT之家热评(整合Spring Boot+Elasticsearch+Redis+Mybatis)

爬取主程序使用Jsoup解析网页源代码@Component public class WebCrawler { private static final String encoding = "u...
  • CrazyLai1996
  • CrazyLai1996
  • 2017-08-27 17:44:22
  • 1373

【爬虫】WebMagic结合Spring mvc爬取数据进行存储

工作4年多了,也没写过什么博客,去年回老家入职一家国企,工作稍微轻松些,没有在深圳的时候那么忙。最近感觉精力充沛(轻松的工作还是蛮养人的),想把自己研究或者使用到的相关技术做一个记录。第一、对这些知识...
  • w1054993544
  • w1054993544
  • 2016-07-07 19:18:44
  • 5529

springboot使用webmagic框架来抓取自己的博客信息

因为看自己的博客文章的一些信息要上网登录什么的步骤,有时候显得很麻烦,所以今天抽空学了webmagic爬虫框架,让自己的文章信息直接展示在控制台,如下图: 一、创建一个java项目(其实是不是spr...
  • h295928126
  • h295928126
  • 2016-12-14 18:26:58
  • 2308

WebCollector java爬虫使用笔记

webcollector使用笔记,方便新手学习
  • whos2002110
  • whos2002110
  • 2015-01-16 09:47:52
  • 36217
    个人资料
    专栏达人 持之以恒
    等级:
    访问量: 88万+
    积分: 5836
    排名: 5503
    博客专栏
    music