Divide and Conquer

[Github] 내 깃허브 레포 전부 크롤링하기 본문

2025/CICD

[Github] 내 깃허브 레포 전부 크롤링하기

10살 2022. 4. 28. 04:00
728x90

한이음으로 깃랩 알게됐는데 USB 없이 파일 공유할 수 있는 것도 편하고 이전 버전 확인할 수 있는 것도 편한데 뭔가 비밀이 많은 친구인 것 같다...

아무튼 깃허브에 내용이 너무 중구난방이라 이름도 정리하고!!! 내용도 정리하려고 한다

뭐랄까 템플릿 만들어서 규칙을 좀 정하려고... 공개/비공개인지 같이 한 사람이 있는지 뭐 이런 거 파악하려는데 레포가 너무 많고 어지러워서 코드를 한 번 짜보려고 한다

이번에 마모도 조사도 끝나는데 이것도 파이어베이스처럼 연동하거나 아니면 깃허브에 실시간으로 반영되게 하면 진짜 편할 듯


1. 설치

pip install requests
pip install pandas
pip install openpyxl

#pip install beautifulsoup4

 

2. 접근 권한 갱신

링크에 바로 들어갈 수 있으면 좋은데 안 될 수 있음 
참고로 링크 주소 https://github.com/settings/personal-access-tokens/3992309

못들어가면 그냥 세팅에 들어가서 처리해도 된다

  • Settings
  • Personal access tokens
  • Fine-grained personal access tokens
  • Regenerate token 눌러서 갱신하고
  • personal_info.py의 TEST_TOKEN 수정

 

3. 실행

python3 main.py

실행은 이렇게만 하면 되게끔 내가 코드를 짰다!!!
동작에 필요한 코드를 배포한다

이것도 깃허브로 공유하고 싶은데 내 개인정보가 들어가야 하는 코드가 있어서 비공개로 올려뒀다 
참고로 윈도우에서 동작하게끔 짜뒀다!!!해당 부분을 수정하면 된다


main.py

from personal_info import PersonalInfo 

github_id = PersonalInfo.ID
access_token = PersonalInfo.TEST_TOKEN

#from get_repo_list import get_github_repos # 레포 리스트 불러오기
from get_repo_list import get_github_repos_options
from save_excel import save_repos_to_excel

options = {
    'sort_by': 'updated_at',     # 정렬 기준: 'updated_at', 'name', 'language' 등
    #'order': 'desc',             # 정렬 방향: 'asc'(오름차순), 'desc'(내림차순)
    #'private': True,             # 공개/비공개 여부 필터링 
    #'archived': True,            # 보관 여부 필터링 
    #'fork': False,               # 포크 여부 필터링
    #'template': False,           # 템플릿 여부 필터링
    #'language': 'Python',        # 특정 언어 필터링 
    #'stars': 0,                  # 최소 별 수 기준 필터링
    
    # save_excel에서 옵션과 무관하게 저장하도록 내가 수정해 둠
    #'contributors': True,        # 기여자 수 확인 여부
    #'issues': True,              # 열린 이슈 수 확인 여부
    #'wiki': True,                # 위키 존재 여부
    #'projects': True             # 프로젝트 존재 여부
}

repos = get_github_repos_options(github_id, access_token, options)

print(f"\nTotal repositories owned by {github_id}: {len(repos)}")
for index, repo in enumerate(repos, 1):
    #print(f"{index}. {repo['name']} (⭐ {repo['stargazers_count']})")
    print(f"{index}. {repo['name']}")

save_repos_to_excel(repos, "github_repos.xlsx", options)
print("\nRepositories saved to 'github_repos.xlsx'")

personal_info.py

아래 부분을 수정하면 된다!
난 테스트 해본다고 뭐가 많은 거라 필요한 것만 추가해도 된다

class PersonalInfo:
    ID = "본인것"
    PW = "본인것"
    ACCESS_TOKEN = "본인것"
    TEST_TOKEN = "본인것"

get_repo_list.py

import requests

# 그냥 출력만 함
def get_github_repos(github_id, access_token):
    """
    사용자의 깃허브 레포지토리 목록을 최신 업데이트순으로 반환하는 함수

    Args:
        github_id (str): 깃허브 사용자 ID
        access_token (str): 깃허브 개인 액세스 토큰

    Returns:
        list: 최신 업데이트순으로 정렬된 레포지토리 목록
    """
    
    # API 요청 URL
    url = f'https://api.github.com/user/repos'

    # API 요청 헤더
    headers = {
        'Authorization': f'token {access_token}'
    }

    # 페이지네이션을 처리하여 모든 레포지토리를 불러오는 함수
    def fetch_all_repos(url, headers, github_id):
        repos = []
        page = 1
        while True:
            # 페이지 번호를 쿼리 파라미터로 추가 (한 번에 30개씩 가져옴)
            response = requests.get(url, headers=headers, params={'page': page, 'per_page': 30})
            if response.status_code != 200:
                print(f"Error: {response.status_code}, {response.text}")
                break

            data = response.json()
            if not data:
                break  # 더 이상 레포지토리가 없으면 종료
            
            # 본인 소유의 레포지토리만 필터링
            for repo in data:
                if repo['owner']['login'] == github_id:
                    repos.append(repo)
            page += 1

        return repos

    # 소유한 레포지토리 불러오기 (기본 순서 유지)
    repos = fetch_all_repos(url, headers, github_id)

    # 최신 업데이트순으로 정렬
    sorted_repos = sorted(repos, key=lambda repo: repo['updated_at'], reverse=True)
    
    # 레포지토리 이름 출력 및 전체 레포지토리 수 출력
    #print(f"\nTotal repositories owned by {github_id}: {len(sorted_repos)}")
    #for index, repo in enumerate(sorted_repos, 1):
    #    print(f"{index}. {repo['name']}")
    
    return sorted_repos

# 리스트에 대한 옵션 처리
def get_github_repos_options(github_id, access_token, options=None):
    """
    사용자의 깃허브 레포지토리 목록을 옵션에 따라 반환하는 함수.

    Args:
        github_id (str): 깃허브 사용자 ID
        access_token (str): 깃허브 개인 액세스 토큰
        options (dict, optional): 레포지토리 필터 및 정렬 옵션

    Returns:
        list: 정렬 및 필터링된 레포지토리 목록
    """
    
    url = 'https://api.github.com/user/repos'
    headers = {'Authorization': f'token {access_token}'}

    def fetch_all_repos(url, headers, github_id):
        repos = []
        page = 1
        while True:
            response = requests.get(url, headers=headers, params={'page': page, 'per_page': 30})
            if response.status_code != 200:
                raise Exception(f"Error: {response.status_code}, {response.text}")

            data = response.json()
            if not data:
                break

            # 본인 소유의 레포지토리만 필터링
            for repo in data:
                if repo['owner']['login'] == github_id:
                    repos.append(repo)
            page += 1

        return repos

    # 레포지토리 가져오기
    repos = fetch_all_repos(url, headers, github_id)

    # 옵션 처리
    if options:
        
        # 정렬 옵션
        if 'sort_by' in options:
            reverse = options.get('order', 'desc') == 'desc'
            repos = sorted(repos, key=lambda repo: repo[options['sort_by']], reverse=reverse)

        # 필터 옵션
        if 'private' in options:
            repos = [repo for repo in repos if repo.get('private') == options['private']]

        if 'archived' in options:
            repos = [repo for repo in repos if repo.get('archived') == options['archived']]

        if 'language' in options:
            repos = [repo for repo in repos if repo.get('language') == options['language']]

        if 'fork' in options:
            repos = [repo for repo in repos if repo.get('fork') == options['fork']]

        if 'template' in options:
            repos = [repo for repo in repos if repo.get('is_template') == options['template']]
        
        if 'stars' in options:
            repos = [repo for repo in repos if repo.get('stargazers_count', 0) >= options['stars']]
            
    return repos

save_excel.py

import requests
import pandas as pd
import os

from openpyxl import load_workbook
from openpyxl.worksheet.hyperlink import Hyperlink

# 엑셀로 저장
def save_repos_to_excel(repos, filename="github_repos.xlsx", options=None):
    """
    레포지토리 목록을 엑셀 파일로 저장하는 함수.

    Args:
        repos (list): 깃허브 레포지토리 목록
        filename (str): 저장할 엑셀 파일 이름 (기본값: github_repos.xlsx)
        options (dict, optional): 저장할 필드 옵션
    """
    
    # 경로가 지정되지 않았으면 바탕화면에 저장
    if not os.path.isabs(filename):
        desktop_path = os.path.join(os.path.expanduser("~"), 'Desktop')
        filename = os.path.join(desktop_path, filename)

    data = []
    for repo in repos:
        record = {
            'Name': repo['name'],
            'URL': repo['html_url'],
            
            #'Private': repo.get('private', False),
            #'Archived': repo.get('archived', False),
            'Private': '비공개' if repo.get('private', False) else '공개',
            'Archived': 'O' if repo.get('archived', False) else '',
            
            #'Fork': repo.get('fork', False),
            'Fork': 'O' if repo.get('fork', False) else '',
            #'Language': repo.get('language', 'Unknown'),
            #'Last Updated': repo['updated_at'],
            
            #'Stars': repo.get('stargazers_count', 0),
            #'Stars': repo.get('stargazers_count', 0) if repo.get('stargazers_count', 0) > 7 else '',  # Stars가 7보다 크면 표시, 아니면 공백
            'Stars': repo.get('stargazers_count', 0) if repo.get('stargazers_count', 0) > 0 else '',
            
            # 원래 아래 옵션으로 처리해야 하는데 내가 무조건 추가하게 작성해 둠
            #'Template': repo['is_template'],
            #'Template': 'O' if repo.get('is_template', False) else '',  # Template이 True인 경우 'O', 아니면 공백
            'Template': 'O' if repo['is_template'] else '',
            'Wiki': 'O' if repo.get('has_wiki', False) else '',  # Wiki가 True인 경우 'O', 아니면 공백
            'Projects': 'O' if repo.get('has_projects', False) else '',  
            
        }

        # 이 부분만 살려두는 게 맞아
        if options:
            if 'contributors' in options and options['contributors']:
                contributors_url = repo['contributors_url']
                contributors = requests.get(contributors_url).json()
                record['Contributors'] = len(contributors) if isinstance(contributors, list) else 0

            if 'issues' in options and options['issues']:
                record['Issues'] = repo.get('open_issues_count', 0)

            if 'wiki' in options and options['wiki']:
                record['Wiki'] = repo.get('has_wiki', False)

            if 'projects' in options and options['projects']:
                record['Projects'] = repo.get('has_projects', False)

        data.append(record)

    df = pd.DataFrame(data)
    df.to_excel(filename, index=False)
    
    
    # 엑셀 파일을 열어서 URL을 하이퍼링크로 변환
    wb = load_workbook(filename)
    ws = wb.active
    
    # URL 열을 하이퍼링크로 변환
    for row in range(2, len(repos) + 2):  # 첫 번째 행은 헤더이므로 2번째 행부터 시작
        url_cell = ws.cell(row=row, column=2)  # 'URL'은 두 번째 열에 해당
        url = url_cell.value
        if url:
            ws.cell(row=row, column=2).hyperlink = Hyperlink(url, url)
            ws.cell(row=row, column=2).style = 'Hyperlink'

    # 엑셀 파일 저장
    wb.save(filename)

 

반응형
Comments