经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
《花100块做个摸鱼小网站! 》第三篇—热搜表结构设计和热搜数据存储
来源:cnblogs  作者:sum墨  时间:2024/8/20 8:59:58  对本文有异议

??基础链接导航??

服务器 → ?? 阿里云活动地址

看样例 → ?? 摸鱼小网站地址

学代码 → ?? 源码库地址

一、前言

大家好呀,我是summo,第一篇已经教会大家怎么去阿里云买服务器,以及怎么搭建JDK、Redis、MySQL这些环境。第二篇我们把后端的应用搭建好了,并且完成了第一个爬虫(抖音)。那么这一篇我会教大家如何将爬取到的数据保存到数据库,并且可以通过接口获取到,为后面的前端界面提供数据源。

二、表结构设计

热搜表结构一览如下

建表语句如下

  1. CREATE TABLE `t_sbmy_hot_search` (
  2. `id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '物理主键',
  3. `hot_search_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '热搜ID',
  4. `hot_search_excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '热搜摘录',
  5. `hot_search_heat` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '热搜热度',
  6. `hot_search_title` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '热搜标题',
  7. `hot_search_url` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '热搜链接',
  8. `hot_search_cover` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '热搜封面',
  9. `hot_search_author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '热搜作者',
  10. `hot_search_author_avatar` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '热搜作者头像',
  11. `hot_search_resource` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '热搜来源',
  12. `hot_search_order` int DEFAULT NULL COMMENT '热搜排名',
  13. `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  14. `gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
  15. `creator_id` bigint DEFAULT NULL COMMENT '创建人',
  16. `modifier_id` bigint DEFAULT NULL COMMENT '更新人',
  17. PRIMARY KEY (`id`) USING BTREE
  18. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

其中核心字段是hot_search_idhot_search_titlehot_search_urlhot_search_heat这四个字段,这几个字段是一定会有值的,其他的字段看热搜接口给不给值,有就填没有就空,把这个建表语句执行一下就可以创建出热搜记录表了。

三、使用插件生成Java对象

Java映射表结构对象的生成有很多方式,这里我推荐使用mybatis插件的方式生成,操作非常简单,一次配好后面的对象都可以这样生成(ps:我用的开发工具是idea社区版)。

1. 在summo-sbmy-dao这个module的resources目录下创建generator目录

2. 在generator目录创建config.properties文件

内容如下

  1. # JDBC链接
  2. jdbc.url=jdbc:mysql://xxx:3306/summo-sbmy?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
  3. # 用户名
  4. jdbc.user=root
  5. # 密码
  6. jdbc.password=xxx
  7. # 表名
  8. table.name=t_sbmy_hot_search
  9. # 对象名
  10. entity.name=SbmyHotSearchDO
  11. # Mapper
  12. mapper.name=SbmyHotSearchMapper

这是一个配置文件,将需要创建Java的对象的表写到这来再设置好对象名即可。

3. 在generator目录创建generatorConfiguration.xml文件

内容如下

  1. <!DOCTYPE generatorConfiguration
  2. PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  3. "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
  4. <generatorConfiguration>
  5. <properties resource="generator/config.properties"/>
  6. <!-- MySQL数据库驱动jar包路径,全路径 -->
  7. <classPathEntry location="/xxx/mysql-connector-java-8.0.11.jar"/>
  8. <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
  9. <property name="beginningDelimiter" value="`"/>
  10. <property name="endingDelimiter" value="`"/>
  11. <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
  12. <property name="mappers" value="com.baomidou.mybatisplus.core.mapper.BaseMapper"/>
  13. <!-- 插件默认的通用mapper <property name="mappers" itemValue="tk.mybatis.mapper.common.Mapper"/>-->
  14. <property name="caseSensitive" value="true"/>
  15. <property name="lombok" value="Getter,Setter,Builder,NoArgsConstructor,AllArgsConstructor"/>
  16. </plugin>
  17. <!-- 数据库连接 -->
  18. <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
  19. connectionURL="${jdbc.url}"
  20. userId="${jdbc.user}" password="${jdbc.password}"/>
  21. <!-- entity路径 -->
  22. <javaModelGenerator targetPackage="com.summo.sbmy.dao.entity"
  23. targetProject="src/main/java"/>
  24. <!-- xml路径 -->
  25. <sqlMapGenerator targetPackage="mybatis/mapper"
  26. targetProject="src/main/resources/">
  27. <property name="enableSubPackages" value="true"/>
  28. </sqlMapGenerator>
  29. <!-- mapper路径 -->
  30. <javaClientGenerator targetPackage="com.summo.sbmy.dao.mapper"
  31. targetProject="src/main/java"
  32. type="XMLMAPPER">
  33. </javaClientGenerator>
  34. <!-- 自增ID路径 -->
  35. <table tableName="${table.name}" domainObjectName="${entity.name}"
  36. mapperName="${mapper.name}">
  37. <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
  38. </table>
  39. </context>
  40. </generatorConfiguration>

这是生成的逻辑规则,设置指定的entity、dao、mapper等文件位置。这两个文件配置好之后,刷新一下maven仓库,插件就会自动识别到了。

4. 双击mybatis-generator:generate生成对象

找到插件执行的位置

双击mybatis-generator:generate就可以生成对应的DO、Mapper、xml了,不过其他的类像controller、service和repository生成不了,需要自己创建。这里最终的目录结构如下

这里的repository代码是我加上去的,插件生成不了,我还加了一个AbstractBaseDO,它是一个抽象父类,所有的DO都会继承它;还加了一个MetaObjectHandlerConfig,用于对SQL进行切面处理,最后有些代码还有些微调。我把代码贴一下,详情可以见我的代码仓库。

SbmyHotSearchDO.java

  1. package com.summo.sbmy.dao.entity;
  2. import java.util.Date;
  3. import javax.persistence.*;
  4. import com.baomidou.mybatisplus.annotation.IdType;
  5. import com.baomidou.mybatisplus.annotation.TableId;
  6. import com.baomidou.mybatisplus.annotation.TableName;
  7. import com.summo.sbmy.dao.AbstractBaseDO;
  8. import lombok.AllArgsConstructor;
  9. import lombok.Builder;
  10. import lombok.Getter;
  11. import lombok.NoArgsConstructor;
  12. import lombok.Setter;
  13. import lombok.ToString;
  14. @Getter
  15. @Setter
  16. @TableName("t_sbmy_hot_search")
  17. @NoArgsConstructor
  18. @AllArgsConstructor
  19. @Builder
  20. @ToString
  21. public class SbmyHotSearchDO extends AbstractBaseDO<SbmyHotSearchDO> {
  22. /**
  23. * 物理主键
  24. */
  25. @TableId(type = IdType.AUTO)
  26. private Long id;
  27. /**
  28. * 热搜标题
  29. */
  30. @Column(name = "hot_search_title")
  31. private String hotSearchTitle;
  32. /**
  33. * 热搜作者
  34. */
  35. @Column(name = "hot_search_author")
  36. private String hotSearchAuthor;
  37. /**
  38. * 热搜来源
  39. */
  40. @Column(name = "hot_search_resource")
  41. private String hotSearchResource;
  42. /**
  43. * 热搜排名
  44. */
  45. @Column(name = "hot_search_order")
  46. private Integer hotSearchOrder;
  47. /**
  48. * 热搜ID
  49. */
  50. @Column(name = "hot_search_id")
  51. private String hotSearchId;
  52. /**
  53. * 热搜热度
  54. */
  55. @Column(name = "hot_search_heat")
  56. private String hotSearchHeat;
  57. /**
  58. * 热搜链接
  59. */
  60. @Column(name = "hot_search_url")
  61. private String hotSearchUrl;
  62. /**
  63. * 热搜封面
  64. */
  65. @Column(name = "hot_search_cover")
  66. private String hotSearchCover;
  67. /**
  68. * 热搜作者头像
  69. */
  70. @Column(name = "hot_search_author_avatar")
  71. private String hotSearchAuthorAvatar;
  72. /**
  73. * 热搜摘录
  74. */
  75. @Column(name = "hot_search_excerpt")
  76. private String hotSearchExcerpt;
  77. }

SbmyHotSearchMapper.java

  1. package com.summo.sbmy.dao.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
  4. import org.apache.ibatis.annotations.Mapper;
  5. @Mapper
  6. public interface SbmyHotSearchMapper extends BaseMapper<SbmyHotSearchDO> {
  7. }

SbmyHotSearchRepository.java

  1. package com.summo.sbmy.dao.repository;
  2. import com.baomidou.mybatisplus.extension.service.IService;
  3. import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
  4. public interface SbmyHotSearchRepository extends IService<SbmyHotSearchDO> {
  5. }

SbmyHotSearchRepositoryImpl.java

  1. package com.summo.sbmy.dao.repository.impl;
  2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  3. import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
  4. import com.summo.sbmy.dao.mapper.SbmyHotSearchMapper;
  5. import com.summo.sbmy.dao.repository.SbmyHotSearchRepository;
  6. import org.springframework.stereotype.Repository;
  7. @Repository
  8. public class SbmyHotSearchRepositoryImpl extends ServiceImpl<SbmyHotSearchMapper, SbmyHotSearchDO>
  9. implements SbmyHotSearchRepository {
  10. }

AbstractBaseDO.java

  1. package com.summo.sbmy.dao;
  2. import java.io.Serializable;
  3. import java.util.Date;
  4. import com.baomidou.mybatisplus.annotation.FieldFill;
  5. import com.baomidou.mybatisplus.annotation.TableField;
  6. import com.baomidou.mybatisplus.extension.activerecord.Model;
  7. import lombok.Getter;
  8. import lombok.Setter;
  9. @Getter
  10. @Setter
  11. public class AbstractBaseDO<T extends Model<T>> extends Model<T> implements Serializable {
  12. /**
  13. * 创建时间
  14. */
  15. @TableField(fill = FieldFill.INSERT)
  16. private Date gmtCreate;
  17. /**
  18. * 修改时间
  19. */
  20. @TableField(fill = FieldFill.INSERT_UPDATE)
  21. private Date gmtModified;
  22. /**
  23. * 创建人ID
  24. */
  25. @TableField(fill = FieldFill.INSERT)
  26. private Long creatorId;
  27. /**
  28. * 修改人ID
  29. */
  30. @TableField(fill = FieldFill.INSERT_UPDATE)
  31. private Long modifierId;
  32. }

MetaObjectHandlerConfig.java

  1. package com.summo.sbmy.dao;
  2. import java.util.Calendar;
  3. import java.util.Date;
  4. import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
  5. import org.apache.ibatis.reflection.MetaObject;
  6. import org.springframework.context.annotation.Configuration;
  7. @Configuration
  8. public class MetaObjectHandlerConfig implements MetaObjectHandler {
  9. @Override
  10. public void insertFill(MetaObject metaObject) {
  11. Date date = Calendar.getInstance().getTime();
  12. this.fillStrategy(metaObject, "gmtCreate", date);
  13. this.fillStrategy(metaObject, "gmtModified", date);
  14. }
  15. @Override
  16. public void updateFill(MetaObject metaObject) {
  17. Date date = Calendar.getInstance().getTime();
  18. this.setFieldValByName("gmtModified", date, metaObject);
  19. }
  20. }

SbmyHotSearchMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.summo.sbmy.dao.mapper.SbmyHotSearchMapper">
  4. <resultMap id="BaseResultMap" type="com.summo.sbmy.dao.entity.SbmyHotSearchDO">
  5. <!--
  6. WARNING - @mbg.generated
  7. -->
  8. <id column="id" jdbcType="BIGINT" property="id" />
  9. <result column="hot_search_title" jdbcType="VARCHAR" property="hotSearchTitle" />
  10. <result column="hot_search_author" jdbcType="VARCHAR" property="hotSearchAuthor" />
  11. <result column="hot_search_resource" jdbcType="VARCHAR" property="hotSearchResource" />
  12. <result column="hot_search_order" jdbcType="INTEGER" property="hotSearchOrder" />
  13. <result column="gmt_create" jdbcType="TIMESTAMP" property="gmtCreate" />
  14. <result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified" />
  15. <result column="creator_id" jdbcType="BIGINT" property="creatorId" />
  16. <result column="modifier_id" jdbcType="BIGINT" property="modifierId" />
  17. <result column="hot_search_id" jdbcType="VARCHAR" property="hotSearchId" />
  18. <result column="hot_search_heat" jdbcType="VARCHAR" property="hotSearchHeat" />
  19. <result column="hot_search_url" jdbcType="LONGVARCHAR" property="hotSearchUrl" />
  20. <result column="hot_search_cover" jdbcType="LONGVARCHAR" property="hotSearchCover" />
  21. <result column="hot_search_author_avatar" jdbcType="LONGVARCHAR" property="hotSearchAuthorAvatar" />
  22. <result column="hot_search_excerpt" jdbcType="LONGVARCHAR" property="hotSearchExcerpt" />
  23. </resultMap>
  24. </mapper>

四、热搜数据存储

1. 唯一ID生成

上一篇的最后我们把抖音的热搜数据给获取到了,这里表结构也设计好了,到存储逻辑就变得简单很多了。这个有一个点需要注意下,由于抖音的热搜没有自带唯一ID,为了不重复添加,我们需要手动给热搜设置一个ID,生成ID的算法如下:

  1. /**
  2. * 根据文章标题获取一个唯一ID
  3. *
  4. * @param title 文章标题
  5. * @return 唯一ID
  6. */
  7. public static String getHashId(String title) {
  8. long seed = title.hashCode();
  9. Random rnd = new Random(seed);
  10. return new UUID(rnd.nextLong(), rnd.nextLong()).toString();
  11. }
  12. public static void main(String[] args) {
  13. System.out.println(getHashId("当你有一只肥猫就会很显瘦"));
  14. System.out.println(getHashId("当你有一只肥猫就会很显瘦"));
  15. System.out.println(getHashId("当你有一只肥猫就会很显瘦"));
  16. System.out.println(getHashId("当你有一只肥猫就会很显瘦"));
  17. }

运行这串逻辑,输出一下代码

从输出结果来看,同一个标题获取的hashId都是一样的,可以满足我们的要求。

2. 数据存储流程

保存的逻辑流程如下图,核心就是根据那个唯一ID去数据库查询一下,有就跳过,没有就保存。

DouyinHotSearchJob代码如下

  1. package com.summo.sbmy.job.douyin;
  2. import java.io.IOException;
  3. import java.util.List;
  4. import java.util.Random;
  5. import java.util.UUID;
  6. import com.alibaba.fastjson.JSONArray;
  7. import com.alibaba.fastjson.JSONObject;
  8. import com.google.common.collect.Lists;
  9. import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
  10. import com.summo.sbmy.service.SbmyHotSearchService;
  11. import lombok.extern.slf4j.Slf4j;
  12. import okhttp3.OkHttpClient;
  13. import okhttp3.Request;
  14. import okhttp3.Response;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.scheduling.annotation.Scheduled;
  17. import org.springframework.stereotype.Component;
  18. import static com.summo.sbmy.common.enums.HotSearchEnum.DOUYIN;
  19. /**
  20. * @author summo
  21. * @version DouyinHotSearchJob.java, 1.0.0
  22. * @description 抖音热搜Java爬虫代码
  23. * @date 2024年08月09
  24. */
  25. @Component
  26. @Slf4j
  27. public class DouyinHotSearchJob {
  28. @Autowired
  29. private SbmyHotSearchService sbmyHotSearchService;
  30. /**
  31. * 定时触发爬虫方法,1个小时执行一次
  32. */
  33. @Scheduled(fixedRate = 1000 * 60 * 60)
  34. public void hotSearch() throws IOException {
  35. try {
  36. //查询抖音热搜数据
  37. OkHttpClient client = new OkHttpClient().newBuilder().build();
  38. Request request = new Request.Builder().url(
  39. "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word/").method("GET", null).build();
  40. Response response = client.newCall(request).execute();
  41. JSONObject jsonObject = JSONObject.parseObject(response.body().string());
  42. JSONArray array = jsonObject.getJSONArray("word_list");
  43. List<SbmyHotSearchDO> sbmyHotSearchDOList = Lists.newArrayList();
  44. for (int i = 0, len = array.size(); i < len; i++) {
  45. //获取知乎热搜信息
  46. JSONObject object = (JSONObject)array.get(i);
  47. //构建热搜信息榜
  48. SbmyHotSearchDO sbmyHotSearchDO = SbmyHotSearchDO.builder().hotSearchResource(DOUYIN.getCode()).build();
  49. //设置文章标题
  50. sbmyHotSearchDO.setHotSearchTitle(object.getString("word"));
  51. //设置抖音三方ID
  52. sbmyHotSearchDO.setHotSearchId(getHashId(DOUYIN.getCode() + sbmyHotSearchDO.getHotSearchTitle()));
  53. //设置文章连接
  54. sbmyHotSearchDO.setHotSearchUrl(
  55. "https://www.douyin.com/search/" + sbmyHotSearchDO.getHotSearchTitle() + "?type=general");
  56. //设置热搜热度
  57. sbmyHotSearchDO.setHotSearchHeat(object.getString("hot_value"));
  58. //按顺序排名
  59. sbmyHotSearchDO.setHotSearchOrder(i + 1);
  60. sbmyHotSearchDOList.add(sbmyHotSearchDO);
  61. }
  62. //数据持久化
  63. sbmyHotSearchService.saveCache2DB(sbmyHotSearchDOList);
  64. } catch (IOException e) {
  65. log.error("获取抖音数据异常", e);
  66. }
  67. }
  68. /**
  69. * 根据文章标题获取一个唯一ID
  70. *
  71. * @param title 文章标题
  72. * @return 唯一ID
  73. */
  74. public static String getHashId(String title) {
  75. long seed = title.hashCode();
  76. Random rnd = new Random(seed);
  77. return new UUID(rnd.nextLong(), rnd.nextLong()).toString();
  78. }
  79. }

这里的爬虫代码我做了微调,去掉了一些不必要的header和cookie。

数据存储逻辑如下

  1. @Override
  2. public Boolean saveCache2DB(List<SbmyHotSearchDO> sbmyHotSearchDOS) {
  3. if (CollectionUtils.isEmpty(sbmyHotSearchDOS)) {
  4. return Boolean.TRUE;
  5. }
  6. //查询当前数据是否已经存在
  7. List<String> searchIdList = sbmyHotSearchDOS.stream().map(SbmyHotSearchDO::getHotSearchId).collect(
  8. Collectors.toList());
  9. List<SbmyHotSearchDO> sbmyHotSearchDOList = sbmyHotSearchRepository.list(
  10. new QueryWrapper<SbmyHotSearchDO>().lambda().in(SbmyHotSearchDO::getHotSearchId, searchIdList));
  11. //过滤已经存在的数据
  12. if (CollectionUtils.isNotEmpty(sbmyHotSearchDOList)) {
  13. List<String> tempIdList = sbmyHotSearchDOList.stream().map(SbmyHotSearchDO::getHotSearchId).collect(
  14. Collectors.toList());
  15. sbmyHotSearchDOS = sbmyHotSearchDOS.stream().filter(
  16. sbmyHotSearchDO -> !tempIdList.contains(sbmyHotSearchDO.getHotSearchId())).collect(Collectors.toList());
  17. }
  18. if (CollectionUtils.isEmpty(sbmyHotSearchDOS)) {
  19. return Boolean.TRUE;
  20. }
  21. log.info("本次新增[{}]条数据", sbmyHotSearchDOS.size());
  22. //批量添加
  23. return sbmyHotSearchRepository.saveBatch(sbmyHotSearchDOS);
  24. }

这里代码就是我们常说的CRUD了,感觉没有什么可讲的,具体的代码看仓库。

五、小结一下

由于我们这是一个项目,有很多框架级和工程化的东西,细节非常多,如果我把代码全贴出来,那文章估计没法看了,所以后续的文章我只会贴一些核心的代码和逻辑。主要是有些东西没法一时半会跟你们讲清楚,牵扯到脚手架的设计,所以你们先姑妄看之,先一股脑的把代码复制进来,有不理解的地方我可以后面单开文章介绍和解释,讲我为什么要这样做以及这样做有啥好处。

除了代码之外,我还会教大家一些插件的使用,比如上面这个代码生成器,类似于这样的东西我还会很多,我会在后面的文章中慢慢贴出来,让大家学到一些真正好用和好玩的东西。并且从这一篇开始我会放出我的仓库地址,后续的代码会持续更新到这个仓库,由于Github很多人都没法访问,我就用国内的Gitee吧,地址如下:。

番外:百度热搜爬虫

1. 爬虫方案评估

百度热搜是这样的, 接口是:https://top.baidu.com/board?tab=realtime&sa=fyb_realtime_31065

可以看到数据还是很完备的,标题、封面、热度、摘要都有,但是百度热搜和抖音热搜不一样,这个接口返回的HTML网页不是JSON数据,所以我们需要使用处理HTML标签的包:jsoup,依赖如下:

  1. <!-- jsoup -->
  2. <dependency>
  3. <groupId>org.jsoup</groupId>
  4. <artifactId>jsoup</artifactId>
  5. <version>1.12.1</version>
  6. </dependency>

2. 网页解析代码

这个用Postman就不太好使了,我们直接用jsonp进行调用,不逼逼逻辑了,直接上代码,BaiduHotSearchJob:

  1. package com.summo.sbmy.job.baidu;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.Random;
  6. import java.util.UUID;
  7. import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
  8. import com.summo.sbmy.service.SbmyHotSearchService;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.jsoup.Jsoup;
  11. import org.jsoup.nodes.Document;
  12. import org.jsoup.select.Elements;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.scheduling.annotation.Scheduled;
  15. import org.springframework.stereotype.Component;
  16. import static com.summo.sbmy.common.enums.HotSearchEnum.BAIDU;
  17. /**
  18. * @author summo
  19. * @version BaiduHotSearchJob.java, 1.0.0
  20. * @description 百度热搜Java爬虫代码
  21. * @date 2024年08月19
  22. */
  23. @Component
  24. @Slf4j
  25. public class BaiduHotSearchJob {
  26. @Autowired
  27. private SbmyHotSearchService sbmyHotSearchService;
  28. /**
  29. * 定时触发爬虫方法,1个小时执行一次
  30. */
  31. @Scheduled(fixedRate = 1000 * 60 * 60)
  32. public void hotSearch() throws IOException {
  33. try {
  34. //获取百度热搜
  35. String url = "https://top.baidu.com/board?tab=realtime&sa=fyb_realtime_31065";
  36. List<SbmyHotSearchDO> sbmyHotSearchDOList = new ArrayList<>();
  37. Document doc = Jsoup.connect(url).get();
  38. //标题
  39. Elements titles = doc.select(".c-single-text-ellipsis");
  40. //图片
  41. Elements imgs = doc.select(".category-wrap_iQLoo .index_1Ew5p").next("img");
  42. //内容
  43. Elements contents = doc.select(".hot-desc_1m_jR.large_nSuFU");
  44. //推荐图
  45. Elements urls = doc.select(".category-wrap_iQLoo a.img-wrapper_29V76");
  46. //热搜指数
  47. Elements levels = doc.select(".hot-index_1Bl1a");
  48. for (int i = 0; i < levels.size(); i++) {
  49. SbmyHotSearchDO sbmyHotSearchDO = SbmyHotSearchDO.builder().hotSearchResource(BAIDU.getCode()).build();
  50. //设置文章标题
  51. sbmyHotSearchDO.setHotSearchTitle(titles.get(i).text().trim());
  52. //设置百度三方ID
  53. sbmyHotSearchDO.setHotSearchId(getHashId(BAIDU.getDesc() + sbmyHotSearchDO.getHotSearchTitle()));
  54. //设置文章封面
  55. sbmyHotSearchDO.setHotSearchCover(imgs.get(i).attr("src"));
  56. //设置文章摘要
  57. sbmyHotSearchDO.setHotSearchExcerpt(contents.get(i).text().replaceAll("查看更多>", ""));
  58. //设置文章连接
  59. sbmyHotSearchDO.setHotSearchUrl(urls.get(i).attr("href"));
  60. //设置热搜热度
  61. sbmyHotSearchDO.setHotSearchHeat(levels.get(i).text().trim());
  62. //按顺序排名
  63. sbmyHotSearchDO.setHotSearchOrder(i + 1);
  64. sbmyHotSearchDOList.add(sbmyHotSearchDO);
  65. }
  66. //数据持久化
  67. sbmyHotSearchService.saveCache2DB(sbmyHotSearchDOList);
  68. } catch (IOException e) {
  69. log.error("获取百度数据异常", e);
  70. }
  71. }
  72. /**
  73. * 根据文章标题获取一个唯一ID
  74. *
  75. * @param title 文章标题
  76. * @return 唯一ID
  77. */
  78. public static String getHashId(String title) {
  79. long seed = title.hashCode();
  80. Random rnd = new Random(seed);
  81. return new UUID(rnd.nextLong(), rnd.nextLong()).toString();
  82. }
  83. }

其实网页版的数据爬虫也不难,关键就看你能不能快速找到存数据的标签,然后通过选择器获取到标签的属性或内容,Jsoup框架在解析dom是很好用的,也常用在爬虫代码中。

原文链接:https://www.cnblogs.com/wlovet/p/18367044

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号