본문 바로가기
코딩 연습

네이버 부동산 아파트 세대별 면적 추출하는 방법(selenium 활용)

by good4me 2023. 2. 9.

goodthings4me.tistory.com

네이버 부동산 아파트 정보를 통해 단지 내 동별 세대별 호수를 찾는 방법이 있는지 알아봤는데, 공공데이터 포털과 달리 좋은 정보가 많았고, 인테리어나 창호 샷시 등의 홍보용 DM을 보낼 때 상당히 유용할 것 같다.   

 

 

네이버 부동산 아파트 세대 호수와 면적 정보 추출해보기 

1. 네이버 부동산 URL 분석하기

네이버 부동산 URL(https://land.naver.com)에서 검색 부분에 아파트명(예로, 현대)을 입력하면,

  • i) 찾는 아파트 리스트 웹 페이지로 이동하거나
  • ii) 해당 아파트 정보 웹 페이지로 바로 이동을 하게 된다.

네이버 부동산 검색 결과
네이버 부동산 검색 결과

i)의 경우, 웹 페이지 URL은
https://new.land.naver.com/search?ms=37.48224,126.757,17&a=APT:ABYG:JGC&e=RETAIL
처럼 검색 결과로 파라미터가 몇 개 붙는 URL로 이동되고, 그 리스트를 살펴보면 "현대"가 들어간 유사한 명칭이 있는 아파트명과 주소, 아파트 총 세대수, 총 동수, 사용승인월(준공연월), 면적 정보(이하 "기본정보"라 함) 등을 볼 수 있다. 아마도, 아파트 문자를 뺀 나머지 단어("현대")로 유사 검색을 하여 결과를 표시해주는 것으로 보인다.

 

만일, 아파트 명칭이 유일한 경우(예로, "송내자이") 
https://new.land.naver.com/complexes/26987?ms=37.48224,126.757,17&a=APT:ABYG:JGC&e=RETAIL
처럼 아파트 기본정보 웹 페이지(본 포스팅의 세번째 이미지 참조) 바로 이동하게 되어있다.

 

※ 참고) URL 파라미터 의미 추측
URL의 파라미터를 유심히 살펴보면 ms=좌표값, a=APT:ABYG:JGC, e=RETAIL 등이 보이고, 그 아래 부분을 보면 "아파트", "아파트분양권", "재건축" 버튼이 체크되어 있는 것을 알 수 있다.
아마도, APT:ABYG:JGC 부분이 각각을 의미하는 파라미터 값들인 것 같다.
(체크를 해제하면 APT:ABYG:JGC 부분에 영향을 줌)

 

"아파트", "아파트분양권", "재건축"
"아파트", "아파트분양권", "재건축"

 

첫번째 이미지의 웹 페이지 좌측 "검색결과"에서, 찾고자 하는 아파트명과 주소 부분(예로, 현대
서울시 구로구 개봉동 ~ )을 클릭하면 페이지가 전환되고 아래처럼 아파트의 기본정보와 최근 매매 실거래가와 매매가, 전세가 등이 보인다.

 

네이버 부동산 아파트 정보
네이버 부동산 아파트 정보

이 포스팅의 목적은 아파트 동별 세대 호수와 면적을 구하는 것이니 위 이미지에서 "단지정보"를 클릭한다. 그러면 아래 이미지처럼 단지정보부터 시세/실거래가, 동호수/공시가격, 학군정보, 사진 등의 탭이 보이는데, 동별 정보(이름)과 해당 동의 세대(호수)와 면적 정보를 보려면 "동호수/공시가격"을 클릭한다. 

 

네이버 부동산 동호수/공시가격
네이버 부동산 동호수/공시가격

위 이미지의 동별 세대별 호수와 면적 정보를 추출하기 위해 개발자도구(F12)를 펼쳐본다. 아래 이미지는 추출할 위치만을 잘라서 게재한 이미지이며, 그 아래 코드를 보면 추출할 각 요소를 확인할 수 있다.

 

네이버 부동산 개발자도구
네이버 부동산 개발자도구

 

2. 네이버 부동산 아파트 세대 추출 파이썬 코드

import time
from bs4 import BeautifulSoup
from selenium.webdriver.common.by import By
import mychrome_driver as chromedriver


def ext_apt_ho(aptnum):
    driver = chromedriver.chrome_driver()
    url = f'https://new.land.naver.com/complexes/{aptnum}'
    driver.get(url)
    driver.implicitly_wait(5)
    time.sleep(1)
    
    # 아파트 버튼 옆 건물명칭
    bld_title = driver.find_element(By.ID, 'complexTitle').text.strip()
    
    # 좌측 단지정보 버튼 클릭
    driver.find_elements(By.CLASS_NAME, 'complex_link')[0].click() 
    time.sleep(0.5)

    # 세대별 평형정보 추출하는 부분 ##
    dongs = driver.find_element(By.CLASS_NAME, 'detail_contents_inner').find_elements(By.CLASS_NAME, 'tab_item')
    for dong in dongs:
        # print(dong.text[:3])
        if dong.text.strip()[:3] == '동호수':
            dong.click()
            time.sleep(0.5)

    # 동 이름이 많은 경우 꺽쇠가 있음. 꺽쇠를 찾아 클릭하는 코드이며 class="btn_moretab_inner" 없는 경우를 대비하여 예외 처리함
    try:
        btn_moretab = driver.find_element(By.CSS_SELECTOR, '#detailContents4 > div.detail_sorting_tabs > div > div.btn_moretab_box > button > span > i')
        time.sleep(0.5)
        btn_moretab.click()
    except:
        pass
    time.sleep(0.5)

    # 동 이름 클릭
    dongs_a = driver.find_elements(By.CSS_SELECTOR, '#detailContents4 > div > div > div.detail_sorting_tablist > span > a')
    for dong in dongs_a:
        try:
            dong.click()  # 동 이름 클릭 시 하단 '아파트 세대' 변경시킴
        except:
            driver.execute_script('arguments[0].click();', dong)
        time.sleep(0.5)

        # 개발자도구에서 동이름 클릭하면 나오는 호수 부분의 class 속성인 detail_tabpanel_inner 클릭
        page_src = driver.find_element(By.CLASS_NAME, 'detail_tabpanel_inner')
        page_src.click()
        time.sleep(0.5)

        # page_src html 소스코드를 변수에 저장
        # 해당 소스코드 대상으로 bs4와 requests 사용하여 세대별 면적 추출함
        html_src = page_src.get_attribute('innerHTML')
        # print(html_src)

        soup = BeautifulSoup(html_src, 'html.parser')
        house_floors = soup.select('div.detail_box--housenumber div.table_inner div.house_floor')  # 층
        print(f'\n{bld_title} 아파트 {dong.text} (전체 층수: {len(house_floors)})')

        for floor in house_floors:  # 층별로 구분하고 있음.
            # 해당 동의 라인별 같은 층의 호(세대)를 span으로 분리하고 그 하위에 input과 div로 호와 면적을 구분하고 있음
            ho_spans = floor.find_all('span')
            for span in ho_spans:
                # 빈 span <span class="house_number is-blank"></span> 으로 인해 TypeError: 'NoneType' 발생 예외 처리
                try:
                    ho = span.find('input')['value']  # 호 추출
                    py = span.find('div', class_='tooltip_wrap').text.split(' ')[1].strip().replace('㎡','')  #공급 106.51㎡ 형태에서 ㎡ 제거
                    print(f'{ho}호 {py}')
                except:
                    continue
        # break
    driver.quit()

if __name__ == '__main__':
    apt_no = 45
    ext_apt_ho(apt_no)
  • 본 소스 코드는 위 네이버 부동산의 URL중 ~ complexes/26987 ~ 부분에서 숫자로 된 값을 apt_no로 함수에 전달하면 실행이 된다.
  • 위 소스코드 import 부분에서  import mychrome_driver as chromedriver 모듈에 대한 것은  "파이썬 크롬드라이버 자동설치 (chromedriver_autoinstaller)" 포스팅의 "크롬 드라이버 자동 다운로드 코드" 부분의 내용을 참고한다.
  • 셀레니움 크롤링 특성상 브라우저 내 실행하는 interval을 설정해야 에러가 안 나기 때문에 time을 주어야 한다.
  • 동 이름이 많아서 꺽쇠를 클릭해야 하는 경우가 있고, 해당 css 속성값이 없는 경우가 있어서 예외 처리도 필요한다.
  • 동 이름을 클릭했을 때 반응이 느리거나 가끔 반응이 없어서 에러가 나는 경우도 있다. 이를 해결하기 위해서 자바스크립트를 실행하는 코드도 삽입했다.
  • 그리고, 소스코드 중에서 각 세대별 호수와 면적을 추출하는 부분을 서버에 무리를 주지 않고, 보다 용이하게 크롤링하도록 html 코드(아래 이미지 참조)를 변수에 저장하고 requests와 BeautifulSoup 모듈을 활용하여 추출했다.

 

good4me.co.kr

 

세대별 호수와 면적 크롤링 html 코드
세대별 호수와 면적 크롤링 html 코드

위 html 코드를 실행하면 아래와 같은 형태로 보인다.

세대별 호수와 면적 html 실행
세대별 호수와 면적 html 실행

 

▶ 위 소스 코드를 실행한 결과,

 

 

댓글