Categories: '교육'

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 2

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 편에 이어서 추가적으로 결과 값을 조금 더 디테일하게 정리해보려고 합니다. https://fin.land.naver.com/complexes/106861?tab=complex-info

네이버페이 부동산

네이버페이 부동산

m.land.naver.com

여기에서 보면 우리 데이터와 일부 맞지 않는 부분을 확인 할 수 있습니다. 바로 공급면적, 전용면적이 실제 매물에 나와 있는 면적과 다르다는 것입니다. 

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 2

그 이유는 바로 네이버 면적 정보 부분에서 면적 정보를 각각 클릭해야만 해당 면적에 대한 정보를 가져오게 되는데, 우리가 크롤링했던 공급면적, 전용면적 등의 정보는 가장 처음 나오는 면적에 대한 정보를 끌어 왔기 때문이죠, 따라서 면적이라는 부분의 데이터를 기준으로 맞춰서 해당 공급면적을 찾아내고, 그에 맞는 전용면적 ~ 방/욕실에 대한 정보를 수정해야겠습니다.

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 2

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 2

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 2

오늘 글은 시리즈로 구성된 기본편을 기본으로 하고 있습니다. 아직 기본편을 못 보신 분들이라면 아래 글을 한번 읽어 주세요!

2024.09.15 – [부동산/자동화 프로젝트] – 부동산 매물 정보 수집하기 – 부동산 데이터 네이버 부동산 크롤링 및 가공 #1

2024.09.15 – [부동산/자동화 프로젝트] – 부동산 매물 정보 수집하기 – 부동산 데이터 네이버 부동산 크롤링 및 가공 #2

2024.09.15 – [부동산/자동화 프로젝트] – 부동산 매물 정보 수집하기 – 부동산 데이터 네이버 부동산 크롤링 및 가공 #3

2024.09.17 – [부동산/자동화 프로젝트] – [고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기

이 부분은 동적 네트워크를 사용해야 해서, 현재 데이터에서만 정리해볼까 합니다. 현재 정리되어 있는 전체코드는 아래와 같습니다.

from google.colab import drive
import requests
import json
import pandas as pd
from datetime import datetime
from bs4 import BeautifulSoup

# Google Drive 마운트
drive.mount('/content/drive')

# 법정동 코드를 가져오는 함수
def get_dong_codes_for_city(city_name, sigungu_name=None, json_path='/content/drive/MyDrive/district.json'):
    try:
        with open(json_path, 'r', encoding='utf-8') as file:
            data = json.load(file)
    except FileNotFoundError:
        print(f"Error: The file at {json_path} was not found.")
        return None, None

    for si_do in data:
        if si_do['si_do_name'] == city_name:
            if sigungu_name and sigungu_name != '전체':
                for sigungu in si_do['sigungu']:
                    if sigungu['sigungu_name'] == sigungu_name:
                        return [sigungu['sigungu_code']], [
                            {'code': dong['code'], 'name': dong['name']} for dong in sigungu['eup_myeon_dong']
                        ]
            else:  # 시군구 '전체'
                sigungu_codes = [sigungu['sigungu_code'] for sigungu in si_do['sigungu']]
                dong_codes = [
                    {'code': dong['code'], 'name': dong['name']}
                    for sigungu in si_do['sigungu']
                    for dong in sigungu['eup_myeon_dong']
                ]
                return sigungu_codes, dong_codes
    return None, None

# 아파트 코드 리스트 가져오기
def get_apt_list(dong_code):
    down_url = f'https://new.land.naver.com/api/regions/complexes?cortarNo={dong_code}&realEstateType=APT&order='
    header = {
        "Accept-Encoding": "gzip",
        "Host": "new.land.naver.com",
        "Referer": "https://new.land.naver.com/complexes/102378",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0"
    }

    try:
        r = requests.get(down_url, headers=header)
        r.encoding = "utf-8-sig"
        data = r.json()

        if 'complexList' in data and isinstance(data['complexList'], list):
            df = pd.DataFrame(data['complexList'])
            required_columns = ['complexNo', 'complexName', 'buildYear', 'totalHouseholdCount', 'areaSize', 'price', 'address', 'floor']

            for col in required_columns:
                if col not in df.columns:
                    df[col] = None

            return df[required_columns]
        else:
            print(f"No data found for {dong_code}.")
            return pd.DataFrame(columns=required_columns)

    except Exception as e:
        print(f"Error fetching data for {dong_code}: {e}")
        return pd.DataFrame(columns=required_columns)

# 아파트 코드로 상세 정보를 가져오는 함수 (매매 정보 추가)
def get_apt_details(apt_code):
    details_url = f'https://fin.land.naver.com/complexes/{apt_code}?tab=complex-info'
    article_url = f'https://fin.land.naver.com/complexes/{apt_code}?tab=article&tradeTypes=A1'
    
    header = {
        "Accept-Encoding": "gzip",
        "Host": "fin.land.naver.com",
        "Referer": "https://fin.land.naver.com/",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "User-Agent": "Mozilla/5.0"
    }
    
    try:
        # 기본 정보 가져오기
        r_details = requests.get(details_url, headers=header)
        r_details.encoding = "utf-8-sig"
        soup_details = BeautifulSoup(r_details.content, 'html.parser')
        
        # 아파트 이름 추출
        apt_name_tag = soup_details.find('span', class_='ComplexSummary_name__vX3IN')
        apt_name = apt_name_tag.text.strip() if apt_name_tag else 'Unknown'

        # 기본 정보 딕셔너리
        detail_dict = {'complexNo': apt_code, 'complexName': apt_name}
        
        # 기본 상세 정보 추출 (공급면적, 전용면적, 방/욕실 등)
        detail_items = soup_details.find_all('li', class_='DataList_item__T1hMR')
        for item in detail_items:
            term = item.find('div', class_='DataList_term__Tks7l').text.strip()
            definition = item.find('div', class_='DataList_definition__d9KY1').text.strip()
            if term in ['공급면적', '전용면적', '해당면적 세대수', '현관구조', '방/욕실', '위치', '사용승인일', '세대수', '난방', '주차', '전기차 충전시설', '용적률/건폐율', '관리사무소 전화', '건설사']:
                detail_dict[term] = definition
        
        # 매물 정보 가져오기
        r_article = requests.get(article_url, headers=header)
        r_article.encoding = "utf-8-sig"
        soup_article = BeautifulSoup(r_article.content, 'html.parser')
        
        # 매물 리스트
        listings = []
        for item in soup_article.find_all('li', class_='ComplexArticleItem_item__L5o7k'):
            listing = {}
            
            # 매물 이름
            name_tag = item.find('span', class_='ComplexArticleItem_name__4h3AA')
            listing['매물명'] = name_tag.text.strip() if name_tag else 'Unknown'
            
            # 매매 가격
            price_tag = item.find('span', class_='ComplexArticleItem_price__DFeIb')
            listing['매매가'] = price_tag.text.strip() if price_tag else 'Unknown'
            
            # 면적, 층수, 방향
            summary_items = item.find_all('li', class_='ComplexArticleItem_item-summary__oHSwl')
            if len(summary_items) >= 4:
                listing['면적'] = summary_items[1].text.strip() if len(summary_items) > 1 else 'Unknown'
                listing['층수'] = summary_items[2].text.strip() if len(summary_items) > 2 else 'Unknown'
                listing['방향'] = summary_items[3].text.strip() if len(summary_items) > 3 else 'Unknown'
            
            # 이미지
            image_tag = item.find('img')
            listing['이미지'] = image_tag['src'] if image_tag else 'No image'
            
            # 코멘트
            comment_tag = item.find('p', class_='ComplexArticleItem_comment__zN_dK')
            listing['코멘트'] = comment_tag.text.strip() if comment_tag else 'No comment'
            
            # 각 매물마다 기본 상세 정보(공급면적, 방/욕실 등)를 매물에 추가
            combined_listing = {**detail_dict, **listing}
            listings.append(combined_listing)
        
        return listings
    
    except Exception as e:
        print(f"Error fetching details for {apt_code}: {e}")
        return []

# 아파트 정보를 수집하는 함수 (법정동 선택 가능)
def collect_apt_info_for_city(city_name, sigungu_name, dong_name=None, json_path='/content/drive/MyDrive/district.json'):
    sigungu_codes, dong_list = get_dong_codes_for_city(city_name, sigungu_name, json_path)

    if dong_list is None:
        print(f"Error: {city_name} not found in JSON.")
        return None

    all_apt_data = []
    dong_code_name_map = {dong['code']: dong['name'] for dong in dong_list}

    # 법정동 선택
    if dong_name and dong_name != '전체':
        dong_code_name_map = {k: v for k, v in dong_code_name_map.items() if v == dong_name}

    for dong_code, dong_name in dong_code_name_map.items():
        print(f"Collecting apartment codes for {dong_code} ({dong_name})")
        apt_codes = get_apt_list(dong_code)

        if not apt_codes.empty:
            for _, apt_info in apt_codes.iterrows():
                apt_code = apt_info['complexNo']
                print(f"Collecting details for {apt_code}")
                listings = get_apt_details(apt_code)
                
                if listings:
                    for listing in listings:
                        # 모든 매물 정보를 결합
                        listing['dong_code'] = dong_code
                        listing['dong_name'] = dong_name
                        all_apt_data.append(listing)
        else:
            print(f"No apartment codes found for {dong_code}")

    if all_apt_data:
        final_df = pd.DataFrame(all_apt_data)
        final_df['si_do_name'] = city_name
        final_df['sigungu_name'] = sigungu_name
        final_df['dong_name'] = dong_name if dong_name else '전체'
        
        # 엑셀 파일로 저장
        file_path = f'/content/drive/MyDrive/{city_name}_{sigungu_name}_apartments.xlsx'
        final_df.to_excel(file_path, index=False)
        print(f"Data saved to {file_path}")
    else:
        print("No data to save.")

# 함수 호출 예시
collect_apt_info_for_city("서울특별시", "강남구", "개포동")

자 다음편에서는 이 서비스를 스트림릿으로 연계해서 실제 사용자가 편하게 웹에서 선택하여 사용할 수 있도록 정리해보겠습니다.

 

urjent

Share
Published by
urjent

Recent Posts

북한 발사체 발사 동해상 포착 및 합동참모본부 제원 정밀 분석 상황

북한이 2026년 8월 6일 동해상으로 정체가 확인되지 않은 미상 발사체를 발사했습니다. 합동참모본부는 해당 발사 사실을…

14시간 ago

추석 민생지원금 지급 지역 및 금액 확인과 지자체별 신청 방법 안내

일부 지방자치단체가 추석 명절을 앞두고 지역 경제 활성화와 가계 부담 완화를 위해 주민 1인당 30만…

17시간 ago

나는 솔로 33기 모태솔로 특집 무주 솔로나라 33번지 첫인상 선택과 반전 결과

나는 솔로 33기가 세 번째 모태솔로 특집으로 돌아와 전북 무주에 마련된 솔로나라 33번지에서 청춘남녀들의 첫…

22시간 ago

현대차 51만대 리콜 실시 제네시스 G80 및 투싼 아반떼 결함 원인 분석

현대자동차와 기아를 포함한 4개 자동차 회사가 총 51만 2393대에 달하는 대규모 자발적 리콜을 실시합니다. 국토교통부는…

1일 ago

지방국립대 내년부터 등록금 0원 추진, 신입생 6만 명 전액 장학금 검토

교육부는 이르면 내년부터 전국 30곳의 지방국립대학교에 입학하는 신입생들에게 등록금 전액을 장학금으로 지원하여 사실상 등록금 0원을…

1일 ago

경기도 재정 비상 상황 선언과 7700억 원 규모 감액 추경 및 세출 구조조정 단행

추미애 경기도지사는 2026년 8월 5일 경기도청 브리핑룸에서 기자회견을 열고 경기도 재정 비상 상황을 공식 선언했습니다.…

1일 ago