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;}}
因作者对前端不太熟练,只是实现了一些简单的数据处理逻辑,前端效果展示:
相关文章:
GitHub爬虫项目详解
前言 闲来无事浏览GitHub的时候,看到一个仓库,里边列举了Java的优秀开源项目列表,包括说明、仓库地址等,还是很具有学习意义的。但是大家也知道,国内访问GitHub的时候,经常存在访问超时的问题,…...
辅助驾驶功能开发-功能对标篇(7)-NOA领航辅助系统-上汽荣威
1.横向对标参数 厂商上汽荣威车型荣威RX5(燃油车)上市时间2022Q3方案10V3R摄像头前视摄像头1*(8M)侧视摄像头4后视摄像头1环视摄像头4DMS摄像头1雷达毫米波雷达34D毫米波雷达/超声波雷达12激光雷达/域控供应商1*(宏景智驾)辅助驾驶软件供应商地平线高精度地图中海庭芯片J3合作…...
第0次 序言
突然想起有好多书没有看,或者看了也没留下任何记录,以后有空必须得好好整理才行,这次就从《Linux命令行和shell脚本编程大全开始》 本文完全是闲聊,自娱自乐,我觉得做开发是一件很快乐的事情,但是工作是开发…...
ESP32设备驱动-OLED显示单个或多个DS18B20传感器数据
OLED显示单个或多个DS18B20传感器数据 文章目录 OLED显示单个或多个DS18B20传感器数据1、DS18B20介绍2、硬件准备3、软件准备4、代码实现4.1 读取单个DS18B20数据4.2 驱动多个DS18B20传感器4.3 OLED显示DS18B20数据在本文中,我们将介绍如何ESP32驱动单个或多个DS18B20传感器,…...
MongoDB快速上手
文章目录 1、mongodb相关概念1.1、业务应用场景1.2、MongoDB简介1.3、体系结构1.3.1 数据库 (databases) 管理语法1.3.2 集合 (collection) 管理语法 1.4、数据模型1.5、MongoDB的特点 2、单机部署3、基本常用命令3.1、案例需求3.2、数据库操作3.2.1 选择和创建数据库3.2.2 数据…...
maven 初学
1. maven 安装 配置安装 路径 maven 下载位置: D:\software\apache-maven-3.8.6 默认仓库位置: C:\Users\star-dream\.m2\repository 【已更改】 本地仓库设置为:D:\software\apache-maven-3.8.6\.m2\repository 镜像已更改为阿里云中央镜像仓库 <mirrors>…...
解决WPF+Avalonia在openKylin系统下默认字体问题
一、openKylin简介 openKylin(开放麒麟) 社区是在开源、自愿、平等和协作的基础上,由基础软硬件企业、非营利性组织、社团组织、高等院校、科研机构和个人开发者共同创立的一个开源社区,致力于通过开源、开放的社区合作ÿ…...
智能合约漏洞,Dyna 事件分析
智能合约漏洞,Dyna 事件分析 1. 漏洞简介 https://twitter.com/BlockSecTeam/status/1628319536117153794 https://twitter.com/BeosinAlert/status/1628301635834486784 2. 相关地址或交易 攻击交易 1: https://bscscan.com/tx/0x7fa89d869fd1b89e…...
Elasticsearch基础篇(四):Elasticsearch7.x的官方文档学习(Set up Elasticsearch)
Set up Elasticsearch 1 Configuring Elasticsearch(配置 Elasticsearch)1.1 Setting JVM Options(设置JVM选项)1.2 Secure Settings(安全设置)Introduction(介绍)Using the Keystore(使用密钥库)Applying Changes(应用更改)Reloadable Secure Settings(可重新加载的安全设置)R…...
二叉树的遍历方式和代码
二叉树的三种遍历和代码 1.前序遍历2.中序遍历3.后序遍历4.三种遍历方式的代码实现 1.前序遍历 学习二叉树结构,最简单的方式就是遍历。所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具…...
小样本学习——匹配网络
目录 匹配网络 (1)简单介绍: (2)专业术语 (3)主要思想 (4)训练过程 问题 回答 MANN 匹配网络 (1)简单介绍: Matching netwo…...
CSS 常用样式 之字体属性
font-weight(字体粗细) 字体粗细用于设置文本的粗细程度,可以使用如下的值: normal:正常字体(默认)bold:加粗字体bolder:更加加粗lighter:更加细 代码实例…...
nodejs+vue游戏测评交流系统elementui
可以实现首页、发布招募、公司资讯、我的等,另一方面来说也可以提高在游戏测评交流方面的效率给相关管理人员的工作带来一定的便利。在我的页面可以对游戏攻略、我的收藏管理、实际上如今信息化成为一个未来的趋势或者可以说在当前现代化的城市典范中,发布招募等功能…...
1.2.OpenCV技能树--第一单元--OpenCV安装
目录 1.文章内容来源 2.OpenCV安装 3.课后习题代码复现 4.易错点总结与反思 1.文章内容来源 1.题目来源:https://edu.csdn.net/skill/opencv/opencv-662dbd65c89d4ddb9e392f44ffe16e1a?category657 2.资料来源:https://edu.csdn.net/skill/opencv/opencv-662dbd65c89d4ddb…...
全志ARM926 Melis2.0系统的开发指引⑥
全志ARM926 Melis2.0系统的开发指引⑥ 编写目的9. 系统启动流程9.1. Shell 部分9.2.Orange 和 desktop 部分9.3. app_root 加载部分9.4. home 加载部分 10. 显示相关知识概述10.1. 总体结构10.2. 显示过程10.3. 显示宽高参数关系 -. 全志相关工具和资源-.1 全志固件镜像修改工具…...
Junit单元测试为什么不能有返回值?
这个问题的产生来源于我们老师上节课说的我们班一个男生问他的想法,刚开始听到这个还觉得挺有意思,我之前使用单元测试好像下意识的就将它的返回值写为void,一般都是进行简单的测试,也从没思考过在某个单元测试中调用另一个单元测试ÿ…...
【成像光敏描记图提取和处理】成像-光电容积描记-提取-脉搏率-估计(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Ubuntu无法引导启动的修复
TLDR:使用Boot-Repair工具。 Boot-Repair Boot-Repair是一个简单的工具,用于修复您在Ubuntu中可能遇到的常见启动问题,例如在安装Windows或其他Linux发行版后无法启动Ubuntu时,或者在安装Ubuntu后无法启动Windows时,…...
Windows电脑上的多开软件是否安全?
在Windows电脑上使用多开软件可以让使用者同时运行多个相同或不同的程序,这对于某些需要同时操作多个账号或实例的用户来说非常有用。但是很多人担心使用多开软件是否安全。 多开软件的安全问题主要在于它们可能会破坏操作系统的稳定性和安全性,导致系统…...
U盘支持启动区+文件存储区的分区方法
准备新U盘 启动diskgenius ,先建立一个主分区(7G),剩余空间建立为第二分区,然后设定第二分区激活。 diskgenius格式化 用diskgenius格式化,在格式化的过程中有一个 写入dos系统的选项,在格式…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
