
오늘 늦으 ㄹ뻔햇어요 휴
지각안한 나 나이스.
시작 .
오늘 아침 과제는 vscode에 새 프로젝트 폴더를 만들어서 지금까지 했던 코랩 파일을 넣는 활동을 가졌어요,,
추가로 GitHub 리포지토리도 만들었습니당,,, 새로만든 리포지토리에도 지금까지 했던 코랩파일을 업로드했답니당..
끝~~
오늘의 순서
- requests.get()으로 실제 요청 보내기
- params로 쿼리 매개변수 전달하기
- headers로 인증 정보 보내기
- timeout으로 무한 대기 방지하기
- raise_for_status()로 HTTP 오류 감지하기
- RequestException으로 네트워크 예외 처리하기
- 재시도 함수 call_with_retry() 만들기
- .env로 API 키 분리하기
- fetch_data()로 필요한 필드만 추출하기
- api_client.py 조립
- api_result.json 저장
- GET과 POST 차이 이해하기
1. API 조회 서비스란?
어제 탐지 룰에서 특정 IP가 의심스럽다는 사실까지는 확인할 수 있었다.
예를 들어 다음 IP가 탐지됐다고 하자.
185.220.101.34
하지만 현재 가지고 있는 로그만으로는 다음 정보를 알 수 없다.
- 어느 나라에 속한 IP인지
- 어떤 네트워크 사업자가 사용하는지
- 어떤 기관에서 관리하는지
- 외부 보안 정보가 있는지
이때 IP 조회 API 서비스를 사용할 수 있다.
IP 주소를 API에 전달하면 서버가 해당 IP와 관련된 정보를 데이터 형태로 돌려준다.
내 프로그램
↓
IP 주소 전달
↓
IP 조회 API
↓
국가 / 네트워크 / 사업자 등의 정보
2. requests란?
파이썬에서 HTTP 요청을 보낼 때 많이 사용하는 외부 라이브러리가 requests다.
import requests
파이썬 기본 모듈에도 HTTP 요청 기능이 있지만, requests는 코드가 간결하고 읽기 쉽기 때문에 널리 사용된다.
로컬 환경에 설치되어 있지 않다면 다음 명령으로 설치할 수 있다.
pip install requests
코랩에는 대부분 이미 설치되어 있다.
3. requests.get()으로 요청 보내기
가장 기본적인 GET 요청은 다음과 같다.
import requests
response = requests.get("https://ipwho.is/8.8.8.8")
requests.get()을 실행하면 서버로 요청을 보내고 응답을 받아 response 변수에 저장한다.
구조는 다음과 같다.
requests.get()
↓
HTTP 요청
↓
서버
↓
HTTP 응답
↓
response
4. status_code 확인하기
응답을 받았다면 먼저 상태코드를 확인한다.
print(response.status_code)
예:
200
200은 요청이 정상적으로 처리됐다는 뜻이다.
대표적인 상태코드는 다음과 같다.
코드의미
| 200 | 요청 성공 |
| 201 | 데이터 생성 성공 |
| 401 | 인증 문제 |
| 403 | 권한 문제 |
| 404 | 요청한 자원을 찾을 수 없음 |
| 500 | 서버 내부 오류 |
중요한 습관은 다음과 같다.
본문을 읽기 전에 상태코드부터 확인한다.
실패한 요청의 응답 본문은 우리가 기대하는 구조가 아닐 수 있기 때문이다.
5. response.json()
API가 JSON 형태로 응답했다면 .json()으로 파이썬 값으로 변환할 수 있다.
data = response.json()
print(data)
예를 들어 서버가 다음과 같은 JSON을 보냈다고 하자.
{
"ip": "8.8.8.8",
"country": "United States"
}
.json()을 호출한 결과는 파이썬 딕셔너리다.
print(data["country"])
결과:
United States
6. json.loads()와 response.json() 비교
어제는 다음처럼 JSON 문자열을 직접 파싱했다.
import json
text = '{"country": "Korea"}'
data = json.loads(text)
오늘은 HTTP 응답 객체에서 바로 사용한다.
data = response.json()
역할을 비교하면 다음과 같다.
방식역할
| json.loads(text) | JSON 문자열 → 파이썬 값 |
| response.json() | HTTP 응답의 JSON 본문 → 파이썬 값 |
즉, .json()이 응답 본문을 파싱하는 작업까지 대신해 준다.
7. params란?
API를 사용할 때 URL 뒤에 조건을 붙이는 경우가 있다.
예:
?fields=country,connection
직접 문자열을 이어 붙일 수도 있다.
url = "https://ipwho.is/8.8.8.8?fields=country,connection"
하지만 이렇게 직접 조립하면 공백이나 한글 같은 특수문자의 URL 인코딩을 직접 신경 써야 한다.
그래서 requests에서는 params를 사용한다.
response = requests.get(
"https://ipwho.is/8.8.8.8",
params={
"fields": "country,connection"
}
)
8. params를 사용하는 이유
params에 딕셔너리를 넘기면 requests가 알아서 URL을 조립하고 인코딩한다.
params = {
"fields": "country,connection"
}
개념적으로는 다음 주소가 만들어진다.
https://ipwho.is/8.8.8.8?fields=country,connection
따라서 다음 습관이 좋다.
URL 뒤에 ?key=value를 직접 이어 붙이지 말고 params를 사용한다.
9. headers란?
HTTP 요청에는 주소와 쿼리뿐 아니라 헤더(Header) 라는 정보도 붙일 수 있다.
헤더에는 요청과 관련된 부가 정보를 담는다.
대표적으로 다음과 같은 정보가 들어갈 수 있다.
- 인증 정보
- 토큰
- API 키
- 데이터 형식
- 클라이언트 정보
예:
headers = {
"X-Api-Key": "demo-key-1234"
}
요청에 넣으면 다음과 같다.
response = requests.get(
"https://postman-echo.com/headers",
headers=headers
)
10. 인증 정보는 왜 headers에 넣을까?
API 키나 토큰 같은 인증 정보는 일반적인 검색 조건과 성격이 다르다.
예를 들어
?city=seoul
같은 값은 조회 조건이다.
반면
API_KEY
TOKEN
은 사용자의 인증 정보다.
따라서 보통 다음처럼 구분한다.
종류위치
| 조회 조건 | params |
| 인증 정보 | headers |
11. params와 headers 비교
구분paramsheaders
| 목적 | 조회 조건 전달 | 요청 부가정보 전달 |
| 대표 예 | 검색어, 필터 | API 키, 토큰 |
| URL 표시 | 일반적으로 URL에 포함됨 | URL에는 직접 나타나지 않음 |
예:
requests.get(
url,
params={"fields": "country"},
headers={"X-Api-Key": api_key}
)
12. 요청 기본 구조 한눈에 보기
response = requests.get(
url,
params=params,
headers=headers
)
각 부분의 역할은 다음과 같다.
url
↓
어디로 요청할지
params
↓
무엇을 조회할지
headers
↓
누가 요청하는지 등의 부가정보
13. 그런데 네트워크는 항상 성공하지 않는다
HTTP 요청은 파일을 읽는 것과 다르다.
내 컴퓨터 밖에 있는 서버와 통신하기 때문에 여러 가지 문제가 발생할 수 있다.
예:
- 인터넷 연결 끊김
- 상대 서버 점검
- 서버 과부하
- 요청 제한
- 응답 지연
- 잘못된 주소
- 인증 실패
- 서버 내부 오류
따라서 API 코드를 작성할 때는
성공할 것이라고 가정하는 것보다 실패에 대비하는 것이 중요하다.
14. timeout이 필요한 이유
다음처럼 요청을 보냈다고 하자.
requests.get(url)
상대 서버가 응답하지 않는다면 프로그램이 오랫동안 기다릴 수 있다.
그래서 timeout을 지정한다.
response = requests.get(
url,
timeout=5
)
뜻은
최대 5초까지만 응답을 기다린다.
이다.
15. timeout의 목적
timeout을 지정하는 이유는 크게 두 가지다.
무한 대기 방지
서버가 응답하지 않는 상태로 프로그램이 계속 멈춰 있는 것을 방지한다.
시스템 자원 보호
하나의 느린 요청 때문에 프로그램 전체가 계속 잡혀 있는 상황을 줄인다.
따라서 네트워크 요청에는 가능하면 timeout을 명시하는 것이 좋다.
16. raise_for_status()
상태코드가 404나 500이어도 requests.get() 자체가 항상 파이썬 오류를 발생시키는 것은 아니다.
이때 사용하는 것이
response.raise_for_status()
다.
예:
response = requests.get(
"https://ipwho.is/8.8.8.8",
timeout=3
)
response.raise_for_status()
17. raise_for_status()의 역할
응답이 정상이라면 아무 일도 하지 않는다.
200
↓
그대로 다음 코드 실행
하지만 4xx 또는 5xx 상태코드라면 예외를 발생시킨다.
404
↓
raise_for_status()
↓
예외 발생
500
↓
raise_for_status()
↓
예외 발생
그래야 try · except로 처리할 수 있다.
18. try·except와 함께 사용하기
import requests
try:
response = requests.get(
"https://ipwho.is/8.8.8.8",
timeout=5
)
response.raise_for_status()
data = response.json()
except requests.RequestException:
print("요청에 실패했습니다.")
네트워크 요청에서 발생하는 다양한 예외를 한 번에 처리할 때 requests.RequestException을 사용할 수 있다.
19. RequestException이란?
requests에서는 상황에 따라 여러 네트워크 예외가 발생할 수 있다.
예:
- 연결 실패
- 시간 초과
- HTTP 상태코드 오류
이런 requests 관련 예외들의 상위 개념으로 사용할 수 있는 것이
requests.RequestException
이다.
예:
except requests.RequestException:
print("API 요청 실패")
20. 재시도가 필요한 이유
API 요청이 한 번 실패했다고 해서 계속 실패한다는 뜻은 아니다.
일시적인 네트워크 문제일 수도 있고 서버가 잠깐 바빴을 수도 있다.
따라서 일정 횟수 다시 시도할 수 있다.
예:
1번째 요청 → 실패
2번째 요청 → 실패
3번째 요청 → 성공
이런 상황이라면 첫 번째 실패에서 프로그램을 끝낼 필요가 없다.
21. call_with_retry()
재시도 기능을 함수로 만들 수 있다.
import requests
def call_with_retry(url, tries=3):
for i in range(tries):
try:
response = requests.get(
url,
timeout=5
)
response.raise_for_status()
return response.json()
except requests.RequestException:
print(f"{i + 1}번째 실패")
return None
22. call_with_retry() 실행 흐름
함수의 흐름은 다음과 같다.
요청 시작
↓
성공?
┌───────┴───────┐
Yes No
↓ ↓
JSON 반환 실패 출력
↓
다시 시도
↓
모든 시도 실패?
↓
return None
23. range(tries)
다음 코드는
for i in range(tries):
tries 횟수만큼 반복한다.
예:
tries = 3
이면
i = 0
i = 1
i = 2
총 세 번 실행된다.
사용자에게 표시할 때는 0번째 실패가 어색하므로 1을 더한다.
print(f"{i + 1}번째 실패")
24. 성공하면 return
다음 코드가 중요하다.
return response.json()
요청이 성공하는 순간 함수가 종료된다.
예를 들어 첫 번째 실패 후 두 번째 요청에서 성공했다면
1회차 → 실패
2회차 → 성공 → return
3회차 → 실행 안 됨
이 된다.
남은 재시도 횟수는 사용하지 않는다.
25. 모두 실패하면 None
모든 시도가 실패하면 반복문이 끝난다.
그다음
return None
을 실행한다.
따라서 이 함수를 사용하는 쪽에서는 결과를 확인해야 한다.
data = call_with_retry(url)
if data:
print(data)
26. API 키를 코드에 직접 쓰면 안 되는 이유
다음 코드를 생각해 보자.
API_KEY = "abcd1234-secret-key"
프로그램이 혼자 사용하는 동안에는 문제가 없어 보인다.
하지만 코드를 GitHub에 올리면 API 키도 함께 공개될 수 있다.
소스코드 공개
↓
API 키 노출
↓
다른 사람이 키 사용 가능
그래서 비밀값은 코드와 분리하는 것이 좋다.
27. 환경변수란?
환경변수는 프로그램 설정값이나 비밀값을 코드가 아닌 실행 환경 쪽에서 관리하는 방식이다.
대표적으로 다음 데이터를 코드 밖에 둘 수 있다.
- API 키
- 데이터베이스 비밀번호
- 토큰
- 서버 주소
- 개발·운영 환경 설정
장점은 다음과 같다.
장점설명
| 보안 | 코드가 공개돼도 비밀값 노출 위험 감소 |
| 환경 분리 | 개발·테스트·운영 값 변경이 쉬움 |
| 협업 | 각자 자신의 키를 사용할 수 있음 |
| 유지보수 | 코드를 수정하지 않고 설정 변경 가능 |
28. .env 파일
학습 단계에서는 환경변수 형태의 값을 .env 파일에 둘 수 있다.
예:
API_KEY=demo-key-1234
코드에는 실제 키를 직접 작성하지 않는다.
29. .env 읽기
오늘은 별도의 라이브러리를 사용하지 않고 파일 읽기와 split()을 이용한다.
api_key = None
with open(".env", encoding="utf-8") as f:
for line in f:
parts = line.strip().split("=", 1)
if parts[0] == "API_KEY":
api_key = parts[1]
이제 실제 키는 api_key 변수에 들어간다.
30. split("=", 1)이 중요한 이유
다음 코드를 보자.
line.split("=", 1)
뒤의 1은
처음 발견한 =에서 딱 한 번만 나눈다.
는 뜻이다.
예:
API_KEY=abc=123=xyz
다음처럼 나누면
line.split("=")
결과는
[
"API_KEY",
"abc",
"123",
"xyz"
]
가 된다.
API 키가 여러 조각으로 잘려 버린다.
31. split("=", 1)의 결과
line.split("=", 1)
을 사용하면
[
"API_KEY",
"abc=123=xyz"
]
처럼 첫 번째 =만 기준으로 나뉜다.
따라서 실제 비밀값 안에 =가 있어도 유지된다.
32. .env.example
실제 .env 파일은 비밀값이 있기 때문에 Git에 올리면 안 된다.
하지만 프로그램을 사용하는 사람은 어떤 환경변수가 필요한지 알아야 한다.
그래서 .env.example 파일을 만들 수 있다.
API_KEY=
실제 값은 넣지 않고 변수 이름만 알려 준다.
33. .env와 .env.example 비교
파일내용Git 업로드
| .env | 실제 API 키 | 하지 않음 |
| .env.example | 변수 이름만 | 가능 |
예:
.env
API_KEY=demo-key-1234
.env.example
API_KEY=
34. .gitignore
Git이 추적하지 않아야 하는 파일은 .gitignore에 적는다.
예:
.env
그러면 일반적인 Git 사용 흐름에서 .env가 버전 관리 대상에서 제외된다.
비밀값이나 불필요한 파일을 저장소에 올리지 않기 위해 사용하는 파일이다.
35. .gitignore 예시
.env
__pycache__/
*.log
예를 들어 이렇게 작성하면
- .env
- 파이썬 캐시 폴더
- .log 파일
을 Git이 추적하지 않도록 설정할 수 있다.
36. fetch_data()
API 응답에는 우리가 사용하지 않는 값도 많이 포함될 수 있다.
그 응답 전체를 그대로 저장할 필요는 없다.
필요한 값만 골라 새 딕셔너리를 만든다.
def fetch_data(ip):
data = call_with_retry(
f"https://ipwho.is/{ip}"
)
if data and data["success"]:
return {
"ip": ip,
"country": data["country"],
"isp": data["connection"]["isp"]
}
else:
return None
37. fetch_data()가 하는 일
함수의 흐름은 다음과 같다.
IP 입력
↓
call_with_retry()
↓
API 호출
↓
성공?
↓
필요한 값만 선택
↓
새 딕셔너리 반환
예를 들어 API가 매우 많은 데이터를 반환하더라도
{
"ip": "8.8.8.8",
"country": "United States",
"isp": "Google LLC"
}
처럼 필요한 데이터만 남긴다.
38. if data and data["success"]
다음 조건을 보자.
if data and data["success"]:
두 가지를 확인한다.
data
data
API 호출 자체가 성공했는지를 확인한다.
모든 재시도가 실패했다면
None
이 들어 있을 수 있다.
data["success"]
HTTP 요청은 성공했어도 API 서비스 자체가 실패 결과를 반환하는 경우가 있다.
예를 들어 HTTP 상태코드가 200이어도 본문이 다음과 같을 수 있다.
{
"success": false,
"message": "invalid ip"
}
즉,
상태코드 200 = API 업무 처리도 무조건 성공
은 아니다.
따라서 서비스에서 제공하는 success 값도 확인해야 한다.
39. 상태코드 200인데 실패할 수 있다
HTTP 수준에서는 요청을 정상적으로 받았다는 뜻일 수 있다.
HTTP 200
하지만 API의 실제 처리 결과는 다음처럼 실패일 수 있다.
{
"success": false
}
따라서 두 단계를 구분해야 한다.
HTTP 요청 성공?
↓
API 업무 처리 성공?
40. 필요한 필드만 골라 저장하는 이유
응답 전체를 저장하는 대신 필요한 값만 선택하면 다음 장점이 있다.
장점설명
| 단순성 | 데이터 구조가 작고 이해하기 쉬움 |
| 일관성 | 필요한 필드만 동일한 형태로 유지 |
| 저장 공간 | 불필요한 데이터 감소 |
| 유지보수 | 이후 코드가 필요한 필드만 알면 됨 |
| 보안 | 필요 없는 정보 저장을 줄일 수 있음 |
41. api_client.py 조립
오늘 만든 기능은 크게 세 부분으로 나눌 수 있다.
.env 읽기
↓
call_with_retry()
↓
fetch_data()
↓
JSON 저장
한 파일로 조립하면 api_client.py가 된다.
42. api_client.py 예시 구조
import json
import requests
# 1. API 키 읽기
api_key = None
with open(".env", encoding="utf-8") as f:
for line in f:
parts = line.strip().split("=", 1)
if parts[0] == "API_KEY":
api_key = parts[1]
# 2. 재시도 함수
def call_with_retry(url, tries=3):
for i in range(tries):
try:
response = requests.get(
url,
timeout=5
)
response.raise_for_status()
return response.json()
except requests.RequestException:
print(f"{i + 1}번째 요청 실패")
return None
# 3. 필요한 데이터만 선택
def fetch_data(ip):
data = call_with_retry(
f"https://ipwho.is/{ip}"
)
if data and data["success"]:
return {
"ip": ip,
"country": data["country"],
"isp": data["connection"]["isp"]
}
return None
# 4. API 조회
result = fetch_data("8.8.8.8")
# 5. 결과 저장
if result:
with open(
"api_result.json",
"w",
encoding="utf-8"
) as f:
json.dump(
result,
f,
ensure_ascii=False,
indent=2
)
43. 전체 실행 흐름
.env
↓
API_KEY 읽기
↓
IP 입력
↓
fetch_data()
↓
call_with_retry()
↓
requests.get()
↓
timeout 적용
↓
raise_for_status()
↓
response.json()
↓
API success 확인
↓
필요한 값만 추출
↓
api_result.json
44. api_result.json
정상적으로 조회됐다면 다음과 같은 파일을 만들 수 있다.
{
"ip": "8.8.8.8",
"country": "United States",
"isp": "Google LLC"
}
이제 프로그램을 종료해도 결과가 파일에 남는다.
다음 프로그램에서 다시 읽을 수도 있다.
with open(
"api_result.json",
encoding="utf-8"
) as f:
data = json.load(f)
45. GET과 POST
오늘은 실제로 GET 요청을 사용했다.
GET과 POST의 핵심 차이는 데이터를 다루는 목적이다.
메서드주요 목적
| GET | 데이터를 가져온다 |
| POST | 데이터를 보내 새 데이터를 만들거나 처리한다 |
46. GET
GET은 조회할 때 사용한다.
예:
- IP 정보 조회
- 검색
- 게시글 읽기
- 사용자 정보 조회
일반적으로 조회 조건은 params로 전달한다.
requests.get(
url,
params={
"keyword": "python"
}
)
47. POST
POST는 서버에 데이터를 보내 새로운 작업을 만들거나 처리할 때 많이 사용한다.
예:
- 회원가입
- 게시글 작성
- 티켓 생성
- 주문 생성
보낼 데이터는 보통 요청 본문에 넣는다.
payload = {
"title": "서버 오류",
"priority": "high"
}
예를 들어 앞으로는 다음과 같은 형태를 사용할 수 있다.
requests.post(
url,
json=payload
)
오늘은 직접 POST 요청을 구현하지 않고 개념만 익혔다.
48. GET과 POST 비교
구분GETPOST
| 목적 | 조회 | 생성·제출 |
| 데이터 위치 | 주로 params | 주로 json=payload |
| 예 | IP 조회 | 티켓 생성 |
| 반복 요청 | 일반적으로 같은 데이터를 다시 조회 | 같은 작업이 중복 생성될 수 있음 |
49. 오늘 나온 주요 문법
코드의미
| requests.get(url) | GET 요청을 보낸다 |
| response.status_code | HTTP 상태코드 확인 |
| response.json() | JSON 응답을 파이썬 값으로 변환 |
| params={...} | 조회 조건 전달 |
| headers={...} | 인증 정보 등 헤더 전달 |
| timeout=5 | 최대 5초 기다림 |
| raise_for_status() | 4xx·5xx 응답을 예외로 변환 |
| except requests.RequestException | requests 관련 예외 처리 |
| for i in range(tries) | 지정한 횟수만큼 재시도 |
| return None | 모든 시도가 실패했음을 표시 |
| split("=", 1) | 첫 번째 =에서만 나눔 |
| json.dump() | 결과를 JSON 파일로 저장 |
50. 파일별 역할
오늘 결과물을 파일 기준으로 보면 다음과 같다.
project/
├─ api_client.py
├─ api_result.json
├─ .env
├─ .env.example
└─ .gitignore
파일역할
| api_client.py | API 요청·재시도·데이터 가공 |
| api_result.json | API 조회 결과 |
| .env | 실제 비밀값 저장 |
| .env.example | 필요한 환경변수 이름 안내 |
| .gitignore | Git에서 제외할 파일 지정 |
51. 어제부터 오늘까지 연결하기
어제는 로그에서 의심스러운 IP를 탐지했다.
비정형 로그
↓
정규표현식
↓
정규화
↓
탐지 룰
↓
의심 IP 발견
하지만 여기까지는 우리 로그 안의 정보만 사용한 것이다.
오늘은 그 IP를 외부 서비스에 보내 추가 정보를 가져왔다.
의심 IP 발견
↓
requests.get()
↓
외부 IP API
↓
국가 / ISP 정보
↓
필요한 값 추출
↓
api_result.json
즉, 로그 분석 프로그램이 처음으로 외부 시스템과 연결되기 시작한 단계라고 볼 수 있다.
52. 실패까지 포함한 전체 흐름
실제 프로그램은 성공 경로만 생각해서는 안 된다.
API 요청
↓
응답이 왔는가?
┌──────┴──────┐
No Yes
↓ ↓
timeout HTTP 상태 확인
↓ ↓
재시도 4xx / 5xx?
↓ ┌─────┴─────┐
Yes No
↓ ↓
예외 처리 JSON 읽기
↓ ↓
재시도 API success 확인
↓
필요한 값 추출
↓
파일 저장
오늘은 오전만 수업을 하고 오후에는 특강을 듣는 시간입니당~~~!
남는 시간에 과제랑 자격증 공부도 좀 해야겠어용
그럼 아됴
'공부 > SKT ALEPH' 카테고리의 다른 글
| AI·자동화 기초(에이전트·API·스크립팅) - 7 (0) | 2026.10.06 |
|---|---|
| AI·자동화 기초(에이전트·API·스크립팅) - 6 (0) | 2026.10.02 |
| AI·자동화 기초(에이전트·API·스크립팅) - 4 (0) | 2026.09.29 |
| AI·자동화 기초(에이전트·API·스크립팅) - 3 (0) | 2026.09.28 |
| AI·자동화 기초(에이전트·API·스크립팅) - 2 (0) | 2026.09.23 |
