urllib(얼리브, 유얼리브)
# urllib.request의 핵심함수
- urlopen(url): URL을 열고 HTTP 요청을 보내는 가장 기본적이고 핵심적인 함수로 url 인자에는 일반 문자열 주소나 Request 객체를 모두 넣을 수 있습니다.
- Request(url, headers={}): 복잡한 HTTP 요청을 정의할 수 있는 클래스로 headers에 User-Agent(브라우저 정보)를 숨겨 넣거나, method에 'POST', 'PUT', 'DELETE' 등을 명시하여 단순 GET 외의 요청을 커스텀할 때 생성하여 urlopen에 전달합니다.
- urlretrieve(url): 네트워크의 파일을 로컬 컴퓨터로 즉시 다운로드하고 저장하는 편리한 함수입니다.
## urllib을 활용한 데이터 수집
import urllib.request
response = urllib.request.urlopen("https://sfac.dothome.co.kr/")
# print(type(response)) → <class 'http.client.HTTPResponse'>
# pre = response.read() # bytes 단위로 출력
# print(pre) → b'<!DOCTYPE html>\r\n~ <!-- [\xed\x95\x84\xec\x88\x98~
html = response.read().decode("utf-8")
print(html)
- import urllib.request
- 파이썬에 기본적으로 내장된 네트워크 관련 라이브러리입니다. 웹 페이지에 접속하고 데이터를 가져오는 기능을 수행합니다.
- response = urllib.request.urlopen("https://sfac.dothome.co.kr/")
- 해당 URL로 HTTP GET 요청을 보냅니다.
- 서버가 응답을 보내오면 그 결과를 response 객체에 담습니다. 이 객체 안에는 상태 코드(200 OK 등), 헤더 정보, 그리고 우리가 원하는 HTML 데이터가 들어있습니다.
- pre = response.read()
- 서버로부터 받은 데이터를 바이트(bytes) 형태의 날것 그대로 읽어옵니다.
- 사람이 읽을 수 있는 텍스트가 아니라 컴퓨터가 이해하는 이진 데이터(0과 1) 형태라, 눈으로 보면 깨진 문자처럼 보일 수 있습니다.
- html = response.read().decode("utf-8")
- read()로 읽어온 바이트 데이터를 utf-8 방식(웹에서 가장 많이 사용하는 문자 인코딩)으로 디코딩(해석)합니다.
- 이 과정을 거쳐야 비로소 우리가 웹 브라우저에서 보는 'HTML 태그가 포함된 텍스트'로 변환됩니다.
import urllib.request
url = "https://sfac.dothome.co.kr/"
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'}
request = urllib.request.Request(url, headers=headers)
response = urllib.request.urlopen(request)
html = response.read().decode('utf-8')
print(html)
- url = "https://sfac.dothome.co.kr/"
- 데이터를 수집하고자 하는 대상 웹 페이지의 주소를 변수에 저장합니다.
- headers = {'User-Agent': ...}
- [요청 헤더(Headers)]
- 웹 서버에게 "나는 웹 브라우저야"라고 속이는 정보입니다. 서버는 보안을 위해 아무런 정보 없이 접속하는 파이썬 코드의 요청을 무시하거나 차단하는 경우가 많은데, 이 User-Agent 값을 넣어주면 서버가 실제 사용자가 접속한 것으로 간주하여 데이터를 허용합니다.
- request = urllib.request.Request(url, headers=headers)
- [Request 클래스]
- 단순히 주소만 넘기는 게 아니라, 주소와 헤더 정보를 결합하여 하나의 '요청 패키지'를 만드는 과정입니다. 서버에게 보낼 봉투에 '수신인 주소'와 '보내는 사람(브라우저 정보)'을 함께 적어 넣는 것과 같습니다.
- response = urllib.request.urlopen(request)
- 앞서 만든 '요청 패키지'를 사용하여 실제로 서버에 접속하고 응답을 받아옵니다.
import urllib.request
image_url = "https://sfac.dothome.co.kr/images/kakao.png"
save_path = "kakao.png"
# filename, headers = urllib.request.urlretrieve(image_url, save_path)
try:
# 파일 다운로드 실행
filename, headers = urllib.request.urlretrieve(image_url, save_path)
print(f"다운로드 성공! 저장된 파일: {filename}")
except Exception as e:
print(f"다운로드 중 오류 발생: {e}")
- urllib.request.urlretrieve(image_url, save_path)
- [urlretrieve]
- 웹에 있는 파일을 특정 경로로 직접 다운로드하여 저장하는 함수입니다. 이전의 urlopen이 데이터를 '읽어와서(read)' 변수에 담는 방식이었다면, 이 함수는 읽어오는 즉시 파일로 생성해 줍니다.
- 반환값으로 저장된 파일의 경로(filename)와 서버 응답 정보(headers)를 넘겨줍니다. (반환값은 오직 2개)
- 첫 번째 자리(filename): 함수는 무조건 '다운로드된 파일 경로'를 보냅니다.
- 두 번째 자리(headers): 함수는 무조건 '서버의 응답 헤더 정보'를 보냅니다.
- try ... except Exception as e:
- [예외 처리 (Exception Handling)]
- 네트워크 환경은 변수가 많습니다(인터넷 끊김, 서버 접속 거부 등). 이때 프로그램이 무조건 멈추지 않도록, "에러가 발생하면(try) 그 내용을 출력하고(except) 정상적으로 종료하라"는 안전장치를 거는 코드입니다.
import urllib.request
# 다운로드 상태를 출력할 콜백 함수 정의
def show_progress(block_num, block_size, total_size):
"""
block_num: 지금까지 다운로드된 블록 수
block_size: 블록 한 개의 크기 (바이트)
total_size: 전체 파일 크기 (바이트, 서버가 제공하지 않으면 -1)
"""
if total_size > 0:
downloaded = block_num * block_size
percent = (downloaded / total_size) * 100
# \r을 사용하여 한 줄에서 숫자가 계속 갱신되도록 처리
print(f"\r다운로드 진행 중: {percent:.2f}%", end="")
else:
print("\r다운로드 중...", end="")
# 대용량 예시 파일
file_url = "https://sfac.dothome.co.kr/static/SQLyog855.zip"
save_path = "SQLyog855.zip"
print("다운로드를 시작합니다.")
urllib.request.urlretrieve(file_url, save_path, reporthook=show_progress)
print("\n다운로드가 완료되었습니다!")
- def show_progress(block_num, block_size, total_size):
- [콜백(Callback) 함수]
- 다운로드 진행 상황을 보고받기 위해 미리 정의해둔 함수입니다. urlretrieve 함수가 데이터를 조금씩 받아올 때마다, 중간중간 이 함수를 호출해서 현재 상태를 업데이트합니다.
- percent = (downloaded / total_size) * 100
- 전체 크기 대비 현재까지 받은 데이터 양을 계산하여 백분율(%)을 구합니다.
- print(f"\r다운로드 진행 중: {percent:.2f}%", end="")
- [\r (캐리지 리턴)]
- 줄 바꿈(\n) 대신 \r을 사용하면 커서가 줄의 맨 앞으로 이동합니다. 이를 통해 이전 출력값을 지우고 그 자리에 숫자를 계속 덮어씌워, 진행률이 한 줄에서 변하는 것처럼 보이게 만듭니다.
- end=""는 출력 후 자동으로 줄을 바꾸지 않도록 설정하는 옵션입니다.
- urllib.request.urlretrieve(..., reporthook=show_progress)
- [reporthook]
- urlretrieve의 옵션 중 하나입니다. "다운로드 상황을 보고할 때마다 show_progress 함수를 실행해달라"는 명령입니다.
# urllib.error
urllib.error에서 발생하는 두 예외는 오류가 발생한 시점(네트워크 연결 전 vs 연결 후)에 따라 명확히 구분됩니다.
URLError는 서버를 찾지 못했을 때 발생하고, HTTPError는 서버에는 접속했으나 서버가 에러를 응답했을 때 발생합니다.
## 1. URLError (서버 접속 실패)
서버와 네트워크 연결 자체가 이루어지지 않은 경우에 발생합니다. HTTP 프로토콜 단계까지 가지도 못한 상태입니다.
- 발생하는 경우:
- 인터넷 연결 끊김: 랜선이 뽑혔거나 와이파이가 연결되지 않았을 때잘못된
- URL 주소: 존재하지 않는 도메인 주소를 입력했을 때 (예: https://not-exists-domain-12345.com)
- 오타: 오타로 인해 없는 도메인을 요청했을 때
- 서버 다운: 요청한 웹 서버의 전원이 꺼져 있거나 물리적으로 작동하지 않을 때
- 방화벽 차단: 네트워크 방화벽이 해당 주소로의 접근을 막았을 때
## 2. HTTPError (서버 응답 오류)
서버에는 정상적으로 연결되었지만, 서버가 요청을 처리하는 과정에서 에러 코드(4xx 또는 5xx)를 반환한 경우에 발생합니다. HTTPError는 URLError 자식 클래스(하위 클래스)입니다.
- 발생하는 경우:
- 404 Not Found: 요청한 페이지나 파일(경로)이 서버에 존재하지 않을 때
- 403 Forbidden: 해당 페이지에 접근할 권한이 없을 때 (예: 로그인 필요, 크롤링 차단 등)
- 400 Bad Request: 요청 양식이 잘못되었을 때
- 500 Internal Server Error: 웹 서버 자체에 프로그램 오류가 발생했을 때
- 503 Service Unavailable: 서버가 과부하 상태이거나 점검 중일 때
import urllib.request
import urllib.error
# 다운로드할 정확한 파일 주소 (예시)
file_url = "https://sfac.dothome.co.kr/static/SQLyog855.zip"
save_path = "SQLyog855_agent.zip"
# 1. 브라우저로 위장할 User-Agent 설정
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
req = urllib.request.Request(file_url, headers=headers)
try:
print("다운로드를 시작합니다.")
# 2. urlopen에 Request 객체 전달 및 로컬 파일 쓰기 모드(wb) 오픈
with urllib.request.urlopen(req) as response, open(save_path, "wb") as out_file:
# 3. 서버가 알려준 전체 파일 크기 확인
total_size = int(response.getheader("Content-Length", 0))
downloaded = 0
block_size = 1024 * 8 # 8KB 단위로 끊어서 다운로드
while True:
# 4. 블록 단위로 읽기
buffer = response.read(block_size)
if not buffer:
break
downloaded += len(buffer)
out_file.write(buffer)
# 5. 실시간 진행률 출력
if total_size > 0:
percent = (downloaded / total_size) * 100
print(f"\r다운로드 진행 중: {percent:.2f}%", end="")
else:
print(f"\r다운로드 중... ({downloaded} bytes)", end="")
print("\n다운로드가 완료되었습니다!")
except urllib.error.HTTPError as e:
print(f"\nHTTP 오류 발생 ({e.code}): 주소가 틀렸거나 봇 차단 필터에 걸렸습니다.")
except Exception as e:
print(f"\n오류 발생: {e}")
- urllib.error.HTTPError
- 왜 발생하는 가? file_url이 잘못되었거나(404), 서버가 자동화된 접근(봇)을 차단했을 때 (403) 발생합니다.
- 중요포인트: e.code를 통해 400번대, 500번대 에러를 구분하여 디버깅할 수 있습니다.
- 일반 Exception (그 외 에러)
- urllib.error.URLError이 발생하면 이쪽으로 걸리게 됩니다.
- 대표적인 URLError 상황:
- DNS 오류: 도메인 주소를 IP로 변환하지 못할 때 (인터넷 끊김, 도메인 이름 오타)
- Connection Refused: 서버가 아예 응답을 거부하거나 서버 자체가 내려갔을 때
- Timeout: 서버 응답이 너무 오래 걸릴 때
# urllib.parse
## URL 파싱(Parsing)이란?
'파싱'이란 긴 문자열로 된 데이터를 컴퓨터가 이해할 수 있는 작은 단위(구성 요소)로 쪼개는 과정을 말합니다.
단순히 문자열을 자르는 것(split)보다 훨씬 안전합니다. 왜냐하면 URL에는 복잡한 규칙(프로토콜, 도메인, 포트, 경로 등)이 존재하는데, urlparse는 이 규칙에 맞춰 가장 완벽하고 안전하게 요소를 분리해주기 때문입니다.
from urllib.parse import urlparse, urlencode
url = 'http://www.example.com/path?query=1#fragment'
# URL 파싱
parsed_url = urlparse(url)
# http://www.example.com/path?query=1#fragment라는 통 문자열을 분석하여 객체(Object) 형태로 반환
print("파싱된 URL 구성 요소:")
print("프로토콜:", parsed_url.scheme) # http (데이터 통신 규약)
print("호스트:", parsed_url.hostname) # www.example.com (서버 주소)
print("경로:", parsed_url.path) # /path (서버 내 상세 경로)
print("쿼리:", parsed_url.query) # query=1 (전송할 데이터)
print("프래그먼트:", parsed_url.fragment) # fragment (페이지 내 특정 위치)
# 쿼리 문자열 인코딩 (새로운 파라미터 추가)
query_params = {'key1': 'value1', 'key2': 'value2'}
encoded_query = urlencode(query_params)
print("인코딩된 쿼리 문자열:", encoded_query)
# 새로운 URL 생성
new_url = f"{parsed_url.scheme}://{parsed_url.hostname}{parsed_url.path}?{encoded_query}"
print("새로운 URL:", new_url)
- 쿼리 문자열 인코딩: urlencode(query_params)
- 개념: 웹에서는 한글이나 공백, 특수문자를 그대로 URL에 넣으면 서버가 이해하지 못하거나 오류가 날 수 있습니다. 이를 안전하게 컴퓨터용 언어(Percent Encoding)로 바꾸는 작업이 인코딩
- urlencode는 딕셔너리 데이터를 key1=value1&key2=value2 형태의 올바른 웹 URL 형식으로 자동 변환해 줍니다.
- 핵심 요약
- urlparse: URL을 분석(쪼개기)해서 각 정보를 추출할 때 사용합니다.
- urlencode: 딕셔너리(데이터)를 변환(인코딩)해서 URL에 붙이기 좋은 문자열로 만들 때 사용합니다.
Request
# requests란 무엇인가?
우리가 브라우저 주소창에 주소를 입력하면 웹 서버로부터 정보를 받아오는 것처럼, 파이썬 코드로 이 과정을 대신 수행하는 도구
GET
import requests
# url = "https://sfac.dothome.co.kr/"
url = "http://ssacademy.dothome.co.kr/001.html"
response = requests.get(url)
print(response.text) # 문자열로 출력
# print(response.status_code) # HTTP 상태 코드 출력
# print(response.headers) # 응답 헤더 출력
# print(response.content) # bytes로 출력
# print(response.json()) # JSON으로 출력 (응답이 JSON 형식일 때) --> 딕셔너리로 자동 변환 반환
# print(response.encoding) # 응답 인코딩 출력
# print(response.url) # 최종 URL 출력 (리다이렉션이 있을 때)
# print(response.history) # 리다이렉션 히스토리 출력
# print(response.cookies.get_dict()) # 서버에서 받은 쿠키 출력 (딕셔너리 형태)
# print(response.elapsed) # 요청-응답 시간 출력
# print(response.cookies.get("cookie_name")) # 특정 쿠키 값 출력 (쿠키 이름이 "cookie_name"인 경우)
코드의 핵심 동작 분석
- requests.get(url): 해당 URL에 "데이터를 주세요"라고 요청(GET)을 보냅니다.
- response 객체: 서버로부터 받은 응답 전체를 담고 있는 상자입니다.
response.status_code
- 200 OK(요청 성공)
서버가 클라이언트의 요청을 성공적으로 처리했을 때 사용하는 가장 일반적인 상태 코드입니다. 보통 데이터를 조회하거나 수정, 삭제할 때 반환됩니다.
- 주요 사용 목적: 데이터 조회(GET), 기존 데이터 수정(PUT/PATCH), 데이터 삭제(DELETE)
- 의미: "네가 요청한 작업을 잘 끝냈고, 결과 데이터를 응답에 담아 보낼게."
- 응답 본문(Body): 클라이언트가 요청한 데이터나 처리 결과 메시지가 포함되는 경우가 많습니다.
- 201 Created(자원 생성 성공)
서버가 요청을 받아들여 새로운 자원(데이터)을 내부에 성공적으로 만들어냈을 때 사용하는 상태 코드입니다. 주로 회원가입이나 게시글 작성 같은 작업에서 사용됩니다.
- 주요 사용 목적: 새로운 데이터 추가(POST)
- 의미: "네가 요청한 데이터를 기반으로 서버에 새 레코드(자원)를 성공적으로 생성했어."
- 응답 헤더/본문: 새로 생성된 데이터의 고유 주소(URL) 가 응답 헤더의 Location 항목에 담겨 오거나, 본문에 새로 만들어진 객체 정보(예: 새로 발급된 id 번호 등)가 포함되는 것이 웹 표준 관례입니다.
POST
import requests
url = "https://example.com"
headers = {"User-Agent": "Mozilla/5.0"}
payload = {
"username": "my_id",
"password": "my_password"
}
response = requests.post(url, headers=headers, data=payload)
코드 핵심 동작 분석
- url: 데이터를 보낼 목적지 주소입니다.
- headers: 서버에게 "나는 브라우저(Mozilla/5.0)를 사용 중이야"라고 알려주어, 서버가 이를 로봇이 아닌 일반 사용자의 요청으로 인식하게 합니다.
- data=payload: 실제로 서버에 전달할 정보입니다. payload라는 딕셔너리에 담긴 username과 password 키-값 쌍이 서버로 전송됩니다.
requests.post()를 사용할 때 데이터를 보내는 방식은 크게 세 가지(params, data, json)로 나뉩니다.
1. params (URL 파라미터)
데이터를 URL 주소 뒤에 쿼리 스트링(?key=value)으로 붙여서 보냅니다. 주로 GET 요청에서 사용하지만, POST에서도 주소에 정보를 담아야 할 때 사용합니다.
- 용도: 검색 키워드, 페이지 번호, 필터링 조건 등.
- 형태: https://example.com/api?key1=value1
- 코드: requests.post(url, params={"id": "123"})
2. data (폼 데이터 - Form Data)
데이터를 웹 페이지의 HTML <form> 태그를 통해 전송하는 방식과 동일하게 보냅니다.
- 용도: 일반적인 로그인, 파일 업로드 등.
- Content-Type: 주로 application/x-www-form-urlencoded 헤더를 사용합니다.
- 형태: key1=value1&key2=value2 (문자열 직렬화)
- 코드: requests.post(url, data={"username": "my_id", "password": "123"})
3. json (JSON 데이터)
데이터를 JSON 형식(직렬화된 객체)으로 몸체(Body)에 담아 보냅니다. 최근 REST API 통신에서 가장 많이 사용하는 방식입니다.
- 용도: API 서버와 통신할 때, 복잡한 데이터 구조를 보낼 때.
- Content-Type: application/json으로 자동 설정됩니다.
- 형태: {"key1": "value1", "key2": "value2"} (파이썬 딕셔너리를 JSON 문자열로 변환)
- 코드: requests.post(url, json={"username": "my_id", "password": "123"})
| 구분 | params | data | json |
| 전달 위치 | URL 뒤 (Query String) | 요청 본문 (Body) | 요청 본문 (Body) |
| 주 목적 | 데이터 필터링/요청 | 폼 데이터 전송 | API 데이터 교환 |
| 데이터 형식 | key=value | key=value (URL-encoded) | JSON 문자열 |
| 주요 헤더 | 없음 | application/x-www-form-urlencoded | application/json |
* GET VS POST: 데이터 전송 방식 차이
| 구분 | GET | POST |
| 주 목적 | 정보를 가져올 때 사용 | 정보를 보낼 때 사용 |
| 데이터 전달 | URL 뒤에 데이터가 붙음 (예: ?id=123) | 메시지 **몸체(Body)**에 데이터가 숨겨짐 |
| 보안성 | 낮음 (주소창에 다 보임) | 상대적으로 높음 (Body에 숨겨짐) |
| 주 사용처 | 검색, 페이지 이동 | 로그인, 회원가입, 글쓰기 |
——————————————————————————
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
'학습일지' 카테고리의 다른 글
| [스나이퍼팩토리] 한컴AI아카데미(26.06.08) Selenium (0) | 2026.06.09 |
|---|---|
| [스나이퍼팩토리] 한컴AI아카데미(26.06.05) 웹 스크랩핑(BeautifulSoup) (0) | 2026.06.05 |
| [스나이퍼팩토리] 한컴AI아카데미(26.06.02) GitHub CI/CD (0) | 2026.06.04 |
| [스나이퍼팩토리] 한컴AI아카데미(26.05.29) Docker compose (0) | 2026.05.29 |
| [스나이퍼팩토리] 한컴AI아카데미(26.05.28) Docker Hub (0) | 2026.05.28 |