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

news2024/10/26 10:44:38

背景

因为最近三年用的 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 = 0
    while 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 += 1
            print(f"请求超时: {url},重试第 {retries} 次")
            time.sleep(2)  # 重试前等待 2 秒
        except requests.RequestException as e:
            print(f"请求发生错误: {e}")
            retries += 1
            time.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] += 1
                    repos_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:
            break

        all_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_star
    next_star = None

    while 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) + 1
        return total_count, total_pages
    else:
        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_human
    else:
        print(f"无法获取速率限制信息,状态码: {response.status_code}")
        return None, None

if __name__ == "__main__":
    # 设置起始 Star 数和最小 Star 数
    # START_STAR = 141084   # 最大的 Star 数
    START_STAR = 475       # 最大的 Star 数
    MIN_STAR = 200          # 最小的 Star 数
    PER_PAGE = 100
    MAX_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.coloradmin.cn/o/2223929.html

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

相关文章

解决运行jar错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序

报错 众所周知jdk8以上都没有Javafx java -jar target/myyscan-1.0-SNAPSHOT.jar 错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序解决 https://gluonhq.com/products/javafx/ 去下载/javafx/到本地&#xff0c;选择自己的型号 然后记得指定路径 java --m…

React类组件详解

React类组件是通过创建class继承React.Component来创建的&#xff0c;是React中用于构建用户界面的重要部分。以下是对React类组件的详细解释&#xff1a; 一、定义与基本结构 类组件使用ES6的class语法定义&#xff0c;并继承自React.Component。它们具有更复杂的功能&#xf…

R语言机器学习算法实战系列(十二)线性判别分析分类算法 (Linear Discriminant Analysis)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍LDA的原理LDA的步骤教程下载数据加载R包导入数据数据预处理数据描述数据切割构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve保存模型总结优点:缺…

《性能之巅:洞悉系统、企业与云计算》-观测工具-笔记

《性能之巅&#xff1a;洞悉系统、企业与云计算》第一章&#xff08;绪论&#xff09;和第二章&#xff08;方法&#xff09;的笔记&#xff0c;请参考Part 1&#xff0c;第三章&#xff08;操作系统&#xff09;的笔记&#xff0c;请参考Part 2&#xff0c;本文是第四章——观…

鸿蒙开发融云demo发送文本消息

鸿蒙开发融云demo发送文本消息 融云鸿蒙版是不带UI的&#xff0c;得自己一步步搭建。 这次说如何发送文本消息&#xff0c;并且显示文本消息 一、思路 发送用&#xff1a;IMEngine.getInstance().sendMessage 显示文本&#xff1a; Text(ImUtils.dealMyTextContent(this.ms…

Linux:磁盘深潜:探索文件系统、连接之道与库的奥秘

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 磁盘 1.1 磁盘的物理结构 1.2 磁盘的存储结构 1.3 磁盘的逻辑结构 ​编辑二 文件系统 2.1 简单了解 2.2 inode 2.3 文件描述符&#xff0c;进程与文件系统的关系 ​编辑2…

格姗知识圈博客网站开源了!

格姗知识圈博客 一个基于 Spring Boot、Spring Security、Vue3、Element Plus 的前后端分离的博客网站&#xff01;本项目基本上是小格子一个人开发&#xff0c;由于工作和个人能力原因&#xff0c;部分技术都是边学习边开发&#xff0c;特别是前端&#xff08;工作中是后端开…

模型选择拟合

1.通过多项式拟合交互探索概念 import math import numpy as np import torch from torch import nn from d2l import torch as d2l 2.使用三阶多项式来生成训练和测试数据的标签 max_degree 20 # 多项式的最大阶数 n_train, n_test 100, 100 # 训练和测试数据集大小 true…

SQL Server 当前日期及其未来三天的日期

当前日期及其未来三天的日期&#xff0c;并分别以 YYYY-MM-DD 和 yyyyMMdd 的格式展示 1、当前日期及其未来三天的日期&#xff0c;以 YYYY-MM-DD的格式展示 WITH CurrentDate AS (SELECT GETDATE() AS 当前日期 ) -- 使用 CONVERT 函数 SELECTCONVERT(VARCHAR(10), 当前日期,…

【论文笔记】MLSLT: Towards Multilingual Sign Language Translation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: MLSLT: Towards Multiling…

springboot医疗物品采购系统-计算机设计毕业源码10210

摘 要 本文基于Spring Boot框架&#xff0c;设计并实现了一个医疗物品采购系统。该系统旨在解决医疗物品采购中的管理和信息化问题&#xff0c;提供便捷的服务和支持。通过系统的设计与实现&#xff0c;实现了医疗物品的供应商家管理、物品类型管理、物品仓库管理、采购计划管…

小渡Ai论文写作:文献综述黑科技LitMaps30s找到所有文献

✨Litmaps 究竟是什么呢&#xff1f; Litmaps 是一款极为强大的文献搜索与追踪工具&#xff01; 它不但能够助力你找到相关的研究文献&#xff0c;还能够依据你提供的文献生成一张学术地图&#xff0c;清晰地展现文献之间的引用链以及研究发展趋势。 恰似一张璀璨的星空图&a…

Linux中安装配置SQLite3,并实现C语言与SQLite3的交互。

前言 SQLite 是一个软件库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是在世界上最广泛部署的 SQL 数据库引擎。本次实验介绍在Linux上实现C语言和SQLite3的交互&#xff0c;利用C语言编写相关语句&#xff0c;连接数据库、操作数…

python实战(三)——文本向量化/文本表示

一、概念 文本向量化是自然语言处理领域的重要环节&#xff0c;也是现在大语言模型开发重要基础。计算机程序无法理解文字信息&#xff08;实际上非数值类型的信息都无法理解&#xff09;&#xff0c;因此我们需要将文字信息转换成计算机程序可理解的数值类型。通俗来说就是我们…

Python 精品学习资料收藏下载,Python 字符串处理备忘单

重点在这&#xff1a; &#xff08;请移步图中作者处&#xff0c;获取高清大图下载链接&#xff09; Python 中的字符串&#xff08;String&#xff09;是一种用于表示文本的数据类型。字符串是不可变的&#xff0c;这意味着一旦创建&#xff0c;你不能改变字符串中的字符&am…

【学习AI-相关路程-mnist手写数字分类-python-硬件:jetson orin NX-自我学习AI-基础知识铺垫-遇到问题(1) 】

【学习AI-相关路程-mnist手写数字分类-python-硬件&#xff1a;jetson orin NX-自我学习AI-基础知识铺垫-遇到问题&#xff08;1&#xff09; 】 1、前言2、先行了解&#xff08;1&#xff09;学习基础知识-了解jetson orin nx 设备&#xff08;2&#xff09;学习python&AI…

ClickHouse在百度MEG数据中台的落地和优化

导读 百度MEG上一代大数据产品存在平台分散、质量不均和易用性差等问题&#xff0c;导致开发效率低下、学习成本高&#xff0c;业务需求响应迟缓。为了解决这些问题&#xff0c;百度MEG内部开发了图灵3.0生态系统&#xff0c;包括Turing Data Engine(TDE)计算引擎、Turing Dat…

从0到1,搭建vue3项目

一 Vite创建Vue3项目 1.1.创建Vue3项目 1.1.1.运行创建项目命令 # 使用 npm npm create vitelatest 1.1.2、填写项目名称 1.1.3、选择前端框架 1.1.4、选择语法类型 1.1.5、按提示运行代码 1.1.6浏览器问 localhost:5173 预览 1.2项目结构 1.2.1vite.config.ts 1.2.2 pac…

基于线性回归(Linear Regression)的房屋价格预测

基于线性回归&#xff08;Linear Regression&#xff09;的房屋价格预测 一元线性回归加载数据假设函数损失函数&#xff08;代价函数&#xff09;梯度下降函数完整代码 多变量线性回归加载数据集特征缩放假设函数损失函数梯度下降函数算法步骤完整代码 线性回归是统计学中的一…

【二轮征稿启动】第三届环境工程与可持续能源国际会议持续收录优质稿件

第三届环境工程与与可持续能源国际会议&#xff08;EESE 2024&#xff09;由中南林业科技大学主办&#xff0c;湖南农业大学协办&#xff0c;将于2024年12月20日-22日在湖南长沙召开。 大会邀请到国家杰出青年科学基金获得者、华中科技大学能源与动力工程学院冯光教授&#xf…