Selenium으로 네이버 지도 크롤링하기

Doyeon·2023년 3월 14일
4
post-thumbnail

Selenium을 이용하여 네이버 지도를 크롤링해보자!

Selenium 세팅은 다음 글을 참고한다.
Selenium 사용하여 웹 페이지 크롤링하기(세팅)

크롤링의 순서는 다음과 같다.

  1. 크롬 드라이버를 세팅한다.
  2. 브라우저에서 url을 로딩한다.
  3. findElement(s) 로 원하는 요소를 찾는다.

Java Spring boot로 크롤링

다음 코드는 장소 검색 후 첫번째 검색결과를 클릭하면 나오는 상세 창에서 도로명, 지번 주소를 찾는다.

코드

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.List;

@Service
public class CrawlingData {

    private WebDriver driver;

    private static final String keyword = "주차장";
    private static final String url = "https://map.naver.com/v5/search";

    public void process() {

        // 크롬 드라이버 세팅 (드라이버 설치 경로 입력)
        System.setProperty("webdriver.chrome.driver", "{path/to/chromedriver}");

        ChromeOptions options = new ChromeOptions();
        options.addArguments("--remote-allow-origins=*");

        // 브라우저 선택
        driver = new ChromeDriver(options);

        getDataList();

        // 탭 닫기
        driver.close();
        // 브라우저 닫기
        driver.quit();
    }

    // 데이터 가져오기
    private void getDataList() {

        // (1) 브라우저에서 url로 이동한다.
        driver.get(url);
        // 브라우저 로딩될 때까지 잠시 기다린다.
        driver.manage().timeouts().implicitlyWait(Duration.ofMillis(1000));

        // (2) 검색결과 iframe으로 frame을 바꾼다.
        driver.switchTo().frame(driver.findElement(By.cssSelector("iframe#searchIframe")));

				// 검색 결과 장소 목록을 elements에 담는다.
        List<WebElement> elements = driver.findElements(By.cssSelector(".C6RjW>.place_bluelink"));

        System.out.println("TestTest**********************************");
        System.out.println("elements.size() = " + elements.size());
        
				// (3) 첫번째 검색결과를 클릭한다.
				elements.get(0).click();

				// 요소가 로드될 때까지 기다린다.
        driver.manage().timeouts().implicitlyWait(Duration.ofMillis(500));
				// 현재 프레임에서 상위 프레임으로 이동한다.
        driver.switchTo().defaultContent();

        driver.manage().timeouts().implicitlyWait(Duration.ofMillis(3000));
        // (4) 상세정보가 나오는 프레임으로 이동한다.
				driver.switchTo().frame(driver.findElement(By.cssSelector("iframe#entryIframe")));
				
				// (5) 상세정보 프레임에서 주소 정보가 들어있는 곳으로 이동한다.
        List<WebElement> placeSectionContents = driver.findElements(By.cssSelector(".place_section_content"));
        WebElement homeElement = placeSectionContents.get(1);

        // (6) "주소" 버튼 요소를 찾아 클릭한다.
        WebElement addressButton = homeElement.findElement(By.className("LDgIH"));
        addressButton.click();

        // (7) "도로명"과 "지번" 정보가 들어있는 div 요소를 찾아서, 해당 정보를 가져온다.
        WebElement addressDiv = driver.findElement(By.className("Y31Sf"));
        List<WebElement> addressInfos = addressDiv.findElements(By.className("nQ7Lh"));

        for (WebElement addressInfo : addressInfos) {
            WebElement addressType = addressInfo.findElement(By.tagName("span"));
            String address = addressInfo.getText().replace(addressType.getText(), "").trim();
            System.out.println(addressType.getText() + " : " + address);
        }

    }

}

처음에는 크롤링한 데이터를 바로 Spring repository에 저장할 생각에 Java로 크롤링 코드를 만들었다.
그런데 DB에 저장할 컬럼에 맞게 데이터 형식을 맞춰야 하고, 많은 양의 데이터를 수집해야 하기 때문에, Json 파일 형태로 크롤링한 데이터를 만들어놓고, DB에 저장할 때 원하는 컬럼값으로 형식을 맞춰 저장하기로 했다.
그래서 크롤링이 더 간편한 파이썬을 사용해 구현해보았다.


Python으로 크롤링

다음 코드는 상세 창을 띄우지 않고 검색 결과 프레임 안에서 검색 결과를 하나씩 반복하며 장소명, 장소타입, 도로명 주소, 지번 주소를 크롤링한다. 모든 장소를 읽었으면 다음 페이지를 눌러 똑같이 반복한다.

코드

import json
import time
from time import sleep

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

# --크롬창을 숨기고 실행-- driver에 options를 추가해주면된다
# options = webdriver.ChromeOptions()
# options.add_argument('headless')

url = 'https://map.naver.com/v5/search'
driver = webdriver.Chrome('./chromedriver')  # 드라이버 경로
# driver = webdriver.Chrome('./chromedriver',chrome_options=options) # 크롬창 숨기기
driver.get(url)
key_word = '파주시 야당동 주차장'  # 검색어

# css 찾을때 까지 10초대기
def time_wait(num, code):
    try:
        wait = WebDriverWait(driver, num).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, code)))
    except:
        print(code, '태그를 찾지 못하였습니다.')
        driver.quit()
    return wait

# frame 변경 메소드
def switch_frame(frame):
    driver.switch_to.default_content()  # frame 초기화
    driver.switch_to.frame(frame)  # frame 변경

# 페이지 다운
def page_down(num):
    body = driver.find_element(By.CSS_SELECTOR, 'body')
    body.click()
    for i in range(num):
        body.send_keys(Keys.PAGE_DOWN)

# css를 찾을때 까지 10초 대기
time_wait(10, 'div.input_box > input.input_search')

# (1) 검색창 찾기
search = driver.find_element(By.CSS_SELECTOR, 'div.input_box > input.input_search')
search.send_keys(key_word)  # 검색어 입력
search.send_keys(Keys.ENTER)  # 엔터버튼 누르기

sleep(1)

# (2) frame 변경
switch_frame('searchIframe')
page_down(40)
sleep(3)

# 주차장 리스트
parking_list = driver.find_elements(By.CSS_SELECTOR, 'li.VLTHu')
# 페이지 리스트
next_btn = driver.find_elements(By.CSS_SELECTOR, '.zRM9F> a')

# dictionary 생성
parking_dict = {'주차장정보': []}
# 시작시간
start = time.time()
print('[크롤링 시작...]')

# 크롤링 (페이지 리스트 만큼)
for btn in range(len(next_btn))[1:]:  # next_btn[0] = 이전 페이지 버튼 무시 -> [1]부터 시작
    parking_list = driver.find_elements(By.CSS_SELECTOR, 'li.VLTHu')
    names = driver.find_elements(By.CSS_SELECTOR, '.YwYLL')  # (3) 장소명
    types = driver.find_elements(By.CSS_SELECTOR, '.YzBgS')  # (4) 장소 유형

    for data in range(len(parking_list)):  # 주차장 리스트 만큼
        print(data)

        sleep(1)
        try:
            # 지번, 도로명 초기화
            jibun_address = ''
            road_address = ''

            # (3) 주차장명 가져오기
            parking_name = names[data].text
            print(parking_name)

            # (4) 유형
            parking_type = types[data].text
            print(parking_type)

            # (5) 주소 버튼 누르기
            address_buttons = driver.find_elements(By.CSS_SELECTOR, '.Q8Zql > a')
            address_buttons.__getitem__(data).click()

            # 로딩 기다리기
            sleep(1)

            # (6) 주소 눌렀을 때 도로명, 지번 나오는 div
            addr = driver.find_elements(By.CSS_SELECTOR, '.jg1ED > div')

            # 지번만 있는 경우
            if len(addr) == 1 and addr.__getitem__(0).text[0:2] == '지번':
                jibun = addr.__getitem__(0).text
                last_index = jibun.find('복사우\n')    # 복사버튼, 우편번호 제외하기 위함
                jibun_address = jibun[2:last_index]
                print("지번 주소:", jibun_address)

            # 도로명만 있는 경우
            elif len(addr) == 1 and addr.__getitem__(0).text[0:2] == '도로':
                road = addr.__getitem__(0).text
                last_index = road.find('복사우\n')     # 복사버튼, 우편번호 제외하기 위함
                road_address = road[3:last_index]
                print("도로명 주소:", road_address)

            # 도로명, 지번 둘 다 있는 경우
            else:
                # 도로명
                road = addr.__getitem__(0).text
                road_address = road[3:(len(road) - 2)]
                print("도로명 주소:", road_address)

                # 지번
                jibun = addr.__getitem__(1).text
                last_index = jibun.find('복사우\n')    # 복사버튼, 우편번호 제외하기 위함
                jibun_address = jibun[2:last_index]
                print("지번 주소:", jibun_address)

            # dict에 데이터 집어넣기
            dict_temp = {
                'name': parking_name,
                'parking_type': parking_type,
                'road_address': road_address,
                'jibun_address': jibun_address
            }

            parking_dict['주차장정보'].append(dict_temp)
            print(f'{parking_name} ...완료')

            sleep(1)

        except Exception as e:
            print(e)
            print('ERROR!' * 3)

            # dict에 데이터 집어넣기
            dict_temp = {
                'name': parking_name,
                'parking_type': parking_type,
                'road_address': road_address,
                'jibun_address': jibun_address
            }

            parking_dict['주차장정보'].append(dict_temp)
            print(f'{parking_name} ...완료')

            sleep(1)

    # 다음 페이지 버튼 누를 수 없으면 종료
    if not next_btn[-1].is_enabled():
        break

    if names[-1]:  # 마지막 주차장일 경우 다음버튼 클릭
        next_btn[-1].click()

        sleep(2)

    else:
        print('페이지 인식 못함')
        break

print('[데이터 수집 완료]\n소요 시간 :', time.time() - start)
driver.quit()  # 작업이 끝나면 창을 닫는다.

# json 파일로 저장
with open('data/store_data.json', 'w', encoding='utf-8') as f:
    json.dump(parking_dict, f, indent=4, ensure_ascii=False)
  • 장소목록 : parking_list = driver.find_elements(By.CSS_SELECTOR, 'li.VLTHu')
  • 장소명 : names = driver.find_elements(By.CSS_SELECTOR, '.YwYLL')
  • 장소 유형 : types = driver.find_elements(By.CSS_SELECTOR, '.YzBgS')
  • 주소 클릭 : address_buttons = driver.find_elements(By.CSS_SELECTOR, '.Q8Zql > a')
  • 클릭하면 나오는 주소 부분 : addr = driver.find_elements(By.CSS_SELECTOR, '.jg1ED > div')

참고자료

자바, Spring Boot로 크롤링하기 - Selenium 이용 (동적페이지), 속도 개선 방법
Selenium을 이용한 크롤링
네이버 맵 크롤링 (selenium,bs4,re) - 키워드 리뷰

profile
🔥

1개의 댓글

comment-user-thumbnail
2024년 10월 30일

안녕하세요. 본문내용 그대로 복사해서 사용 했는대 아래 내용으로 더 진행이 안되고 있어서요
어떤 부분을 확인해야 할까요?
[크롤링 시작...]
Traceback (most recent call last):
File "c:\Users\user\Documents\판다스연습\import json.py", line 163, in
if names[-1]: # 마지막 주차장일 경우 다음버튼 클릭

답글 달기