검색어가 정해져있을 때, 드라이브 한탭 에서 크롤링 하는 방법 📌
크롤링할때마다 새 창이 켜져서 예상시간 3일뜨는 거보고 남기는 기록,,
- 하나의 탭에서 크롤링 할수 있게 수정했고 10시간정도로 줄였다.
네이버 지도를 크롤링할 때, 주로 페이지번호 1, 페이지 번호 2 등 페이지 번호 기준으로 크롤링을 한다.
그러나 검색어를 지정하고, 검색어별/URL별로 크롤링을 해야할 때 속도와 성능을 최적화하는 코드를 설명해보고자 한다.
def crawl_multiple_naver_places(urls_data):
driver = None
all_places_data = []
try:
# Chrome 드라이버 한 번만 초기화
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
# 각 URL에 대해 크롤링 수행
for idx, url_data in enumerate(urls_data, 1):
try:
url = url_data['url']
print(f"\n크롤링 진행 중... ({idx}/{len(urls_data)}): {url}")
driver.get(url)
# iframe 전환
iframe = wait.until(EC.presence_of_element_located((By.ID, "entryIframe")))
driver.switch_to.frame(iframe)
# 1. 홈탭 정보 수집
base_info = get_base_info(driver, wait)
# 2. 리뷰탭 정보 수집
review_info = get_review_info(driver, wait)
# 원본 데이터와 크롤링 데이터 병합
# original_data에서 첫 번째 쉼표까지만 가져오기
original_data = url_data['original_data'].split(',')[0]
# 장소별 정보를 리스트에 추가
place_data = {
"original_data": original_data, # 가맹점명만 포함
"base_info": base_info,
"review_info": review_info
}
all_places_data.append(place_data)
# 기본 프레임으로 돌아가기
driver.switch_to.default_content()
print(f"✓ {idx}번째 장소 크롤링 완료")
except Exception as e:
print(f"⚠ {idx}번째 URL 크롤링 실패: {str(e)}")
continue
# 모든 데이터를 하나의 JSON 파일로 저장
if all_places_data:
save_multiple_to_json(all_places_data)
return all_places_data
except Exception as e:
print(f"크롤링 중 치명적 에러 발생: {str(e)}")
return None
finally:
if driver:
driver.quit()
print("\n크롤링 완료 - 드라이버 종료")
사용 예시 - TEST
if __name__ == "__main__":
urls = [
{
"original_data": "통큰돼지, 가정식, 제주 제주시 용담이동 2682-9번지 통큰돼지",
"search_term": "통큰돼지 용담이동",
"url": "https://map.naver.com/p/search/%ED%86%B5%ED%81%B0%EB%8F%BC%EC%A7%80%20%EC%9A%A9%EB%8B%B4%EC%9D%B4%EB%8F%99"
},
{
"original_data": "한그릇, 단품요리 전문, 제주 서귀포시 색달동 2315-1번지 한그릇",
"search_term": "한그릇 색달동",
"url": "https://map.naver.com/p/search/%ED%95%9C%EA%B7%B8%EB%A6%87%20%EC%83%89%EB%8B%AC%EB%8F%99"
}
]
result = crawl_multiple_naver_places(urls)
단계별로 설명해보겠다
STEP 1 . 함수 정의와 초기화, Chrome 웹드라이버 설정
def crawl_multiple_naver_places(urls_data):
driver = None
all_places_data = []
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
URL과 원본 데이터를 포함한 딕셔너리 리스트를 매개변수로 받는다.
Chrome 웹드라이버는 최초에 크롤링 창을 열때 초기화한다.
WebDriverWait로 최대 10초까지 대기하도록 설정한다.
for idx, url_data in enumerate(urls_data, 1):
url = url_data['url']
print(f"\n크롤링 진행 중... ({idx}/{len(urls_data)}): {url}")
각 URL 에 대해 순차적으로 크롤링을 수행한다. (진행 상황 표시)
여러 URL을 반복문을 통해 드라이버를 재사용하여 효율적으로 처리할 수 있다.
✅ WebDriverWait 이란?
WebDriverWait(driver, 10)은 Selenium에서 특정 조건을 만족할때까지 대기하는 명시적 대기 기능을 제공합니다.
지정된 조건이 만족될 때까지 대기하다가 , 조건 만족 시 즉시 다음 코드 실행한다.
최대 대기 시간 초과 시 TimeoutException가 발생한다.
# 기본 설정
wait = WebDriverWait(driver, 10) # 최대 10초 대기
# 사용 예시
# 요소가 클릭 가능할 때까지 대기
element = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.submit")))
# iframe이 존재할 때까지 대기
iframe = wait.until(EC.presence_of_element_located((By.ID, "entryIframe")))
✅ expected_conditions 이란?
# iframe이 존재할 때까지 대기
iframe = wait.until(EC.presence_of_element_located((By.ID, "entryIframe")))
위 코드를 예시로 보면, Selenium의 'Expected Conditions(EC)'는 특정 요소가 DOM에 존재할때까지 기다리는 조건으로,
By.ID를 통해 "entryframe"요소가 나올때 까지 대기하는 코드이다.
구성요소:
- presence_of_element_located: 요소가 DOM에 존재하는지 확인하는 메서드
- By.ID: 요소를 찾을 방법 (ID로 찾기)
- "entryIframe": 찾고자 하는 요소의 ID 값
from selenium.webdriver.support import expected_conditions as EC
# 1. 요소가 존재하는지 확인
wait.until(EC.presence_of_element_located((By.ID, "myElement")))
# 2. 요소가 클릭 가능한지 확인
wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.submit")))
# 3. 요소가 화면에 보이는지 확인
wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "menu")))
# 4. 요소가 사라질 때까지 대기
wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, "loading")))
# 5. 특정 텍스트가 요소에 존재하는지 확인
wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, "price"), "1000원"))
요소를 찾는 다른 방법들
# ID로 찾기
By.ID
# CSS 선택자로 찾기
By.CSS_SELECTOR
# 클래스 이름으로 찾기
By.CLASS_NAME
# XPath로 찾기
By.XPATH
STEP2. 네이버 지도의 iframe을 찾아 전환한다. (동적 네트워크 웹 크롤링의 안정성 향)
✅ iframe이란?
iframe(Inline Frame)은 웹페이지 안에 또 다른 웹페이지를 삽입할 수 있는 HTML 요소, "웹페이지 속의 웹페이지"
예)
지도 서비스 내에 가게 정보를 보여줄 때
유튜브 영상을 다른 웹사이트에 임베드할 때
페이스북 좋아요 버튼을 웹사이트에 넣을 때
- iframe에 접근하기 위해서는 반드시 해당 iframe으로 전환해야함
- 작업 완료 후에는 반드시 기본 프레임으로 돌아가야 함
- iframe이 로드될 때까지 기다려야 할 수 있음 (아래 코드에서 WebDriverWait 사용)
<iframe id="entryIframe" src="페이지주소">
<!-- iframe 내부의 콘텐츠 -->
</iframe>
네이버 지도는 메인 페이지 안에 가게 상세 정보를 iframe으로 보여주는 구조를 사용하고 있어서, 크롤링 시 이 iframe으로의 전환이 필요하다. iframe으로 아래 창(entryIframe)으로 전환한다.
# iframe이 나타날 때까지 최대 10초 기다림
iframe = wait.until(EC.presence_of_element_located((By.ID, "entryIframe")))
# 찾은 iframe으로 드라이버의 컨텍스트 전환
driver.switch_to.frame(iframe)
Selenium의 By.ID를 통해 "entryframe"요소가 나올때 까지 대기한다.
- EC: Expected Conditions의 약자
- presence_of_element_located: 요소가 DOM에 존재하는지 확인하는 메서드
- By.ID: 요소를 찾을 방법 (ID로 찾기)
- "entryIframe": 찾고자 하는 요소의 ID 값
STEP3.데이터를 수집한다
base_info = get_base_info(driver, wait) # 홈탭 정보
review_info = get_review_info(driver, wait) # 리뷰탭 정보
두개의 별도 함수를 통해 기본 정보와 리뷰 정보를 수집했다.
따로 설명하진 않겠다.
selenuim을 사용해, HTML코드를 분석해 크롤링했다.
##### 탭 별로 크롤링하는 함수 정의
# 1. 홈 탭의 정보 저장
def get_base_info(driver, wait):
try:
# # 홈 탭 클릭
# home_tab = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a._tab-menu[href*='/home']")))
# driver.execute_script("arguments[0].click();", home_tab)
# time.sleep(3) # 페이지 로딩을 위한 대기 시간
# 기본 정보 수집
base_info = {
"store_name": safe_find_element(driver, By.CLASS_NAME, "GHAhO"),
# "address": safe_find_element(driver, By.CLASS_NAME, "LDgIH"),
"business_type": safe_find_element(driver, By.CLASS_NAME, "lnJFt"),
"rating": safe_find_element(driver, By.CSS_SELECTOR, "span.PXMot.LXIwF").replace("별점", "").strip(),
"visitor_reviews": safe_find_element(driver, By.CSS_SELECTOR, "a[href*='/review/visitor']").replace("방문자 리뷰 ", ""),
"blog_reviews": safe_find_element(driver, By.CSS_SELECTOR, "a[href*='/review/ugc']").replace("블로그 리뷰 ", ""),
"location_info": safe_find_element(driver, By.CLASS_NAME, "zPfVt"),
"phone": safe_find_element(driver, By.CLASS_NAME, "xlx7Q"),
"Amenities and Services": safe_find_element(driver, By.CLASS_NAME, "xPvPE")
}
# 영업 시간 수집
try:
# 요일별 영업시간 수집
business_hours = {}
# # 영업시간 버튼 클릭 (펼치기)
# try:
# business_button = driver.find_element(By.CSS_SELECTOR, "a.gKP9i.RMgN0")
# driver.execute_script("arguments[0].click();", business_button)
# time.sleep(1) # 클릭 후 잠시 대기
# except:
# pass
# # 요일별 영업시간 요소들 찾기
# time_elements = driver.find_elements(By.CSS_SELECTOR, "div.w9QyJ:not(.vI8SM):not(.yN6TD) span.A_cdD")
# for element in time_elements:
# try:
# day = element.find_element(By.CLASS_NAME, "i8cJw").text
# hours = element.find_element(By.CLASS_NAME, "H3ua4").text
# business_hours[day] = hours
# except Exception as e:
# continue
# 정기 휴무일 정보 수집 (CSS 선택자 수정)
try:
holiday_element = driver.find_element(By.CSS_SELECTOR, "div.w9QyJ.yN6TD span.A_cdD")
holiday_info = holiday_element.text
except:
holiday_info = "정보 없음"
# # 정렬된 영업시간 정보를 base_info에 추가
# day_order = ['월', '화', '수', '목', '금', '토', '일']
# ordered_business_hours = {}
# for day in day_order:
# ordered_business_hours[day] = business_hours.get(day, '영업시간 정보 없음')
# base_info["business_hours"] = ordered_business_hours
# if holiday_info and holiday_info != "정보 없음":
# base_info["holiday_info"] = holiday_info
# else:
# base_info["holiday_info"] = "휴무일 정보 없음"
except Exception as e:
# print(f"영업 시간 수집 중 에러: {str(e)}")
# base_info["business_hours"] = {}
base_info["holiday_info"] = "정보 없음"
return base_info
except Exception as e:
print(f"기본 정보 수집 중 에러: {str(e)}")
return None
# 2. 리뷰 탭 정보 저장
def get_review_info(driver, wait):
try:
# 리뷰 탭 클릭
review_tab = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a._tab-menu[href*='/review']")))
driver.execute_script("arguments[0].click();", review_tab)
time.sleep(0.2) # 탭 전환 대기 0.5초 - 매우 빠름 / 3초 - 안정적
review_info = {
"menu_keywords": [],
"characteristic_keywords": [],
"reviews": [],
"companions": []
}
# 1. 메뉴 키워드 수집
try:
menu_keywords = driver.find_elements(By.XPATH, "//span[contains(@class, 'TT3eD') and text()='메뉴']/following-sibling::span//a[@class='T00ux']/span[1]")
review_info["menu_keywords"] = [keyword.text for keyword in menu_keywords if keyword.text.strip()]
except Exception as e:
print(f"메뉴 키워드 수집 중 에러: {str(e)}")
# 2. 특징 키워드 수집
try:
characteristic_keywords = driver.find_elements(By.XPATH, "//span[contains(@class, 'TT3eD') and text()='특징']/following-sibling::span//a[@class='T00ux']/span[1]")
review_info["characteristic_keywords"] = [keyword.text for keyword in characteristic_keywords if keyword.text.strip()]
except Exception as e:
print(f"특징 키워드 수집 중 에러: {str(e)}")
# 3. 리뷰 수집 (10개)
try:
reviews = []
while len(reviews) < 5:
# 현재 페이지의 리뷰 수집
current_reviews = driver.find_elements(By.CSS_SELECTOR, "div.pui__vn15t2 a.pui__xtsQN-")
# 새로운 리뷰 추가
for review in current_reviews:
if review.text.strip() not in reviews: # 중복 제거
reviews.append(review.text.strip())
# 목표 개수 달성했으면 종료
if len(reviews) >= 5:
break
try:
# 더보기 버튼 찾기 및 클릭
more_button = driver.find_element(By.CSS_SELECTOR, "a.fvwqf")
if more_button.is_displayed():
driver.execute_script("arguments[0].click();", more_button)
time.sleep(0.5) # 로딩 대기
else:
break # 더보기 버튼이 없으면 종료
except:
break # 더보기 버튼을 찾을 수 없으면 종료
review_info["reviews"] = reviews[:30] # 최대 30개까지만 저장
# review_info["total_reviews"] = len(reviews) # 실제 수집된 리뷰 수 저장
except Exception as e:
print(f"리뷰 수집 중 에러: {str(e)}")
# 4. 동행자 정보 수집
try:
companions = driver.find_elements(By.CSS_SELECTOR, "span.pui__V8F9nN em")
companion_texts = []
for companion in companions:
companion_text = companion.text.strip()
if companion_text and not any(keyword in companion_text for keyword in ["예약", "대기", "바로","이내"]):
companion_texts.append(companion_text)
review_info["companions"] = list(set(companion_texts)) # 중복 제거
except Exception as e:
print(f"동행자 정보 수집 중 에러: {str(e)}")
return review_info
except Exception as e:
print(f"리뷰 정보 수집 중 에러 발생: {str(e)}")
return None
def safe_find_element(driver, by, value):
try:
# 1. 최대 1초 동안 요소를 찾기를 시도
element = WebDriverWait(driver, 1).until(
EC.presence_of_element_located((by, value))
)
# 2. 찾은 요소의 텍스트 반환
return element.text
except:
# 3. 요소를 찾지 못하면 "정보 없음" 반환
# 누락된 정보 또한 안전하게 처리
return "정보 없음"
safe_find_element함수는 웹 요소를 안전하게 찾고 텍스트를 추출하는 함수이다.
- driver: Selenium WebDriver 인스턴스
- by: 요소 찾기 방법 (By.ID, By.CLASS_NAME 등)
- value: 찾을 요소의 식별자
#사용예시
# 가게 이름 찾기
store_name = safe_find_element(driver, By.CLASS_NAME, "GHAhO")
# 전화번호 찾기
phone = safe_find_element(driver, By.CLASS_NAME, "xlx7Q")
STEP4. 데이터를 저장한다.
original_data = url_data['original_data'].split(',')[0] #가맹점명 추출
place_data = {
"original_data": original_data, #가맹점명
"base_info": base_info, #홈탭정보
"review_info": review_info #리뷰탭 정보
}
all_places_data.append(place_data) #데이터저장
원본데이터는 아래Json형식과 같다.
{
"original_data": "한그릇, 단품요리 전문, 제주 서귀포시 색달동 2315-1번지 한그릇",
"search_term": "한그릇 색달동",
"url": "https://map.naver.com/p/search/%ED%95%9C%EA%B7%B8%EB%A6%87%20%EC%83%89%EB%8B%AC%EB%8F%99"
}
따라서, 원본데이터에서 첫번째 쉼표까지 텍스트만 추출해서 original_data에 가맹점명을 넣어준다.
STEP5. 예외처리 및 결과저장
try:
# 크롤링 코드
except Exception as e:
print(f"⚠ {idx}번째 URL 크롤링 실패: {str(e)}")
continue
if all_places_data:
save_multiple_to_json(all_places_data)
finally:
if driver:
driver.quit()
개별 URL의 크롤링 실패 시에도 전체 프로세스를 계속 진행하기 위한 코드이다.
에러 메세지(e)를 출력한 뒤 계속 진행한다.
수집된 데이터는 Json 파일로 저장한다.
'■ Tools > Python' 카테고리의 다른 글
[FastAPI] FastAPI이용한 백엔드 구축 가이드라인 (2) parameter (0) | 2025.03.06 |
---|---|
[FastAPI] FastAPI이용한 백엔드 구축 가이드라인 (1) 설치 및 Uvicorn을 통한 테스트 (0) | 2025.03.06 |
[GIT] 깃으로 협업하기 가이드라인 (0) | 2024.01.08 |
[python] 07. 패키지와 모듈 (1) | 2023.12.31 |
[python] 06.객체지향프로그래밍 (0) | 2023.12.31 |