파이썬 기초 강좌 – 네트워크 프로그램에 관심이 있으신가요? 이 블로그 글에서는 Python을 사용하여 HTTP 프로토콜을 통해 웹 페이지를 가져오고, 데이터를 파싱하는 방법을 설명합니다. 소켓을 통해 데이터를 전송하고, urllib과 BeautifulSoup을 활용한 실용적인 예제들을 통해 네트워크 프로그래밍의 기초를 배우실 수 있습니다. 바이너리 파일 처리와 웹 스크래핑까지 다루는 이 글을 통해 더 깊이 있는 네트워크 프로그래밍 세계로 빠져보세요.
1. 파이썬 기초 강좌 – 하이퍼텍스트 전송 프로토콜(HTTP)
- HTTP 프로토콜은 웹의 기본 요소이며, Python의
socket
라이브러리에서 지원됩니다. - 소켓은 두 프로그램 간의 양방향 통신 채널을 제공하며, 네트워크를 통해 데이터를 읽고 쓸 수 있습니다.
2. 세계에서 가장 간단한 웹 브라우저
- HTTP를 설명하는 가장 간단한 방법은 Python에서 기본 웹 브라우저를 만드는 것입니다.
- 이 브라우저는 웹 서버에 연결하여 GET 요청을 보내고, 서버의 응답을 표시합니다.
- (실습)
# socket1.py
import socket
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect(('data.pr4e.org', 80))
cmd = 'GET http://data.pr4e.org/romeo.txt HTTP/1.0\r\n\r\n'.encode()
mysock.send(cmd)
while True:
data = mysock.recv(512)
if len(data) < 1:
break
print(data.decode(), end='')
mysock.close()
data.pr4e.org
서버의 80번 포트에 연결을 설정합니다.romeo.txt
파일을 가져오기 위한 GET 요청을 보냅니다.- 데이터를 512바이트씩 지속적으로 받아오고, 모두 받을 때까지 출력합니다.
3. HTTP를 통해 이미지 가져오기
- 이 섹션에서는 HTTP를 통해 바이너리 데이터를 가져와 로컬에 저장하는 예를 확장하여 설명합니다.
- (실습)
# urljpeg.py
import socket
import time
HOST = 'data.pr4e.org'
PORT = 80
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect((HOST, PORT))
mysock.sendall(b'GET http://data.pr4e.org/cover3.jpg HTTP/1.0\r\n\r\n')
picture = b""
while True:
data = mysock.recv(5120)
if len(data) < 1:
break
picture += data
mysock.close()
pos = picture.find(b"\r\n\r\n")
picture = picture[pos+4:]
with open("cover3.jpg", "wb") as fhand:
fhand.write(picture)
이 파이썬 코드는 네트워크 소켓을 사용하여 HTTP 요청을 보내고, 이미지를 다운로드하는 과정을 보여줍니다. 아래는 코드의 주요 단계를 요약한 설명입니다:
- 소켓 생성 및 연결:
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
을 사용하여 TCP 소켓을 생성합니다.mysock.connect((HOST, PORT))
을 통해 지정된 호스트(data.pr4e.org
)와 포트(80
)에 연결합니다.
- HTTP GET 요청 보내기:
mysock.sendall(b'GET http://data.pr4e.org/cover3.jpg HTTP/1.0\r\n\r\n')
을 사용하여 서버에 HTTP GET 요청을 보냅니다. 이 요청은cover3.jpg
파일을 요청합니다.
- 서버 응답 수신 및 데이터 저장:
picture = b""
를 사용하여 빈 바이트 문자열을 초기화합니다.while True
루프를 통해 서버로부터 데이터를 반복적으로 수신하여picture
변수에 추가합니다. 수신된 데이터가 없을 때 루프가 종료됩니다.
- 헤더 분리:
pos = picture.find(b"\r\n\r\n")
를 사용하여 HTTP 헤더와 본문을 구분하는 위치를 찾습니다.picture = picture[pos+4:]
를 통해 헤더를 제거하고 이미지 데이터만 남깁니다.
- 이미지 파일로 저장:
with open("cover3.jpg", "wb") as fhand:
를 사용하여 바이너리 쓰기 모드로cover3.jpg
파일을 엽니다.fhand.write(picture)
을 통해 이미지 데이터를 파일에 씁니다.
4. urllib을 사용하여 웹 페이지 가져오기
urllib
라이브러리는 HTTP의 저수준 세부 사항을 추상화하여 웹 페이지를 가져오는 과정을 단순화합니다.- (실습)
# urllib1.py
import urllib.request
fhand = urllib.request.urlopen('http://data.pr4e.org/romeo.txt')
for line in fhand:
print(line.decode().strip())
urllib.request.urlopen
을 사용하여 URL을 엽니다.- 파일처럼 객체의 줄을 반복하면서 각 줄을 디코딩하여 출력합니다.
5. urllib을 사용하여 바이너리 파일 읽기
- 텍스트 파일과 유사하게, 바이너리 파일도
urllib
을 사용하여 가져와 로컬에 저장할 수 있습니다. - (실습)
# curl1.py
import urllib.request
img = urllib.request.urlopen('http://data.pr4e.org/cover3.jpg').read()
with open('cover3.jpg', 'wb') as fhand:
fhand.write(img)
- URL에서 바이너리 파일을 모두 읽어옵니다.
- 바이너리 데이터를 로컬 파일에 ‘바이너리 쓰기’ 모드로 저장합니다.
6. HTML 파싱 및 웹 스크래핑
- 웹 스크래핑은 HTML 페이지를 가져와서 특정 패턴을 찾기 위해 데이터를 파싱하는 작업입니다.
- 정규 표현식이나 BeautifulSoup 같은 라이브러리를 사용하여 이를 수행할 수 있습니다.
- (실습) BeautifulSoup 사용
# pip install beautifulsoup4
import urllib.request
from bs4 import BeautifulSoup
url = 'http://www.dr-chuck.com/page1.htm'
html = urllib.request.urlopen(url).read()
soup = BeautifulSoup(html, 'html.parser')
tags = soup('a')
for tag in tags:
print(tag.get('href', None))
- URL을 열고 HTML 내용을 읽어옵니다.
- BeautifulSoup을 사용하여 HTML을 파싱합니다.
- 모든 하이퍼링크(
a
태그의href
속성)를 추출하여 출력합니다.
연습문제
연습문제 1
socket1.py
프로그램을 수정하여 사용자에게 URL을 입력받고, 잘못된 형식의 URL 또는 존재하지 않는 URL에 대한 오류를 처리합니다.- 샘플 실행:
Enter URL: http://data.pr4e.org/romeo.txt
... (출력 내용) ...
- 정답:
import socket
url = input('Enter URL: ')
try:
host = url.split('/')[2]
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect((host, 80))
cmd = f'GET {url} HTTP/1.0\r\n\r\n'.encode()
mysock.send(cmd)
while True:
data = mysock.recv(512)
if len(data) < 1:
break
print(data.decode(), end='')
mysock.close()
except:
print('Invalid URL')
연습문제 2
socket1.py
프로그램을 수정하여 3000자 이상을 출력하지 않도록 하되, 전체 문서를 가져와 문자 수를 세어 출력합니다.- 샘플 실행:
... (처음 3000자) ...
Total characters: 5678
- 정답:
import socket
url = input('Enter URL: ')
try:
host = url.split('/')[2]
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect((host, 80))
cmd = f'GET {url} HTTP/1.0\r\n\r\n'.encode()
mysock.send(cmd)
count = 0
while True:
data = mysock.recv(512)
if len(data) < 1:
break
count += len(data)
if count <= 3000:
print(data.decode(), end='')
print(f'\nTotal characters: {count}')
mysock.close()
except:
print('Invalid URL')
연습문제 3
urllib
을 사용하여 연습문제 2를 복제하고, 3000자까지 출력한 후 전체 문서의 문자 수를 세어 출력합니다.- 샘플 실행:
... (처음 3000자) ...
Total characters: 5678
- 정답:
import urllib.request
url = input('Enter URL: ')
try:
fhand = urllib.request.urlopen(url)
count = 0
while True:
data = fhand.read(512)
if len(data) < 1:
break
count += len(data)
if count <= 3000:
print(data.decode(), end='')
print(f'\nTotal characters: {count}')
except:
print('Invalid URL')
연습문제 4
urllinks.py
프로그램을 수정하여 HTML 문서에서 단락(p
) 태그를 세고, 프로그램의 출력으로 단락 수를 표시합니다.- 샘플 실행:
Paragraph count: 5
- 정답:
import urllib.request
from bs4 import BeautifulSoup
url = input('Enter URL: ')
try:
html = urllib.request.urlopen(url).read()
soup = BeautifulSoup(html, 'html.parser')
tags = soup('p')
print(f'Paragraph count: {len(tags)}')
except:
print('Invalid URL')
연습문제 5 (고급)
- 소켓 프로그램을 수정하여 헤더와 빈 줄을 받은 후에만 데이터를 표시합니다.
- 샘플 실행:
... (헤더 이후 데이터) ...
- 정답:
import socket
url = input('Enter URL: ')
try:
host = url.split('/')[2]
mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mysock.connect((host, 80))
cmd = f'GET {url} HTTP/1.0\r\n\r\n'.encode()
mysock.send(cmd)
header_done = False
while True:
data = mysock.recv(512)
if len(data) < 1:
break
if not header_done:
data_str = data.decode()
end_header = data_str.find('\r\n\r\n')
if end_header != -1:
header_done = True
data = data[end_header+4:]
else:
continue
print(data.decode(), end='')
mysock.close()
except:
print('Invalid URL')
- URL 입력:
url = input('Enter URL: ')
을 사용하여 사용자가 입력한 URL을 받습니다.
- 호스트 이름 추출 및 소켓 생성:
host = url.split('/')[2]
을 통해 URL에서 호스트 이름을 추출합니다.mysock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
을 사용하여 TCP 소켓을 생성합니다.
- 서버에 연결 및 HTTP GET 요청 보내기:
mysock.connect((host, 80))
을 통해 호스트의 80번 포트에 연결합니다.cmd = f'GET {url} HTTP/1.0\r\n\r\n'.encode()
를 통해 GET 요청 명령을 생성하고,mysock.send(cmd)
를 사용하여 서버에 보냅니다.
- 데이터 수신 및 헤더 분리:
header_done = False
로 헤더가 처리되지 않았음을 표시합니다.while True
루프를 통해 서버로부터 데이터를 반복적으로 수신합니다.- 수신된 데이터가 없으면 루프를 종료합니다 (
if len(data) < 1: break
). - 처음 수신된 데이터에서 헤더와 본문을 분리합니다. 헤더가 끝나는 위치를 찾고 (
end_header = data_str.find('\r\n\r\n')
), 헤더 이후의 데이터를 저장합니다. - 헤더가 처리되면
header_done = True
로 표시하고, 본문 데이터를 출력합니다 (print(data.decode(), end='')
).
- 소켓 닫기 및 예외 처리:
- 데이터 수신이 완료되면
mysock.close()
를 통해 소켓을 닫습니다. - 예외가 발생하면 (예: 잘못된 URL),
Invalid URL
메시지를 출력합니다.
네트워크 프로그래밍의 세계를 탐구하면서, 우리는 소켓을 이용한 간단한 웹 브라우저 구현부터 시작하여 HTTP 프로토콜을 이해하고 데이터를 가져오는 방법을 배웠습니다. 이러한 기본적인 소켓 프로그래밍 기술은 웹 스크래핑, 데이터 수집 및 다양한 네트워크 애플리케이션 개발에 필수적입니다. 또한, Python의 urllib 및 BeautifulSoup 라이브러리를 통해 복잡한 HTML 페이지를 쉽게 파싱하고 데이터를 추출하는 방법을 익혔습니다. 이를 통해 우리는 인터넷의 방대한 데이터에 접근하고 활용할 수 있는 강력한 도구를 손에 넣게 되었습니다. 이러한 지식을 바탕으로 더 복잡한 네트워크 프로그램을 작성하고, 다양한 데이터를 수집하여 유용한 인사이트를 도출할 수 있을 것입니다. 계속해서 학습하고 실습하여 네트워크 프로그래밍의 전문가가 되기를 바랍니다.
파이썬 기초 강좌: 11. 정규표현식 예제와 실습 (Python Basics – Regular expressions Examples and Exercises)
파이썬 기초 강좌: 13. 웹서비스 사용하기 예제와 실습 (Python Basics – Using Web Services Examples and Exercises)