Go 版本升级 | 统计 Github 社区 Go 版本分布情况

背景

因为最近三年用的 Go 版本是 1.16,但最新的版本升级到了 1.23,很多依赖的三方包最新文件都已经升级,使用了泛型以及 GO 新版本的特性,导致我只能适配 Go1.16 的三方包旧版本,但这种问题发生的频率多了后,自然就感觉到了麻烦和落后,所以打算升级 Go 版本。

但升级到哪个版本?我的考虑的重心有一点在于主流 Go 服务在用哪个版本。
而社区环境,最佳的选择当然是 Github。
所以,首先目标是统计 Github 上 Go 项目中使用的版本分布情况。

统计目标

Github 上,Go 项目数量有一百六十多万:
在这里插入图片描述
抓取全部仓库数据进行统计,有点不现实。
所以我根据 starts 数量分析,最后确定抓取 stars 数量大于 200 的仓库数据:
在这里插入图片描述
总共只有一万多一点,所以主流和有价值的仓库占比,确实很少,大部分都是私人仓库而已。

数据抓取方式

Github 有开放 API,通过在 GitHub 账号重心生成 Token,就可以调用。
在这里插入图片描述
在这里插入图片描述
API 包括仓库搜索、仓库详情信息获取等,详细可以查阅 官方 API 文档。

使用 GITHUB_TOKEN 鉴权,调用 API 存在部分限制,比如:

  1. GitHub Search API 每次搜索,分页每页最多 100,每次查询结果分页,超过 1000 个结果就会报错 422。
  2. GitHub API 的速率限制为 5000 次请求/小时

所以,在使用 API 时,结合统计目标的总数量,要考虑到上述两个限制。

数据抓取脚本

import requests
import base64
from collections import defaultdict
import time
import os
import csv
import configparser
from concurrent.futures import ThreadPoolExecutor, as_completed# 读取配置文件
config = configparser.ConfigParser()
config.read('config.ini')
GITHUB_TOKEN = config['GITHUB']['TOKEN']# 设置请求头,包含认证信息
headers = {}
if GITHUB_TOKEN:headers = {'Authorization': f'token {GITHUB_TOKEN}'}# 检查 API 速率限制
def check_rate_limit(headers):remaining = int(headers.get('X-RateLimit-Remaining', 0))reset_time = int(headers.get('X-RateLimit-Reset', time.time()))if remaining == 0:sleep_time = reset_time - time.time() + 1  # 等待到重置时间print(f"API 请求达到速率限制,等待 {sleep_time} 秒...")time.sleep(sleep_time)else:print(f"剩余请求次数: {remaining}")# 构建 GitHub API 查询 URL
def build_search_url(query_params, page=1, per_page=100):base_url = f"https://api.github.com/search/repositories"query = '+'.join(query_params)  # 将查询条件组装成字符串url = f"{base_url}?q={query}&sort=stars&order=desc&per_page={per_page}&page={page}"return url# 获取 Go 仓库信息,增加重试机制以避免数据丢失
def search_go_repos(query_params, page=1, per_page=100, max_retries=3):url = build_search_url(query_params, page=page, per_page=per_page)retries = 0while retries < max_retries:try:response = requests.get(url, headers=headers, timeout=10)  # 设置超时为 10 秒# 检查速率限制check_rate_limit(response.headers)if response.status_code == 200:return response.json()['items']else:print(f"API 请求失败,状态码: {response.status_code}")return []except requests.Timeout:retries += 1print(f"请求超时: {url},重试第 {retries} 次")time.sleep(2)  # 重试前等待 2 秒except requests.RequestException as e:print(f"请求发生错误: {e}")retries += 1time.sleep(2)  # 重试前等待 2 秒print(f"请求失败超过最大重试次数,跳过该页数据: {url}")return []# 获取仓库的 go.mod 文件内容,并提取 Go 版本号
def get_go_version(owner, repo):url = f"https://api.github.com/repos/{owner}/{repo}/contents/go.mod"response = requests.get(url, headers=headers)# 检查速率限制check_rate_limit(response.headers)if response.status_code == 200:content = response.json().get('content')if content:decoded_content = base64.b64decode(content).decode('utf-8')for line in decoded_content.splitlines():if line.startswith('go '):return line.split()[1]return None# 使用并发来加速处理仓库详细信息
def process_repos_with_concurrency(repos, version_counts, repos_data, max_workers=5):with ThreadPoolExecutor(max_workers=max_workers) as executor:future_to_repo = {executor.submit(get_go_version, repo['owner']['login'], repo['name']): repo for repo in repos}for future in as_completed(future_to_repo):repo = future_to_repo[future]owner = repo['owner']['login']repo_name = repo['name']repo_url = repo['html_url']stars = repo['stargazers_count']created_at = repo['created_at']updated_at = repo['pushed_at']try:version = future.result()if version:version_counts[version] += 1repos_data.append([repo_name, repo_url, stars, version, created_at, updated_at])else:repos_data.append([repo_name, repo_url, stars, '未检测到版本号', created_at, updated_at])except Exception as e:print(f"处理仓库 {repo_name} 时出错: {e}")# 统计 Go 版本分布并导出仓库信息到 CSV
def collect_and_save_repo_data(query_params, start_page=1, max_pages=1, per_page=10, max_workers=5):version_counts = defaultdict(int)repos_data = []all_repos = []  # 用于收集所有返回的仓库for page in range(start_page, start_page + max_pages):print(f"正在处理第 {page}/{start_page + max_pages - 1} 页的数据...")repos = search_go_repos(query_params, page, per_page)if not repos:breakall_repos.extend(repos)  # 将所有查询结果收集到一起# 并发处理仓库 go.mod 文件process_repos_with_concurrency(repos, version_counts, repos_data, max_workers)# 每处理一页数据就将数据写入 CSV 文件write_to_csv("go_repos_info.csv", repos_data)repos_data = []  # 清空 repos_data 以便处理下一页数据return version_counts, all_repos  # 返回版本统计和所有仓库信息# 判断文件是否存在,追加数据或创建新文件
def write_to_csv(filename, repos_data):file_exists = os.path.isfile(filename)  # 判断文件是否存在mode = 'a' if file_exists else 'w'  # 如果文件存在则以追加模式打开,否则以写模式创建with open(filename, mode=mode, newline='', encoding='utf-8') as file:writer = csv.writer(file)# 如果文件不存在,写入表头if not file_exists:writer.writerow(['仓库名', '仓库地址', 'Star 数量', 'Go 版本号', '创建时间', '最近更新时间'])# 写入仓库数据writer.writerows(repos_data)# 自动调整 star 范围并分页查询
def paginate_through_stars(start_star=131084, min_star=300, per_page=100, max_workers=5):current_star = start_starnext_star = Nonewhile current_star > min_star:query_params = [f'language:Go', f'stars:{min_star}..{current_star}']# 获取总仓库数和总页数total_repos, total_pages = get_total_repos_and_pages(query_params, per_page)print(f"正在查询 stars: <{current_star} 的范围,找到 {total_repos} 个仓库")if total_repos == 0:print(f"在 stars: <{current_star} 范围内没有找到仓库,程序终止。")break# 设置查询的最大页数max_pages = min(total_pages, 10)  # 一次最多查询 10 页version_counts, all_repos = collect_and_save_repo_data(query_params, start_page=1, max_pages=max_pages, per_page=per_page, max_workers=max_workers)# 获取所有仓库中最小的 star 数,确保正确的 star 数排序if all_repos:sorted_repos = sorted(all_repos, key=lambda repo: repo['stargazers_count'], reverse=False)next_star = sorted_repos[0]['stargazers_count'] - 1  # 获取最小 star 数,且减1。(stars:300..4175 查询的是 star 数 大于等于 300 且小于等于 4175 的仓库,所以要减1,避免重复)print(f"调整下一个查询的 star 范围为 stars: <{next_star}")current_star = next_star  # 更新查询范围else:print("无法找到下一个 star 范围,程序结束。")break# 输出统计结果
def print_version_stats(version_counts):print("\nGo 版本分布统计结果:")for version, count in sorted(version_counts.items(), key=lambda x: x[0]):print(f"Go 版本: {version}, 使用次数: {count}")# 获取总仓库数量和页数
def get_total_repos_and_pages(query_params, per_page=100):url = build_search_url(query_params, page=1, per_page=per_page)response = requests.get(url, headers=headers)# 检查速率限制check_rate_limit(response.headers)if response.status_code == 200:total_count = response.json()['total_count']total_pages = (total_count // per_page) + 1return total_count, total_pageselse:print(f"无法获取总仓库数量,状态码: {response.status_code}")return 0, 0# 获取当前 GITHUB TOKEN 的剩余次数和恢复时间
def get_rate_limit():url = "https://api.github.com/rate_limit"response = requests.get(url, headers=headers)if response.status_code == 200:data = response.json()remaining = data['rate']['remaining']reset_time = data['rate']['reset']  # 重置时间(UNIX 时间戳)reset_time_human = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(reset_time))  # 将重置时间转换为可读格式print(f"当前剩余请求次数: {remaining}")print(f"请求次数将在 {reset_time_human} 重置")# 返回剩余次数和重置时间,供其他地方使用return remaining, reset_time_humanelse:print(f"无法获取速率限制信息,状态码: {response.status_code}")return None, Noneif __name__ == "__main__":# 设置起始 Star 数和最小 Star 数# START_STAR = 141084   # 最大的 Star 数START_STAR = 475       # 最大的 Star 数MIN_STAR = 200          # 最小的 Star 数PER_PAGE = 100MAX_WORKERS = 10# 自动分页查询并处理数据paginate_through_stars(START_STAR, MIN_STAR, PER_PAGE, MAX_WORKERS)# 检查当前 TOKEN 状态# get_rate_limit()

这个脚本通过控制 stars 条件、分页 条件,查询所有 Go 仓库数据,获取 Go 仓库下 go.mod 中的版本定义。

并且加入了并发处理,保证查询速度,同时考虑到 Token 的限制,所以还加了一个 TOKEN 请求速度检查和等待机制。

直接在 python 环境下运行就可以,实践过的。

Go 分布统计结果

得到的数据表如下图所示:
在这里插入图片描述
对数据做了处理后,统计结果如下:
在这里插入图片描述
在这里插入图片描述
一目了然,目前社区中主流的版本为 1.22、1.21。

所以,我也打算把 Go 版本先升级到 1.22。

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

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

相关文章

分享几个办公类常用的AI工具

办公类 WPS AI讯飞智文iSlideProcessOn亿图脑图ChatPPT WPS AI 金山办公推出的协同办公 AI 应用&#xff0c;具有文本生成、多轮对话、润色改写等多种功能&#xff0c;可以辅助用户进行文档编辑、表格处理、演示文稿制作等办公操作。 https://ai.wps.cn/ 讯飞智文 科大讯飞推…

从零开始:AI制作PPT工具大比拼

现在真的万物皆可AI了&#xff0c;甚至是令人头疼的PPT&#xff0c;也可以直接用AI来搞定了。作为一个PPT新手&#xff0c;我最近对AI制作PPT这个话题产生了浓厚的兴趣。我决定亲自试一试市面上几款热门的AI制作PPT工具&#xff1a;笔灵AIPPT、轻竹PPT、博思白板AIPPT和KimiAI。…

了解Oracle表结构查询:获取列信息与注释

目录 1. 基本知识2. Demo3. 补充Mysql 1. 基本知识 Oracle数据库中&#xff0c;表结构信息包含列名、数据类型、长度、可空性、默认值以及字段注释等&#xff0c;这些信息对于理解数据库设计和维护非常重要 基本的属性要点如下&#xff1a; 表名&#xff08;TABLE_NAME&…

uniApp 加载google地图 并规划路线

uniApp 加载google地图 并规划路线 备注:核心代码实例 备注: 打开谷歌地图失败的话 参考google开发文档 https://developers.google.com/maps/documentation/urls/ios-urlscheme?hlzh-cn#swift核心代码 mounted() {this.loadGoogleMapsScript(); }, methods: {//加载loadGo…

LCD手机屏幕高精度贴合

LCD手机屏幕贴合&#xff0c;作为智能手机生产线上至关重要的一环&#xff0c;其质量直接关乎用户体验与产品竞争力。这一工艺不仅要求屏幕组件间的无缝对接&#xff0c;达到极致的视觉与触觉效果&#xff0c;还需确保在整个生产过程中&#xff0c;从材料准备到最终成品&#x…

不同类型的 LED 驱动电源在检测方法上有哪些不同?-纳米软件

1.传统 LED 驱动电源检测方法&#xff1a; 通常会提取 LED 驱动电源性能指标参数中较为重要的几个因子&#xff0c;如电压稳定性、电流波动范围等。利用诸如 k-means 聚类分析方法&#xff0c;实现对不同厂家、使用寿命不同的 LED 驱动电源快速有效的分类2。这种方法主要是通过…

海外媒体发稿:外媒宣发之《时代》杂志 TIME 的魅力

海外媒体发稿&#xff1a;外媒宣发之《时代》杂志 TIME 的魅力 海外媒体发稿&#xff1a;外媒宣发之《时代》杂志 TIME 在当今全球化的信息时代&#xff0c;媒体的影响力无远弗届。对于企业、组织和个人而言&#xff0c;能够在具有广泛影响力的世界媒体上发声&#xff0c;无疑…

面试中的一个基本问题:如何在数据库中存储密码?

面试中的一个基本问题&#xff1a;如何在数据库中存储密码&#xff1f; 在安全面试中&#xff0c;“如何在数据库中存储密码&#xff1f;”是一个基础问题&#xff0c;但反映了应聘者对安全最佳实践的理解。以下是安全存储密码的最佳实践概述。 了解风险 存储密码必须安全&am…

【Java小白图文教程】-05-数组和排序算法详解

精品专题&#xff1a; 01.《C语言从不挂科到高绩点》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12753294.html?spm1001.2014.3001.5482 02. 《SpringBoot详细教程》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12789841.html?spm1001.20…

Redis 发布订阅 总结

前言 相关系列 《Redis & 目录》&#xff08;持续更新&#xff09;《Redis & 发布订阅 & 源码》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;《Redis & 发布订阅 & 总结》&#xff08;学习总结/最新最准/持续更新&#xff09;《Redis &a…

《Python游戏编程入门》注-第2章2

《Python游戏编程入门》的“2.2.5 绘制线条”中提到了通过pygame库绘制线条的方法。 1 相关函数介绍 通过pygame.draw模块中的line()函数来绘制线条&#xff0c;该函数的格式如下所示。 line(surface, color, start_pos, end_pos, width1) -> Rect 其中&#xff0c;第一…

UnityShader——基础篇之学习Shader所需的数学基础——下

裁剪空间 顶点接下来要从观察空间转换到裁剪空间&#xff08;也被称为齐次裁剪空间&#xff09; 中&#xff0c;这个用于变换的矩阵叫做裁剪矩阵&#xff0c;也被称为投影矩阵 裁剪空间的目标是能够方便地对渲染图元进行裁剪&#xff1a;完全位于这块空间内部的图元将会被保留&…

[Redis] Redis数据持久化

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

【设计模式系列】装饰器模式

目录 一、什么是装饰器模式 二、装饰器模式中的角色 三、装饰器模式的典型应用场景 四、装饰器模式在BufferedReader中的应用 一、什么是装饰器模式 装饰器模式是一种结构型设计模式&#xff0c;用于在不修改对象自身的基础上&#xff0c;通过创建一个或多个装饰类来给对象…

数据结构与算法分析:你真的理解排序算法吗——计数排序(代码详解)

一、算法描述 一个会计师负责对一个小饭店的账本进行审核。每天晚上饭店打洋时&#xff0c;饭店主人记录白 天的总销售额&#xff0c;然后打印出有总额和日期的收据。这些收据存放在一个大盒子里面.每 年年终&#xff0c;会计师审核盒子中的这些收据&#xff0c;检查是否有的已…

Java.6--多态-设计模式-抽象父类-抽象方法

一、多态 1.定义--什么是多态&#xff1f; a.同一个父类的不同子类对象&#xff0c;在做同一行为的时候&#xff0c;有不同的表现形式&#xff0c;这就是多态。&#xff08;总结为&#xff1a;一个父类下的不同子类&#xff0c;同一行为&#xff0c;不同表现形式。&#xff0…

【力扣】GO解决子序列相关问题

文章目录 一、引言二、动态规划方法论深度提炼子序列问题的通用解法模式 三、通用方法论应用示例&#xff1a;最长递增子序列&#xff08;LeetCode题目300&#xff09;Go 语言代码实现 四、最长连续递增序列&#xff08;LeetCode题目674&#xff09;Go 语言代码实现 五、最长重…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括&#xff1a;1. **反编译**: 反编译是将已编译的.NET程序集&#xff08;如.dll或.exe文件&#xff09;转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…

手机淘宝自动下单退货自动化RPA脚本机器人

使用手机集线器连接多个手机并发运行。 脚本分3个部分&#xff08;读取本地连接下单&#xff0c;退货获取退货地址信息&#xff0c;填写快递单号&#xff09; 脚本部分图结构看下面的图片 部分数据统计展示

基于vue框架的的高校设备信息管理系统的设计与实现tx6d7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;设备管理员,设备维护员,设备类别,设备,设备入库,设备分发,设备调拨,定期维护,维护任务,设备运行记录 开题报告内容 基于Vue框架的高校设备信息管理系统的设计与实现开题报告 一、项目背景及意义 随着高校教育事业的蓬勃发展&#xff…