Python에서 URL 인코딩하는 방법 (urllib.parse 완벽 가이드)
Python의 URL 인코딩은 문자열을 percent-encoding하는 urllib.parse.quote()와 딕셔너리를 쿼리 문자열로 인코딩하는 urllib.parse.urlencode()를 사용합니다. 이 가이드에서는 quote(), unquote(), urlencode(), parse_qs()를 실용적인 예제와 함께 다룹니다.
quote()로 URL 인코딩하기
urllib.parse.quote() 함수는 문자열을 percent-encoding하기 위한 Python의 기본 도구입니다. URL에서 안전하게 사용할 수 없는 문자를 그에 대응하는 percent-encoding된 형태로 변환합니다. 기본적으로 슬래시(/)는 안전한 문자로 간주하지만, 이 동작은 원하는 대로 바꿀 수 있습니다.
from urllib.parse import quote
# 기본 인코딩
print(quote('hello world'))
# 출력: hello%20world
# 특수 문자 인코딩
print(quote('price=10&qty=2'))
# 출력: price%3D10%26qty%3D2
# 기본적으로 /는 인코딩되지 않음
print(quote('path/to/file'))
# 출력: path/to/file
# 슬래시도 인코딩하려면 safe=''로 설정
print(quote('path/to/file', safe=''))
# 출력: path%2Fto%2Ffile
# 유니코드 문자 인코딩
print(quote('cafe'))
# 출력: caf%C3%A9
# 추가로 안전한 문자를 지정
print(quote('key=value&foo=bar', safe='=&'))
# 출력: key=value&foo=bar
safe 매개변수는 무엇을 인코딩할지 제어하는 핵심입니다. 기본값은 safe='/'입니다. 영숫자와 _.-~를 제외한 모든 것을 인코딩하려면 safe=''로 설정하세요. 이는 JavaScript의 encodeURIComponent()와 동일합니다.
quote_plus()도 있는데, 이 함수는 quote()와 비슷하게 동작하지만 공백을 %20 대신 +로 인코딩합니다. 이는 HTML 폼 데이터(application/x-www-form-urlencoded)에서 사용되는 형식입니다.
from urllib.parse import quote_plus
print(quote_plus('hello world'))
# 출력: hello+world
print(quote_plus('key=value&name=John Doe'))
# 출력: key%3Dvalue%26name%3DJohn+Doe
unquote()로 URL 디코딩하기
urllib.parse.unquote() 함수는 percent-encoding을 되돌려서 %XX 시퀀스를 원래 문자로 변환합니다. unquote_plus()도 있는데, 이 함수는 추가로 + 기호를 공백으로 변환합니다.
from urllib.parse import unquote, unquote_plus
# 기본 디코딩
print(unquote('hello%20world'))
# 출력: hello world
print(unquote('caf%C3%A9'))
# 출력: cafe (악센트 포함)
# unquote는 +를 공백으로 변환하지 않음
print(unquote('hello+world'))
# 출력: hello+world
# unquote_plus는 +를 공백으로 변환
print(unquote_plus('hello+world'))
# 출력: hello world
# 전체 URL 디코딩
url = 'https://example.com/search?q=C%2B%2B%20programming'
print(unquote(url))
# 출력: https://example.com/search?q=C++ programming
폼 데이터를 디코딩할 때는 HTML 폼이 공백을 +로 인코딩하므로 항상 unquote_plus()를 사용하세요. 공백이 %20으로 인코딩되는 일반적인 URL 디코딩에는 unquote()를 사용하세요.
urlencode()로 쿼리 문자열 인코딩하기
urllib.parse.urlencode() 함수는 딕셔너리나 튜플 리스트를 받아 올바른 형식의 쿼리 문자열로 변환합니다. Python에서 쿼리 문자열을 만드는 가장 편리한 방법입니다.
from urllib.parse import urlencode
# 딕셔너리를 쿼리 문자열로
params = {
'q': 'python programming',
'page': 1,
'lang': 'en'
}
print(urlencode(params))
# 출력: q=python+programming&page=1&lang=en
# 튜플 리스트 (순서 유지, 중복 키 허용)
params = [
('tag', 'python'),
('tag', 'web'),
('sort', 'date')
]
print(urlencode(params))
# 출력: tag=python&tag=web&sort=date
# 리스트 값에 doseq=True 사용
params = {
'tag': ['python', 'web', 'api'],
'sort': 'date'
}
print(urlencode(params, doseq=True))
# 출력: tag=python&tag=web&tag=api&sort=date
# quote_via로 공백 인코딩 방식 제어
from urllib.parse import quote
params = {'q': 'hello world'}
print(urlencode(params, quote_via=quote))
# 출력: q=hello%20world (+ 대신 %20 사용)
기본적으로 urlencode()는 내부적으로 quote_plus()를 사용하므로 공백이 +가 됩니다. 공백을 %20으로 인코딩해야 한다면 위와 같이 quote_via=quote를 전달하세요.
parse_qs()로 쿼리 문자열 파싱하기
urllib.parse.parse_qs() 함수는 쿼리 문자열을 다시 딕셔너리로 파싱합니다. 쿼리 매개변수는 여러 값을 가질 수 있으므로 딕셔너리의 각 값은 리스트입니다. 튜플 리스트를 반환하는 parse_qsl()도 있습니다.
from urllib.parse import parse_qs, parse_qsl
# 쿼리 문자열을 딕셔너리로 파싱
qs = 'q=python+programming&page=1&lang=en'
result = parse_qs(qs)
print(result)
# 출력: {'q': ['python programming'], 'page': ['1'], 'lang': ['en']}
# 참고: 값은 항상 리스트
print(result['q'][0]) # 'python programming'
# 같은 키에 여러 값이 있는 경우 처리
qs = 'tag=python&tag=web&tag=api'
result = parse_qs(qs)
print(result)
# 출력: {'tag': ['python', 'web', 'api']}
# parse_qsl은 튜플 리스트를 반환
result = parse_qsl(qs)
print(result)
# 출력: [('tag', 'python'), ('tag', 'web'), ('tag', 'api')]
# 빈 값 유지 (기본적으로는 생략됨)
qs = 'name=John&email=&age=30'
print(parse_qs(qs, keep_blank_values=True))
# 출력: {'name': ['John'], 'email': [''], 'age': ['30']}
urlparse로 전체 URL 인코딩하기
완전한 URL을 다룰 때 Python의 urlparse()와 urlunparse() 함수를 사용하면 URL을 안전하게 분해하고 재구성할 수 있습니다. URL의 구조를 깨뜨리지 않고 특정 부분만 수정해야 할 때 특히 유용합니다.
from urllib.parse import urlparse, urlunparse, urlencode, quote
# URL을 구성 요소로 파싱
url = 'https://example.com/search?q=hello&page=1#results'
parsed = urlparse(url)
print(parsed.scheme) # 'https'
print(parsed.netloc) # 'example.com'
print(parsed.path) # '/search'
print(parsed.query) # 'q=hello&page=1'
print(parsed.fragment) # 'results'
# 구성 요소로부터 URL 조립
from urllib.parse import ParseResult
new_url = urlunparse(ParseResult(
scheme='https',
netloc='api.example.com',
path='/v2/search',
params='',
query=urlencode({'q': 'python & java', 'limit': 10}),
fragment=''
))
print(new_url)
# 출력: https://api.example.com/v2/search?q=python+%26+java&limit=10
# 특수 문자가 포함된 경로 세그먼트를 안전하게 추가
base = 'https://example.com/files/'
filename = 'my report (final).pdf'
safe_url = base + quote(filename, safe='')
print(safe_url)
# 출력: https://example.com/files/my%20report%20%28final%29.pdf
최신 Python 코드에서는 매개변수를 딕셔너리로 전달하면 URL 인코딩을 자동으로 처리해 주는 requests 라이브러리 사용을 고려해 보세요. httpx 라이브러리도 비슷한 자동 인코딩 기능을 제공합니다.
import requests
# requests는 인코딩을 자동으로 처리
response = requests.get(
'https://api.example.com/search',
params={
'q': 'python & java',
'page': 1,
'sort': 'relevance'
}
)
print(response.url)
# https://api.example.com/search?q=python+%26+java&page=1&sort=relevance