파이썬 550줄과 GitHub Actions 3개로 10년간 죽지 않은 무료 API 카탈로그. 코드보다 운영 정책이 더 중요하다는 사실을 증명한 레포.
왜 이런 레포가 43만 스타를 받았나.
GitHub에는 Awesome List라고 부르는 큐레이션 레포 장르가 있다. "Awesome React", "Awesome Python", "Awesome Self-hosted" 같이 어떤 주제에 대해 좋은 자료·라이브러리·서비스 링크를 README 하나에 모아놓은 형태다. 누구나 PR을 보내 항목을 추가할 수 있고, 사이트도 DB도 없이 그냥 마크다운 파일 한 장이 전부다.
이 장르에서 단연 1위가 public-apis/public-apis다. 회원가입·결제 없이 누구나 호출할 수 있는 "공개 API" 1,400개 가까이를 카테고리별로 묶어놨다. 그런데 43만 스타라는 숫자는 단순히 "리스트가 길어서"가 아니다. 10년 넘게 죽지 않고 살아남은 운영 시스템이 진짜 자산이다.
README.md 한 파일이 SSOT다. 백업·검색·diff·history는 git이 알아서 처리한다..github/workflows/*.yml에 YAML로 워크플로를 정의하면 push, PR, cron(스케줄) 등에 반응해 자동으로 코드를 실행해준다. public-apis 운영의 심장.한 문장으로 줄이면.
public-apis는 단순한 리스트가 아니라 운영 시스템이다. 핵심은 세 가지 — (1) README.md 한 파일이 DB·웹사이트·API 전부를 대신하고, (2) Python 550줄짜리 검증 스크립트가 PR마다 형식과 알파벳 순서를 강제하고, (3) GitHub Actions 크론잡이 매일 새벽 1,400개 링크가 살아있는지 확인한다.
"코드보다 운영 정책이 자산이다"라는 오픈소스 큐레이션의 본질을 가장 단단하게 증명한 사례다. 서버 비용 0원, 광고 0개, 회원가입 0번. 그런데 43만 스타.
2016년 첫 커밋 이후 10년, 어떻게 살아남았나.
비슷한 Awesome 리스트는 GitHub에 수천 개 있다. 대부분 첫 1~2년은 활발하다가 PR이 쌓이며 죽는다. 메인테이너 한두 명이 매일 들어오는 PR을 손으로 검토하다가 지쳐버리기 때문. public-apis가 살아남은 결정적 이유는 사람이 검토하기 전에 봇이 형식·중복·링크 작동 여부까지 다 검사한다는 점이다.
format.py)가 알파벳 순서·컬럼 개수·대소문자·설명 길이까지 체크| 비교 대상 | 형태 | 자동 검증 | 비고 |
|---|---|---|---|
| public-apis | GitHub README | O (형식 + 매일 링크) | 완전 무료, 광고 0 |
| RapidAPI Hub | 상용 플랫폼 | O | 유료 API 섞임, 회원가입 필수 |
| APIs.guru | OpenAPI 컬렉션 | X | 스펙 파일 위주, 진입장벽 높음 |
| 일반 Awesome Lists | GitHub README | X | 관리 안 됨, 죽은 링크 많음 |
이 레포는 마치 "무료 도서관의 통합 카드 목록" 같다. 수만 명의 자원봉사자(컨트리뷰터)가 새 도서(API)를 등록하면, 사서(GitHub Actions 봇)가 형식 규칙을 검사하고 매일 새벽 책장을 돌며 "이 책 아직 있나요?"(링크가 살아있나요?)를 자동으로 확인한다. 코드 한 줄 없이도 거대한 데이터베이스가 굴러간다.
코어 검증 도구 · CI/CD · 의존성.
놀랍게도 이 레포의 코드는 파이썬 ~550줄이 전부다. "API 리스트 레포"라기보다 "Awesome 리스트를 운영하는 최소 도구 모음"으로 봐야 한다.
requests 라이브러리, 동기 직렬 순회, Cloudflare 우회 휴리스틱 포함+)만 추출해 신규 링크만 검증 (CI 비용 절약)certifi==2021.10.8 # SSL 인증서 번들 charset-normalizer==2.0.10 # 인코딩 자동 감지 idna==3.3 # 국제화 도메인 처리 requests==2.27.1 # HTTP 클라이언트 (핵심) urllib3==1.26.8 # requests의 내부 의존
검증 스크립트의 동작이 라이브러리 업데이트로 깨지는 걸 막기 위해 모든 버전이 핀고정되어 있다. 하지만 그 대가로 보안 패치를 못 받는다. 2021년 11월 이후 4년 이상 동결된 셈. 자체 운영 환경에 똑같이 적용할 때는 Dependabot 같은 자동 PR 봇을 함께 돌리는 게 안전하다.
전체 흐름도 → 핵심 패턴 하나하나.
┌────────────────────────────────────────────────────────────────┐
│ 외부 컨트리뷰터 │
│ (전 세계) │
└───────────────┬────────────────────────────────────────────────┘
│ Fork → README 수정 → PR 생성
▼
┌────────────────────────────────────────────────────────────────┐
│ GitHub 저장소 (master) │
│ │
│ README.md (203 KB, 1,400+ APIs, 50+ 카테고리) │
│ │
└───────────────┬────────────────────────────────────────────────┘
│ PR 생성 → 자동 트리거
▼
┌────────────────────────────────────────────────────────────────┐
│ GitHub Actions Runner (Ubuntu) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ format.py │ │ links.py │ │ test_pkg.yml │ │
│ │ 형식 검사 │→ │ 추가 링크 검증 │→ │ unittest │ │
│ │ (정규식) │ │ (HTTP 요청) │ │ │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
│ │
└───────────────┬────────────────────────────────────────────────┘
│ 모두 통과 시
▼
┌────────────────────────────────────────────────────────────────┐
│ 사람 리뷰어 (메인테이너) │
│ 마케팅성 PR 차단, 분류 정정 등 │
└───────────────┬────────────────────────────────────────────────┘
│ 머지
▼
┌────────────────────────────────────────────────────────────────┐
│ 매일 00:00 UTC: 전체 링크 검사 │
│ (validate_links.yml — cron job) │
│ → 죽은 링크 발견 시 이슈로 자동 보고 │
└────────────────────────────────────────────────────────────────┘
대부분의 큐레이션 사이트는 PostgreSQL DB + 어드민 패널을 만든다. public-apis는 README.md 자체가 DB다. 백업·검색·diff·history 모두 git이 처리한다. 43만 스타라는 트래픽에도 서버 비용이 0원. 사용자는 그냥 GitHub에서 텍스트 검색(Ctrl+F)으로 원하는 API를 찾는다.
종이 명부 한 권만 두고 모든 회원이 직접 펜으로 적되, 펜을 들기 전에 "글자 크기·줄 띄움·알파벳 순서"를 자동 검사하는 사서가 있다고 생각해보자. public-apis는 정확히 이 방식으로 굴러간다. DB는 사치다.
매번 1,400개 링크를 다 확인하면 GitHub Actions의 호출 한도와 외부 사이트 부담이 둘 다 폭증한다. 그래서 PR 단계에서는 github_pull_request.sh가 diff에서 + 라인만 추출해서 신규 링크만 검사한다.
# scripts/github_pull_request.sh 핵심 로직 curl -L "$DIFF_URL" -o diff.txt # PR diff 다운로드 cat diff.txt | egrep "\+" > additions.txt # 추가 라인만 추출 python scripts/validate/links.py additions.txt # 새 링크만 검증
전체 검사는 매일 자정 cron으로 분리해 부하를 시간 축으로 분산했다. PR 응답은 빠르게, 전체 점검은 느리게 — 책임 분리가 깔끔하다.
format.py는 정규식 3개로 README 전체 구조를 파싱한다. Markdown 파서 라이브러리(markdown-it, mistune 등)를 쓰지 않는 이유는 "표가 정해진 형식인지" 자체가 검사 대상이라 관대한 파서로는 오류를 못 잡기 때문이다.
anchor_re = re.compile(anchor + r'\s(.+)') # ### 카테고리 category_title_in_index_re = re.compile(r'\*\s\[(.*)\]') # 인덱스의 [카테고리] link_re = re.compile(r'\[(.+)\]\((http.*)\)') # [TITLE](URL)
에러 메시지는 줄 번호를 L003 형식으로 정렬해 출력한다 — IDE에서 바로 점프할 수 있게.
링크 검사 시 일부 사이트가 봇이라며 차단한다. 그래서 links.py는 가짜 User-Agent 4개를 무작위 회전하고, 403/503 응답을 받으면 Cloudflare 보호 페이지인지 17가지 마커로 판별해 "정상"으로 처리한다.
# links.py의 has_cloudflare_protection() 일부
cloudflare_flags = [
'403 Forbidden', 'cloudflare', 'Security check',
'Checking your browser before accessing',
'DDoS protection by', 'Ray ID:', '_cf_chl', ...
]
if code in [403, 503] and server == 'cloudflare':
if any(flag in resp.text for flag in cloudflare_flags):
return True # → 정상으로 간주, 오류 안 띄움
이 휴리스틱은 완벽하지 않다. Cloudflare가 보호 페이지의 마크업·문구를 바꾸면 검사기가 거짓 양성/거짓 음성을 일으킨다. 실제 이슈 트래커에는 "왜 X 링크가 안 되는데 통과되었나요?" 보고가 종종 올라온다. 휴리스틱의 본질적 한계 — 외부 시스템 의존은 깨지기 마련.
public-apis는 자동 검사를 100% 신뢰하지 않는다. PR이 자동 검사를 통과해도 메인테이너가 카테고리·설명·홍보성 여부를 사람이 직접 본다. 자동화는 메인테이너의 검토 부담을 줄이는 도구일 뿐, 의사결정 주체는 여전히 사람이다.
어디에 뭐가 있는지.
public-apis/
├── README.md # 203KB, 모든 API 카탈로그 (이게 전부)
├── CONTRIBUTING.md # 기여 규칙 — 마케팅 PR 거부 명시
├── LICENSE # MIT
├── .gitignore
├── .gitattributes
│
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── assets/ # 로고 이미지
│ └── workflows/
│ ├── test_of_push_and_pull.yml # PR 검증
│ ├── test_of_validate_package.yml # 스크립트 자체 테스트
│ └── validate_links.yml # 매일 링크 검사 cron
│
└── scripts/
├── README.md # 검증 스크립트 사용법
├── requirements.txt # 5개 의존성 (버전 핀고정)
├── github_pull_request.sh # PR diff 추출 후 검증
├── validate/ # 검증 패키지
│ ├── __init__.py
│ ├── format.py # 형식 검증 (정규식 + 알파벳 정렬)
│ └── links.py # HTTP 요청으로 링크 검사
└── tests/ # 검증 스크립트의 단위 테스트
├── test_validate_format.py
└── test_validate_links.py
scripts 안에도 tests 디렉토리가 있다 — 도구 코드도 테스트하는 문화. __init__.py 명시로 Python 패키지화해서 테스트에서 임포트 가능. YAML 워크플로 3분리는 트리거가 다른 작업끼리 파일을 나누는 단일 책임 원칙(SRP)의 실전 적용이다.
기술 영역마다 무엇을 가져갈지.
re.compile 3개로 분류·인덱스·링크 추출List[str], Tuple[bool, List], 별칭(APIList = List[str])L003 형태로 정렬해 IDE에서 바로 점프실습: 자신만의 awesome 리스트를 만들고 카테고리당 최소 N개 강제 검증을 직접 구현
push, pull_request, schedule(cron)을 한 레포에서 모두 사용if: github.event_name == 'pull_request'로 트리거별 분기'0 0 * * *' = 매일 00:00 UTCenv: FILENAME: README.md로 워크플로 전체에 전파실습: 자신의 블로그 레포에 "매일 자정 죽은 외부 링크 검사" 액션 붙이기
DUMMY_SCHEME="https") 변수를 두어, 정규식 기반 URL 추출기가 변수명에 잡히지 않게 함실습: 본인 레포 PR에서 diff만 받아 변경 줄 수를 자동 댓글로 다는 액션 만들기
실습: asyncio + aiohttp로 병렬화하되 세마포어로 동시 20개 제한, 속도 비교
Add XYZ API 형식으로 자동화·검색 용이실습: 자신의 사이드 프로젝트 레포에 CONTRIBUTING.md + PR 템플릿 + 이슈 템플릿 3종 세트 도입
로컬에서 검증 돌릴 때의 최소 사양.
| 항목 | 요구사항 | 비고 |
|---|---|---|
| Python | 3.8 이상 | 워크플로 YAML에 명시 |
| OS | Linux / macOS / Windows | 스크립트는 OS 중립, 단 bash 스크립트는 Linux·Mac (Git Bash로 Windows에서도 가능) |
| 네트워크 | 외부 인터넷 접속 | 링크 검사 시 HTTP 요청 1,400회 |
| 메모리 | ~100MB | 매우 가벼움 |
| 시간 | 전체 검사 30분~수시간 | 1,400개 링크 직렬 처리 |
| GitHub Actions 비용 | 0원 (공개 레포) | 비공개 레포는 분 단위 한도 소진 주의 |
github_pull_request.sh는 bash 전용 — Windows에서는 Git Bash 또는 WSL에서 실행해야 한다. 또한 format.py가 마크다운을 읽을 때 CRLF/LF 차이로 정규식이 어긋날 수 있으니, git config core.autocrlf input으로 줄바꿈을 LF로 통일하는 것이 안전하다.
난이도별 실습 — Easy → Hard.
1) git clone https://github.com/public-apis/public-apis.git 2) cd public-apis 3) python -m pip install -r scripts/requirements.txt 4) 형식만 검사: python scripts/validate/format.py README.md 5) 중복 링크만: python scripts/validate/links.py README.md -odlc 6) 모든 링크 작동(수시간): python scripts/validate/links.py README.md
배우는 것: 검증 스크립트 옵션 구조, 단계별 검사 분리의 의미
README.md에서 임의의 한 줄을 골라 알파벳 순서를 뒤집거나, 설명을 100자보다 길게 만들고 format.py를 실행. 정확히 어떤 에러 메시지가 어떤 형식(L003 등)으로 나오는지 확인. 정규식이 어디서 매칭에 실패했는지 추적.
배우는 것: 정규식 디버깅, 에러 메시지 설계, IDE 점프 패턴
현재 links.py는 1,400개 링크를 직렬로 검사. asyncio + aiohttp 또는 concurrent.futures.ThreadPoolExecutor로 병렬화하고 시간을 측정. 주의: 너무 많은 동시 요청은 외부 서버 부담 + 봇 차단 트리거 — asyncio.Semaphore(20)으로 동시 20개 제한이 적절. Cloudflare 우회 휴리스틱도 유지해야 함.
배우는 것: 비동기 HTTP, 동시성 제한, 외부 호출 매너
관심 주제(예: "Awesome Korean Open-Source AI Models")로 README + 검증 스크립트 + GitHub Action을 갖춘 레포를 만들어보기. format.py를 포크해서 표 컬럼 구조만 자기 입맛에 맞게 변경. CONTRIBUTING.md + PR/Issue 템플릿까지 세팅.
배우는 것: 큐레이션 운영의 전체 사이클, 문서·자동화·정책의 결합
README.md를 파싱해 카테고리/Auth/HTTPS/CORS 조건으로 검색할 수 있는 FastAPI 기반 검색 서비스 구축. 매일 cron으로 README를 다시 받아 인덱스 갱신. SQLite 또는 Whoosh로 풀텍스트 인덱싱. 배포는 Vercel/Fly.io/Railway 무료 티어.
배우는 것: 마크다운 파서, 풀텍스트 검색, 정기 동기화 패턴, 무료 배포
매일 cron 결과를 받아 죽은 링크가 발견되면 자동으로 PR을 생성해 해당 줄을 제거하거나 대체 URL(Wayback Machine)을 제안하는 GitHub App 작성. 봇 인증(installation token), Rate Limit, idempotency(같은 링크 반복 PR 방지)까지 고려.
배우는 것: GitHub App 인증, Octokit, 자동 PR 워크플로, 봇 신뢰도 관리
주차별로 따라가면 운영 자동화 마스터.
format.py를 한 줄씩 읽으며 정규식 동작 시각화. regex101.com에 패턴을 붙여넣고 실제 README 라인이 어떻게 캡처되는지 확인. 타입 힌트(typing 모듈) 공식 문서 1독.
links.py의 예외 처리 6종(SSLError, ConnectionError, Timeout, TooManyRedirects, RequestException 등) 재현. User-Agent 위조, 헤더 조작, 리다이렉트 추적 이해. HTTP 4xx/5xx 분류 학습.
YAML 워크플로 문법, cron 표현식, secrets/env, matrix 빌드, 의존성 캐싱(actions/cache), reusable workflow까지. 자신의 사이드 프로젝트에 "야간 자동화 작업" 1개 도입.
CONTRIBUTING.md, ISSUE_TEMPLATE, PR_TEMPLATE 정독. "커뮤니티가 잘 굴러가게 만드는 문서·자동화"를 모방해 자신의 레포에 적용. CODE_OF_CONDUCT, SECURITY.md도 추가.
자주 마주칠 단어들.
-L 옵션으로 리다이렉트 추적. 거의 모든 Unix에 기본 탑재.0 0 * * * = 매일 자정. 5자리는 분/시/일/월/요일.+는 추가, -는 삭제. PR 검증의 핵심 입력.\d+는 "숫자 1개 이상", \[(.+)\]는 대괄호 안 캡처.레포 · 소스 · 학습 리소스.
git clone 후 python scripts/validate/format.py README.md로 정상 통과 확인. 모든 규칙이 한 번에 잡힌다는 사실을 눈으로 체험.format.py가 어떤 줄 번호·어떤 메시지로 어떻게 짚어주는지 관찰. 에러 메시지 설계의 좋은 예..github/workflows/의 YAML 3개를 열어 트리거(push/pull_request/schedule)와 실행 step을 줄 단위로 이해. 본인 사이드 프로젝트에 cron 액션 1개 적용.