@daikikatsuragawa

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

GitHubのリポジトリの40,000件を超えるStarの付与履歴を全て取得したい

解決したいこと

GitHubの特定のリポジトリのStarの付与履歴を全て取得したいと考えています。そこで、GitHubのREST APIを使い、以下のドキュメントを参考に取得を試みました。

ドキュメントからは把握できず、挙動からの推測ではありますが、取得可能なStarの付与履歴は40,000件までのようです。そこで、40,000件を超えるStarの付与履歴を全て取得する方法について相談させていただきたいです。また、GitHubのREST APIによって、取得可能なStarの付与履歴が40,000件までという説明があれば、教えていただきたいです。

発生している問題・エラー

Starの付与履歴の取得スクリプト

import requests

# 対象リポジトリとREST API エンドポイント
# 参考:https://docs.github.com/ja/rest/activity/starring
owner = "pandas-dev"
repo = "pandas"
url = f"https://api.github.com/repos/{owner}/{repo}/stargazers"

# personal access token
# 参考:https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
token = "XXXXXXXXXX"

headers = {
    "Accept": "application/vnd.github.v3.star+json",
    "Authorization": f"token {token}",
}

params = {"per_page": 100, "page": 1}

stargazers = []

while True:
    response = requests.get(url, params=params, headers=headers)
    stargazers += response.json()

    # 次のページが存在する場合、パラメータを更新
    if "next" in response.links.keys():
        params["page"] += 1
    else:
        break

len(stargazers)
取得可能なStarの付与履歴の件数
40000

上記は以下のリポジトリについてのStarの付与履歴ですが、この件数に対して実際のStar数は42k(約42,000)件であり、不足していることが推測されます。

0 likes

2Answer

API仕様には特に上限についての記述は見受けられませんでした。

response.linksに"last"が含まれていた時に、そのpage番号が想定と合っているかを確認してみてはいかがでしょうか。
また、stargazer[]にjsonを追加する時、別途整数値も同時にカウントアップさせて、len()の結果と齟齬がないか突き合わせてみてはいかがでしょう。

1Like

Comments

  1. あと、40kよりはるかに大きいレポジトリに対してはどのような値になるでしょうか。

    こちらは150kほどあります。
    不足具合によっては、stargazers[]がパンクしている可能性も考えられますね。

  2. 立て続けにすみません。
    whileループで連続してリクエストしているので、レート制限に引っかかってる可能性もありますね。

  3. @iiokazuya
    コメントいただき、ありがとうございます🙇

    response.linksに"last"が含まれていた時に、そのpage番号が想定と合っているかを確認してみてはいかがでしょうか。

    "last"が含まれるresponse.linksの"last"の内容を確認すると以下となります。

    # 省略
    params = {"per_page": 100, "page": 1}
    response = requests.get(url, params=params, headers=headers)
    response.links["last"]
    
    {'url': 'https://api.github.com/repositories/858127/stargazers?per_page=100&page=400',
     'rel': 'last'}
    

    URLクエリパラメータが?per_page=100&page=400となっていて、そもそも取得可能なStarの付与履歴は40,000件(100×400)ということになりそうですね…やはりGitHubのREST APIの仕様(限界)のような気もしてきました。

    また、stargazer[]にjsonを追加する時、別途整数値も同時にカウントアップさせて、len()の結果と齟齬がないか突き合わせてみてはいかがでしょう。

    こちらは、一致するため、取得可能なStarの付与履歴は40,000件という状況下では齟齬のない結果となっています。

    # 省略
    stargazers = []
    count = 0
    
    while True:
        response = requests.get(url, params=params, headers=headers)
        stargazers += response.json()
        count =+ 1
    
        # 次のページが存在するか否かを確認
        if "next" in response.links.keys():
            params["page"] += 1
        else:
            break
    
    len(stargazers) == count * 100
    
    True
    

    あと、40kよりはるかに大きいレポジトリに対してはどのような値になるでしょうか。
    https://github.com/microsoft/vscode
    こちらは150kほどあります。
    不足具合によっては、stargazers[]がパンクしている可能性も考えられますね。

    こちらも同様に、取得可能なStarの付与履歴は40,000件でした。

    立て続けにすみません。
    whileループで連続してリクエストしているので、レート制限に引っかかってる可能性もありますね。

    タイミングを空けて再実行しても同じなので、これは問題なさそうです。

  4. なるほど、どうも40,000件というのが、何か定められたもののようですね。
    GitHubのデータベースをパンクさせないように、リポジトリごとに40,000件以前の履歴は捨ててる可能性もありますね。(件数だけは記憶している)

  5. なるほど、どうも40,000件というのが、何か定められたもののようですね。

    おそらくそうだと思っています。

    GitHubのデータベースをパンクさせないように、リポジトリごとに40,000件以前の履歴は捨ててる可能性もありますね。(件数だけは記憶している)

    参考までにですが、「4,0000件以降の履歴を取得できない」が正しそうです。正確には以下だけで断定はできないのですが、ここ取得できた履歴の最新の日付が半年ほど前のもので、この規模のリポジトリのStarが半年ほどつかない可能性は低そうで、最古〜4,0000件までの履歴を取得しているようです。

    # 省略
    max([stargazer["starred_at"] for stargazer in stargazers])
    
    2023-11-15T17:18:58Z
    

    参考までに、他のツール(Webアプリケーション)を確認してみたところ、この半年でもStarは追加されていそうです。

  6. (すみません、間違えて既存回答のコメントにしちゃいました。新規回答に移動します。お騒がせしました)

(1年以上前の質問なので、すでに解決済、あるいは不要になっているかもしれませんが…。1回目ミスったので再投稿でごめんなさい)

REST API ではなく、GraphQL を使うのはいかがでしょうか?
実際に 40,000 件以上の取得を試したわけではありませんが、以下のお試しページ(初回はGitHubのアカウントでサインインが必要。右上の Sign In から)で以下のようなクエリを投げてみました。

ちなみに first: 10 は指定された位置(今回は未指定なので、先頭)から10件の意味で、ソート順( orderBy )はスターを付けた日時( STARRED_AT )の昇順( ASC )です。

その後の項目は取得したい項目だけを記載しています(今回は starredAtnode.login 。また次ページ情報取得のための pageInfo )。必要な情報が他にもあれば、追記する必要があります。

query {
  repository(owner: "pandas-dev", name: "pandas") {
    stargazers(first: 10, orderBy: {field: STARRED_AT, direction: ASC}) {
      edges {
        starredAt
        node {
          login
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

結果(JSON)は以下のような感じ。

{
  "data": {
    "repository": {
      "stargazers": {
        "edges": [
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "sbusso"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "auser"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "pfig"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "ludwig"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "bryanveloso"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "eyecat"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "cosmin"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "mcroydon"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "Rickasaurus"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "babo"
            }
          }
        ],
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOnYyOpK5MjAxMC0wOC0yNFQxMDozNzozMyswOTowMM4AAvgA"
        }
      }
    }
  }
}

次ページの有無は hasNextPage でわかります。
次ページを取得するには endCursor の値を使って以下のように after に指定します。

query {
  repository(owner: "pandas-dev", name: "pandas") {
    stargazers(first: 10, after: "Y3Vyc29yOnYyOpK5MjAxMC0wOC0yNFQxMDozNzozMyswOTowMM4AAvgA", orderBy: {field: STARRED_AT, direction: ASC}) {
      edges {
        starredAt
        node {
          login
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

結果:

{
  "data": {
    "repository": {
      "stargazers": {
        "edges": [
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "ask"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "bmabey"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "abisani"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "jonpierce"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "mikong"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "cartazio"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "olifante"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "brunojm"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "jackscott"
            }
          },
          {
            "starredAt": "2010-08-24T01:37:33Z",
            "node": {
              "login": "mrflip"
            }
          }
        ],
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOnYyOpK5MjAxMC0wOC0yNFQxMDozNzozMyswOTowMM4ACDbB"
        }
      }
    }
  }
}

first に指定可能な値は 100 が上限なので、何ページも取得する必要があります。

ちなみに directionDESC を指定すれば、直近のスターを付けた情報も取得可能です。

query {
  repository(owner: "pandas-dev", name: "pandas") {
    stargazers(first: 10, orderBy: {field: STARRED_AT, direction: DESC}) {
      edges {
        starredAt
        node {
          login
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

結果:

{
  "data": {
    "repository": {
      "stargazers": {
        "edges": [
          {
            "starredAt": "2025-07-29T18:04:10Z",
            "node": {
              "login": "TommyLemon"
            }
          },
          {
            "starredAt": "2025-07-29T15:17:13Z",
            "node": {
              "login": "maeno14"
            }
          },
          {
            "starredAt": "2025-07-29T13:11:08Z",
            "node": {
              "login": "MadelineColbert"
            }
          },
          {
            "starredAt": "2025-07-29T12:51:45Z",
            "node": {
              "login": "nikasrmz"
            }
          },
          {
            "starredAt": "2025-07-29T11:38:53Z",
            "node": {
              "login": "ArinaChi"
            }
          },
          {
            "starredAt": "2025-07-29T08:16:57Z",
            "node": {
              "login": "Slav-VL"
            }
          },
          {
            "starredAt": "2025-07-29T07:41:28Z",
            "node": {
              "login": "BaptisteBlouin"
            }
          },
          {
            "starredAt": "2025-07-29T07:31:04Z",
            "node": {
              "login": "KGSbzv"
            }
          },
          {
            "starredAt": "2025-07-29T06:05:45Z",
            "node": {
              "login": "TaeKyungg2"
            }
          },
          {
            "starredAt": "2025-07-29T05:31:02Z",
            "node": {
              "login": "sooolji"
            }
          }
        ],
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOnYyOpK5MjAyNS0wNy0yOVQxNDozMTowMiswOTowMM4kRPRb"
        }
      }
    }
  }
}

最初にも言った通り、実際に 40,000 件以上を取得したわけではありませんが、最新の情報も取得できるので、期待はできるかと。

クエリやレスポンスの詳細は GitHub Docs を見るとわかると思います。

import requests

owner = "pandas-dev"
repo = "pandas"

token = "XXXXXXXXXXX"
query = """
query($owner: String!, $repo: String!, $after: String) {
  repository(owner: $owner, name: $repo) {
    stargazers(first: 10, after: $after, orderBy: { field: STARRED_AT, direction: ASC }) {
      edges { starredAt node { login } }
      pageInfo { hasNextPage endCursor }
    }
  }
}
"""
after = None
for i in range(3): # or while True:
    variables = {
        "owner": owner,
        "repo": repo,
        "after": after
    }
    response = requests.post(
        "https://api.github.com/graphql",
        headers={ "Authorization": f"bearer {token}" },
        json={ "query": query, "variables": variables }
    )
    data = response.json()
    # print(data)
    stargazers = data["data"]["repository"]["stargazers"]
    edges = stargazers["edges"]
    for edge in edges:
        print(edge["starredAt"], edge["node"]["login"])

    page_info = stargazers["pageInfo"]
    if not page_info["hasNextPage"]:
        break
    after = page_info["endCursor"]
0Like

Your answer might help someone💌