
urllib은 Python의 표준 라이브러리로, URL 작업을 위한 여러 모듈을 제공함.
이 중에서 urllib.request 모듈은 HTTP/HTTPS 요청 처리를 위한 것임.
참고로 urllib 라이브러리의 주요 구성 모듈은 다음과 같음:
urllib.request- HTTP/HTTPS 요청 처리urllib.parse- URL 파싱 및 구성urllib.error- urllib 관련 예외urllib.robotparser- robots.txt 파일 처리
urllib.request 모듈
웹 요청을 보내고 응답을 받는 기능을 제공.
urlopen() 함수
- URL을 열고 파일형 객체인,
http.client.HTTPResponse객체를 반환. - 가장 기본적인 웹 요청 함수임.
Signature:
urllib.request.urlopen(
url,
data=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
*, cafile=None, capath=None, cadefault=False, context=None
)
Parameters:
url: (str|Request)- 요청할 URL 또는 Request 객체
data: (bytes, optional)- POST 데이터.
None이면 GET 요청None이 아니면 POST 요청 임.
timeout: (float, optional)- 타임아웃 시간(sec).
- 기본값은 전역 설정 이용.
cafile: (str, optional)- CA 인증서 파일 경로 문자열.
capath: (str, optional)- CA 인증서 디렉터리 경로 문자열.
cadefault: (bool, optional)- 시스템 CA 인증서 사용 여부
context: (ssl.SSLContext, optional)- SSL 컨텍스트
return:
http.client.HTTPResponse객체- HTTP 응답 객체
예제: GET 요청
아래의 모든 예제는 httpbin.org 를 이용함.
참고로, httpbin.org 는 테스트용 HTTP 요청/응답 서비스 를 제공하는 사이트임.
import urllib.request
# --------------------
# 1. 기본 GET 요청 보내기
# urlopen('https://httpbin.org/get') 는 GET 요청 전송
with urllib.request.urlopen('https://httpbin.org/get') as response:
# ------------------
# 2. 응답 상태 코드 확인
print(f"상태 코드: {response.getcode()}") # 정상 응답이면 보통 200 (OK)
# ---------------
# 3. 응답 헤더 확인
# - response.headers : HTTP 응답 헤더를 담고 있는 객체
# - dict(...) 로 변환하면 dictionary 형태로 보기 편함
print(f"헤더: {dict(response.headers)}")
# --------------------
# 4. 응답 본문(body) 읽기
# - response.read() : 바이트(bytes)로 반환됨
# - .decode('utf-8') → 문자열(str)로 변환
data = response.read().decode('utf-8')
# 응답 내용 전체는 길 수 있으므로 앞부분 100자만 출력
print(f"응답 내용: {data[:100]}...")
httpbin.org의 /get 엔드포인트는 클라이언트가 보낸 GET 요청 정보를 JSON 형식으로 돌려줌.
- 때문에 헤더에서
'Content-Type': 'application/json'를 확인 가능함.
참고로, Content-Type 헤더는 응답 body가 어떤 형식인지 알려주는 메타데이터임.
- HTML :
text/html - 일반 텍스트 :
text/plain - JSON :
application/json
예제: POST 요청
이는 웹 브라우저의 <form>태그를 통해 키와 값을 보내는 예제임.
참고로,
POST에서는 Content-Type: application/x-www-form-urlencoded를 사용하는 폼인코딩 (아래의 예제 코드) 이 외에도
REST API에서 흔히 사용되는 Content-Type: application/json 와
파일업로드에 사용되는 form-data인 멀티파트(Content-Type: multipart/form-data)도 있음.
urllib.request 빌트인 라이브러리를 통해서도 구현가능하나, 보통은 requests 라이브러리를 보다 많이 이용함:
그러므로 여기선 가장 간단한 Content-Type: application/x-www-form-urlencoded를 사용하는 폼인코딩 예제만 다룬다.
import urllib.request
# ----------------------
# 1. 전송할 POST 데이터 정의
# - POST 요청 시 data 매개변수에 bytes 타입을 전달해야 함.
# - "name=hong&age=30" : form 데이터 형식(key=value&key=value)
# - application/x-www-form-urlencoded Content-Type 방식의 본문에 해당
post_data = b'name=hong&age=30'
# ----------------
# 2. POST 요청 전송
# - urlopen() 호출 시 data 인자를 지정하면
# - 자동으로 "POST" 방식 요청이 전송됨.
# - (data=None 이면 GET 요청)
with urllib.request.urlopen('https://httpbin.org/post', data=post_data) as response:
# ---------------
# 3. 응답 본문 읽기
result = response.read().decode('utf-8')
# response.read() : 응답을 바이트(bytes) 데이터로 읽음
# decode('utf-8') : UTF-8 문자열(str)로 변환
# ------------------------------------
# 4. 결과 출력
print(result)
# httpbin.org/post 는 요청 내용을 JSON으로 돌려주는 테스트 서버
# 응답 JSON에는 우리가 보낸 post_data(name=hong, age=30),
# 요청 헤더, IP, URL 등의 정보가 포함됨
timeout 설정
요청에 대한 응답을 기다리는 timeout을 설정.
지정된 timeout 안에 응답이 오지 않는 경우 타임아웃 예외가 발생함.
import urllib.request
try:
# ------------------------------------------
# httpbin.org/delay/5 는 응답을 5초 늦게 주는 테스트용 API
# timeout=3 → 최대 3초 동안만 기다리도록 설정
# 따라서 5초가 지나야 응답이 오는데, 3초까지만 기다리므로 타임아웃 발생
with urllib.request.urlopen('https://httpbin.org/delay/5', timeout=3) as response:
# 만약 3초 안에 응답이 오면 아래 코드 실행
print("요청 성공")
except urllib.error.URLError as e:
# ------------------------------------------
# URLError:
# - timeout 발생
# - DNS 조회 실패
# - 연결 거부(Connection Refused) 등
# 네트워크 관련 오류가 발생했을 때 공통적으로 발생하는 예외
print(f"타임아웃 오류: {e}")
e.reason을 이용하면
타임아웃인지, 다른 네트워크 오류인지 구분할 수 있음.
Request 클래스
- HTTP 요청을 추상화한 객체.
- 헤더, 메서드 등을 보다 세밀하게 제어할 수 있음.
Signature:
class urllib.request.Request(
url,
data=None,
headers={},
origin_req_host=None,
unverifiable=False,
method=None
)
Parameters:
url: (str)- 요청할 URL
data: (bytes, optional)- 요청 본문 데이터
headers: (dict, optional)- HTTP 헤더 딕셔너리
origin_req_host: (str, optional)- 원본 요청 호스트
unverifiable: (bool, optional)- 요청 검증 불가 여부
method: (str, optional)- HTTP 메서드 (GET, POST, PUT 등)
주요 메서드:
add_header(key, val): 헤더 추가get_header(header_name, default=None): 헤더 값 조회get_method(): HTTP 메서드 반환
예제: GET 요청
참고로, GET 요청은 원칙적으로 body가 없음
import urllib.request
import json
# -------------------
# 1. 보낼 HTTP 헤더 정의
headers = {
'User-Agent': 'Python Client 1.0', # 서버에 전달할 클라이언트 정보
'Accept': 'application/json', # 서버 응답으로 JSON 형식 원함
'Authorization': 'Bearer token123' # 인증 토큰 (예: OAuth2 방식)
}
# ------------------
# 2. Request 객체 생성
# URL: httpbin.org/headers → 서버가 받은 헤더를 그대로 JSON으로 돌려주는 테스트 API
request = urllib.request.Request('https://httpbin.org/headers', headers=headers)
# --------------------
# 3. 요청 보내고 응답 받기
with urllib.request.urlopen(request) as response:
# 응답 본문 읽기 (바이트 -> 문자열 변환 -> JSON 디코딩)
data = json.loads(response.read().decode('utf-8'))
# ----------------------------
# 4. 서버가 실제로 받은 헤더 확인
print("서버가 받은 헤더:", data['headers'])
참고로 HTTP에서 GET 메서드의 request 의 구조는 다음과 같음:
GET <경로> <프로토콜>
<헤더1>: <값>
<헤더2>: <값>
...
<빈 줄>
[본문 - GET은 일반적으로 없음]
예제: POST 요청
import urllib.request
import json
# ----------------------------------------
# 1. 전송할 데이터 (Python dict)
post_data = {
'name': '홍길동',
'age': 30,
'city': '서울'
}
# ----------------------------------------
# 2. JSON 직렬화 (dict -> JSON 문자열 -> bytes)
# - json.dumps() : dict를 JSON 문자열로 변환
# - ensure_ascii=False : 한글이 \uXXXX 로 깨지지 않고 그대로 출력되도록 함
# - encode('utf-8') : 문자열을 UTF-8 바이트로 변환 (HTTP 요청 body는 bytes여야 함)
json_data = json.dumps(
post_data,
ensure_ascii=False
).encode('utf-8')
# -------------------
# 3. Request 객체 생성
# - data=json_data : POST 요청으로 전송
# - headers={'Content-Type': 'application/json'} : 본문이 JSON임을 명시
request = urllib.request.Request(
'https://httpbin.org/post',
data=json_data,
headers={'Content-Type': 'application/json'}
)
# ---------------------
# 4. 요청 전송 및 응답 처리
with urllib.request.urlopen(request) as response:
# response.read() : 응답 body (bytes)
# decode('utf-8') : 문자열로 변환
# json.loads() : JSON 파싱 (Python dict로 변환)
result = json.loads(response.read().decode('utf-8'))
# 서버가 받은 JSON 데이터 출력
# httpbin.org/post 는 요청의 JSON 본문을 그대로 "json" 키에 담아 응답함
print("전송된 JSON:", result['json'])
참고로 HTTP에서 POST 메서드의 request 의 구조는 다음과 같음:
POST <경로> <프로토콜>
<헤더1>: <값>
<헤더2>: <값>
...
<빈 줄>
<본문>
PUT 요청
POST가 새로 생성하거나 서버가 알아서 처리하는 용도로 사용되는 HTTP 메서드라면
PUT은 클라이언트가 특정 위치의 (기존) 리소스를 지정해서 교체하는 용도로 사용됨.
POST와 비슷한 형태이나, method='PUT' 을 통해 PUT요청임을 정확히 명시.
import urllib.request
# ----------------------------------------
# PUT 요청 생성
# - url: 요청할 주소
# - data: PUT 요청의 body (bytes 타입 필수)
# - method='PUT': HTTP 메서드를 PUT으로 지정
request = urllib.request.Request(
'https://httpbin.org/put',
data=b'updated data', # 서버에 전송할 데이터
method='PUT' # HTTP 메서드 지정
)
# ----------------------------------------
# 요청 전송 및 응답 처리
with urllib.request.urlopen(request) as response:
result = response.read().decode('utf-8')
print(result)
urlretrieve()
URL의 내용을 파일로 직접 다운로드.
어느 정도 큰 파일 다운로드에 유용 (매우 큰 파일은 urlopen()으로 열고 chunk를 지정하여 읽어들여야 함).
- 단순히 한 번에 다운로드를 통해 저장하는 구조.
- 더 세밀한 제어(스트리밍, 커스텀 헤더, 예외 처리, 진행 상황 관리 등)를 하기 어려움.
대용량 파일에선 urlopen()을 통한 다음의 방식을 권장:
url = "https://httpbin.org/bytes/10485760" # 10MB짜리 테스트 파일
filename = "downloaded_large_file.bin"
chunk_size = 8192 # 보통 8KB 또는 16KB 정도 사용
with urllib.request.urlopen(url) as response, open(filename, 'wb') as out_file:
# total_size = resp.headers.get("Content-Length")
# total_size = int(total_size) if total_size is not None else -1
while True:
# 지정한 크기만큼 읽기
chunk = response.read(chunk_size)
if not chunk: # 더 이상 읽을 데이터가 없으면 종료
break
out_file.write(chunk) # 바로 파일에 기록
signature:
urllib.request.urlretrieve(
url,
filename=None,
reporthook=None,
data=None,
)
parameters:
url: (str)- 다운로드할 URL
filename: (str, optional)- 저장할 파일명.
None이면 임시파일 생성
reporthook: (callable, optional)- 진행률 콜백 함수
data(bytes, opotional)- POST 데이터
return:
tuple: (filename, headers)- 튜플 객체
예제:
import urllib.request
import sys
def progress_hook(block_count, block_size, total_size):
downloaded = block_count * block_size
if total_size is None or total_size <= 0:
# 총 크기를 모를 때: 다운로드한 바이트만 표시
sys.stdout.write(f"\r진행: {downloaded} bytes 다운로드 중...")
else:
percent = min(100, downloaded * 100 / total_size)
sys.stdout.write(
f"\r진행률: {percent:.1f}% ({downloaded}/{total_size} bytes)"
)
sys.stdout.flush()
# # 파일 다운로드
# # url = 'https://httpbin.org/bytes/10240'
# url = "https://github.com/google/fonts/raw/main/ofl/nanumgothic/NanumGothic-Regular.ttf"
# save_path = "NanumFont.ttf"
# 최신 CaskaydiaCove Nerd Mono 다운로드 URL (GitHub Release)
url = "https://sourceforge.net/projects/nerd-fonts.mirror/files/v3.2.1/CascadiaMono.zip/download"
save_path = "CaskaydiaCoveNerdMono.zip"
filename, headers = urllib.request.urlretrieve(
url,
save_path,
reporthook=progress_hook,
)
print(f'\n다운로드 완료: {filename}')
build_opener()
커스터마이즈된 URL opener를 생성하기 위한 함수.
urllib.request.urlopen()은 기본 opener를 사용: 기본 opener는 표준 handler 들을 사용.- HTTP/HTTPS/리다이렉트/에러처리 핸들러 등으로 구성됨.
build_opener()로 만든 opener는 기본 opener에서 명시적 제어(쿠키·프록시·인증 등)를 위해 바꾸려는 기능에 해당하는 handler를 넘겨주어 해당 기능을 대체함.- 없는 핸들러의 경우에 기능이 추가됨.
- 기본 opener에 있는 핸들러가 넘겨진 경우 대체됨.
urllib의 opener 는 쿠키, 프록시, 인증 리다이렉트, SSL, 디버그 등을 제어하는 handler를 조합하여 동작 체인 구성한 객체로 요청(Request)을 open 할 때, 내부적으로 각 handler를 순서대로 거치며 기능을 수행함.
- handler들의 처리순서는 고정된 우선순위가 있음
- 때문에
build_opner()에 handler들을 넘겨줄 때 순서는 고민할 필요가 거의 없음.
urllib의 handler는 HTTP 요청·응답 처리 과정에서 쿠키, 프록시, 인증, 리다이렉트, SSL 등 특정 기능을 담당하는 모듈화된 구성 요소임.
signature :
urllib.request.build_opener(*handlers)
parameters :
*handlers:- Handler 객체들
HTTPCookieProcessor,ProxyHandler등
return :
OpenerDirector객체- 커스터마이즈된 opener 객체
예제: 쿠키 처리
HTTP 쿠키는 웹 서버가 클라이언트에 저장해두고 이후 요청마다 자동으로 전송되는 key=value 쌍의 작은 데이터로, 세션 유지·인증·사용자 설정 등에 활용됨 (값은 빈 문자열수도 있음).
다음 예제는 HTTPCookieProcessor(CookieJar) 를 통해,
- 서버가 보낸 쿠키를 자동 저장하고
- 이후 요청에 자동 전송하는 쿠키 처리 객체를 만들고,
- 이를
build_opner()를 통해 오프너를 생성하여 요청을 보내는 방법을 보여줌.
import urllib.request
from http.cookiejar import CookieJar
# ----------------------------------------
# 1. 쿠키를 저장할 CookieJar 객체 생성: 쿠키 저장소
cookie_jar = CookieJar()
# ----------------------------------------
# 2. CookieJar와 연결된 HTTPCookieProcessor 생성
# - HTTP 응답에서 받은 쿠키를 cookie_jar에 저장
# - 이후 요청 시 cookie_jar에 저장된 쿠키를 자동 전송
# ----------------------------------------
cookie_processor = urllib.request.HTTPCookieProcessor(cookie_jar)
# ----------------------------------------
# 3. opener 생성
# - 기본 urlopen 대신 opener.open()을 사용하면
# 쿠키를 포함한 고급 동작을 지원
opener = urllib.request.build_opener(cookie_processor)
# ----------------------------------------
# 4. 첫 번째 요청: 쿠키 설정
# - httpbin.org/cookies/set/session/abc123
# - "session=abc123" 쿠키를 응답에 담아 내려줌
# - HTTPCookieProcessor가 자동으로 cookie_jar에 저장
response1 = opener.open('https://httpbin.org/cookies/set?session=abc123')
# ----------------------------------------
# 5. 두 번째 요청: 쿠키 자동 전송
# - httpbin.org/cookies 는 서버가 받은 쿠키를 JSON으로 반환
# - opener는 cookie_jar에 저장된 "session=abc123" 쿠키를 자동으로 전송
response2 = opener.open('https://httpbin.org/cookies')
# ----------------------------------------
# 6. 결과 출력
# - {"cookies": {"session": "abc123"}} 형태의 JSON 반환
print(response2.read().decode('utf-8'))
참고로, 일반적으로 쿠키는
- 서버가
Set-Cookie헤더로 클라이언트에 전달하면, - 클라이언트는 이를 저장(메모리 또는 파일)해두었다가
- 같은 도메인·경로 조건으로 또 요청할 경우 자동으로
Cookie헤더에 포함해 전송하는 방식으로 동작함.
주의할 점은 http.cookiejar.CookieJar는 기본적으로 세션 쿠키 (메모리에 저장)만 관리한다.
Expires 또는 Max-Age가 지정된 persistent cookie는 MozillaCookieJar나 LWPCookieJar 같은 하위 클래스를 써서 파일로 저장되도록 구현해야함.
httpbin.org는 간단한 테스트용 사이트라, 세션 쿠키만 가능함.
/cookie/set엔드포인트는 뒤에 넘겨지는 키와 값으로 쿠키를 설정하라는 응답헤더를 전송해줌:Set-Cookie: session=abc123; Path=/- 복수의 쿠키도 설정 가능:
https://httpbin.org/cookies/set?name=hong&age=30&city=seoul
- 복수의 쿠키도 설정 가능:
/cookie엔드포인트는 요청에서 보내는 쿠키를 json으로 다시 전송해줌.
예제: Proxy 설정
프록시(Proxy)는 클라이언트와 서버 사이에 중계 역할을 하는 서버로, 클라이언트의 요청을 대신 전달하고 그 응답을 돌려주는 역할을 수행함.
- Proxy를 통해 IP 주소를 숨기거나, 보안·캐싱·필터링 같은 기능을 추가할 수 있음.
다음의 예제는 이런 프록시 서버를 코드에서 지정해 요청을 경유하는 방법을 보여줌.
import urllib.request
# ----------------------------------------
# 1. ProxyHandler 생성
# - 프로토콜별로 사용할 프록시 서버 지정
proxy_handler = urllib.request.ProxyHandler({
'http': 'http://proxy.example.com:8080',
'https': 'https://proxy.example.com:8080'
})
# ----------------------------------------
# 2. build_opener 로 오프너 생성
# - 기본 opener에 ProxyHandler를 추가/대체한 opener를 만듦
proxy_opener = urllib.request.build_opener(proxy_handler)
# ----------------------------------------
# 3. custom opener 사용
# - opener.open()을 사용하면 설정된 프록시를 거쳐 요청 전송
# - 여기서는 예시라 실제 프록시 주소는 동작하지 않음
# with proxy_opener.open('http://httpbin.org/ip') as response:
# print(response.read().decode('utf-8'))
예제: password 인증
특정 서버의 자원이 패스워드 인증을 요구할 경우 이를 처리하는 handler사용 예제.
build_opener의 사용법만을 보여주는 예제라 암호를 코드에 그대로 사용하는 등의 간단한 방식으로 구현함- 이는 보안이 매우 취약하므로 실무에서 절대 이 방식으로 해선 안됨.
- 사용된
HTTPPasswordMgrWithDefaultRealm은 인증 자격(사용자명·비밀번호)을 메모리에 단순 저장하는 구조임. - 실무에서는 환경 변수, 비밀 관리 시스템(AWS Secrets Manager, Kubernetes Secret 등등)을 활용하여 자격 증명을 안전하게 관리하는 방식을 사용.
- 실무에서 사용되는 자격 관리에 대한 부분은 이 문서의 범위 밖의 내용임.
- 또한 사용된
HTTPBasicAuthHandler객체는 HTTP Basic Authentication을 수행하며, 이 경우 사용자명과 비밀번호가 모두 Base64인코딩되어 전송되므로 암호화 안된 평문과 다름없음.- 이것도 매우 보안에 취약한 부분임.
- 단, HTTPS 환경에서 사용해야할 경우, 해결됨.
- uri 에서 항상
"https://로 시작해야함.
uri 는 uniform resourece identifier로 리소스를 식별하는 모든 문자열을 의미한다.
이 문서에서 url 대신에 uri라고 기재한 이유는
실제 URL의 앞부분에 붙여지는 문자열을 가리키고 있으며, 파이썬 공식문서에서도 uri라고 기재했기 때문임.
import urllib.request
# ----------------------------------------
# 1. 패스워드 매니저 생성
# - 요청 대상 서버와 사용자 계정을 관리
# - add_password(realm, uri, user, passwd)
# realm=None : 모든 realm에 적용 (해당 uri에서 패스워드가 필요한 자원을 realm으로 설정)
password_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(
None, # realm: None이면 기본 영역(Realm 무시하고 해당 uri 모조리 지정)
'https://httpbin.org', # 보호된 리소스가 있는 서버/도메인
'user', # 사용자명
'pass' # 비밀번호
)
# ----------------------------------------
# 2. BasicAuth 핸들러 생성
# - HTTP Basic 인증 처리기
# - 서버가 401 Unauthorized + WWW-Authenticate 헤더를 내려주면
# 이 핸들러가 사용자/비밀번호를 포함한 Authorization 헤더 생성
# - 즉, HTTPBasicAuthHandler는 401 응답 시 Authorization 헤더 자동 생성
auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager)
# ----------------------------------------
# 3. opener 생성
# - build_opener()로 auth_handler를 포함한 opener 구성
# - opener.open() 사용 시 자동으로 인증 헤더 추가
auth_opener = urllib.request.build_opener(auth_handler)
# ----------------------------------------
# 4. 요청 예시 (httpbin 기본 인증 테스트 엔드포인트)
# - /basic-auth/<user>/<passwd>
# - 올바른 user/pass를 넣으면 200 OK 반환
url = 'https://httpbin.org/basic-auth/user/pass'
with auth_opener.open(url) as response:
print(response.read().decode('utf-8'))
참고로, 401 응답코드는 요청이 서버에 도달했지만, 인증이 필요하거나 제공된 인증 정보가 잘못되었음을 의미함.
같이 보면 좋은 자료들
2025.08.29 - [Python] - [requests] Python의 requests 라이브러리 사용법.
[requests] Python의 requests 라이브러리 사용법.
requests는 Python에서 HTTP 요청을 간단하고 직관적으로 보낼 수 있게 해주는 가장 널리 쓰이는 라이브러리임.복잡한 소켓 프로그래밍이나 urllib 모듈보다 훨씬 쉬운 인터페이스를 제공GET/POST/PUT/PATCH
ds31x.tistory.com
'Python' 카테고리의 다른 글
| [Ex] 사칙연산기 (CLI) 간단 구현하기-입력받기. (0) | 2025.09.15 |
|---|---|
| [requests] Python의 requests 라이브러리 사용법. (2) | 2025.08.29 |
| Exceptions and Debugging Tools (1) | 2025.08.18 |
| CLI Program에서의 arguments - argparse모듈 (1) | 2025.08.12 |
| match statement-Structural Pattern Matching-match/case (3) | 2025.08.11 |