Views: 189
GitHub issuesからテキストをダウンロードしてCSVファイル出力
Nobuyuki SAMBUICHI
ISO/TC295 Audit data services/SG1 Semantic model Convener
ISOでは、文書作成の共同作業を次の形式のコメントシートを用いて行っています。
MB/NC |
Line number |
Clause/ Subclause |
Paragraph/ Figure/ Table/ |
Type of comment |
Comments |
Proposed change |
Observations of the secretariat |
JISC Nobu |
877 |
6.7.2 |
ed |
“Directive 2014/55/EU [1]” reference document is not existed. |
Remove [1]. |
||
このシートにコメントを記入して共有し、会議で討議した結果を右側の決定事項に記入して記録を残すという手順です。
関連する資料は、ISOの文書共有サイトにアップロードして参加者が確認できるようにしてありますが、この方法は前世紀的な手順なので、GitHubのProjectを作成文書ごとに作成して、討議内容をissueとして共有する方式としました。
国によっては、GitHubへのアクセスができないところもあるので、issueの経過を共有するためにCSVファイルとしてダウンロードして、毎週回覧するという運用で会議を進めることとしました。
この記事は、GitHubからissueのテキストと関連情報をダウンロードしてコメントシートをCSVファイルに出力するまでを紹介するものです。
作業は、Windows 10とMac OSで実行しています。備忘録です。
1. 前提環境の整備と確認
1.1. GitHubからIssueのテキストを取得する方法
GitHubからIssueのテキストを取得するには、GitHub REST APIを利用することができます。以下に基本的な手順を示します:
-
認証:プライベートリポジトリにアクセスするか、ユーザーの代わりにアクションを実行する場合は、リクエストを認証する必要があります。先に述べたように、個人アクセストークンを使用するか、必要に応じてOAuthを使用して認証することができます。
筆者注:個人アクセストークンを使用しました。その手順は後述。
-
GETリクエストを使用してIssueを取得:GitHub APIのGET /repos/{owner}/{repo}/issuesエンドポイントを使用して、特定のリポジトリからIssueを取得します。{owner}を所有者のユーザー名または組織名に、{repo}をリポジトリ名に置き換えます。このAPI呼び出しでは、ステート(オープン、クローズ)、ラベルなどのさまざまなパラメータを指定してIssueをフィルタリングすることができます。
例のリクエスト:
GET /repos/{owner}/{repo}/issues
-
Issue情報の抽出:API呼び出しのレスポンスを解析し、JSON形式のIssueリストが含まれるでしょう。各Issueオブジェクトには、Issue番号、タイトル、説明、コメントなどの情報が含まれます。
-
Issueのテキストの処理:各Issueオブジェクトから必要な情報(タイトル、説明、コメントなど)を抽出します。このデータを必要に応じて適切に処理し、ファイルにエクスポートしたり、テキストの解析を行ったりすることができます。
注意:各Issueについてより詳細な情報が必要な場合は、GET /repos/{owner}/{repo}/issues/{issue_number}エンドポイントに個別のGETリクエストを行うこともできます。{issue_number}は取得したい特定のIssue番号です。
例のリクエスト:
GET /repos/{owner}/{repo}/issues/{issue_number}
-
リポジトリに大量のIssueがある場合は、ページネーションに対応する必要があります。GitHub APIは、レスポンスヘッダー(Linkヘッダー)でページネーションリンクを提供しており、必要に応じて複数のリクエストで全てのIssueを取得するのに役立ちます。
これらの手順に従うことで、GitHub REST APIを使用してGitHubからIssueのテキストを取得することができます。
1.2. APIキーを指定する方法
GitHub APIへのリクエストでAPIキーを指定するには、HTTPリクエストのヘッダーにそれを含める必要があります。使用する認証メソッドによって、使用する特定のヘッダーが異なります。
以下は、「Bearer Token」認証メソッドを使用してAPIキーを指定する方法です。「Authorization」ヘッダーを使用します:
-
個人アクセストークンを生成する:まだ個人アクセストークンを生成していない場合は、前述の手順に従って生成してください。トークンには必要なリソースへのアクセス権限があることを確認してください。
-
「Authorization」ヘッダーを構築する:HTTPリクエストのヘッダーに、「Authorization」ヘッダーを追加します。「Bearer YOUR_TOKEN_HERE」という値を指定します。”YOUR_TOKEN_HERE”は生成した個人アクセストークンに置き換えてください。
例のヘッダー:
Authorization: Bearer YOUR_TOKEN_HERE
-
APIリクエストを行う:構築したヘッダーをAPIリクエストに含めて、リクエストを認証および承認します。GitHub APIはトークンを検証し、必要な権限がある場合にアクセスを許可します。
注意:ヘッダーを含める方法は、使用しているプログラミング言語やHTTPクライアントライブラリによって異なります。具体的な構文や方法については、ドキュメントや使用しているAPIクライアントライブラリの参照先を確認してください。
「Authorization」ヘッダーにAPIキーを指定することで、GitHub APIへのリクエストを認証し、目的のリソースにアクセスすることができます。APIキーを安全に保管し、権限のない個人と共有しないように注意してください。
1.3. 認証の手順についてさらに詳細を提供してください
もちろんです!GitHub APIを介してリポジトリのIssueにアクセスし、認証するために必要な権限を持つ個人アクセストークンを生成するための、さらに詳細な手順を以下に示します:
-
GitHubアカウントにサインイン:GitHubのウェブサイト(https://github.com)にアクセスし、資格情報を使用してサインインします。
-
アカウント設定にアクセス:サインインしたら、ページの右上隅にあるプロフィール画像をクリックし、ドロップダウンメニューから「Settings」を選択します。

-
開発者設定に移動:設定ページの左側のサイドバーで、スクロールして「Developer settings」をクリックします。

-
新しい個人アクセストークンを生成:開発者設定ページで、利用可能なオプションから「Personal access tokens」をクリックします。

-
新しいトークンを作成:個人アクセストークンページで、「Generate new token」ボタンをクリックします。
-
必要な情報を入力:「Note」フィールドにトークンに説明的な名前を付けてください。後で目的を簡単に特定できるようにします。また、必要に応じてトークンに追加のスコープや権限を追加することもできます。Issueへのアクセスには通常、「repo」スコープが必要です。
-
トークンを生成:必要な情報を入力した後、ページの一番下にある「Generate token」ボタンをクリックします。
-
生成されたトークンを保存:ボタンをクリックすると、GitHubが個人アクセストークンを生成します。このトークンをコピーして安全な場所に保存してください。トークンはもう一度表示されないため、注意して保存してください。このトークンはGitHubアカウントへのアクセス権限を提供するため、注意して扱い、権限のない個人と共有しないようにしてください。
-
トークンを使用して認証する:リポジトリのIssueにアクセスするための次のAPIリクエストでは、生成したトークンを「Authorization」ヘッダーとして含めます。ヘッダーの値は、”Bearer YOUR_TOKEN_HERE”の形式で指定します。
これらの手順に従うことで、GitHub APIを介してリポジトリのIssueにアクセスするために必要な権限を持つ個人アクセストークンを生成することができます。トークンを安全に保管し、許可された目的のためにのみ使用してください。
筆者注:この手順で作成したトークンの有効期限は、1か月程度です。
1.4. Postmanを使用してGETリクエストを送信する方法
Postmanを使用してGETリクエストを送信するには、次の手順に従います:
-
Postmanを開く:コンピューター上でPostmanアプリケーションを起動します。Postmanがインストールされていない場合は、公式のPostmanウェブサイト(https://www.postman.com/)からダウンロードしてインストールします。
-
新しいリクエストを作成する:Postmanウィンドウの左上隅にある「New」ボタンをクリックして新しいリクエストを作成します。
-
リクエストの詳細を指定する:新しく作成したリクエストタブで、リクエストURLフィールドにAPIエンドポイントのURLを入力します。たとえば、GitHubリポジトリからIssueを取得するためのGETリクエストを送信する場合、URLは次のようになります:https://api.github.com/repos/{owner}/{repo}/issues。{owner}にはリポジトリの所有者のユーザー名または組織名を、{repo}にはリポジトリ名を置き換えます。
-
HTTPメソッドを選択する:リクエストタブで、リクエストURLフィールドの横にあるドロップダウンメニューから「GET」を選択します。
-
ヘッダーを追加する(オプション):APIが特定のヘッダーを要求する場合は、リクエストURLフィールドの下にある「Headers」タブをクリックしてヘッダーを追加できます。ヘッダーキーと値を指定するために、「Add row」ボタンをクリックします。
-
リクエストを送信する:リクエストURLフィールドの横にある「Send」ボタンをクリックして、GETリクエストを送信します。
*レスポンスを表示する:リクエストが送信されると、Postmanは画面下部にレスポンスを表示します。レスポンスボディ、ヘッダー、その他の詳細を表示することができます。
以上です!Postmanを使用してGETリクエストを送信することに成功しました。Postmanは、ヘッダーやパラメーター、認証など、リクエストのさまざまな側面をカスタマイズしてテストすることができる多目的なツールです。APIのテストや開発において非常に強力なツールです。
2. Pythonプログラム
PostmanでGETをテストしてAPIでテキストが抽出できたので、プログラム作成に着手。
ChatGPTに質問しながら開発しました。ページネーションに対応するところとProjectのstatusの取得に工夫が必要でした。Projectのstatusは、API経由ではうまくいかず、画面からメニューでエキスポートしました。
作成したプログラムを最後に記載しましたので参考にしてください。以降のChatGPTとのQ&Aは、問題を指摘しながらその都度修正を加えていった経過を記録しています。コーディングの仕方はよく知っているのですが、人のプログラマなら当然このくらい常識で配慮するだろうという機能でも明確な指示がないと書いてくれないので、途中からChatGPTには完成形を期待するのではなく、問題対応のコーディングを確認しながらこちらの手作業で仕上げることにしています。
2.1. CSVファイルでissueリストを出力するPythonプログラムを
issueに対するresponceも含めて
issue のリストは 61件.
APIの実行にはBearerトークンが必要
日本語でコメントを追加してください。
以下は、GitHub APIを使用して、61件のissueリストをCSVファイルに出力するPythonプログラムの例です。Bearerトークンを使用してAPIリクエストを実行するため、適切なトークンをプログラムに追加してください。
import requests
import csv
# Bearerトークンを指定してAPIリクエストを送信する関数
def send_api_request(url, headers):
response = requests.get(url, headers=headers)
return response.json()
# CSVファイルにissueリストを出力する関数
def export_issues_to_csv(issues):
with open('issues.csv', 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Issue Number', 'Title', 'Description', 'Response'])
for issue in issues:
issue_number = issue['number']
title = issue['title']
description = issue['body']
response = issue['comments_url'] # responseはコメントのURLとして使用します
writer.writerow([issue_number, title, description, response])
# Bearerトークンを指定してAPIリクエストを送信するためのヘッダー
headers = {
'Authorization': 'Bearer YOUR_TOKEN_HERE',
'Accept': 'application/vnd.github.v3+json'
}
# APIリクエストで取得するissueのURL
url = 'https://api.github.com/repos/{owner}/{repo}/issues'
# APIリクエストを送信してissueリストを取得
response = send_api_request(url, headers)
issues = response
# issueリストをCSVファイルに出力
export_issues_to_csv(issues)
# 出力完了メッセージ
print('CSVファイルへの出力が完了しました。')
上記のプログラムを使用する際には、次の手順を実行してください:
-
Bearerトークン: YOUR_TOKEN_HERE の部分をGitHubのBearerトークンに置き換えてください。
-
APIのURL: url の {owner} をリポジトリの所有者のユーザー名や組織名に、{repo} をリポジトリ名に置き換えてください。
必要に応じて、プログラムに日本語のコメントを追加してください。
プログラムを実行すると、issueのリストが issues.csv という名前のCSVファイルに出力されます。CSVファイルには、issue番号、タイトル、説明、およびレスポンス(コメントのURL)の情報が含まれます。
2.2. issueに対するresponceも含めてください
issue のリストは 61件.
以下は、GitHub APIを使用して、61件のissueリストとそれに対するresponse(コメント)を含めて、CSVファイルに出力するPythonプログラムの例です。Bearerトークンを使用してAPIリクエストを実行するため、適切なトークンをプログラムに追加してください。
import requests
import csv
# Bearerトークンを指定してAPIリクエストを送信する関数
def send_api_request(url, headers):
response = requests.get(url, headers=headers)
return response.json()
# issueのresponse(コメント)を取得する関数
def get_issue_response(url, headers):
response = send_api_request(url, headers)
comments = response
return comments
# CSVファイルにissueリストとresponseを出力する関数
def export_issues_with_response_to_csv(issues):
with open('issues_with_response.csv', 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Issue Number', 'Title', 'Description', 'Response'])
for issue in issues:
issue_number = issue['number']
title = issue['title']
description = issue['body']
response_url = issue['comments_url'] # responseはコメントのURLとして使用します
response = get_issue_response(response_url, headers)
writer.writerow([issue_number, title, description, response])
# Bearerトークンを指定してAPIリクエストを送信するためのヘッダー
headers = {
'Authorization': 'Bearer YOUR_TOKEN_HERE',
'Accept': 'application/vnd.github.v3+json'
}
# APIリクエストで取得するissueのURL
url = 'https://api.github.com/repos/{owner}/{repo}/issues'
# APIリクエストを送信してissueリストを取得
response = send_api_request(url, headers)
issues = response
# issueリストとresponseをCSVファイルに出力
export_issues_with_response_to_csv(issues)
# 出力完了メッセージ
print('CSVファイルへの出力が完了しました。')
上記のプログラムを使用する際には、次の手順を実行してください:
-
Bearerトークン: YOUR_TOKEN_HERE の部分をGitHubのBearerトークンに置き換えてください。
-
APIのURL: url の {owner} をリポジトリの所有者のユーザー名や組織名に、{repo} をリポジトリ名に置き換えてください。
必要に応じて、プログラムに日本語のコメントを追加してください。
プログラムを実行すると、issueのリストとそれに対するresponse(コメント)が issues_with_response.csv という名前のCSVファイルに出力されます。CSVファイルには、issue番号、タイトル、説明、およびresponse(コメント)の情報が含まれます。
2.3. url = ‘https://api.github.com/repos/{owner}/{repo}/issues’ は間違い
owner = ‘somename’
repo = ‘somerepo’
url =f ‘https://api.github.com/repos/{owner}/{repo}/issues’
tokenについても同様に
token = ‘sometoken’
f’Bearer {token}’
としてください。
なぜ次の指定があるのでしょうか
‘Accept’: ‘application/vnd.github.v3+json’
申し訳ありません、先ほどの回答においてご指摘の間違いがあります。以下に修正版のプログラムを示します。
import requests
import csv
# Bearerトークンを指定してAPIリクエストを送信する関数
def send_api_request(url, headers):
response = requests.get(url, headers=headers)
return response.json()
# issueのresponse(コメント)を取得する関数
def get_issue_response(url, headers):
response = send_api_request(url, headers)
comments = response
return comments
# CSVファイルにissueリストとresponseを出力する関数
def export_issues_with_response_to_csv(issues):
with open('issues_with_response.csv', 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Issue Number', 'Title', 'Description', 'Response'])
for issue in issues:
issue_number = issue['number']
title = issue['title']
description = issue['body']
response_url = issue['comments_url'] # responseはコメントのURLとして使用します
response = get_issue_response(response_url, headers)
writer.writerow([issue_number, title, description, response])
# Bearerトークンを指定してAPIリクエストを送信するためのヘッダー
owner = 'somename'
repo = 'somerepo'
url = f'https://api.github.com/repos/{owner}/{repo}/issues'
token = 'sometoken'
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github.v3+json'
}
# APIリクエストを送信してissueリストを取得
response = send_api_request(url, headers)
issues = response
# issueリストとresponseをCSVファイルに出力
export_issues_with_response_to_csv(issues)
# 出力完了メッセージ
print('CSVファイルへの出力が完了しました。')
修正点:
* url の指定を修正し、f文字列を使用して正しいURLを生成します。
* token を指定する箇所を修正し、f文字列を使用して正しいトークンを指定します。
* Accept ヘッダーは、GitHub APIのバージョンを指定するために使用されます。application/vnd.github.v3+json は、APIバージョン3を指定するためのヘッダーです。
修正後のプログラムは、指定されたリポジトリのissueリストとそれに対するresponse(コメント)を含むCSVファイルに出力します。
2.4. issuesが30件しか取得できません。61件です。
申し訳ありません、GitHub APIの制限により、一度のリクエストで取得できるissueの件数には制限があります。デフォルトでは、APIが一度のリクエストで最大30件のissueを返します。
61件のissueを取得するためには、ページネーションを使用して複数のAPIリクエストを行い、全てのissueを取得する必要があります。
以下に修正版のプログラムを示します。この修正版では、ページネーションを使用して全てのissueを取得し、CSVファイルに出力します。
import requests
import csv
# Bearerトークンを指定してAPIリクエストを送信する関数
def send_api_request(url, headers, params=None):
response = requests.get(url, headers=headers, params=params)
return response.json()
# issueのresponse(コメント)を取得する関数
def get_issue_response(url, headers):
response = send_api_request(url, headers)
comments = response
return comments
# CSVファイルにissueリストとresponseを出力する関数
def export_issues_with_response_to_csv(issues):
with open('issues_with_response.csv', 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Issue Number', 'Title', 'Description', 'Response'])
for issue in issues:
issue_number = issue['number']
title = issue['title']
description = issue['body']
response_url = issue['comments_url'] # responseはコメントのURLとして使用します
response = get_issue_response(response_url, headers)
writer.writerow([issue_number, title, description, response])
# Bearerトークンを指定してAPIリクエストを送信するためのヘッダー
owner = 'somename'
repo = 'somerepo'
url = f'https://api.github.com/repos/{owner}/{repo}/issues'
token = 'sometoken'
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github.v3+json'
}
# APIリクエストを送信して全てのissueを取得
issues = []
page = 1
per_page = 30
while True:
params = {'page': page, 'per_page': per_page}
response = send_api_request(url, headers, params)
if len(response) == 0:
break
issues.extend(response)
page += 1
# issueリストとresponseをCSVファイルに出力
export_issues_with_response_to_csv(issues)
# 出力完了メッセージ
print('CSVファイルへの出力が完了しました。')
修正後のプログラムでは、ページネーションを使用して全てのissueを取得します。リクエストごとに30件ずつ取得し、取得したissueをリストに追加していきます。取得できるissueがなくなるまでこの処理を繰り返し、最終的なissueリストをCSVファイルに出力します。
3. 使用中のプログラム
TSVファイルで指定した次のファイルは、画面からエキスポートしたTSVファイルです。
status_file1 = ‘Issues/SG1 Report Part 1 – View 1.tsv’
status_file2 = ‘Issues/SG1 Report Part 2 – View 1.tsv’

import requests
import csv
import re
import json
userDict = {
'GitHub ID1':'担当者名1',
'GitHub ID2':'担当者名2'
}
# Projectデータ初期化
status_data = []
status_dict = {}
project_data = []
project_dict = {}
# TSVファイル指定
status_file1 = 'Issues/SG1 Report Part 1 - View 1.tsv'
status_file2 = 'Issues/SG1 Report Part 2 - View 1.tsv'
# Bearerトークンを指定してAPIリクエストを送信するためのヘッダー
owner = "所有者"
repo = "レポジトリー"
url = f'https://api.github.com/repos/{owner}/{repo}/issues'
token = "トークンキー"
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github.v3+json'
}
# Bearerトークンを指定してAPIリクエストを送信する関数
def send_api_request(url, headers, params=None):
response = requests.get(url, headers=headers, params=params)
return response.json()
# APIリクエストで取得したresponseで指定されたurlのデータを取得する関数
def get_response_data(url, headers):
response = send_api_request(url, headers)
return response
def export_issues_to_csv(issues):
with open(csv_file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Issue Number", "#", "Status", "MB", "Clause", "Comment", "Title", "Body", "User", "URL"])
for issue in issues:
issue_number = issue["number"]
title = issue["title"]
body = issue["body"]
url = issue["html_url"]
userid = issue["user"]["login"]
user = userDict[userid]
if "labels" in issue:
comment_type = ''
member_body = 'None'
clause = ''
for label in issue["labels"]:
name = label["name"]
description = label["description"]
if name in ['te', 'ed', 'ge']:
comment_type = name
elif name.startswith("MB"):
member_body = name[2:]
else:
clause = f'{name} / {description}'
project_status = ''
if url in status_dict:
project_status = status_dict[url]
# CSVファイルにissueデータ出力
i = 0
writer.writerow([issue_number, i, project_status, member_body, clause, comment_type, title, body, user, url])
if 'comments_url' in issue:
comments_url = issue['comments_url'] # comments_urlはコメントのURLとして使用します
comments = get_response_data(comments_url, headers)
for comment in comments:
body = comment["body"]
if body:
url = comment["html_url"]
userid = comment["user"]["login"]
user = userDict[userid]
# CSVファイルにcommentsデータ出力
i += 1
writer.writerow([issue_number, i, '', '', '', '', '', body, user, url])
# APIリクエストを送信して全てのissueを取得
issues = []
page = 1
per_page = 30
while True:
params = {'page': page, 'per_page': per_page}
response = send_api_request(url, headers, params)
if len(response) == 0:
break
issues.extend(response)
page += 1
# TSVファイル読み込み
print("TSVファイル読み込み",status_file1)
with open(status_file1, 'r', encoding="utf-8-sig") as file:
reader = csv.reader(file, delimiter='\t')
row_headers = next(reader) # Skip the header row
for row in reader:
for i in range(len(row)):
row[i].replace('\t',' ')
status_data.append(dict(zip(row_headers, row)))
print("TSVファイル読み込み",status_file2)
with open(status_file2, 'r', encoding="utf-8-sig") as file:
reader = csv.reader(file, delimiter='\t')
row_headers = next(reader) # Skip the header row
for row in reader:
for i in range(len(row)):
row[i].replace('\t',' ')
status_data.append(dict(zip(row_headers, row)))
for data in status_data:
url = data['URL']
status_dict[url] = data['Status']
# CSVファイルにissueリストとresponse、プロジェクトデータ、担当者を出力する関数
csv_file_path = "Issues/issues_with_responses.csv"
print("CSVファイル出力",csv_file_path)
# issueリストとresponse、プロジェクトデータ、担当者をCSVファイルに出力
export_issues_to_csv(issues)
# 出力完了メッセージ
print('CSVファイルへの出力が完了しました。',csv_file_path)