GitHub爬虫项目详解

前言

闲来无事浏览GitHub的时候,看到一个仓库,里边列举了Java的优秀开源项目列表,包括说明、仓库地址等,还是很具有学习意义的。但是大家也知道,国内访问GitHub的时候,经常存在访问超时的问题,于是就有了这篇文章,每日自动把这些数据爬取下来,随时看到热点排行。
仓库地址:https://github.com/akullpp/awesome-java
仓库页面截图:在这里插入图片描述

分析

根据以往爬虫经验,先确定好思路,再开始开发代码效率会更高。那么,第一步,找一下我们的数据来源。
具体步骤:先开启F12,刷新网页,根据关键词搜索,看数据来源是哪个接口(此处以列表里的Maven为例,其他也可以)
在这里插入图片描述
可以看到,项目列表都是来源于这个.md文档的1250行,可以看到,这是一个标准的JSON数据,我们把这行数据复制出来进行分析(由于数据太长,不做展示),继续搜索后发现,我们需要的项目列表和说明,都在其中richText字段里,如下:
在这里插入图片描述
而这个富文本数据都是Unicode编码,为了方便查看结构,我们将其转为中文,可以用如下的正则匹配,批量转换

        richData = richData.replaceAll("/\\\\u([0-9a-f]{3,4})/i", "&#x\\1;");

转换完之后继续看这个富文本数据

在这里插入图片描述
我们需要的东西对应的是一个一个的<li>标签和<a>标签,找到数据源之后就可以正式开始开发了。

项目开发

1、准备工作

  • 开发框架选择SpringBoot,持久层框架使用MyBatis。除必要的基础依赖以外,还需要引入以下依赖:
    jsoup:对网页结构分析,解析数据
    okhttp:HTTP客户端,访问页面使用。
    fastjson:解析JSON数据
  • 关系型数据库选择Mysql,非关系型数据库选择Redis
  • 编辑配置文件
    在这里插入图片描述

2、项目列表解析代码开发

根据前期分析的思路,首先使用okhttp客户端,访问https://github.com/akullpp/awesome-java/blob/master/README.md页面,获取到响应正文。

    public String getPage(String url) {try {// 1.创建okhttp客户端对象OkHttpClient okHttpClient = new OkHttpClient();// 2.创建request对象 (用Request的静态类创建)Request request = new Request.Builder().url(url).build();// 3.创建一个Call对象,负责进行一次网络访问操作Call call = okHttpClient.newCall(request);// 4.发送请求到服务器,获取到response对象Response response = call.execute();// 5.判断响应是否成功if (!response.isSuccessful()) {System.out.println("请求失败!");return null;}return response.body().string();}catch (Exception e){log.error("请求页面出错:{}",e.getMessage());return null;}}

获取到正文后如图所示:
在这里插入图片描述

接着我们使用Jsoup对网页结构进行解析,因为需要的数据处于<Script>标签,因此我们只提取这个标签数据即可,代码为:

Document document = Jsoup.parse(html);// 2.使用 getElementsByTag,拿到所有的标签    elements相当于集合类。每个element对应一个标签
Elements elements = document.getElementsByTag("script");

提取之后效果如图:
在这里插入图片描述
需要的数据在列表最后一位,取到之后因其是HTML语法,我们需要将其处理转为标准JSON,然后根据第一步分析的结果,根据key提取richText所在的值,并将Unicode转为中文。

String li = elements.get(elements.size()-1).toString().replace("<script type=\"application/json\" data-target=\"react-app.embeddedData\">","").replace("</script>","");
JSONObject pageRes = JSONObject.parseObject(li);String richData = pageRes.getJSONObject("payload").getJSONObject("blob").getString("richText");
richData = richData.replaceAll("/\\\\u([0-9a-f]{3,4})/i", "&#x\\1;");

处理结果为:
在这里插入图片描述
转换完的字符串还是标准的HTML语法,继续用Jsoup解析结构,获取到所有的<li>标签和<a>标签

在这里插入图片描述
将需要的数据提取出来,再根据提取出来的数据继续爬取项目详情页,格式为:https://github.com/作者名/仓库名(因代码基本一致,此处不再赘述),获取项目对应的StartCount、forkCount、IssuesCount,转换为数据库实体对象并存储即可。

3、定时任务

编写定时任务代码,每天三点执行爬取任务,因为可能存在连接超时,因此增加五十次失败重试。执行结束后不管成功失败,微信推送执行结果

 private static String PageUrl = "https://github.com/akullpp/awesome-java/blob/master/README.md";//[秒] [分] [小时] [日] [月] [周]@Scheduled(cron = "0 0 3 * * ?")public void crawlerTaskFunction() throws InterruptedException {// 1.获取入口页面int count = 1;String html = crawlerService.getPage(PageUrl);if(html == null){//如果失败,重试五十次,间隔五秒for (int i = 0; i < 50; i++) {Thread.sleep(5000L);count++;log.error("抓取页面失败,正在第 {} 次重新尝试",i+1);html = crawlerService.getPage(PageUrl);if(html != null){break;}}if(html == null){log.error("抓取页面失败,正在发送失败消息!");JSONObject re = new JSONObject();re.put("本次重试次数:", 50);re.put("时间:", MyUtils.nowTime());//微信推送执行结果消息System.out.println(MyUtils.sendMsgNoUrl(re,MsgToken,"今日任务执行失败,请手动调用接口重新爬取!"));return;}}// 2.解析入口页面,获取项目列表List<ProjectDTO> projects = crawlerService.parseProjectList(html);//发送成功消息log.info("抓取页面完成,开始解析!");JSONObject re = new JSONObject();re.put("时间:", MyUtils.nowTime());re.put("本次重试次数:", count);re.put("本次项目总数:", projects.size());//微信推送执行结果消息System.out.println(MyUtils.sendMsgNoUrl(re,MsgToken,"任务执行成功,请去查看效果!"));if (CollectionUtils.isEmpty(projects)) {return;}// 3.遍历项目列表,利用线程池实现多线程// executorService提交任务:1)submit 有返回结果  2)execute 无返回结果// 此处使用submit是为了得知是否全部遍历结束,方便进行存到数据库操作ExecutorService executorService = Executors.newFixedThreadPool(10);  //固定大小10的线程池List<Future<?>> taskResults = new ArrayList<>();
//        for (int i = 0; i < 10; i++) {for (int i = 0; i < projects.size(); i++) {ProjectDTO project = projects.get(i);Future<?> taskResult = executorService.submit(new Runnable() {@Overridepublic void run() {try {System.out.println("crawling " + project.getName() + ".....");String repoName = getRepoName(project.getUrl());String jsonString = crawlerService.getRepo(repoName);// 解析项目数据parseRepoInfo(jsonString, project);System.out.println("crawling " + project.getName() + "done !");} catch (Exception e) {e.printStackTrace();}}});taskResults.add(taskResult);}// 等待所有任务执行结束,再进行下一步for (Future<?> taskResult : taskResults) {try {// 调用get会阻塞,直到该任务执行完毕,才会返回if (taskResult != null) taskResult.get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}//代码到这里,说明所有任务都执行结束,结束线程池executorService.shutdown();// 4.保存到数据库crawlerService.batchSave(projects);}

在这里插入图片描述

4、前端调用接口开发

对前端开放两个接口,一个为数据库数据的日期列表接口,一个根据日期查询当日数据接口,同时对参数进行非空验证

    @GetMapping("/list")public JSONObject verifySign(@RequestParam("time") String time) {JSONObject resp = new JSONObject();if(StringUtils.isEmpty(time) || time.equals("null")){resp.put("code",400);resp.put("data",null);resp.put("msg","time 参数错误!");return resp;}resp.put("code",200);resp.put("msg","请求成功");resp.put("data",crawlerService.getListByTime(time));return resp;}@GetMapping("/timeList")public JSONObject timeList() {JSONObject resp = new JSONObject();resp.put("code",200);resp.put("msg","请求成功");resp.put("data",crawlerService.timeList());return resp;}

在根据日期查询当日数据的接口中,因其每日的数据都是固定的,因此添加redis缓存,提高性能

        String redisKey = "crawler_"+time;boolean containsKey = redisUtils.containThisKey(redisKey);if(containsKey){String value = redisUtils.get(redisKey);return JSONObject.parseArray(value,ProjectDTO.class);}List<ProjectDTO> list = crawlerMapper.getListByTime(time);redisUtils.set(redisKey,JSONObject.toJSONString(list));return list;

其中redisUtils为自己写的Redis工具类,具体代码如下:

package com.simon.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;@Component
public class RedisUtils {@Autowiredpublic StringRedisTemplate redisTemplate;public String get(String key){if(StringUtils.isEmpty(key)){return null;}return redisTemplate.opsForValue().get(key);}public boolean set(String key,String value){if(StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){return false;}redisTemplate.opsForValue().set(key,value);return true;}public boolean setTimeOut(String key,String value,Long timeOut){if(StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){return false;}redisTemplate.opsForValue().set(key,value,timeOut, TimeUnit.SECONDS);return true;}public boolean delete(String key){if(StringUtils.isEmpty(key) ){return false;}Boolean isDelete = redisTemplate.delete(key);return isDelete != null ? isDelete : false;}public boolean containThisKey(String key){if(StringUtils.isEmpty(key) ){return false;}Boolean hasKey = redisTemplate.hasKey(key);return hasKey != null && hasKey;}}

因作者对前端不太熟练,只是实现了一些简单的数据处理逻辑,前端效果展示:在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/95998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸡群优化(CSO)算法(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

《Secure Analytics-Federated Learning and Secure Aggregation》论文阅读

背景 机器学习模型对数据的分析具有很大的优势&#xff0c;很多敏感数据分布在用户各自的终端。若大规模收集用户的敏感数据具有泄露的风险。 对于安全分析的一般背景就是认为有n方有敏感数据&#xff0c;并且不愿意分享他们的数据&#xff0c;但可以分享聚合计算后的结果。 联…

【算法练习Day13】二叉树的层序遍历翻转二叉树对称二叉树

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 二叉树的层序遍历翻转二叉树…

【二】spring boot-设计思想

spring boot-设计思想 简介&#xff1a;现在越来越多的人开始分析spring boot源码&#xff0c;拿到项目之后就有点无从下手了&#xff0c;这里介绍一下springboot源码的项目结构 一、项目结构 从上图可以看到&#xff0c;源码分为两个模块&#xff1a; spring-boot-project&a…

ipa文件怎么把应用上架到苹果ios系统下载的App Store商城

注册为苹果开发者&#xff1a;首先&#xff0c;您需要注册为苹果开发者。前往苹果开发者网站&#xff08;https://developer.apple.com/&#xff09;&#xff0c;点击"Enroll"按钮&#xff0c;并按照相关步骤注册和付费&#xff08;开发者账号需要年度费用&#xff0…

SpringCloud Alibaba - Seata 四种分布式事务解决方案(TCC、Saga)+ 实践部署(下)

目录 一、Seata 分布式解决方案 1.1、TCC 模式 1.1.1、TCC 模式理论 对比 TCC 和 AT 模式的一致性和隔离性 TC 的工作模型 1.2.2、TCC 模式优缺点 1.2.3、TCC 模式注意事项&#xff1a;空回滚 1.2.4、TCC 模式注意事项&#xff1a;业务悬挂 1.2.5、实现 TCC 模式 案例…

(六)正点原子STM32MP135移植——内核移植

目录 一、概述 二、编译官方代码 三、移植 四、编译 一、概述 前面已经移植好了TF-A、optee、u-boot&#xff0c;在u-boot能正常跑起来的情况下&#xff0c;现在来移植内核。 二、编译官方代码 进入kernel目录 2.1 解压源码、打补丁 /* 解压源码 */ tar xf linux-6.1.28.…

【Go语言实战】(25) 分布式算法 MapReduce

MapReduce 写在前面 身为大数据专业的学生&#xff0c;其实大学我也多多少少接触过mapreduce&#xff0c;但是当时觉得这玩意太老了&#xff0c;觉得这和php一样会被时代淘汰。只能说当时确实太年轻了&#xff0c;没有好好珍惜那时候的学习资源… 现在回过头来看mapreduce&a…

想做好接口测试,先把这些概念搞清楚了

接口一般来说有两种&#xff0c;一种是程序内部的接口&#xff0c;一种是系统对外的接口。 系统对外的接口 比如你要从别的网站或服务器上获取资源或信息&#xff0c;别人肯定不会把数据库共享给你&#xff0c;他只能给你提供一个他们写好的方法来获取数据&#xff0c;你引用…

【BBC新闻文章分类】使用 TF 2.0和 LSTM 的文本分类

一、说明 NLP上的许多创新是如何将上下文添加到词向量中。常见的方法之一是使用递归神经网络

数据结构之带头双向循环链表

目录 链表的分类 带头双向循环链表的实现 带头双向循环链表的结构 带头双向循环链表的结构示意图 空链表结构示意图 单结点链表结构示意图 多结点链表结构示意图 链表创建结点 双向链表初始化 销毁双向链表 打印双向链表 双向链表尾插 尾插函数测试 双向链表头插 …

如何选择合适的自动化测试工具?

自动化测试是高质量软件交付领域中最重要的实践之一。在今天的敏捷开发方法中&#xff0c;几乎任一软件开发过程都需要在开发阶段的某个时候进行自动化测试&#xff0c;以加速回归测试的工作。自动化测试工具可以帮助测试人员以及整个团队专注于自动化工具无法处理的各自任务&a…

【数据结构---排序】很详细的哦

本篇文章介绍数据结构中的几种排序哦~ 文章目录 前言一、排序是什么&#xff1f;二、排序的分类 1.直接插入排序2.希尔排序3.选择排序4.冒泡排序5.快速排序6.归并排序总结 前言 排序在我们的生活当中无处不在&#xff0c;当然&#xff0c;它在计算机程序当中也是一种很重要的操…

关掉在vscode使用copilot时的提示音

1. 按照图示的操作File --> Preferences --> Settings 2. 搜索框输入关键字Sound&#xff0c;因为是要关掉声音&#xff0c;所以找有关声音的设置 3. 找到如下图所示的选项 Audio Cues:Line Has Inline Suggetion,将其设置为Off 这样&#xff0c;就可以关掉suggest code时…

Elasticsearch:什么时候应该考虑在 Elasticsearch 中添加协调节点?

仅协调节点&#xff08;coordinating only nodes&#xff09;充当智能负载均衡器。 仅协调节点的这种特殊角色通过减轻数据和主节点的协调责任&#xff0c;为广泛的集群提供了优势。 加入集群后&#xff0c;这些节点与任何其他节点类似&#xff0c;都会获取完整的集群状态&…

毕业设计选题之Android基于移动端的线上订餐app外卖点餐安卓系统源码 调试 开题 lw

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

C# - Opencv应用(1) 之VS下环境配置详解

C# - Opencv应用&#xff08;1&#xff09; 之VS下环境配置详解 有时候&#xff0c;单纯c#做前端时会联合C实现的dll来落地某些功能由于有时候会用C - Opencv实现算法后封装成dll&#xff0c;但是有时候会感觉麻烦&#xff0c;不如直接通过C#直接调用Opencv在此慢慢总结下C# -…

SpringBoot vue云办公系统

SpringBoot vue云办公系统 系统功能 云办公系统 登录 员工资料管理: 搜索员工 添加编辑删除员工 导入导出excel 薪资管理: 工资账套管理 添加编辑删除工资账套 员工账套设置 系统管理: 基础信息设置 部门管理 职位管理 职称管理 权限组管理 操作员管理 开发环境和技术 开发语…

选择适合户外篷房企业的企业云盘解决方案

“户外篷房企业用什么企业云盘好&#xff1f;Zoho WorkDrive企业网盘可以帮助户外篷房企业实现文档统一管理、提高工作效率、加强团队协作&#xff0c;并且支持各种文件类型的预览和编辑。” S公司是一家注重管理规范的大型户外篷房企业&#xff0c;已经有10余年的经验。作为设…

string和const char*参数类型选择的合理性对比

在编程中&#xff0c;我们经常需要处理字符串类型的参数。在C中&#xff0c;有两种常见的表示字符串的参数类型&#xff0c;即string和const char*。本文将对比这两种参数类型的特点&#xff0c;分析其在不同情况下的合理性&#xff0c;以便程序员能够根据实际需求做出正确的选…