URL (퍼센트) 인코딩이란?

URL 인코딩은 퍼센트 인코딩이라고도 불리며 URL에 문자를 표현하는 문자 인코딩 방법입니다. 알파벳이나 숫자 등 몇몇 문자를 제외한 나머지는 1바이트 단위로 묶인 16진수로 인코딩하는 방식입니다.

불곰

→ url encode

%EB%B6%88%EA%B3%B0

왜 해야 하는가

GET 방식을 통해 HTTP 요청을 할 때 쿼리 파라미터가 붙는 경우가 생기는데 URL은 ASCII 코드값만 사용됩니다. 이 쿼리 파라미터에 한글이 포함될 경우, ASCII 코드만으로 표현을 할 수 없어서 인코딩을 진행해야 합니다. 호출하는 API마다 쿼리 파라미터에 한글 문자 그대로를 지원하는 경우도 있지만 그렇지 않은 경우도 있으므로 미리 인코딩을 거친 형식으로 전송하는 것이 바람직합니다.

url encode

파이썬에서는 내장함수로 urllib라는 패키지를 사용해 url 인코딩을 진행할 수 있습니다.

from urllib import parse

url = parse.urlparse('https://brownbears.tistory.com?name=불곰&params=123')

query = parse.parse_qs(url.query)
result = parse.urlencode(query, doseq=True)

print(query)
print(result)


# {'name': ['불곰'], 'params': ['123']}
# name=%EB%B6%88%EA%B3%B0&params=123


parse.parse_qs로 쿼리스트링을 분리시켜 dict 타입으로 만든 다음, parse.urlencode(쿼리스트링(dict 타입), doseq=value에서 리스트 형태를 유지할 지 여부) 를 통해 쿼리 스트링 (파라미터)를 인코딩합니다. 만약 doseq 옵션을 False로 설정하면 아래와 같이 추출이 됩니다.

result = parse.urlencode(query) # doseq의 기본값은 False
print(result)


# name=%5B%27%EB%B6%88%EA%B3%B0%27%5D&params=%5B%27123%27%5D
# name=['불곰']&params=['123'] 를 인코딩한 결과와 동일


만약 튜플에 담긴 변수를 인코딩 하고 싶은 경우가 있는데 위와 동일하게 urlencode()를 사용하면 됩니다.

query = [('name', '불곰'), ('params', 123)]
result = parse.urlencode(query, doseq=True)

print(result)
# name=%EB%B6%88%EA%B3%B0&params=123


만약 변형할 때, 인코딩 charset을 지정하고 싶은 경우, urlencode에 encode 옵션값을 지정하면 됩니다.

result = parse.urlencode(query, encoding='UTF-8', doseq=True)
print(result)


# name=%EB%B6%88%EA%B3%B0&params=123


단순한 문자열을 인코딩 또는 디코딩을 하고 싶은 경우 아래와 같이 다른 함수를 사용하여 진행할 수 있습니다.

from urllib import parse


text = '불곰'

enc = parse.quote(text)
dec = parse.unquote(enc)

print(enc)
print(dec)


# %EB%B6%88%EA%B3%B0
# 불곰

urllib

위에서는 url 인코딩을 하는 방법만 설명했는데 urllib를 간략하게 설명하는 것이 좋아 보여서 아래에서 설명합니다.

from urllib import parse

url = parse.urlparse('https://brownbears.tistory.com?name=불곰&params=123')

print(url)


# ParseResult(scheme='https', netloc='brownbears.tistory.com', path='', params='', query='name=불곰&params=123', fragment='')


urlparse 함수에 url을 진행하면 <class 'urllib.parse.ParseResult'> 타입으로 위와 같이 결과가 반환됩니다.


urlparse에서 나오는 결과는 아래와 같이 세분화 할 수 있습니다.

from urllib import parse

# scheme://username:password@host:port/path;params?query#fragment
parse_result = parse.urlparse('https://brownbear:123@127.0.0.1:8080/path;params?name=불곰&params=123#id1')
print(parse_result)

print(parse_result.scheme)
print(parse_result.username)
print(parse_result.password)
print(parse_result.hostname)
print(parse_result.port)
print(parse_result.netloc)
print(parse_result.path)
print(parse_result.params)
print(parse_result.query)
print(parse_result.fragment)


# ParseResult(scheme='https', netloc='brownbear:123@127.0.0.1:8080', path='/path', params='params', query='name=불곰&params=123', fragment='id1')
# https
# brownbear
# 123
# 127.0.0.1
# 8080
# brownbear:123@127.0.0.1:8080
# /path
# params
# name=불곰&params=123
# id1

속성

  • scheme: http, https, ftp, smtp 등등 스키마
  • username: 사용자 명
  • password: 비밀번호
  • hostname: 호스트 명
  • port: 포트 번호
  • netloc: 네트워크 정보
  • path: 경로
  • params: url 파라미터
  • query: 쿼리 스트링
  • fragment: fragment 식별자


URL 에서 쿼리 스트링만 분리하는 방식은 위에서 설명한 parse.parse_qs와 parse.parse_qsl 이 있습니다. 둘의 차이는 반환 타입이 dictionary 인지 list 인지의 차이입니다.

from urllib import parse

# scheme://username:password@host:port/path;params?query#fragment
parse_result = parse.urlparse('https://brownbear:123@127.0.0.1:8080/path;params?name=불곰&params=123#id1')

a = parse.parse_qs(parse_result.query)
b = parse.parse_qsl(parse_result.query)

print(a)
print(b)


# {'name': ['불곰'], 'params': ['123']}
# [('name', '불곰'), ('params', '123')]