포스트

[Python 100일 챌린지] Day 51 - requests 라이브러리 기초

[Python 100일 챌린지] Day 51 - requests 라이브러리 기초

Phase 6 시작! 🎉 requests.get('https://api.github.com').json() → Python 딕셔너리로 변환! 😊

날씨 API, 주식 API, 뉴스 API… 웹에서 데이터 가져오기는 이제 필수 기술! 3줄 코드로 전 세계 데이터를 내 프로그램으로 가져옵니다!

(40-50분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: HTTP와 API의 개념 이해하기

1.1 HTTP란?

💡 HTTP(HyperText Transfer Protocol)는 웹에서 데이터를 주고받는 약속입니다. 브라우저에서 웹사이트를 열면 HTTP로 서버에 “이 페이지 주세요”라고 요청하고, 서버가 HTML을 보내주는 것입니다.

HTTP 요청/응답 흐름:

1
2
3
4
5
[내 프로그램] --요청--> [서버]
     "날씨 정보 주세요"

[내 프로그램] <--응답-- [서버]
     "서울: 23°C, 맑음"

1.2 API란?

💡 API(Application Programming Interface)는 프로그램끼리 데이터를 주고받는 창구입니다. 날씨 앱이 기상청 서버에서 날씨를 가져오는 것, 카카오톡이 서버에서 메시지를 가져오는 것 모두 API를 통해 이루어집니다.

API 예시:

  • 날씨 API: “서울 날씨 알려줘” → {"temp": 23, "weather": "맑음"}
  • GitHub API: “torvalds 정보 알려줘” → {"name": "Linus Torvalds", "repos": 7}
  • 환율 API: “달러-원 환율 알려줘” → {"rate": 1350.5}

1.3 HTTP 메서드

메서드 용도 예시
GET 데이터 조회 날씨 조회, 사용자 정보 조회
POST 데이터 생성/전송 로그인, 글 작성
PUT 데이터 수정 프로필 수정
DELETE 데이터 삭제 게시글 삭제

💡 오늘은 가장 많이 사용하는 GET과 POST를 집중적으로 배웁니다!

1.4 HTTP 상태 코드

서버가 요청을 처리한 결과를 숫자로 알려줍니다:

코드 의미 설명
200 OK 성공! 요청이 정상 처리됨
201 Created 새로운 데이터가 생성됨
400 Bad Request 요청이 잘못됨 (내 실수)
401 Unauthorized 인증 필요 (로그인 필요)
404 Not Found 요청한 데이터가 없음
500 Server Error 서버 오류 (서버 문제)

💡 외우기 팁: 2xx = 성공, 4xx = 내 실수, 5xx = 서버 문제


🎯 학습 목표 2: requests 라이브러리로 GET 요청 보내기

2.1 requests 설치

1
pip install requests

💡 왜 requests를 사용할까요? Python 기본 라이브러리 urllib도 있지만, requests가 훨씬 간단하고 직관적입니다. 전 세계 Python 개발자가 가장 많이 사용하는 HTTP 라이브러리입니다.

2.2 첫 번째 GET 요청

1
2
3
4
5
6
7
8
9
10
11
import requests

# GitHub API에 GET 요청
response = requests.get('https://api.github.com')

# 상태 코드 확인
print(f"상태 코드: {response.status_code}")  # 200

# 응답 본문 (JSON → Python dict)
data = response.json()
print(f"현재 사용자 URL: {data['current_user_url']}")

출력:

1
2
상태 코드: 200
현재 사용자 URL: https://api.github.com/user

2.3 쿼리 파라미터 사용하기

💡 쿼리 파라미터는 URL 뒤에 ?key=value 형태로 추가 정보를 전달하는 방법입니다. 검색어, 페이지 번호 등을 전달할 때 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

# 방법 1: URL에 직접 작성 (비추천)
response = requests.get('https://httpbin.org/get?name=Alice&age=25')

# 방법 2: params 사용 (권장!)
params = {
    'name': 'Alice',
    'age': 25,
    'city': '서울'  # 한글도 자동으로 인코딩됨
}
response = requests.get('https://httpbin.org/get', params=params)

# 실제 요청된 URL 확인
print(f"요청 URL: {response.url}")
# https://httpbin.org/get?name=Alice&age=25&city=%EC%84%9C%EC%9A%B8

# 응답 확인
data = response.json()
print(data['args'])  # {'name': 'Alice', 'age': '25', 'city': '서울'}

2.4 실전: GitHub 사용자 정보 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests

def get_github_user(username):
    """GitHub 사용자 정보 가져오기"""
    url = f'https://api.github.com/users/{username}'

    response = requests.get(url)

    if response.status_code == 200:
        user = response.json()
        return {
            'login': user['login'],
            'name': user.get('name', '이름 없음'),
            'bio': user.get('bio', '소개 없음'),
            'public_repos': user['public_repos'],
            'followers': user['followers']
        }
    elif response.status_code == 404:
        print(f"'{username}' 사용자를 찾을 수 없습니다.")
        return None
    else:
        print(f"❌ 오류 발생: {response.status_code}")
        return None


# 사용 예시
user = get_github_user('torvalds')  # 리눅스 창시자
if user:
    print(f"👤 {user['name']} (@{user['login']})")
    print(f"📝 {user['bio']}")
    print(f"📦 공개 저장소: {user['public_repos']}")
    print(f"👥 팔로워: {user['followers']}")

출력:

1
2
3
4
👤 Linus Torvalds (@torvalds)
📝 None
📦 공개 저장소: 7개
👥 팔로워: 200000명

🎯 학습 목표 3: POST 요청과 데이터 전송하기

3.1 JSON 데이터 전송

💡 POST 요청은 서버에 데이터를 보낼 때 사용합니다. 로그인, 회원가입, 글 작성 등에 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

# 보낼 데이터
data = {
    'name': 'Alice',
    'email': 'alice@example.com',
    'age': 25
}

# POST 요청 (json= 파라미터 사용)
response = requests.post(
    'https://httpbin.org/post',
    json=data  # 자동으로 JSON 변환 + Content-Type 헤더 설정
)

print(f"상태 코드: {response.status_code}")

# 서버가 받은 데이터 확인
result = response.json()
print(f"서버가 받은 데이터: {result['json']}")

출력:

1
2
상태 코드: 200
서버가 받은 데이터: {'name': 'Alice', 'email': 'alice@example.com', 'age': 25}

3.2 폼 데이터 전송

💡 폼 데이터는 웹 브라우저에서 로그인 폼을 제출하는 것과 같은 형식입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

# 폼 데이터 (로그인 예시)
form_data = {
    'username': 'alice',
    'password': 'secret123'
}

# data= 파라미터 사용 (application/x-www-form-urlencoded)
response = requests.post(
    'https://httpbin.org/post',
    data=form_data
)

result = response.json()
print(f"전송된 폼 데이터: {result['form']}")

json= vs data= 비교: | 파라미터 | Content-Type | 용도 | |———-|————–|——| | json= | application/json | API 통신 | | data= | application/x-www-form-urlencoded | 웹 폼 전송 |

3.3 헤더 추가하기

💡 HTTP 헤더는 요청에 추가 정보를 담습니다. 인증 토큰, 클라이언트 정보 등을 전달합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

# 커스텀 헤더
headers = {
    'User-Agent': 'MyPythonApp/1.0',  # 내 앱 이름
    'Accept': 'application/json',     # JSON 응답 요청
    'Authorization': 'Bearer YOUR_TOKEN'  # 인증 토큰
}

response = requests.get(
    'https://api.github.com/user',
    headers=headers
)

print(f"상태 코드: {response.status_code}")

🎯 학습 목표 4: 응답 처리와 에러 핸들링

4.1 응답 객체 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

response = requests.get('https://api.github.com')

# 상태 확인
print(f"상태 코드: {response.status_code}")  # 200
print(f"성공 여부: {response.ok}")           # True (200-299면 True)

# 헤더 확인
print(f"Content-Type: {response.headers['Content-Type']}")

# 본문 가져오기
print(f"텍스트: {response.text[:100]}...")   # 문자열
print(f"바이트: {response.content[:50]}...")  # 바이트
data = response.json()                        # dict (JSON인 경우)

# 인코딩
print(f"인코딩: {response.encoding}")  # utf-8

4.2 상태 코드 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests

def safe_get(url):
    """안전한 GET 요청"""
    response = requests.get(url)

    if response.status_code == 200:
        return response.json()
    elif response.status_code == 404:
        print("❌ 요청한 리소스를 찾을 수 없습니다.")
    elif response.status_code == 401:
        print("🔒 인증이 필요합니다.")
    elif response.status_code >= 500:
        print("⚠️ 서버에 문제가 발생했습니다.")
    else:
        print(f"❓ 알 수 없는 오류: {response.status_code}")

    return None


# 테스트
data = safe_get('https://api.github.com/users/torvalds')
if data:
    print(f"이름: {data['name']}")

4.3 예외 처리

💡 네트워크 요청은 언제든 실패할 수 있습니다! 인터넷 연결 끊김, 서버 다운, 타임아웃 등 다양한 오류가 발생할 수 있으므로 반드시 예외 처리를 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

def fetch_data(url):
    """예외 처리가 포함된 데이터 조회"""
    try:
        response = requests.get(url, timeout=5)  # 5초 타임아웃
        response.raise_for_status()  # 4xx, 5xx 오류 시 예외 발생
        return response.json()

    except requests.exceptions.Timeout:
        print("⏱️ 요청 시간이 초과되었습니다.")
    except requests.exceptions.ConnectionError:
        print("🔌 서버에 연결할 수 없습니다.")
    except requests.exceptions.HTTPError as e:
        print(f"🚫 HTTP 오류: {e}")
    except requests.exceptions.RequestException as e:
        print(f"❌ 요청 오류: {e}")

    return None


# 사용
data = fetch_data('https://api.github.com/users/torvalds')
if data:
    print(f"가져온 데이터: {data['name']}")

4.4 타임아웃 설정

1
2
3
4
5
6
7
8
9
10
import requests

# 단일 타임아웃 (전체 5초)
response = requests.get('https://api.example.com', timeout=5)

# 연결/읽기 타임아웃 분리
response = requests.get(
    'https://api.example.com',
    timeout=(3, 10)  # 연결 3초, 읽기 10초
)

💡 타임아웃을 항상 설정하세요! 설정하지 않으면 서버가 응답하지 않을 때 프로그램이 영원히 멈춥니다.


🎯 학습 목표 5: 실전 API 활용하기

5.1 무료 공개 API로 연습하기

💡 httpbin.org는 HTTP 요청을 테스트할 수 있는 무료 서비스입니다. 보낸 데이터를 그대로 돌려주어 학습에 유용합니다.

1
2
3
4
5
6
7
8
import requests

# httpbin으로 요청 테스트
response = requests.get('https://httpbin.org/get', params={'name': 'Alice'})
print(response.json())

response = requests.post('https://httpbin.org/post', json={'message': 'Hello'})
print(response.json())

5.2 실전 예제: 환율 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests

def get_exchange_rate(base='USD', target='KRW'):
    """환율 조회 (무료 API)"""
    url = f'https://api.exchangerate-api.com/v4/latest/{base}'

    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()

        data = response.json()
        rate = data['rates'].get(target)

        if rate:
            return {
                'base': base,
                'target': target,
                'rate': rate,
                'date': data['date']
            }
        else:
            print(f"'{target}' 통화를 찾을 수 없습니다.")
            return None

    except requests.exceptions.RequestException as e:
        print(f"❌ 오류: {e}")
        return None


# 사용
result = get_exchange_rate('USD', 'KRW')
if result:
    print(f"💱 1 {result['base']} = {result['rate']:,.2f} {result['target']}")
    print(f"📅 기준일: {result['date']}")

출력:

1
2
💱 1 USD = 1,350.00 KRW
📅 기준일: 2025-04-20

5.3 실전 예제: 파일 다운로드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests

def download_file(url, filename):
    """파일 다운로드 (진행률 표시)"""
    try:
        response = requests.get(url, stream=True, timeout=30)
        response.raise_for_status()

        # 파일 크기 확인
        total_size = int(response.headers.get('content-length', 0))

        with open(filename, 'wb') as f:
            downloaded = 0

            for chunk in response.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
                    downloaded += len(chunk)

                    # 진행률 표시
                    if total_size:
                        percent = (downloaded / total_size) * 100
                        print(f"\r다운로드 중: {percent:.1f}%", end='')

        print(f"\n✅ 다운로드 완료: {filename}")
        return True

    except requests.exceptions.RequestException as e:
        print(f"\n❌ 다운로드 실패: {e}")
        return False


# 사용 예시
# download_file('https://example.com/file.zip', 'downloaded.zip')

💡 실전 팁 & 주의사항

✅ 베스트 프랙티스

  1. 항상 타임아웃 설정
    1
    
    requests.get(url, timeout=5)  # 필수!
    
  2. 예외 처리 필수
    1
    2
    3
    4
    5
    
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        print(f"오류: {e}")
    
  3. 상태 코드 확인
    1
    2
    
    if response.ok:  # 200-299
        data = response.json()
    
  4. API 키는 환경 변수로
    1
    2
    
    import os
    api_key = os.environ.get('API_KEY')
    

⚠️ 주의사항

  • API 호출 제한: 대부분의 API는 분당/시간당 호출 횟수 제한이 있음
  • 인증 토큰 보안: 코드에 토큰을 직접 넣지 말고 환경 변수 사용
  • 에러 응답 처리: 성공만 가정하지 말고 실패 케이스도 처리

🧪 연습 문제

문제 1: GitHub 저장소 목록 조회

GitHub API를 사용하여 특정 사용자의 공개 저장소 목록을 가져오는 함수를 작성하세요.

요구사항:

  • 사용자 이름을 입력받아 저장소 목록 반환
  • 각 저장소의 이름, 설명, 스타 수 출력
  • 에러 처리 포함
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests

def get_user_repos(username, max_repos=5):
    """GitHub 사용자의 저장소 목록 조회"""
    url = f'https://api.github.com/users/{username}/repos'

    params = {
        'sort': 'updated',  # 최근 업데이트순
        'per_page': max_repos
    }

    try:
        response = requests.get(url, params=params, timeout=5)
        response.raise_for_status()

        repos = response.json()
        result = []

        for repo in repos:
            result.append({
                'name': repo['name'],
                'description': repo['description'] or '설명 없음',
                'stars': repo['stargazers_count'],
                'url': repo['html_url']
            })

        return result

    except requests.exceptions.HTTPError:
        if response.status_code == 404:
            print(f"'{username}' 사용자를 찾을 수 없습니다.")
        else:
            print(f"❌ HTTP 오류: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"❌ 요청 오류: {e}")

    return None


# 테스트
repos = get_user_repos('torvalds')
if repos:
    print(f"\n📦 저장소 목록:")
    for repo in repos:
        print(f"{repo['stars']:,} - {repo['name']}")
        print(f"     {repo['description'][:50]}...")

문제 2: API 클라이언트 클래스

재사용 가능한 API 클라이언트 클래스를 작성하세요.

요구사항:

  • base_url과 선택적 api_key를 받아 초기화
  • get(), post() 메서드 구현
  • 타임아웃과 에러 처리 포함
해답 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import requests

class APIClient:
    """재사용 가능한 API 클라이언트"""

    def __init__(self, base_url, api_key=None, timeout=10):
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout
        self.headers = {
            'User-Agent': 'PythonAPIClient/1.0',
            'Accept': 'application/json'
        }

        if api_key:
            self.headers['Authorization'] = f'Bearer {api_key}'

    def get(self, endpoint, params=None):
        """GET 요청"""
        url = f'{self.base_url}/{endpoint.lstrip("/")}'

        try:
            response = requests.get(
                url,
                params=params,
                headers=self.headers,
                timeout=self.timeout
            )
            response.raise_for_status()
            return response.json()

        except requests.exceptions.RequestException as e:
            print(f"GET 오류 [{endpoint}]: {e}")
            return None

    def post(self, endpoint, data=None):
        """POST 요청"""
        url = f'{self.base_url}/{endpoint.lstrip("/")}'

        try:
            response = requests.post(
                url,
                json=data,
                headers=self.headers,
                timeout=self.timeout
            )
            response.raise_for_status()
            return response.json()

        except requests.exceptions.RequestException as e:
            print(f"POST 오류 [{endpoint}]: {e}")
            return None


# 사용 예시
github = APIClient('https://api.github.com')
user = github.get('/users/torvalds')
if user:
    print(f"이름: {user['name']}")
    print(f"팔로워: {user['followers']}")

📝 오늘 배운 내용 정리

개념 설명 예시
HTTP 웹 데이터 전송 프로토콜 GET, POST, PUT, DELETE
API 프로그램 간 데이터 교환 창구 GitHub API, 날씨 API
상태 코드 요청 처리 결과 200(성공), 404(없음), 500(서버오류)
requests.get() GET 요청 requests.get(url, params=params)
requests.post() POST 요청 requests.post(url, json=data)
response.json() JSON 응답 파싱 딕셔너리로 변환
타임아웃 요청 대기 시간 제한 timeout=5
예외 처리 네트워크 오류 처리 RequestException

핵심 코드 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests

# 기본 GET 요청
response = requests.get('https://api.example.com/data', timeout=5)
if response.ok:
    data = response.json()

# 파라미터와 함께 GET
params = {'key': 'value'}
response = requests.get(url, params=params, timeout=5)

# POST 요청 (JSON)
data = {'name': 'Alice'}
response = requests.post(url, json=data, timeout=5)

# 헤더 추가
headers = {'Authorization': 'Bearer TOKEN'}
response = requests.get(url, headers=headers, timeout=5)

# 안전한 요청 패턴
try:
    response = requests.get(url, timeout=5)
    response.raise_for_status()
    return response.json()
except requests.exceptions.RequestException as e:
    print(f"오류: {e}")

🔗 관련 자료


📚 이전 학습

Day 50: 미니 프로젝트: 개인 가계부 ⭐⭐⭐⭐

Phase 5 마무리 프로젝트로 JSON, CSV, 예외 처리를 활용한 가계부 앱을 만들었습니다!

📚 다음 학습

Day 52: HTTP 심화 - PUT, DELETE와 인증

다음 시간에는 PUT/DELETE 메서드, 세션 관리, API 인증 방식을 배웁니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 51/100 Phase 6: 웹 스크래핑과 API #100DaysOfPython

이제와서 시작하는 Python 마스터하기 - Day 51 완료! 🎉

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.