크롤링(Crawling)은 웹사이트의 HTML 문서나 API 데이터를 자동으로 수집하여 원하는 정보를 추출하는 기술입니다. 사람이 직접 웹페이지를 열어 뉴스, 상품 정보, 댓글, 주가, 날씨 등을 확인하는 과정을 프로그램이 대신 수행한다고 생각하면 이해하기 쉽습니다.
HTML은 웹 브라우저에서 실행되는 웹페이지의 구조와 정보를 표현하기 위해 사용하는 표준 마크업 언어입니다. 문서의 제목, 본문, 이미지, 링크, 입력창 등 다양한 콘텐츠의 의미와 구조를 정의하며, 인터넷 상의 대부분의 웹페이지가 HTML을 기반으로 만들어집니다. HTML 자체는 프로그래밍 언어처럼 복잡한 연산을 수행하기보다는 웹 문서의 뼈대를 구성하는 역할을 하며, CSS는 화면의 디자인을 담당하고 JavaScript는 동적인 기능을 구현합니다. 또한 웹 크롤링에서는 사이트의 데이터가 HTML 구조 안에 저장되어 있기 때문에, 원하는 정보를 수집하고 분석하기 위해 반드시 이해해야 하는 핵심 기술로 사용됩니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>내 웹페이지</title>
</head>
<body>
<h1>안녕하세요</h1>
<p>파이썬 크롤링 수업입니다.</p>
</body>
</html>

Requests는 Python에서 웹 서버에 HTTP 요청을 보내고 응답을 받아오기 위해 사용하는 대표적인 라이브러리입니다. 웹 브라우저가 사이트에 접속할 때처럼 Python 코드에서도 웹페이지, 이미지, JSON 데이터 등을 서버로부터 가져올 수 있게 해주며, 웹 크롤링과 API 연동에서 가장 많이 사용되는 기본 도구 중 하나입니다. requests.get()으로 웹페이지를 요청하거나, requests.post()로 데이터를 서버에 전송할 수 있으며, 응답 결과로 HTML 코드, 이미지 데이터, 상태 코드 등을 쉽게 다룰 수 있습니다.
python -m pip install requests
Beautiful Soup의 from bs4 import BeautifulSoup 코드는 Python에서 HTML이나 XML 문서를 쉽게 분석하고 원하는 데이터를 추출하기 위해 BeautifulSoup 클래스를 불러오는 코드입니다. 여기서 bs4는 BeautifulSoup4 라이브러리를 의미하며, 웹 크롤링에서 가장 많이 사용하는 라이브러리 중 하나입니다.
# 설치
python -m pip install bs4
import requests
from bs4 import BeautifulSoup
# 가상 서버 주소
url = "http://127.0.0.1:5500/1.html"
# 서버에 요청 보내기
response = requests.get(url)
# HTML 코드 가져오기
html = response.text
# HTML 분석
soup = BeautifulSoup(html, "html.parser")
# 데이터 추출
h1 = soup.find("h1").text
p = soup.find("p").text
# 출력
print("제목:", h1)
print("내용:", p)
👉 find()는 Beautiful Soup에서 HTML 문서 안의 특정 태그를 검색할 때 사용하는 함수입니다. 태그 이름, 클래스(class), 아이디(id) 등의 조건을 기준으로 원하는 요소를 찾을 수 있으며, 조건에 맞는 첫 번째 요소만 반환합니다. 예를 들어 find("h1")은 첫 번째 <h1> 태그를 가져오고, .text를 사용하면 태그 안의 글자만 추출할 수 있습니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>뉴스</title>
</head>
<body>
<div>
<h1>뉴스 제목</h1>
<p>뉴스 내용</p>
</div>
</body>
</html>
👉 <div> 태그는 HTML에서 여러 요소를 하나의 영역으로 묶어주는 컨테이너(Container) 역할을 하는 태그입니다. 위 코드에서는 <div>가 부모 노드가 되고, 그 안에 <h1>과 <p> 태그가 자식 노드 형태로 포함되어 트리 구조를 형성합니다. 즉, 뉴스 제목과 뉴스 내용을 하나의 그룹으로 묶어 관리하는 역할을 하며, 웹페이지를 구조적으로 구분할 때 매우 자주 사용됩니다. 실제 웹사이트에서는 메뉴 영역, 게시글 영역, 댓글 영역 등을 대부분 <div> 단위로 나누어 구성하기 때문에, 웹 크롤링에서도 특정 <div> 내부의 데이터를 찾는 방식으로 원하는 정보를 추출하는 경우가 많습니다.
import requests
from bs4 import BeautifulSoup
# 가상 서버 주소
url = "http://127.0.0.1:5500/2.html"
# 서버에 요청 보내기
response = requests.get(url)
# HTML 가져오기
html = response.text
# HTML 분석
soup = BeautifulSoup(html, "html.parser")
# 부모 태그(div) 가져오기
div = soup.find("div")
# 자식 태그 가져오기
news_title = div.find("h1").text
news_content = div.find("p").text
# 출력
print("뉴스 제목:", news_title)
print("뉴스 내용:", news_content)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>하이퍼 링크</title>
</head>
<body>
<a href="https://naver.com">네이버</a>
<a href="https://google.com">구글</a>
</body>
</html>
👉 <a> 태그는 다른 웹페이지나 사이트로 이동할 수 있는 하이퍼링크(Hyperlink)를 만드는 태그입니다. href 속성에는 이동할 주소(URL)를 작성하며, 태그 사이의 글자는 실제 화면에서 클릭 가능한 링크 텍스트로 표시됩니다. 웹 크롤링에서는 <a> 태그의 href 값을 수집하여 게시글 주소, 상품 링크, 뉴스 URL 등을 추출하는 데 매우 자주 활용됩니다.
import requests
from bs4 import BeautifulSoup
# 가상 서버 주소
url = "http://127.0.0.1:5500/3.html"
# 서버 요청
response = requests.get(url)
# HTML 가져오기
html = response.text
# HTML 분석
soup = BeautifulSoup(html, "html.parser")
# 모든 a 태그 가져오기
links = soup.find_all("a")
# 링크와 글자 출력
for link in links:
text = link.text
href = link["href"]
print("사이트 이름:", text)
print("링크 주소:", href)
print()
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>이미지</title>
</head>
<body>
<img src="류지.jpeg" />
</body>
</html>
👉 <img> 태그는 웹페이지에 이미지를 출력할 때 사용하는 태그이며, src 속성에는 불러올 이미지 파일의 경로 또는 주소를 작성합니다. <img> 태그는 종료 태그 없이 사용하는 대표적인 단일 태그이며, 웹사이트의 사진, 상품 이미지, 프로필 이미지 등을 출력할 때 매우 많이 사용됩니다. 또한 웹 크롤링에서는 <img> 태그의 src 값을 수집하여 이미지 주소를 추출하거나 이미지를 다운로드하는 방식으로 활용됩니다.
import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
# HTML 페이지 요청
url = "http://127.0.0.1:5500/4.html"
response = requests.get(url)
# HTML 파싱
soup = BeautifulSoup(response.text, "html.parser")
# img 태그 찾기
img_tag = soup.find("img")
# 이미지 주소 가져오기
img_src = img_tag["src"]
# 상대경로 → 절대경로 변환
img_url = urljoin(url, img_src)
print("이미지 주소:", img_url)
# images 폴더 생성
os.makedirs("images", exist_ok=True)
# 이미지 다운로드
img_response = requests.get(img_url)
# 파일 이름 추출
file_name = img_src.split("/")[-1]
# 저장 경로
save_path = os.path.join("images", file_name)
# 이미지 저장
with open(save_path, "wb") as file:
file.write(img_response.content)
print("이미지 저장 완료")
pen() 함수는 파일을 읽거나 저장할 때 사용하는 함수입니다. 텍스트 파일(txt), CSV, 이미지 파일 등을 열어서 데이터를 읽거나 저장할 수 있으며, 크롤링·데이터분석·AI 개발 등 거의 모든 분야에서 사용되는 매우 중요한 기능입니다.
파일객체 = open("파일명", "모드", encoding="utf-8")

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>표</title>
</head>
<body>
<table border="1">
<tr>
<th>이름</th>
<th>나이</th>
</tr>
<tr>
<td>김사과</td>
<td>20</td>
</tr>
</table>
</body>
</html>
👉 <table> 태그는 데이터를 행(Row)과 열(Column) 형태로 정리하여 표(Table)를 만드는 태그입니다. 이러한 표 구조는 학생 정보, 상품 목록, 통계 데이터 등을 표현할 때 자주 사용되며, 웹 크롤링에서는 <table> 내부의 행과 열을 반복적으로 탐색하여 데이터를 수집하는 방식으로 많이 활용됩니다.
import requests
from bs4 import BeautifulSoup
# 가상 서버 주소
url = "http://127.0.0.1:5500/5.html"
# 서버 요청
response = requests.get(url)
# HTML 가져오기
html = response.text
# HTML 분석
soup = BeautifulSoup(html, "html.parser")
# table 가져오기
table = soup.find("table")
# 제목(th) 추출
headers = table.find_all("th")
print("제목")
for header in headers:
print(header.text)
# 내용(td) 추출
datas = table.find_all("td")
print("\n내용")
for data in datas:
print(data.text)
CSS는 HTML로 만들어진 웹페이지의 색상, 크기, 배치, 글꼴, 애니메이션 등 화면의 디자인과 스타일을 담당하는 스타일 시트 언어입니다. HTML이 웹페이지의 구조와 내용을 정의한다면, CSS는 그 요소들을 어떻게 보이게 할지를 결정하는 역할을 합니다. 예를 들어 글자 색상을 변경하거나, 이미지 크기를 조절하거나, 화면을 반응형으로 배치하는 작업 등을 CSS로 처리합니다. 또한 클래스(class)와 아이디(id)를 이용해 특정 요소에만 스타일을 적용할 수 있으며, 웹사이트를 더 보기 좋고 사용자 친화적으로 만드는 핵심 기술입니다.
/* style1.css */
#title {
font-size: 30px;
color: blue;
font-weight: bold;
}
.item {
border: 1px solid gray;
padding: 10px;
margin: 10px;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>쇼핑몰</title>
<link rel="stylesheet" href="style1.css" />
</head>
<body>
<div id="title">IT</div>
<div class="item">
<h2>노트북</h2>
<p>150만원</p>
</div>
<div class="item">
<h2>키보드</h2>
<p>10만원</p>
</div>
</body>
</html>
👉 id와 class는 HTML 요소를 구분하고 선택하기 위해 사용하는 속성입니다. id는 하나의 요소에만 부여하는 고유한 이름으로, 보통 페이지 안에서 단 하나만 존재해야 하며 특정 영역을 정확하게 선택할 때 사용합니다. 반면 class는 여러 요소에 공통으로 부여할 수 있는 이름으로, 같은 디자인이나 기능을 여러 곳에 반복 적용할 때 사용합니다. CSS에서는 id를 #이름 형태로 선택하고, class는 .이름 형태로 선택하여 스타일을 적용합니다. 또한 웹 크롤링에서도 특정 데이터 영역을 찾기 위해 id와 class를 기준으로 원하는 요소를 탐색하는 경우가 매우 많습니다.
import requests
from bs4 import BeautifulSoup
# 가상 서버 주소
url = "http://127.0.0.1:5500/6.html"
# 서버 요청
response = requests.get(url)
# HTML 가져오기
html = response.text
# HTML 분석
soup = BeautifulSoup(html, "html.parser")
# id 선택자
title = soup.select_one("#title").text
print("카테고리:", title)
# class 선택자
items = soup.select(".item")
for item in items:
name = item.select_one("h2").text
price = item.select_one("p").text
print("상품명:", name)
print("가격:", price)
print()
👉 select()는 Beautiful Soup에서 CSS 선택자(CSS Selector) 문법을 사용하여 원하는 HTML 요소들을 여러 개 선택할 때 사용하는 함수입니다. 태그명(h1), 클래스(.item), 아이디(#title) 등을 조합하여 원하는 요소를 탐색할 수 있으며, 조건에 맞는 모든 요소를 리스트 형태로 반환합니다. 예를 들어 soup.select(".item")은 class="item"인 모든 요소를 가져오며, 반복문과 함께 사용하여 상품 목록, 뉴스 리스트, 게시글 목록 등을 한 번에 크롤링할 때 매우 많이 사용됩니다. select_one()이 첫 번째 요소 하나만 가져온다면, select()는 여러 개의 요소를 모두 가져오는 차이가 있습니다.
import requests
from bs4 import BeautifulSoup
import pandas as pd
site = 'https://basicenglishspeaking.com/daily-english-conversation-topics/'
request = requests.get(site)
soup = BeautifulSoup(request.text, 'html.parser')
div = soup.find('div', {'class': 'thrv-columns'})
links = div.find_all('a')
data = []
for i, link in enumerate(links, start=1):
topic = link.text.strip()
url = link.get('href')
data.append({
'번호': i,
'토픽': topic,
'링크': url
})
df = pd.DataFrame(data)
df
import requests
from bs4 import BeautifulSoup
def daum_news(news_id):
url = f'https://v.daum.net/v/{news_id}'
headers = {
'User-Agent': 'Mozilla/5.0'
}
try:
request = requests.get(url, headers=headers)
# 요청 실패 시 에러 발생
request.raise_for_status()
soup = BeautifulSoup(request.text, 'html.parser')
# 제목 가져오기
title = soup.find('h3', {'class': 'tit_view'})
if title:
title = title.text.strip()
else:
title = '제목없음'
# 본문 가져오기
content = soup.find('div', {'class': 'article_view'})
if content:
content = content.text.strip()
else:
content = '내용없음'
return {
'title': title,
'content': content
}
except Exception as e:
print('에러 발생:', e)
return None
# 사용 예제
news = daum_news('20260606174204098')
print('제목:')
print(news['title'])
print('\n본문:')
print(news['content'])
👉 headers는 웹 브라우저가 서버에 보내는 추가 정보이며, 크롤링에서는 서버가 요청을 정상적인 브라우저 접속으로 인식하도록 도와주는 역할을 합니다. User-Agent는 “어떤 브라우저와 운영체제로 접속했는가”를 나타내는 정보로, 이를 설정하지 않으면 일부 사이트는 프로그램(봇) 접속으로 판단하여 데이터를 차단하거나 빈 페이지를 반환할 수 있습니다. Referer는 “어떤 페이지에서 이동해 왔는가”를 나타내는 정보이며, 벅스 같은 사이트는 내부 페이지 이동처럼 보여야 정상 응답을 주는 경우가 있어 함께 설정하는 경우가 많습니다. 즉, headers는 크롤링 프로그램이 실제 사람이 사용하는 브라우저처럼 보이게 만들어 사이트 차단을 줄이고 안정적으로 데이터를 가져오기 위해 사용하는 중요한 설정입니다.
robots.txt는 웹사이트 운영자가 검색 엔진이나 크롤링 프로그램(봇)에게 “어떤 페이지는 접근 가능하고, 어떤 페이지는 접근하지 말아야 하는지”를 알려주는 규칙 파일입니다. 보통 사이트 주소 뒤에 /robots.txt를 붙여 확인할 수 있으며, 예를 들어 https://example.com/robots.txt 형태로 접근합니다. 이 파일에는 특정 경로를 수집 금지(Disallow) 하거나 허용(Allow) 하는 규칙이 작성되어 있으며, 검색 엔진이나 크롤러는 이를 참고하여 사이트에 과도한 부하를 주지 않고 운영 정책을 존중하며 데이터를 수집합니다. 다만 robots.txt는 강제 차단 시스템이 아니라 “권장 규칙”에 가까우므로 기술적으로 접근은 가능할 수 있지만, 실무에서는 반드시 robots.txt와 사이트 이용약관을 확인하고 서버에 무리를 주지 않는 범위에서 크롤링하는 것이 중요합니다.
import requests
from bs4 import BeautifulSoup
import pandas as pd
def bugs_artist_tracks(artist_id):
url = f"https://music.bugs.co.kr/artist/{artist_id}/tracks"
headers = {
"User-Agent": "Mozilla/5.0",
"Referer": "https://music.bugs.co.kr/"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
rows = soup.select("table.list.trackList tbody tr")
data = []
for row in rows:
title_tag = row.select_one("p.title a")
artist_tag = row.select_one("p.artist a")
album_tag = row.select_one("a.album")
if not title_tag:
continue
title = title_tag.get_text(strip=True)
artist = artist_tag.get_text(strip=True) if artist_tag else ""
album = album_tag.get_text(strip=True) if album_tag else ""
data.append({
"곡명": title,
"아티스트": artist,
"앨범명": album
})
df = pd.DataFrame(data)
df.index = df.index + 1
file_name = f"bugs_artist_{artist_id}_tracks.csv"
df.to_csv(file_name, encoding="utf-8-sig")
return df
df = bugs_artist_tracks("20260859")
df
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
def bugs_artist_tracks_all_pages(artist_id, max_page=20):
headers = {
"User-Agent": "Mozilla/5.0",
"Referer": "https://music.bugs.co.kr/"
}
all_data = []
for page in range(1, max_page + 1):
url = (
f"https://music.bugs.co.kr/artist/{artist_id}/tracks"
f"?type=RELEASE&sort=P&page={page}&roleCode=0&highQualityOnly="
)
response = requests.get(url, headers=headers)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
rows = soup.select("table.trackList tbody tr")
page_data = []
for row in rows:
title_tag = row.select_one("p.title a")
artist_tag = row.select_one("p.artist a")
album_tag = row.select_one("a.album")
if title_tag is None:
continue
page_data.append({
"곡명": title_tag.get_text(strip=True),
"아티스트": artist_tag.get_text(strip=True) if artist_tag else "",
"앨범명": album_tag.get_text(strip=True) if album_tag else "",
"페이지": page
})
if len(page_data) == 0:
print(f"{page}페이지에 곡이 없어 종료합니다.")
break
all_data.extend(page_data)
print(f"{page}페이지 크롤링 완료: {len(page_data)}곡")
time.sleep(0.5)
df = pd.DataFrame(all_data)
if not df.empty:
df = df.drop_duplicates(subset=["곡명", "아티스트", "앨범명"])
df.index = df.index + 1
file_name = f"bugs_artist_{artist_id}_tracks.csv"
df.to_csv(file_name, encoding="utf-8-sig")
print(f"CSV 저장 완료: {file_name}")
print(f"총 {len(df)}곡 수집 완료")
return df