일단 파이썬을 사용해본 적이 없지만...
데이터 크롤링을 해야하기 때문에 실행방법을 찾아보면서 시도하려고 한다
일단 가져오려는 자료가 몇백개는 가볍게 넘기 때문에 빨리 성공을 해봐야 할 것 같다
사용 IDE : 인텔리제이 ultimate
Build #IU-253.30387.90, built on January 22, 2026
Runtime version: 21.0.9+1-b1163.94 amd64 (JCEF 137.0.17)
혹시 이런 자세한 정보도 필요할까봐...

크롤링을 위해서는 당연히 파이썬 프로젝트가 필요하다
이름의 통일성을 위해 docent_crawling이라는 이름으로 만들어줬고
생성 폴더도 본 프로젝트(docentNote)가 생성된 워크스페이스 폴더에 만들어줬다
인터프리터 타입 : project venv
프로젝트 풀더 안에 가상환경을 세팅해놨다는 뜻이다
프로젝트마다 필요한 도구 버전이 다를 수 있어서 가상환경을 세팅해놓는 편이 좋다
requests: 웹사이트에 접속해서 데이터를 가져오는 도구
BeautifulSoup4: 가져온 HTML 데이터에서 원하는 정보만 골라내는 도구
Selenium (선택): 로그인이 필요하거나 화면을 아래로 내려야 나오는 이미지를 잡을 때 사용
각 라이브러리를 사용하는 이유
: 크롤링 과정에서 맡은 역할이 뚜렷하고 효율적이기 때문
requests는 파이썬에서 가장 빠르고 간결하게 웹 서버에 "데이터 좀 주세요"라고 요청을 보내는 도구구체적인 사용 이유: 웹 브라우저(크롬, 엣지 등)를 띄우지 않고 오직 '텍스트 데이터'만 주고받기 때문에 속도가 압도적으로 빠르다
역할: 서버로부터 HTML 코드 덩어리나 이미지 바이너리(0과 1로 된 파일 데이터)를 내 컴퓨터로 실어 나르는 역할
한계: 단순히 데이터를 받아올 뿐이라서, 받아온 데이터 안에서 특정 정보를 찾는 기능은 없다
BeautifulSoup4는 requests가 가져온 지저분한 HTML 코드 뭉치를 파이썬이 이해할 수 있는 구조(Tree 구조)로 만들어주는 도구구체적인 사용 이유: HTML은 복잡한 태그(<div, <a>, <img)로 얽혀 있는데, 여기서 "클래스 이름이 'wp-post-image'인 이미지 주소만 뽑아줘" 같은 복잡한 명령을 단 한 줄로 수행할 수 있게 해준다
역할: 고화질 이미지의 src(주소)나 작품의 제목, 화가 이름 같은 '핵심 정보'만 핀셋으로 뽑아내는 필터링을 담당
한계: 스스로 웹사이트에 접속하는 기능은 없기 때문에 반드시 requests가 가져온 데이터가 있어야 작동
Selenium은 실제 웹 브라우저를 직접 띄워서 사람이 하는 행동(클릭, 스크롤, 타이핑)을 그대로 재현하는 자동화 도구구체적인 사용 이유:
동적 로딩 해결: 최근 웹사이트들은 스크롤을 끝까지 내려야만 이미지가 로딩되는 '무한 스크롤'** 방식이 많아졌다. requests는 첫 화면만 가져오지만, Selenium은 스크롤을 직접 내려서 숨겨진 이미지를 나타나게 할 수 있다
인증 및 상호작용: 로그인 버튼을 누르거나, 팝업창을 닫거나, 특정 버튼을 눌러야 데이터가 나오는 사이트에서 유일한 해결책이 된다
역할: 사람이 직접 브라우저를 조작하듯 행동하며 숨겨진 데이터를 억지로 끌어내는 조작을 담당
한계: 브라우저를 직접 띄우고 실행하므로 메모리를 많이 차지하고 속도가 매우 느리다
내가 크롤링 해야하는 사이트는 무한스크롤이 아닌 정적인 사이트지만
많은 사람들이 선택한 방법 == 오류상황에 대한 대처법이 하나라도 있을 가능성이 높다
그리고 나중에 동적인 페이지를 크롤링 할 상황이 생기면 여기에서 조금만 수정해주면 되기 때문에
앞으로의 활용 가능성을 생각해서 셀레니움을 사용하기로 했다
프레임워크는 내장된 터미널로 설치했다
제일 속 편하게 설치할 수 있는 방법이다

(.venv)
== 가상환경이 활성화 된 상태인걸 확인해야 한다
pip install requests beautifulsoup4 selenium webdriver-manager
== 프레임워크를 한번에 설치하는 명령어
requests: 웹 페이지의 데이터를 요청하고 가져오는 역할
beautifulsoup4: 가져온 HTML 코드에서 이미지 주소만 추출
selenium: 브라우저를 직접 조작해 스크롤을 내리거나 버튼을 누름
webdriver-manager: 셀레니움을 쓸 때 브라우저 드라이버를 자동으로 관리해줘서 편리
엔터를 누르면 뭐가 쭉 설치가 되는데 오른쪽 하단의 로딩바까지 다 사라지면 설치 확인을 해야한다
pip list
명령어를 입력했을 때 나오는 목록에서 네가지 라이브러리 이름이 다 떠있으면 설치 성공


프로젝트 이름에 우클릭 - new - 파이썬 파일
main 이라는 이름으로 파일을 생성해준다
import requests # 접속 도구 로드 #
from bs4 import BeautifulSoup # 추출 도구 로드 #
import os # 폴더 관리 도구 로드 #
# 1. 저장 폴더 준비 #
folder_name = 'crawled_images' # 이름 설정 #
if not os.path.exists(folder_name): # 폴더 존재 확인 #
os.makedirs(folder_name) # 폴더 생성 #
# 2. 대상 사이트 설정 #
url = 'https://www.google.com/search?q=dog&tbm=isch' # 주소 설정 #
headers = {'User-Agent': 'Mozilla/5.0'} # 브라우저 정보 설정 #
# 3. 데이터 수집 #
response = requests.get(url, headers=headers) # 사이트 데이터 가져옴 #
soup = BeautifulSoup(response.text, 'html.parser') # HTML 분석 설정 #
# 4. 이미지 추출 및 저장 #
images = soup.find_all('img') # 이미지 태그 추출 #
for i, img in enumerate(images[:10]): # 10개만 반복 설정 #
img_url = img.get('src') # 이미지 주소 가져옴 #
if img_url and img_url.startswith('http'): # 유효 주소 확인 #
try:
img_data = requests.get(img_url).content # 바이너리 가져옴 #
file_path = f'{folder_name}/dog_{i}.jpg' # 파일 경로 설정 #
with open(file_path, 'wb') as f: # 파일 생성 #
f.write(img_data) # 데이터 저장 #
print(f'{i}번 이미지 저장 완료') # 진행 상태 출력 #
except:
print(f'{i}번 이미지 저장 실패') # 에러 차단 #
해당 코드를 실행하면

설정해놓은 이름대로 폴더가 생기고

9번 이미지까지 저장이 됐다
테스트는 성공
그리고 놀랍게도 이 과정동안 오류가 없었다
너무 놀랍게도...
여기서 하나 고려할 점이 생겼다
지금 나는 그림만 몇백장을 저장해야하는데
이걸 한 폴더에 관리하는건 분명히 비효율적일 것 같다
../crawling_image/movement/painter 구조대로 관리를 하면 좋을 것 같은데

시각화 한다면 이런 식으로?
DB는 이미지를 파일 경로로 저장하는 방식을 사용한다 == 즉 텍스트 데이터
이렇게 폴더 구조를 세분화 한다면 어쩌다가 같은 이름의 그림이 있어도
url 즉 경로가 다르기 때문에 DB가 헷갈릴 일이 없다
일단 미술사조/화가 범위를 좁히자
크롤링 할 사이트의 카테고리에서
위주로 찾았다
르네상스 (2)
다빈치 https://artvee.com/artist/leonardo-da-vinci/
미켈란젤로 https://artvee.com/artist/michelangelo/
르네상스는 이름부터 유명하지만...
이 시대 그림은 사람들이 대부분 헐벗었고 종교화가 컨셉이었던 시대라서 예수의 고난이나 피흘리고 다친 상태의 그림들이 많아서 부득이하게 유명한 화가 두명만 선정했다
바로크 (7)
루벤스 https://artvee.com/artist/peter-paul-rubens/page/1/
렘브란트 https://artvee.com/artist/rembrandt-van-rijn/
요하네스 https://artvee.com/artist/johannes-vermeer/
벨라스케스 https://artvee.com/artist/diego-velazquez/
브뤼헐 https://artvee.com/artist/jan-brueghel-the-elder/
칼데이 https://artvee.com/artist/francesco-caldei/
혼토르스트 https://artvee.com/artist/gerard-van-honthorst/
로코코 (7)
와토 https://artvee.com/artist/jean-antoine-watteau/
부셰 https://artvee.com/artist/francois-boucher/
프라고나르 https://artvee.com/artist/jean-honore-fragonard/
티에폴로 https://artvee.com/artist/giovanni-battista-tiepolo/
브룅 https://artvee.com/artist/elisabeth-louise-vigee-le-brun/
카날레토 https://artvee.com/artist/canaletto/
게인즈버러 https://artvee.com/artist/thomas-gainsborough/
낭만주의 (4)
프리드리히 https://artvee.com/artist/caspar-david-friedrich/
터너 https://artvee.com/artist/joseph-mallord-william-turner/
콘스터블 https://artvee.com/artist/john-constable/
고야 https://artvee.com/artist/francisco-de-goya/
리얼리즘 (7)
쿠르베 https://artvee.com/artist/gustave-courbet/
밀레 https://artvee.com/artist/jean-francois-millet/
도미에 https://artvee.com/artist/honore-daumier/
레핀 https://artvee.com/artist/ilya-efimovich-repin/
닐슨 https://artvee.com/artist/amaldus-nielsen/
소른 https://artvee.com/artist/anders-zorn/
로비 https://artvee.com/artist/jean-baptiste-robie/
인상주의 (7)
모네 https://artvee.com/artist/claude-monet/
르누아르 https://artvee.com/artist/pierre-auguste-renoir/page/1/
드가 https://artvee.com/artist/edgar-degas/
피사로 https://artvee.com/artist/camille-pissarro/
마네 https://artvee.com/artist/edouard-manet/
모리조 https://artvee.com/artist/berthe-morisot/
소로야 https://artvee.com/artist/joaquin-sorolla/
탈인상주의 (7)
고흐 https://artvee.com/artist/vincent-van-gogh/
고갱 https://artvee.com/artist/paul-gauguin/page/3/
세잔 https://artvee.com/artist/paul-cezanne/
쇠라 https://artvee.com/artist/georges-seurat/
루아조 https://artvee.com/artist/gustave-loiseau/
뤼셀베르그 https://artvee.com/artist/theo-van-rysselberghe/
시다네르 https://artvee.com/artist/henri-le-sidaner/
아르누보 (6)
무하 https://artvee.com/artist/alphonse-mucha/
세레 https://artvee.com/artist/jules-cheret/
프리바리브몽 https://artvee.com/artist/henri-privat-livemont/
뮈니에 https://artvee.com/artist/georges-meunier/
닐슨 https://artvee.com/artist/kay-rasmus-nielsen/
클림트 https://artvee.com/artist/gustav-klimt/
삽화
레이엔데커 https://artvee.com/artist/joseph-christian-leyendecker/
양이 어마어마해서 크롤링 하는데 시간이 걸릴 것 같다
그래서 일단 두번으로 나눠서 처리하기로
여기서 한번 더 선택을 해야하는데
1. 원본 파일을 다운로드 받을까
2. 사이즈가 줄어든 썸네일을 다운받을까
일단 지금은 용량이 적은 썸네일로 다운받고 혹시나 나중에 내가 이 프로젝트를 더 발전시킨다면 그 때 원본파일을 다운로드 받아야겠다
import os # 폴더 관리 도구 로드 #
import requests # 접속 도구 로드 #
from bs4 import BeautifulSoup # 데이터 추출 도구 로드 #
import time # 차단 방지 지연 도구 로드 #
# 1. 경로 설정 #
base_path = r'C:\jje_works\sts5.0.1_workspace\docentNote\crawling_images' # 루트 경로 설정 #
# 2. 화가 데이터 정리 (1탄: 르네상스 ~ 낭만주의) #
art_data = {
'Renaissance': {
'Da_Vinci': 'https://artvee.com/artist/leonardo-da-vinci/',
'Michelangelo': 'https://artvee.com/artist/michelangelo/'
},
'Baroque': {
'Rubens': 'https://artvee.com/artist/peter-paul-rubens/',
'Rembrandt': 'https://artvee.com/artist/rembrandt-van-rijn/',
'Vermeer': 'https://artvee.com/artist/johannes-vermeer/',
'Velazquez': 'https://artvee.com/artist/diego-velazquez/',
'Brueghel': 'https://artvee.com/artist/jan-brueghel-the-elder/',
'Caldei': 'https://artvee.com/artist/francesco-caldei/',
'Honthorst': 'https://artvee.com/artist/gerard-van-honthorst/'
},
'Rococo': {
'Watteau': 'https://artvee.com/artist/jean-antoine-watteau/',
'Boucher': 'https://artvee.com/artist/francois-boucher/',
'Fragonard': 'https://artvee.com/artist/jean-honore-fragonard/',
'Tiepolo': 'https://artvee.com/artist/giovanni-battista-tiepolo/',
'Le_Brun': 'https://artvee.com/artist/elisabeth-louise-vigee-le-brun/',
'Canaletto': 'https://artvee.com/artist/canaletto/',
'Gainsborough': 'https://artvee.com/artist/thomas-gainsborough/'
},
'Romanticism': {
'Delacroix': 'https://artvee.com/artist/eugene-delacroix/',
'Friedrich': 'https://artvee.com/artist/caspar-david-friedrich/',
'Turner': 'https://artvee.com/artist/joseph-mallord-william-turner/',
'Constable': 'https://artvee.com/artist/john-constable/',
'Goya': 'https://artvee.com/artist/francisco-de-goya/'
}
} # 설정 #
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} # 브라우저 정보 설정 #
# 3. 크롤링 실행 #
for movement, painters in art_data.items(): # 사조별 반복 #
for painter, url in painters.items(): # 화가별 반복 #
# 폴더 생성 #
save_dir = os.path.join(base_path, movement, painter) # 계층 경로 설정 #
if not os.path.exists(save_dir): # 폴더 확인 #
os.makedirs(save_dir) # 폴더 생성 #
try:
response = requests.get(url, headers=headers) # 데이터 가져옴 #
soup = BeautifulSoup(response.text, 'html.parser') # HTML 분석 설정 #
# 수정한 부분: 따옴표 짝을 맞추고 클래스 선택 방식을 변경했습니다. #
items = soup.find_all('div', class_='product-element-top') # 컨테이너 추출 #
for i, item in enumerate(items[:20]): # 20개 반복 설정 #
img_tag = item.find('img') # 이미지 태그 가져옴 #
if img_tag:
# lazy 로딩용 주소 또는 일반 주소 가져옴 #
img_url = img_tag.get('data-lazy-src') or img_tag.get('src') # 주소 가져옴 #
if img_url:
img_data = requests.get(img_url).content # 바이너리 가져옴 #
file_name = f"{painter.lower()}_{i}.jpg" # 이름 설정 #
full_path = os.path.join(save_dir, file_name) # 경로 설정 #
with open(full_path, 'wb') as f: # 파일 생성 #
f.write(img_data) # 데이터 저장 #
print(f"[{movement}] {painter} - {i}번 저장 완료") # 상태 출력 #
time.sleep(1.0) # 지연 시간 설정 #
except Exception as e:
print(f"{painter} 크롤링 중 에러 발생: {e}") # 에러 차단 #
print("1탄 작업이 모두 끝났습니다!") # 완료 #
한꺼번에 너무 빨리 크롤링하면 차단먹을까봐 일부러 1초로 설정했는데
진짜 너무 오래걸린다... 그래도 차단만 안 당하면 좋겠다
그리고 여기까지도 아직 오류가 없다
감개무량이다
그리고 두번째 크롤링
import os # 폴더 관리 도구 로드 #
import requests # 접속 도구 로드 #
from bs4 import BeautifulSoup # 데이터 추출 도구 로드 #
import time # 지연 시간 설정 #
# 1. 절대 경로 설정 #
base_path = r'C:\jje_works\sts5.0.1_workspace\docentNote\crawling_images' # 루트 경로 설정 #
# 2. 화가 데이터 정리 (2탄) #
art_data = {
'Realism': {
'Courbet': 'https://artvee.com/artist/gustave-courbet/',
'Millet': 'https://artvee.com/artist/jean-francois-millet/',
'Daumier': 'https://artvee.com/artist/honore-daumier/',
'Repin': 'https://artvee.com/artist/ilya-efimovich-repin/',
'Nielsen_A': 'https://artvee.com/artist/amaldus-nielsen/',
'Zorn': 'https://artvee.com/artist/anders-zorn/',
'Robie': 'https://artvee.com/artist/jean-baptiste-robie/'
},
'Impressionism': {
'Monet': 'https://artvee.com/artist/claude-monet/',
'Renoir': 'https://artvee.com/artist/pierre-auguste-renoir/',
'Degas': 'https://artvee.com/artist/edgar-degas/',
'Pissarro': 'https://artvee.com/artist/camille-pissarro/',
'Manet': 'https://artvee.com/artist/edouard-manet/',
'Morisot': 'https://artvee.com/artist/berthe-morisot/',
'Sorolla': 'https://artvee.com/artist/joaquin-sorolla/'
},
'Post-Impressionism': {
'Gogh': 'https://artvee.com/artist/vincent-van-gogh/',
'Gauguin': 'https://artvee.com/artist/paul-gauguin/',
'Cezanne': 'https://artvee.com/artist/paul-cezanne/',
'Seurat': 'https://artvee.com/artist/georges-seurat/',
'Loiseau': 'https://artvee.com/artist/gustave-loiseau/',
'Rysselberghe': 'https://artvee.com/artist/theo-van-rysselberghe/',
'Sidaner': 'https://artvee.com/artist/henri-le-sidaner/'
},
'Art_Nouveau': {
'Mucha': 'https://artvee.com/artist/alphonse-mucha/',
'Cheret': 'https://artvee.com/artist/jules-cheret/',
'Livemont': 'https://artvee.com/artist/henri-privat-livemont/',
'Meunier': 'https://artvee.com/artist/georges-meunier/',
'Nielsen_K': 'https://artvee.com/artist/kay-rasmus-nielsen/',
'Klimt': 'https://artvee.com/artist/gustav-klimt/'
},
'Illustration': {
'Leyendecker': 'https://artvee.com/artist/joseph-christian-leyendecker/'
}
} # 설정 #
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} # 접속 설정 #
# 3. 크롤링 실행 #
for movement, painters in art_data.items(): # 사조 반복 #
for painter, url in painters.items(): # 화가 반복 #
# 저장 폴더 생성 #
save_dir = os.path.join(base_path, movement, painter) # 경로 설정 #
if not os.path.exists(save_dir): # 확인 #
os.makedirs(save_dir) # 생성 #
try:
response = requests.get(url, headers=headers) # 데이터 가져옴 #
soup = BeautifulSoup(response.text, 'html.parser') # 분석 설정 #
items = soup.find_all('div', class_='product-element-top') # 태그 추출 #
for i, item in enumerate(items[:20]): # 20개 제한 설정 #
img_tag = item.find('img') # 태그 가져옴 #
if img_tag:
img_url = img_tag.get('data-lazy-src') or img_tag.get('src') # 주소 가져옴 #
if img_url:
img_data = requests.get(img_url).content # 바이너리 가져옴 #
file_name = f"{painter.lower()}_{i}.jpg" # 이름 설정 #
full_path = os.path.join(save_dir, file_name) # 경로 설정 #
with open(full_path, 'wb') as f: # 파일 생성 #
f.write(img_data) # 데이터 저장 #
print(f"[{movement}] {painter} - {i}번 완료") # 상태 출력 #
time.sleep(1.0) # 지연 시간 설정 #
except Exception as e:
print(f"{painter} 에러 발생: {e}") # 에러 차단 #
print("2탄 (리얼리즘~삽화) 수집 완료!") # 완료 #
여기까지가 두번째로 나눈 크롤링 코드
실시간으로 폴더가 생기니까 좀 신기하다

하위폴더로 화가폴더까지 다 만들어졌다

초기 데이터를 위한 크롤링은 여기서 종료