ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Selenium] 파이썬 셀레니움을 이용한 네이버 지도 크롤링 프로그램 만들기
    Python/크롤링 2023. 3. 23. 09:15
    반응형

    파이썬 셀레니움을 이용한 네이버 지도 크롤링 프로그램 만들기


    우리나라 업체 정보는 대부분 네이버 지도에 등록되어 있습니다. 그래서, 이런 정보를 이용하기 위해 다양한 시도가 이루어지고 있지요. 대표적인 것으로는 각종 업체에서 분야별 업체 리스트를 확보하기 위해 크롤링을 이용하며, 사소하게는 개인이 우리 동네 맛집 리스트를 가지고 싶어서 크롤링하는 경우도 있습니다. 이러한 사례를 들어보면 아시겠지만, 크롤링이라는건 제법 간단한 편입니다. 만약 파이썬으로 웹 크롤링을 한다면, 셀레니움(selenium) 이라는 패키지를 이용할 수 있으며, 이번에는 파이썬 셀레니움과 구글 크롬을 이용해서 웹 크롤링을 하는 예시를 소개해 드리겠습니다.

     

     

    이번에 크롤링을 하는 시나리오는, '네이버 지도에 등재된 서울특별시 강남구에 있는 정보통신 기업의 리스트를 싹 긁어보자!' 라는 것입니다. 이 코드는 2023년 3월 기준으로 동작하는 코드이며, 파이썬과 셀레니움, 크롬 버전과 네이버 지도의 업데이트 상황에 따라 다른 결과물이 나오거나 코드에서 에러가 발생할 수도 있습니다. 만약 에러를 최소화하고 싶다면, 아래 개발환경을 따라 설치해 주시면 좋습니다. 특히, 셀레니움은 최신 버전 기준으로, 구버전과 문법이 몇 가지 바뀌었기 때문에 잘 확인해 주세요.


    설치된 파이썬, 크롬, 패키지 버전

    • Python 3.9.13
    • selenium 4.8.2
    • Chrome 111.0.5563.65(공식 빌드, 64비트)
    • Pandas 1.4.4

    만약 셀레니움이 설치되지 않았다면, 프롬프트 창에 pip install selenium 이라고 입력해 주시면 됩니다. 물론, 다른 패키지도 이름만 바꿔서 치면 거의 다 제대로 설치되니 확인해 주세요.

     

     

    우선은 패키지를 불러와야 합니다. 웹 크롤링을 하기 위해 셀레니움은 필수이며, 표 형식의 데이터를 다루기 위해 판다스(Pandas) 패키지도 불러옵니다. 만약 판다스가 싫다면 다른 표 형식의 데이터를 다룰 수 있는 패키지로 대체해도 무방합니다. 크롤링 과정에서는 영향을 주지 않기 때문이지요. 패키지가 다 준비되었다면, import 명령어를 이용해서 불러옵니다. 패키지별로 주석을 적었으니, 어떤 역할을 하는지 살펴보세요.

     

     

    import pandas as pd # 표 형식의 데이터를 다룰 수 있는 pandas를 pd라고 줄여서 불러옵니다
    from selenium import webdriver # 크롬 창을 조종하기 위한 모듈입니다
    from selenium.webdriver.common.by import By # 웹사이트의 구성요소를 선택하기 위해 By 모듈을 불려옵니다
    from selenium.webdriver.support.ui import WebDriverWait # 웹페이지가 전부 로드될때까지 기다리는 (Explicitly wait) 기능을 하는 모듈입니다
    from webdriver_manager.chrome import ChromeDriverManager # 크롬에서 크롤링을 하기 위해, 웹 드라이버를 설치하는 모듈입니다
    from selenium.webdriver.support import expected_conditions as EC # 크롬의 어떤 부분의 상태를 확인하는 모듈입니다
    import time # 정해진 시간만큼 기다리게 하기 위한 패키지입니다

     

     

    패키지를 불러올 때, 같은 셀레니움 패키지 안에 있는 모듈을 따로 불러왔습니다. 이렇게 따로 불러오는 이유는, 매번 모듈을 사용할 때마다 selenium.webdriver.common.by와 같이 코드를 작성하기는 너무 불편하기 때문입니다. 그리고, 파이썬에 무턱대고 패키지 전체를 불러올 경우, 속도가 꽤나 느려지기 때문이기도 하죠. 가벼운 작업 위주로 사용한다면 문제가 없지만, 어쩌다 메모리 에러라도 발생한다면 뼈아픈 손해이기 때문에, 이렇게 불러오는 습관을 들이시면 좋습니다.

     

     

    패키지를 불러왔으면, 셀레니움으로 조종할 수 있는 크롬 창을 켜야 합니다. 이 기능은 webdriver.Chrome으로 실행할 수 있습니다. 나중에 이 크롬 창을 이용하기 위해, driver 라는 변수에 저장을 해 줍시다.

     

     

    driver = webdriver.Chrome(ChromeDriverManager().install()) # 웹 드라이버를 설치하고, 조종할 수 있는 크롬 창을 실행합니다
    C:\Users\jwkan\AppData\Local\Temp\ipykernel_18560\3646641305.py:1: DeprecationWarning: executable_path has been deprecated, please pass in a Service object
      driver = webdriver.Chrome(ChromeDriverManager().install()) # 웹 드라이버를 설치하고, 조종할 수 있는 크롬 창을 실행합니다

     

     

    다음으로는, 네이버 지도를 켜야 합니다. 2023년 3월 기준으로는, 네이버 지도의 주소는 https://map.naver.com/v5 입니다. 만약 이 페이지로 들어간 다음, 검색창을 눌러서 원하는 검색어를 쳐도 좋지만, 주소 자체에 검색어를 넣어서 원하는대로 검색을 할 수도 있습니다. 저는 후자의 방법을 이용해 보겠습니다. 만약 서울 강남구의 정보통신 업체를 입력할 경우, 심플하게 서울 강남구 정보통신이라고만 입력해 주시면 됩니다. 이 검색어를 주소와 합치면,

     

     

    https://map.naver.com/v5/search/서울 강남구 정보통신 으로 표현할 수 있지요. 만약, 이런 주소의 구조를 모르는 분들이라면, 네이버 지도를 켜본 다음 직접 검색을 하면서 주소가 어떻게 바뀌는지 확인해 보시면 좋습니다.

     

     

    이 특징을 이용해, 크롬이 서울 강남구의 정보통신 업체를 검색할 수 있도록 해 줍니다. 이런 기능은 driver.get으로 실행할 수 있습니다. 이 기능은 다른 웹페이지로 이동할 때도 요긴하게 이용할 수 있습니다.

     

     

    driver.get("https://map.naver.com/v5/search/서울 강남구 정보통신")

     

     

    우리가 웹페이지에 접속할 때는 인터넷 회선의 문제, 또는 서버의 문제, 아니면 두 요소 모두 문제가 있기 때문에 접속 시간이 소요됩니다. 만약 이를 고려하지 않고 코드를 바로바로 실행시킬 경우, 마이크로초 단위로 실행되는 코드를 웹페이지 로딩 속도가 따라갈 수 없겠지요. 이러한 문제 때문에 멀쩡한 코드가 자꾸 에러가 발생할 수 있습니다. 만약 이런 문제가 일어나지 않게 하기 위해서는, 비교적 무식한 방법을 이용합니다. 바로 로딩이 다 끝날 때까지 기다리는거죠.

     

     

    로딩을 기다리는 방법은 크게 두 가지로 나뉩니다. Implicity wait과 Explicity wait이지요. 전자는 로딩이 끝났는지는 확인하지 않고 정해진 시간만큼 기다리는 방식입니다. 로딩 속도가 일정하게 빠른 시간을 보여준다면, 시간을 넉넉하게 설정했을 때 절대로 에러가 발생하지 않습니다. 코드를 짜는 사람이 신경쓸만한 일이 별로 없다는 점에서는 아주 좋은 방법이지만, 어쩌다 인터넷 상태가 나빠져서 로딩이 예상보다 오래 걸리게 된다면, 에러가 발생할 수도 있다는 단점이 있습니다.

     


    이런 방법을 해결하기 위해서, Explicity wait이라는 방법이 이용됩니다. 로딩이 얼마나 걸릴지 모르거나, 특정한 구성요소가 로딩되는 즉시 코드를 실행하고 싶은 경우에는 이 방법이 좋습니다. 이 기능은 앞서 불러왔던 EC를 이용해서 실행할 수 있으며, 만약 지도의 검색창이 다 로딩될때까지만 기다리고 싶다면, 아래 코드를 이용할 수 있습니다.

     

     

    try:
        element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "input_search"))
        ) # 네이버 지도의 검색창은 "input_search" 라는 클래스 이름으로 설정되어 있습니다
    finally:
        pass

     

     

    네이버 지도는 특정 영역을 선택하지 않을 경우, 셀레니움의 크롤링 기능이 제대로 적용되지 않습니다. 그래서, 클릭 또는 복사할 영역을 한번 지정해 주는 과정이 필요한데요, 이 작업은 과정에 따라 driver.switch_to.frame 또는 driver.find_elements를 적절하게 이용했습니다.

     

     

    그리고, 네이버 지도의 검색결과에서 모든 페이지를 크롤링하기 위해 반복문을 이용했습니다. 마지막 페이지가 몇 쪽이건 관계 없이, 더이상 새로운 정보가 나오지 않으면 종료되도록 while 반복문을 설정해 줬고요, 그 사이에는 검색 결과(한 페이지에 50건이 출력됩니다)를 하나씩 확인하기 위해 for 반복문을 설정해 줬습니다.

     


    각각의 웹사이트 구성요소는 고유한 XPATH값을 이용해 찾아가도록 해줬습니다. 예를 들어, 업체 이름의 경우에는 현재 페이지의 첫 번째 이름은 //*[@id="_pcmap_list_scroll_container"]/ul/li[1]/div[1]/div[2]/a[1]/div/div/span[1]이고, 두 번째 이름은 //*[@id="_pcmap_list_scroll_container"]/ul/li[2]/div[1]/div[2]/a[1]/div/div/span[1]가 되는 방식입니다. 이 때, 각각의 숫자는 알아서 변경될 수 있도록 문자열 포맷팅을 이용해 줬습니다.

     


    그런데 네이버 지도의 웹 크롤링을 진행하는 도중에 특이한 점이 있었습니다. 바로, 같은 영역의 XPATH 값이 위치에 따라서 조금씩 차이가 나는 사실이었는데요, 어떤 이름은 div[2] 위치에 있는가 하면, 다른 이름은 div[1] 위치에 있는 등 편차가 있었습니다.

     


    그래서, 이런 문제를 해결하기 위해 제가 프로그램을 실행하면서 확보한 경우의 수를 모두 입력해 줬습니다. 조금 더 스마트한 해결 방법이 있을지는 모르겠지만, 당장은 이정도면 괜찮아 보이네요.

     

     

    driver.switch_to.frame("searchIframe")
    
    fnm = '' # 맨 첫번째 상호 이름입니다. 다음 페이지에서도 똑같은 상호가 나온다면, 다음 페이지가 없다고 인식하고, brk 값이 바뀌면서 반복문이 종료되지요
    brk = 1
    res = pd.DataFrame() # 결과 파일은 판다스 데이터프레임으로 입력할겁니다
    empty = '//*[@id="_pcmap_list_scroll_container"]' # 크롤링할 데이터가 있는 영역 중, 빈 공간을 입력해 뒀습니다
    
    while brk: # 페이지 설정
    
        driver.find_element(By.XPATH, empty) # 이렇게 find_element 함수만 사용해 놓으면 그 영역이 인식되더라고요
    
        for i in range(1,51): # 1~50번째 상호를 순회하도록 했습니다
            nm = ['NA'] # 상호가 저장될 변수
            addr = ['NA'] # 주소가 저장될 변수
    
            driver.find_element(By.XPATH, empty)
    
            # 네이버 지도는 이름이 두 가지 영역에 저장되는 것을 확인했습니다. 일단 둘 다 긁어오고, 이름이 있는지는 나중에 체크하도록 했습니다
            nm = driver.find_elements(By.XPATH, f'//*[@id="_pcmap_list_scroll_container"]/ul/li[{i}]/div[1]/div[2]/a[1]/div/div/span[1]')
            nm += driver.find_elements(By.XPATH, f'//*[@id="_pcmap_list_scroll_container"]/ul/li[{i}]/div[1]/div/a[1]/div/div/span[1]')
    
    
            driver.find_element(By.XPATH, empty)
    
            # 주소도 이름과 마찬가지로 했습니다
            addr = driver.find_elements(By.XPATH, f'//*[@id="_pcmap_list_scroll_container"]/ul/li[{i}]/div[1]/div[2]/div/div/div')
            addr += driver.find_elements(By.XPATH, f'//*[@id="_pcmap_list_scroll_container"]/ul/li[{i}]/div[1]/div/div/div/span/a/span[1]')
    
    
            if nm != []: # 이름이 비어있으면 아무것도 안하도록 했습니다
                addr = addr[0].text
                if any(i in addr for i in ['강남구']): # 강남구로 검색했음에도 강남구 인접 지역이 검색되는 경우가 있습니다. 이 때는 주소를 확인해 줘야 합니다
                    res = pd.concat([res, pd.DataFrame([nm[0].text, addr]).T]) # res 데이터프레임에 차곡차곡 쌓아줍니다
                    res.to_csv('./res_naver.csv', index=False) # 데이터가 실시간으로 저장되도록 합니다
    
            if i == 1: # 첫번째 상호를 불러왔다면, 이전 페이지의 첫번째 상호와 같은지 확인해 줍니다
                if fnm == nm:
                    brk = 0
                    break
    
                else:
                    fnm = nm
    
    
        # 다음 페이지로 넘어가는 코드입니다 다음 버튼을 인식해서 클릭하도록 만들었습니다
        driver.find_element(By.XPATH, '//*[@id="app-root"]/div/div[2]/div[2]')
        driver.find_element(By.XPATH, '//*[@id="app-root"]/div/div[2]/div[2]/a[7]').click()
        time.sleep(2) # 페이지 로딩 시간 2초

     

     

    위 코드를 모두 실행한다면, 실시간으로 크롬을 열고 상호를 검색하며, res_naver.csv 파일이 저장되는 과정을 눈으로 볼 수 있습니다. 실제로 마우스 커서가 움직이면서 작업이 이루어지는건 아니기 때문에, 크롬 창을 최소화해놓고 다른 작업을 동시에 해도 특별한 문제는 없습니다.

     


    이렇게 작업을 한다면, 파이썬 내부에서는 res라는 변수에, 외부에는 res_naver.csv파일에 크롤링한 데이터가 저장됩니다. 데이터가 잘 모였는지 일부분만 살펴보기 위해서는, 파일을 직접 열어보거나, res.head()라고 입력을 해 주면 됩니다.

     

     

    res.head()
      0 1
    0 한국정보통신기술인협회 서울 강남구 삼성동
    0 한국통신사업자연합회 서울 강남구 삼성동
    0 피디정보통신 서울 강남구 삼성동
    0 개포디지털혁신파크 서울 강남구 개포동
    0 캐셔레스트 서울 강남구 논현동

     

     

    실행 결과, 서울 강남구에 위치한 정보통신 업체가 정상적으로 입력된 것을 확인할 수 있습니다.

     

     

    지금까지 진행한 크롤링을 동적 크롤링이라고 하는데요, 우리가 흔히 아는 HTML 코드를 파이썬이(정확하게는 뷰티풀수프 패키지가) 복사해 와서 문자열을 파싱하듯이 파싱하는 방식인 정적 크롤링과 대비되는 기술입니다. 이 방법의 장점은, 사용자가 직접 상호작용을 해야 하는 웹사이트의 경우 정적 크롤링에 비해서 더욱 편리하게 크롤링할 수 있다는 점이지요. 최근 제작되는 웹사이트는 현재 창의 크기나 사용자의 입력값 등에 반응하고 변화하는 스타일을 적극적으로 사용하기 때문에, 동적 크롤링을 이용하거나 정적/동적 크롤링을 혼합해서 이용해야 할 때가 많습니다.

     

     

    이 작업에 대한 코드는 직접 복사해서 사용하셔도 되고, 이 글이 적혀있는 ipynb 파일을 별도로 다운로드해도 됩니다.

     

    깃허브 링크

    반응형

    댓글

문의: jwkang3929@naver.com