Step 74. requests 라이브러리 — 파이썬으로 웹과 대화하기
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 73을 마쳤다. HTTP 요청/응답 구조, GET과 POST, 헤더와 상태코드를 안다. 파이썬 스크립트를 작성·실행할 수 있고 pip을 써 봤다.
- 준비물: 파이썬, 터미널 두 개(서버용 하나 + 실험용 하나). 인터넷 연결은 필요 없습니다 — 오늘도 대화 상대는 우리가 직접 만드는 localhost 서버입니다.
- 주의: 오늘 실습은 100% 안전합니다. 모든 요청은 127.0.0.1 안에서만 오갑니다.
지난 시간 우리는 curl로 서버와 대화했습니다. 명령 한 줄에 요청 하나. 훌륭하지만 한계가 있습니다 — 백 개의 페이지를 긁으려면 명령을 백 번 쳐야 하고, 응답에서 원하는 값만 뽑으려면 눈으로 찾아야 합니다. 자동화가 없으면 우리는 수집가가 아니라 타이핑 노예입니다. requests는 파이썬에서 HTTP 요청을 보내는 가장 유명한 라이브러리로, 이 하나로 GET/POST, 파라미터, 헤더, 쿠키를 코드 몇 줄로 다룹니다. 웹 스캐너, 수집기, 테스트 도구의 심장이 바로 이 라이브러리입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- POST와 쿠키를 이해하는 연습용 서버(lab.py)를 파이썬으로 직접 만든다
requests.get/post로 요청을 보내고 응답 객체의 status_code, headers, text를 읽는다- params(주소)와 data(본문)의 차이를 코드로 구분해 쓴다
- Session으로 쿠키를 유지하며 "로그인된 척 연속 행동"의 원리를 실험한다
- timeout과 예외 처리로 멈추지 않는 요청 코드를 만든다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3 + requests 라이브러리 (pip install requests). 실습 상대는 127.0.0.1:8010의 자체 제작 서버 |
| 오늘의 도구 | requests.get/post, 응답 객체, requests.Session, 그리고 서버 쪽의 http.server 모듈 |
| 오늘의 함수 | requests.get(주소, params=, headers=, timeout=), requests.post(주소, data=), r.status_code / r.headers / r.text / r.json() / r.url / r.history |
| 필요한 개념 | 요청/응답 구조(Step 73), 파이썬 사전, 쿠키와 세션 |
| 오늘의 산출물 | lab.py — 연습용 서버, analyze.py — 응답 분석기 |
2-1. 응답 객체라는 선물 상자
requests.get(...)을 호출하면 돌아오는 것은 응답 객체(response object)입니다. "속성(주머니)이 여러 개 달린 선물 상자"라고 생각하세요.
| 속성 | 내용 | 예시 |
|---|---|---|
r.status_code |
상태코드 (숫자) | 200 |
r.headers |
응답 헤더 (사전처럼 사용) | r.headers["Content-Type"] |
r.text |
응답 본문 (문자열) | "<!DOCTYPE html>..." |
r.json() |
본문을 JSON으로 풀어 사전으로 반환 | JSON 응답일 때만 |
r.url |
실제로 요청이 간 최종 주소 | 리다이렉트 확인용 |
r.history |
거쳐 온 리다이렉트 기록 | 경로 추적용 |
지난 시간 curl -v에서 눈으로 읽던 >와 <의 내용이, 이제는 코드에서 점(.)으로 꺼내 쓰는 속성이 된 것입니다.
2-2. params와 data — 싣는 위치의 차이
Step 73에서 배운 GET과 POST의 차이가 코드에서는 이렇게 갈립니다.
params={"id": "admin"}→ 주소 뒤에 붙습니다 (?id=admin). GET 요청의 데이터입니다.data={"pw": "1234"}→ 본문에 들어갑니다. POST 요청의 데이터입니다.
파이썬 사전(dictionary, 이름표-값 쌍의 묶음)을 그대로 넘기면 requests가 알아서 HTTP 형식으로 포장합니다. 우리가 손으로 ?id=admin&pw=1234를 조립할 필요가 없습니다.
2-3. 세션 — 신분증을 계속 내미는 손
HTTP는 원래 "기억력이 없는" 규약입니다. 서버는 각 요청을 별개의 손님으로 봅니다. 로그인에 성공해도 다음 요청에서는 다시 낯선 사람 취급입니다. 이것을 해결하는 것이 쿠키(cookie, 서버가 발급한 신분증 쪽지)이고, 코드에서 쿠키를 자동으로 챙겨 주는 것이 requests.Session()입니다. 세션 객체로 요청하면, 첫 요청에서 받은 쿠키를 다음 요청에 자동으로 붙여 줍니다 — 브라우저가 하던 일의 흉내입니다.
3. 따라 하기
3-1. 설치와 연습용 서버 만들기
입력
pip install requests
이번에는 대화 상대가 될 서버를 직접 만듭니다. 지난 시간의 python -m http.server는 GET만 알았지요. 이 서버는 POST도 받고, 쿠키도 심어 줍니다. lab.py를 만드세요.
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
QUOTES_PAGE = """<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>명언 연습장</title></head>
<body>
<h1>오늘의 명언</h1>
<div class="quote">
<span class="text">가장 큰 위험은 위험 없는 삶이다.</span>
<small class="author">알레르 허브</small>
<div class="tags"><a class="tag" href="/tag/life">life</a></div>
</div>
<div class="quote">
<span class="text">백 번 듣는 것이 한 번 보는 것만 못하다.</span>
<small class="author">속담</small>
<div class="tags"><a class="tag" href="/tag/wisdom">wisdom</a></div>
</div>
<div class="quote">
<span class="text">완벽함은 아무것도 더할 것이 없을 때가 아니라, 뺄 것이 없을 때다.</span>
<small class="author">생텍쥐페리</small>
<div class="tags"><a class="tag" href="/tag/design">design</a> <a class="tag" href="/tag/wisdom">wisdom</a></div>
</div>
</body>
</html>"""
class LabHandler(BaseHTTPRequestHandler):
def _send(self, code, body, ctype="text/html; charset=utf-8", extra=None):
data = body.encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(data)))
for k, v in (extra or {}).items():
self.send_header(k, v)
self.end_headers()
self.wfile.write(data)
def do_GET(self):
path = urlparse(self.path).path
if path == "/":
self._send(200, QUOTES_PAGE)
elif path == "/headers":
lines = [f"{k}: {v}" for k, v in self.headers.items()]
self._send(200, "\n".join(lines), "text/plain; charset=utf-8")
elif path == "/set":
self._send(200, "쿠키를 심었습니다.",
extra={"Set-Cookie": "session_id=abc123; Path=/"})
elif path == "/who":
cookie = self.headers.get("Cookie", "(쿠키 없음)")
self._send(200, f"서버가 받은 쿠키: {cookie}",
"text/plain; charset=utf-8")
elif path == "/old":
self.send_response(301)
self.send_header("Location", "/")
self.end_headers()
else:
self._send(404, "<h1>404 — 그런 페이지 없습니다</h1>")
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length).decode("utf-8")
fields = parse_qs(raw)
flat = {k: v[0] for k, v in fields.items()}
self._send(200, f"서버가 받은 POST 본문: {raw}\n풀어 헤친 결과: {flat}",
"text/plain; charset=utf-8")
if __name__ == "__main__":
print("실습 서버 시작: http://127.0.0.1:8010 (종료: Ctrl+C)")
HTTPServer(("127.0.0.1", 8010), LabHandler).serve_forever()
입력 (터미널 1 — 서버용)
python lab.py
출력 (2026-09-09 실측):
실습 서버 시작: http://127.0.0.1:8010 (종료: Ctrl+C)
읽는 법: 겁내지 마세요. 길지만 하는 일은 단순합니다 — 경로(path)마다 정해진 대답을 돌려주는 분기문입니다. /headers는 받은 헤더를 그대로 돌려주는 거울, /set은 쿠키를 심는 창구, /who는 받은 쿠키를 보여 주는 창구, /echo는 POST 본문을 돌려주는 창구입니다. 이 서버는 이 챕터와 크롤링 챕터에서 계속 씁니다.
왜: 서버를 직접 만들면 "서버는 요청 줄과 헤더를 읽고, 정해진 응답을 돌려주는 프로그램"이라는 것이 몸에 남습니다. 공격자의 눈으로 서버를 보려면 서버의 입장을 한 번은 되어 봐야 합니다.
3-2. 첫 요청과 응답 해부
터미널 2를 열고 req1.py를 만듭니다.
입력
import requests
r = requests.get("http://127.0.0.1:8010/")
print("상태코드:", r.status_code)
print("Content-Type:", r.headers["Content-Type"])
print("본문 앞 60자:", repr(r.text[:60]))
출력 (2026-09-09 실측):
상태코드: 200
Content-Type: text/html; charset=utf-8
본문 앞 60자: '<!DOCTYPE html>\n<html>\n<head><meta charset="utf-8"><title>명언'
읽는 법: 세 줄의 코드로 GET 요청이 날아갔고, 돌아온 상자에서 상태코드와 헤더와 본문을 꺼내 찍었습니다. 본문은 우리가 lab.py에 적은 그 HTML입니다. 지난 시간의 curl 한 줄이 코드 몇 줄로 바뀐 것에 불과해 보이지만, 이제 이 코드를 반복문에 넣을 수 있다는 점이 다릅니다.
3-3. 예측해 보기 — params는 주소 어디에 붙을까
예측입니다. 아래 코드에서 r.url에 찍히는 최종 주소는 어떤 모습일까요?
입력
r = requests.get("http://127.0.0.1:8010/headers", params={"q": "security", "page": "2"})
print("최종 주소:", r.url)
출력 (2026-09-09 실측):
최종 주소: http://127.0.0.1:8010/headers?q=security&page=2
읽는 법: 사전으로 넘긴 두 개의 이름표-값이 ? 뒤에 &로 이어져 붙었습니다. 손으로 조립하던 GET 주소를 requests가 대신 만들어 준 것입니다. 참고로 우리 서버의 /headers는 주소 뒷부분을 무시하고 헤더만 돌려주지만, 주소 자체는 보시다시피 완성되어 날아갔습니다.
왜: 검색창에 단어를 칠 때 주소가 길어지는 현상의 정체가 이것입니다. 그리고 "이 주소의 파라미터를 바꾸면 서버가 다르게 반응할까?"라는 웹 테스트의 첫 질문이 바로 이 자리에서 나옵니다.
3-4. POST와 헤더 변장
입력
r = requests.post("http://127.0.0.1:8010/echo", data={"id": "admin", "pw": "1234"})
print(r.text)
r2 = requests.get("http://127.0.0.1:8010/headers",
headers={"User-Agent": "MySecurityStudyBot/1.0"})
print(r2.text)
출력 (2026-09-09 실측):
서버가 받은 POST 본문: id=admin&pw=1234
풀어 헤친 결과: {'id': 'admin', 'pw': '1234'}
Host: 127.0.0.1:8010
User-Agent: MySecurityStudyBot/1.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
읽는 법: 첫 번째는 POST 본문에 실린 데이터가 서버에 그대로 도착해 풀어 헤쳐진 증거입니다 — 이름=값&이름=값 형태로 실려 갔다는 것도 보입니다. 두 번째는 서버가 본 헤더 목록입니다. 내가 바꾼 User-Agent가 그대로 보이고, requests가 기본으로 붙인 헤더(Accept-Encoding, Connection 등)도 함께 보입니다.
왜: "요청의 모든 부분은 내가 조립할 수 있다"는 확신을 갖는 단계입니다. 웹 테스트 도구의 본질이 이 조립 자유도입니다.
3-5. 세션 — 쿠키가 유지되는지 실험
입력
s = requests.Session()
s.get("http://127.0.0.1:8010/set") # 쿠키를 심는 창구
r = s.get("http://127.0.0.1:8010/who") # 같은 세션으로 확인
print("세션으로:", r.text)
r2 = requests.get("http://127.0.0.1:8010/who") # 세션 없이 새로 요청
print("세션 없이:", r2.text)
출력 (2026-09-09 실측):
세션으로: 서버가 받은 쿠키: session_id=abc123
세션 없이: 서버가 받은 쿠키: (쿠키 없음)
읽는 법: 세션으로 쿠키를 심은 뒤 같은 세션으로 물으니 쿠키가 살아 있습니다. 세션 없이 보낸 요청은 빈 쿠키입니다. 세션이 "신분증 지갑" 역할을 한다는 직접적인 증거입니다.
왜: 로그인 → 글 목록 → 글쓰기처럼 "인증이 필요한 연속 동작"을 코드로 할 때 세션이 필수입니다. 이 흐름을 한 세션에 담는 것이 웹 자동화의 골격입니다. 반대로 공격자가 남의 쿠키를 훔치면(XSS 등으로) 비밀번호 없이 그 사람인 척할 수 있습니다 — 쿠키가 신분증이라는 말의 무게입니다.
3-6. 예측해 보기 — 리다이렉트는 어떻게 기록될까
우리 서버의 /old는 "이사갔다(301), 새 주소는 /"라고만 대답합니다. requests로 요청하면 최종 도착지는 어디이고, 그 기록은 어디에 남을까요?
입력
r = requests.get("http://127.0.0.1:8010/old")
print("최종 주소:", r.url)
print("거쳐온 안내:", [(x.status_code, x.url) for x in r.history])
출력 (2026-09-09 실측):
최종 주소: http://127.0.0.1:8010/
거쳐온 안내: [(301, 'http://127.0.0.1:8010/old')]
읽는 법: requests는 curl과 달리 리다이렉트를 기본으로 따라갑니다. 그리고 r.history에 거쳐 온 발자국(301 안내)이 남습니다. "최종적으로 어디에 도착했는가"와 "어떤 길로 왔는가"가 분리되어 보입니다.
왜: 단축 주소의 실제 목적지 확인, 피싱 사이트의 우회 경로 추적 — 이런 분석이 전부 이 history 속성에서 출발합니다.
3-7. 멈추지 않는 코드 — timeout과 예외 처리
자동화 도구의 생존 장치를 붙입니다.
입력
import requests
try:
r = requests.get("http://127.0.0.1:59999/", timeout=3)
except requests.exceptions.ConnectionError:
print("요청 실패: 서버에 닿지 않습니다")
try:
r = requests.get("http://127.0.0.1:8010/", timeout=3)
print("성공:", r.status_code)
except requests.exceptions.ConnectionError:
print("요청 실패: 서버에 닿지 않습니다")
출력 (2026-09-09 실측):
요청 실패: 서버에 닿지 않습니다
성공: 200
읽는 법: 59999번에는 서버가 없으니 ConnectionError가 나고, 우리가 잡아서 안내 문구를 냈습니다. timeout=3은 "3초 안에 답이 없으면 포기하라"는 장치입니다. 예외를 잡지 않았다면 프로그램은 긴 오류 메시지와 함께 죽었을 것입니다 — 실제 메시지는 ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=59999): Max retries exceeded ... 같은 모양입니다 (2026-09-09 실측).
왜: requests는 timeout 없이는 서버가 침묵하면 무한정 기다립니다. 백 페이지를 긁는 도구가 37번째에서 영원히 멈추는 것을 막는 한 줄입니다.
4. 미션과 연습문제
미션 — 응답 분석기 만들기
analyze.py를 만들어 다음을 수행하세요 (lab.py 서버가 켜져 있어야 합니다).
/headers에 params 두 개를 달아 GET 요청하고, 최종 URL과 상태코드를 출력합니다- 응답 본문에서 서버가 본 User-Agent 줄만 골라 출력합니다
- User-Agent를 자기만의 문자열로 바꿔 같은 요청을 다시 보내고, 서버가 본 내 신상이 바뀌었는지 출력으로 확인합니다
/echo에 POST로 이름표-값 세 개를 보내고, 서버가 풀어 헤친 결과를 출력합니다- 모든 요청을 try/except로 감싸고 timeout=3을 붙여, 서버가 꺼져 있어도 "요청 실패" 안내를 남기고 정상 종료하게 합니다
연습문제
문제 1. params와 data의 차이를 "데이터가 실리는 위치"와 "함께 쓰는 메서드"로 설명해 보세요.
문제 2. r.json()을 호출했더니 JSONDecodeError: Expecting value: line 1 column 1 (char 0)가 떴습니다 (2026-09-09 실측). 무슨 상황이며, 호출 전에 무엇을 확인해야 하나요?
문제 3. 세션 없이 requests.get(...)만으로 로그인과 글쓰기를 연달아 하면 왜 로그인이 풀리는지, 쿠키의 개념으로 설명해 보세요.
문제 4. r.url과 r.history의 차이를 "최종 도착지"와 "거쳐 온 길"로 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
import requests
BASE = "http://127.0.0.1:8010"
try:
# 1. params 달기
r = requests.get(f"{BASE}/headers", params={"q": "security", "page": "2"}, timeout=3)
print("최종 URL:", r.url)
print("상태코드:", r.status_code)
# 2. 서버가 본 User-Agent 줄 고르기
for line in r.text.splitlines():
if line.startswith("User-Agent"):
print("서버가 본 UA:", line)
# 3. 변장 후 재확인
r2 = requests.get(f"{BASE}/headers",
headers={"User-Agent": "MyAnalyzer/2.0"}, timeout=3)
for line in r2.text.splitlines():
if line.startswith("User-Agent"):
print("변장 후 서버가 본 UA:", line)
# 4. POST
r3 = requests.post(f"{BASE}/echo",
data={"id": "admin", "pw": "1234", "memo": "첫 실습"},
timeout=3)
print(r3.text)
except requests.exceptions.ConnectionError:
print("요청 실패: lab.py 서버가 켜져 있는지 확인하세요")
except requests.exceptions.Timeout:
print("요청 실패: 서버가 3초 안에 답하지 않았습니다")
실행 결과 (2026-09-09 실측, 일부):
최종 URL: http://127.0.0.1:8010/headers?q=security&page=2
상태코드: 200
서버가 본 UA: User-Agent: python-requests/2.33.1
변장 후 서버가 본 UA: User-Agent: MyAnalyzer/2.0
서버가 받은 POST 본문: id=admin&pw=1234&memo=%EC%B2%AB+%EC%8B%A4%EC%8A%B5
풀어 헤친 결과: {'id': 'admin', 'pw': '1234', 'memo': '첫 실습'}
아무 변장도 안 했을 때 requests는 스스로를 python-requests/버전이라고 신고한다는 것 — 이것도 오늘의 발견입니다. 그리고 POST 본문에서 한글 "첫 실습"이 %EC%B2%AB...으로 바뀐 것을 보세요. 폼 데이터는 URL 인코딩(주소에 실을 수 있는 글자로의 변환)되어 전송되고, 서버는 그것을 다시 풀어서 씁니다. Step 50의 인코딩 감각이 여기서도 쓰입니다.
검증하는 법: ① 서버를 끄고 실행했을 때 프로그램이 죽지 않고 안내를 남기는가. ② 변장 전후의 User-Agent 줄이 다른가. ③ POST의 세 이름표가 풀어 헤친 결과에 모두 있는가. 셋이 전부 ‘예’이면 완성입니다.
연습문제 해답
문제 1 해답. params는 주소 뒤(?이름=값)에 실리고 주로 GET과 함께 쓰며, data는 요청 본문에 실리고 주로 POST와 함께 씁니다. 둘 다 사전으로 넘기면 requests가 HTTP 형식으로 포장합니다.
문제 2 해답. 응답 본문이 JSON이 아닌 상황입니다 — 우리 실습 서버의 /처럼 HTML이 돌아온 경우입니다 (char 0, 즉 첫 글자부터 JSON이 아니라는 뜻). json() 호출 전에 r.status_code를 보고 r.text[:200]으로 본문 앞부분을 먼저 들여다보는 습관이 필요합니다.
문제 3 해답. HTTP는 요청마다 기억이 없어서, 로그인 성공의 증거(세션 쿠키)를 다음 요청에 다시 내밀어야 합니다. requests.get(...)을 매번 새로 부르면 쿠키를 보관할 지갑이 없어서 매번 낯선 사람이 됩니다. Session 객체가 그 지갑입니다 (3-5 실측: 세션은 session_id=abc123을 유지했고 세션 없는 요청은 빈 쿠키였습니다).
문제 4 해답. r.url은 리다이렉트를 다 따라간 뒤의 최종 주소이고, r.history는 그때까지 거쳐 온 안내(301 등)의 목록입니다. 3-6 실측처럼 최종 주소는 /인데 history에는 (301, .../old)가 남는 식입니다.
완료 기준 체크리스트
- [ ] lab.py 연습 서버를 만들고 켜고 끌 수 있다
- [ ] requests.get/post로 요청하고 status_code와 headers를 읽을 수 있다
- [ ] params와 data의 차이(주소 vs 본문)를 설명할 수 있다
- [ ] Session으로 쿠키가 유지되는 것을 실험으로 확인했다
- [ ] timeout과 try/except를 습관적으로 붙인다
- [ ] r.url과 r.history로 리다이렉트를 추적할 수 있다
- [ ] 미션: 응답 분석기를 완성했다
6. 흔한 실수와 해결
벽 1. 서버를 안 켜고 클라이언트부터 실행했다
증상 (2026-09-09 실측):
requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8010): Max retries exceeded ... (Caused by NewConnectionError(... [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다))
원인: 대화 상대(lab.py)가 켜져 있지 않습니다.
해결: 터미널 1에서 python lab.py로 서버를 먼저 켜세요. 서버 창은 계속 떠 있어야 합니다.
벽 2. 서버를 켤 때 포트가 막혀 있다
증상: OSError: [WinError 10048] ... — 지정한 포트를 이미 쓰고 있습니다.
원인: 꺼지지 않은 이전 서버나 다른 프로그램이 그 번호를 점유 중입니다.
해결: lab.py의 8010을 8020 등으로 바꾸고, 클라이언트 코드의 주소도 같이 바꾸세요.
벽 3. r.json()에서 오류가 난다
증상 (2026-09-09 실측): requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
원인: 응답 본문이 JSON이 아닙니다 (HTML 페이지 등). 상태코드가 200이 아닐 때도 흔합니다.
해결: json() 전에 r.status_code == 200인지 보고, print(r.text[:200])로 본문 앞부분을 먼저 확인하세요.
벽 4. 요청이 한참 멈춰 있다
증상: get()이 몇 분째 돌아오지 않습니다.
원인: 상대가 응답하지 않는데 requests는 기본으로 무한정 기다립니다.
해결: timeout=5를 습관적으로 붙이세요. 답이 없으면 오류를 내며 포기합니다. 자동화 도구의 무한 대기는 곧 멈춤입니다.
벽 5. 분명 쿠키를 받았는데 다음 요청에서 사라진다
증상: /set으로 쿠키를 심었는데 /who에서 "(쿠키 없음)"이 옵니다.
원인: 매번 requests.get(...)으로 새 요청을 보냈습니다. 쿠키가 저장될 지갑이 없습니다.
해결: s = requests.Session()을 만들고 모든 요청을 s.get(...), s.post(...)로 보내세요 (3-5 실측이 그 증거입니다).
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| requests | 파이썬의 대표 HTTP 클라이언트 라이브러리 |
| 응답 객체 | status_code, headers, text, json() 등 주머니 달린 선물 상자 |
| params / data | 주소에 싣기(GET) / 본문에 싣기(POST) |
| 쿠키 | 서버가 발급하는 신분증 쪽지 — 응답의 Set-Cookie로 심고, 요청의 Cookie 헤더로 내민다 |
| Session | 쿠키를 자동으로 보관·첨부하는 지갑 객체 |
| timeout | "이 시간 안에 답 없으면 포기" — 자동화의 생존 장치 |
오늘의 함수
| 함수·속성 | 하는 일 |
|---|---|
requests.get(주소, params=, headers=, timeout=) |
GET 요청 |
requests.post(주소, data=, timeout=) |
POST 요청 |
r.status_code / r.headers / r.text |
상태코드 / 응답 헤더 / 본문 문자열 |
r.json() |
JSON 본문 → 파이썬 사전 (JSON일 때만!) |
r.url / r.history |
최종 주소 / 거쳐 온 리다이렉트 목록 |
requests.Session() |
쿠키 지갑 만들기 |
python lab.py |
연습용 서버 켜기 (종료: Ctrl+C) |
명령어보다 중요한 감각
오늘의 핵심 감각은 응답을 의심하는 습관입니다. 초보는 "응답이 왔으니 성공"이라 믿지만, 실무자의 확인 순서는 정해져 있습니다 — ① status_code 보기 ② r.text 앞 200자 찍어 보기 ③ 그다음에 json()이나 파싱. 이 삼단이 "왜 안 되지?" 하는 디버깅 시간의 절반을 지워 줍니다. 보안과의 연결: 오늘 배운 것은 남용하면 위험합니다. 같은 요청을 쉬지 않고 때리는 코드는 그 자체로 서비스 방해가 되므로, 반복 요청에는 time.sleep(1) 같은 쉼표를 넣는 것이 기술 이전의 태도입니다. 그리고 오늘 직접 만들어 본 lab.py를 기억하세요 — 서버는 결국 "요청을 읽고 정해진 대답을 내놓는 분기문"입니다. 이 그림이 있으면 웹의 어떤 신기술을 만나도 겁이 나지 않습니다.
전부 체크되면 Step 74 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.