2.1 웹 라이브러리 구성
- urllib 패키지 : 웹 클라이언트를 작성하는데 사용되는 모듈들이 들어있음.
- http 패키지 : 크게 서버용/클라이언트용 라이브러리로 나누어 모듈을 담고 있으며, 쿠키 관련 라이브러리도 http패키지 내 서버용과 클라이언트용으로 모듈이 구분된다.
웹클라이언트를 개발하는 경우 주로 urllib 패키지를 사용한다.
http.client 모듈이 HTTP 프로토콜 처리와 관련된 저수준의 클라이언트를 제공하는 반면, urllib 패키지의 모듈들은 HTTP 서버 뿐만 아니라 FTP서버 및 로컬 파일 등을 처리하는데 클라이언트에서 공통적으로 필요한 함수와 클래스를 제공하기 때문이다.
웹 서버 프로그래밍을 할때에는 http.cookie나 http.server 모듈을 사용하지 않고 장고와 같은 웹 프레임워크를 사용하여 개발한다.
2.2 웹 클라이언트 라이브러리
2.2.1 urllib.parse 모듈
: URL의 분해, 조립, 변경 및 URL 문자 인코딩, 디코딩 등을 처리하는 함수를 제공한다.
다음은 urlparse() 함수의 사용 예시이다. urlparse()함수는 URL을 파싱한 결과로 ParseResult 인스턴스를 반환한다.
ParseResult 클래스의 각 송성의 의미는 아래와 같다.
- scheme : URL에 사용된 프로토콜을 의미한다.
- netloc : 네트워크의 위치. 'user:password@host:port' 형식으로 표현되며 HTTP프로토콜인 경우 'host:port' 형식으로 표현된다.
- path : 파일이나 애플리케이션의 경로.
- params : 애플리케이션에 전달될 매개변수, 현재는 사용하지 않는다.
- query : 질의 문자열 또는 매개변수로 앰퍼샌드(&)로 구분된 '이름=값' 형식으로 표시된다.
- fragment : 문서내의 조각, 즉 특정 부분을 지정한다. # 으로 구분하며 앵커라고도 한다.
urlparse() 외에도 urlsplit(), urljoin(), parse_qs(), quote(), encode() 등의 함수가 있다.
참고 URL : https://docs.python.org/3/library/urllib.parse.html
2.2.2 urllib.request 모듈
urllib.request 모듈은 주어진 URL에서 데이터를 가져오는 기본 기능을 제공한다.
그 중 가장 기본이 되는 urlopen() 함수는 아래와 같은 형식으로 사용한다.
urlopen(url, data=None, [timeout])
- url 인자로 지정한 URL을 연결하고 유사 파일 객체를 반환한다. url 인자로 문자열이나 Request 클래스의 인스턴스가 올 수 있다.
- url에 file 스킴을 지정하면 로컬 파일을 열 수 있다.
- 디폴트 요청방식은 GET이고, 웹 서버에 전달할 파라미터가 있다면 질의 문자열을 url 인자에 포함해서 보낸다.
- 요청방식을 POST로 보내고 싶다면 data인자에 질의 문자열을 지정한다.
- 옵션인 timeout 은 응답을 기다리는 타임아웃 시간을 초단위로 표시한다.
# 상황별 urlopen() 함수 사용방법
사용 상황 | 사용 방법 |
URL로 GET/POST 방식의 간단한 요청 처리 | urlopen() 함수만으로 가능 |
PUT, HEAD 메소드 등 헤더 조작이 필요한 경우 | Request 클래스를 같이 사용 |
인증, 쿠키, 프록시 등 복잡한 요청 처리 | 인증/쿠키/프록시 해당 핸들러 클래스를 같이 사용 |
다음은 사용상황별 urlopen() 함수 사용 예시를 하나씩 살펴보자.
# urlopen()함수 - GET 방식 요청
from urllib.request import urlopen
f = urlopen("http://www.example.com")
print(f.read(500).decode('utf-8'))
위 프로그램은 웹 브라우저의 주소창에 "www.example.com"을 입력한 것과 동일한 데이터를 가져온다.
이 때 HTTP GET방식을 디폴트로 사용하여 웹 서버에 요청을 보내며 응답 문자열에서 500 바이트만 읽어서 출력한다.
# urlopen() -POST 방식 요청
- urloepn() 함수 호출 시 data 인자를 지정하면 함수는 자동으로 POST 방식으로 요청을 보낸다.
- data 인자는 URL에 허용된 문자열로 인코딩(urlencode) 되어야하고, 유니코드(str) 타입이 아니라 바이트스트링(bytes) 타입이어야한다.
POST 관련 실습 예제를 실행하기 위해 먼저 POST 요청을 처리할 수 있는 실습 서버가 필요하다. (이후 예제도 동일함)
실습 예제소스를 다운로드 후 ch2-test-server 폴더 내 'python manage.py runserver'를 실행하여 실습서버를 실행한다.
서버가 정상적으로 실행되었다면 'http://127.0.0.1:8000 접근 시 아래와 같이 확인이 가능하다.
실습서버를 실행한 후 아래와 같이 urlopen() 함수를 POST 방식으로 요청할 수 있다.
from urllib.request import urlopen
data = "language=python&framework=django"
f = urlopen("http://127.0.0.1:8000", bytes(data, encoding='utf-8'))
print(f.read().decode('utf-8'))
# urlopen() -Request 클래스로 요청 헤더 지정
만일 요청을 보낼 때 요청헤더를 지정하고 싶다면 URL 지정 방식을 변경하면 된다.
url 인자에 문자열 대신 Request 객체를 지정한다
즉 Request 객체를 생성하고 add_header()로 헤더를 추가하여 웹 서버로 요청을 보내면된다.
POST 요청을 보내려면 urlopen() 함수와 마찬가지로 Request 객체를 생성할 때 data 인자를 지정한다.
from urllib.request import urlopen, Request
from urllib.parse import urlencode
url = 'http://127.0.0.1:8000'
data = {
'name': '하가영',
'email': 'kayoung@naver.com',
'url': 'http://www.naver.com',
}
encData = urlencode(data)
postData = bytes(encData, encoding='utf-8')
req = Request(url, data=postData)
req.add_header('Content-Type', 'application/x-www-form-urlencoded')
f = urlopen(req)
print(f.headers)
print(f.read(500).decode('utf-8'))
# urlopen() - HTTPBasicAuthHandler 클래스로 인증 요청
인증데이터나 쿠키데이터를 추가하여 요청을 보내거나 프록시 서버로 요청을 보내는 등 HTTP의 고급기능을 포함하여 요청을 보낼 수도 있다.
이를 위해서는 각 기능에 맞는 핸들러 객체를 정의하고 그 핸들러를 build_opener() 함수로 오프너에 등록한다.
그런 다음 오프너의 open() 함수를 호출하면 서버로 요청이 전송된다.
urllib, request 모듈에 정의된 HTTPBasicAuthHandler 클래스를 사용하여 인증데이터를 같이 보내는 프로그램 예시를 살펴보자.
인증 데이터인 realm, user, passwd는 모두 서버에서 지정한 것으로 채워서 보낸다.
from urllib.request import HTTPBasicAuthHandler, build_opener
auth_handler = HTTPBasicAuthHandler()
auth_handler.add_password(realm='ksh', user='shkim', passwd='shkimadmin', uri='http://127.0.0.1:8000/auth/')
opener = build_opener(auth_handler)
resp = opener.open('http://127.0.0.1:8000/auth/')
print(resp.read().decode('utf-8'))
여기서 realm은 보안영역을 지정하는 파라미터로 user/passwd는 동일한 보안영역내에서만 유효하다.
realm 값은 서버로부터 받는 401 응답에서 알 수 있으며, realm 값을 추출하여 헤더에 담아 재요청하는 기능은 HTTPBasicAuthHandler에서 알아서 수행한다.
# urlopen() - HTTPCookieProcessor 클래스로 쿠키데이터를 포함하여 요청
urllib, request 모듈에 정의된 HTTPCookieProcessor 클래스를 사용하여 쿠키 데이터를 처리하는 프로그램을 만들어보자.
- 먼저 쿠키를 담기 위한 준비를 하고 서버로 요청을 보낸다.
- 첫번째 응답에서 받은 쿠키(sessionid)를 헤더에 담아서 요청을 보낸다.
- 만일 두번째 요청에 쿠키데이터가 없다면 서버에서 에러로 응답한다.
from urllib.request import Request, HTTPCookieProcessor, build_opener
url = 'http://127.0.0.1:8000/cookie/'
# first request (GET) with cookie handler
# 쿠키 핸들러 생성, 쿠키 데이터 저장은 디폴트로 CookieJar 객체를 사용함
cookie_handler = HTTPCookieProcessor()
opener = build_opener(cookie_handler)
req = Request(url)
resp = opener.open(req)
print("< first Response after GET Request > \n")
print(resp.headers)
print(resp.read().decode('utf-8'))
# second request (POST) with Cookie header
print("-------------------------------------------------------")
data = "language=python&framework=django"
encData = bytes(data, encoding='utf-8')
req = Request(url, encData)
resp = opener.open(req)
print("< second Response after POST Request > \n")
print(resp.headers)
print(resp.read().decode('utf-8'))
# urlopen() - ProxyHandler 및 ProxyBasicAuthHandler 클래스로 프록시 처리
urllib.request 모듈에 정의된 ProxyHandler와 ProxyBasicAuthHandler 클래스로 프록시 서버를 통과하여 웹 서버로 요청을 보내는 프로그램이다.
install_opener() 함수를 사용해 디폴트 오프너를 지정할 수도 있으니 주석을 확인하자.
이 예제를 실행하려면 프록시 서버가 필요하기 대문에 실습은 생략하고 각 코드에 있는 주석 설명으로 코드만 이해하고 넘어가자.
import urllib.request
url = 'http://www.example.com'
proxyServer = 'http://www.proxy.com:3128/'
# 프록시 서버를 통해 웹서버로 요청을 보냅니다.
proxy_handler = urllib.request.ProxyHandler({'http': proxyServer})
# 프록시 서버 설정을 무시하고 웹 서버로 요청을 보냅니다.
# proxy_handler = urllib.request.ProxyHandler({})
# 프록시 서버에 대한 인증을 처리합니다.
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
# 2개의 핸들러를 오프너에 등록합니다.
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
# 디폴트 오프너로 지정하면 urlopen() 함수로 요청을 보낼 수 있습니다.
urllib.request.install_opener(opener)
# opener.open() 대신에 urlopen()을 사용하였습니다.
f = urllib.request.urlopen(url)
print("geturl():", f.geturl())
print(f.read(300).decode('utf-8'))
2.2.3 urllib.request 모듈 예제
urllib 패키지를 활용하여 특정 웹사이트에서 이미지를 검색하여 리스트를 보여주는 코드 예제를 살펴보자.
from urllib.request import urlopen
from html.parser import HTMLParser # HTML 문서 파싱에 사용되는 클래스
class ImageParser(HTMLParser): # HTMLParser 클래스를 사용할때는 상속받는 클래스를 정의하고 필요한 내용을 오버라이드한다.
def handle_starttag(self, tag, attrs):
if tag != 'img': # img 태그를 찾는다.
return
if not hasattr(self,'result'):
self.result = []
for name, value in attrs: # img src 속성을 찾으면 self.result 리스트에 속성값을 추가한다.
if name == 'src':
self.result.append(value)
print("result : ", self.result) # result:['/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png', '/textinputassistant/tia.png']
def parse_image(data): # HTML 문장 내 이미지를 찾고 리스트를 출력
parser = ImageParser()
parser.feed(data) # HTMLParser 클래스를 이용해 HTML 문서를 파싱한 결과를 parser.result에 추가.
dataSet = set(x for x in parser.result) #파싱 결과를 dataSet으로 저장.(중복된 항목이 있다면 set() 기능에 의해 제거됨)
print(dataSet) # {'/textinputassistant/tia.png', '/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png'}
return dataSet
def main(): # 프로그램의 시작점. 구글 사이트를 검색하여 이미지를 찾음.
url = "http://google.co.kr"
with urlopen(url) as f: #구글사이트의 첫 페이지의 내용을 가져온다.
charset = f.headers.get_param('charset') #인코딩 방식(charset) 분석
data = f.read().decode(charset) # 해당 인코딩 방식으로 디코딩하여 data 변수에 할당
dataSet = parse_image(data) # parse_image() 함수 호출.
print("\n>>>>>>>>>> Fetch Images from ", url)
print('\n'.join(sorted(dataSet))) # 찾은 이미지를 라인별로 출력
if __name__ == '__main__':
main() #main() 함수호출
2.2.4 http.client 모듈
대부분의 웹 클라이언트 프로그램은 urllib.request 모듈의 기능만으로도 구현이 가능하다.
하지만 GET, POST 이외의 방식으로 요청을 보내거나, 요청 헤더와 바디 사이에 타이머를 지연시키는 등 urllib.request 모듈로는 쉽게 처리할 수 없는 경우나 HTTP 프로토콜 요청에 대한 세밀한 기능이 필요할 때 http.client 모듈을 사용한다.
urllib.request 모듈은 http.client 모듈에서 제공하는 API를 사용해서 만든 모듈이기 때문에 urllib.request 모듈로 작성한 로직은 http.client 모듈을 적용해도 동일하게 사용할 수 있다.
http.client 모듈을 사용하여 웹 클라이언트를 작성할때 아래와 같은 순서로 코딩하는 것을 권장한다.
- 연결객체 생성 : # conn = http.client.HTTPConnection("www.python.org")
- 요청을 보냄 : # conn.request("GET","/index.html")
- 응답객체 생성 : # response = conn.getresponse()
- 응답데이터를 읽음 : # data = response.read()
- 연결을 닫음 : # conn.close()
위의 http.client 모듈 사용 시 코딩 순서를 참고하여 GET, HEAD, POST, PUT 방식으로 요청을 보내는 방법을 살펴보자.
# http.client - GET 방식 요청
from http.client import HTTPConnection
host = 'www.example.com'
conn = HTTPConnection(host)
conn.request('GET','/')
r1 = conn.getresponse()
print(r1.status, r1.reason) #200 OK
data1=r1.read()
#data1 = r1.read(100)
conn.request('GET','/')
r2 = conn.getresponse()
print(r2.status, r2.reason) #200 OK
data2=r2.read()
print(data2.decode())
conn.close()
- HTTPConnection 클래스를 생성할 때 첫번째 인자는 url이 아니라 host이기 때문에 "https://www.example.com"으로 입력하면 에러가 발생된다.
- conn.request(method, url, body, headers) 형식으로 method, url 인자는 필수이고 body, headers 인자는 옵션이다. request(method) 인자를 통해 GET 방식임을 명시적으로 표현한다.
- r1.msg 속성에는 응답헤더 정보가 들어있다.(Content-Type: text/html; charset=UTF-8 등)
- 데이터를 모두 읽어야 다음 request()를 요청한다. 만일 일부만 읽은 상태에서 request()를 보내면 에러가 발생한다.
# http.client - HEAD 방식 요청
from http.client import HTTPConnection
host = 'www.example.com'
conn = HTTPConnection(host)
conn.request('HEAD','/')
resp = conn.getresponse()
print(resp.status, resp.reason) #200 OK
data=resp.read()
print(len(data)) # 0
print(data == b'') # True
- HTTPConnection 클래스를 생성할 때 첫번째 인자는 url이 아니라 host이기 때문에 "https://www.example.com"으로 입력하면 에러가 발생된다.
- conn.request(method, url, body, headers) 형식으로 method, url 인자는 필수이고 body, headers 인자는 옵션이다. request(method) 인자를 통해 HEAD 방식임을 명시적으로 표현한다.
- resp.msg 속성에는 응답헤더 정보가 들어있다.(Content-Type: text/html; charset=UTF-8 등)
- HEAD 요청에 대한 응답에 헤더는 있지만 바디가 없으므로 data 길이는 0이된다.
# http.client - POST 방식 요청
from http.client import HTTPConnection
from urllib.parse import urlencode
host = '127.0.0.1:8000'
params = urlencode({ # POST 요청으로 보낼 파라미터에 대하여 URL 인코딩 처리
'language' : 'python',
'name' : 'kayoung',
'email' : 'ka@naver.com',
})
headers = { # POST 요청으로 보낼 헤더를 사전 타입으로 지정
'Content-Type' : 'application/x-www-form-urlencoded',
'Accept' : 'text/plain',
}
conn = HTTPConnection(host) # 127.0.0.1:8000 접속 준비
conn.request('post','/', params, headers)
resp = conn.getresponse()
print(resp.status, resp.reason) #200 OK
data=resp.read()
print(data.decode('utf-8'))
conn.close()
- HTTPConnection 클래스를 생성할 때 첫번째 인자는 url이 아니라 host이기 때문에 "https://127.0.0.1:8000"으로 입력하면 에러가 발생된다.
- conn.request(method, url, body, headers) 형식으로 method, url 인자는 필수이고 body, headers 인자는 옵션이다. request(method) 인자를 통해 POST 방식임을 명시적으로 표현한다.
# http.client - PUT 방식 요청
from http.client import HTTPConnection
from urllib.parse import urlencode
host = '127.0.0.1:8000'
params = urlencode({ # PUT 요청으로 보낼 파라미터에 대하여 URL 인코딩 처리
'language' : 'python',
'name' : 'kayoung',
'email' : 'ka@naver.com',
})
headers = { # PUT 요청으로 보낼 헤더를 사전 타입으로 지정
'Content-Type' : 'application/x-www-form-urlencoded',
'Accept' : 'text/plain',
}
conn = HTTPConnection(host) # 127.0.0.1:8000 접속 준비
conn.request('PUT','/', params, headers)
resp = conn.getresponse()
print(resp.status, resp.reason) #200 OK
data=resp.read()
print(data.decode('utf-8'))
conn.close()
2.2.5 http.client 모듈 예제
from pathlib import Path
from http.client import HTTPConnection
from urllib.parse import urljoin, urlunparse
from urllib.request import urlretrieve
from html.parser import HTMLParser
class ImageParser(HTMLParser): # HTMLParser 클래스를 사용할때는 상속받는 클래스를 정의하고 필요한 내용을 오버라이드한다.
def handle_starttag(self, tag, attrs):
if tag != 'img': # img 태그를 찾는다.
return
if not hasattr(self, 'result'):
self.result = []
for name, value in attrs: # img src 속성을 찾으면 self.result 리스트에 속성값을 추가한다.
if name == 'src':
self.result.append(value)
def download_image(url, data): #ImageParser 클래스를 사용하여 이미지를 찾고 그 이미지를 DOWNLOAD 디렉터리에 저장.
downDir = Path('DOWNLOAD') # 만약 DOWNLOAD 디렉터리가 없다면 DOWNLOAD 디렉터리 생성.
downDir.mkdir(exist_ok=True)
parser = ImageParser()
parser.feed(data) # HTMLParser 클래스를 이용해 HTML 문서를 파싱한 결과를 parser.result에 추가.
dataSet = set(x for x in parser.result) #파싱 결과를 dataSet으로 저장.(중복된 항목이 있다면 set() 기능에 의해 제거됨)
for x in sorted(dataSet) : #dataSet을 정렬한 후 하나씩 처리
imageUrl = urljoin(url, x) # 내려받을 소스URL과 타깃 파일명 지정.(urljoin : baseURL과 파일명을 합쳐 완전한 URL을 리턴하는 함수)
basename = Path(imageUrl).name
targetFile = downDir / basename
print("imageUrl", imageUrl) # http://www.google.co.kr/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png
print("targetFile",targetFile) # DOWNLOAD\googlelogo_white_background_color_272x92dp.png
print("Downloading...", imageUrl)
urlretrieve(imageUrl, targetFile) # urlretrieve()은 src로부터 파일을 가져와 targetFile로 생성한다.
def main(): # 프로그램의 시작점. 구글 사이트를 검색하여 이미지를 찾음.
host = "www.google.co.kr" #HTTPConnection의 인자는 url이 아니라 host:port 형식이다.(port 생략시 디폴트 포트:80)
conn = HTTPConnection(host) #구글사이트의 첫 페이지의 내용을 가져온다.
conn.request("GET", '')
resp = conn.getresponse()
charset = resp.msg.get_param('charset') #인코딩 방식(charset) 분석
data = resp.read().decode(charset) # 해당 인코딩 방식으로 디코딩하여 data 변수에 할당
conn.close()
print("\n>>>>>>>>> Download Images from", host)
url = urlunparse(('http', host, '', '', '', '')) # urlunparse():url요소 6개를 튜플로 받아 조립하여 완성된 URL을 리턴하는 함수
download_image(url, data) #download_image 함수 호출
if __name__ == '__main__':
main()
'Spec UP - Backend > Django로 배우는 쉽고 빠른 웹개발 파이썬 프로그래밍' 카테고리의 다른 글
chap 3. Django 프레임워크 (1) | 2024.02.27 |
---|---|
CGI / WSGI / ASGI (0) | 2024.02.21 |
chap 02. CGI/WSGI 라이브러리 (0) | 2024.02.20 |
chap 02. 웹 서버 라이브러리 (0) | 2024.02.20 |
chap 01. 웹프로그래밍의 이해 (0) | 2024.02.01 |
댓글