Categories: '교육'

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

Warning: getimagesize(https://i3.wp.com/blog.kakaocdn.net/dna/kNe8i/btsJFrFyRi2/AAAAAAAAAAAAAAAAAAAAAKQhPCwUWTcHbTmNakh1rUV2_ynyfedWsYkxhgQePuwR/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1767193199&allow_ip&allow_referer&signature=M0iTLHYWyW%2FCh1utPbgQflra3do%3D&ssl=1): Failed to open stream: HTTP request failed! HTTP/1.1 404 File Not Found in /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/components/featured-image/featured-image.php on line 64 Call Stack: 0.0000 360832 1. {main}() /volume1/web/aboda_re/index.php:0 0.0001 361136 2. require('/volume1/web/aboda_re/wp-blog-header.php') /volume1/web/aboda_re/index.php:17 0.1507 8715528 3. require_once('/volume1/web/aboda_re/wp-includes/template-loader.php') /volume1/web/aboda_re/wp-blog-header.php:19 0.1507 8715528 4. do_action($hook_name = 'template_redirect') /volume1/web/aboda_re/wp-includes/template-loader.php:13 0.1507 8715744 5. WP_Hook->do_action($args = [0 => '']) /volume1/web/aboda_re/wp-includes/plugin.php:517 0.1507 8715744 6. WP_Hook->apply_filters($value = '', $args = [0 => '']) /volume1/web/aboda_re/wp-includes/class-wp-hook.php:348 0.1548 8819304 7. AMPforWP\AMPVendor\amp_render('') /volume1/web/aboda_re/wp-includes/class-wp-hook.php:324 0.2602 9305832 8. AMPforWP\AMPVendor\AMP_Post_Template->load() /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/includes/vendor/amp/amp.php:95 0.2602 9305832 9. AMPforWP\AMPVendor\AMP_Post_Template->load_parts($templates = [0 => 'single']) /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/includes/vendor/amp/includes/class-amp-post-template.php:160 0.2602 9306152 10. AMPforWP\AMPVendor\AMP_Post_Template->verify_and_include($file = '/volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/templates/design-manager/swift/single.php', $template_type = 'single') /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/includes/vendor/amp/includes/class-amp-post-template.php:166 0.2611 9307704 11. include('/volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/templates/design-manager/swift/single.php') /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/includes/vendor/amp/includes/class-amp-post-template.php:501 0.2859 9433336 12. amp_featured_image() /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/templates/design-manager/swift/single.php:45 0.2859 9433336 13. ampforwp_framework_get_featured_image() /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/components/components-core.php:225 0.2917 9434504 14. getimagesize($filename = 'https://i3.wp.com/blog.kakaocdn.net/dna/kNe8i/btsJFrFyRi2/AAAAAAAAAAAAAAAAAAAAAKQhPCwUWTcHbTmNakh1rUV2_ynyfedWsYkxhgQePuwR/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1767193199&allow_ip&allow_referer&signature=M0iTLHYWyW%2FCh1utPbgQflra3do%3D&ssl=1') /volume1/web/aboda_re/wp-content/plugins/accelerated-mobile-pages/components/featured-image/featured-image.php:64

[고급] 부동산 정보 필터 고도화 – 네이버 매물 정리하기 편에 이어서 추가적으로 결과 값을 조금 더 디테일하게 정리해보려고 합니다. 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

진해 날씨 및 옷차림 준비 방법: 벚꽃 시즌 건강하게 즐기기

진해에서 벚꽃 시즌을 맞이하고 준비하는 데 있어 날씨와 옷차림이 매우 중요합니다. 많은 이들이 진해 군항제를…

16시간 ago

블랙이글스 에어쇼 명당 위치와 최적의 관람 포인트 안내

블랙이글스 에어쇼는 2026 진해 군항제 기간에 펼쳐지는 화려한 항공 축제로, 최적의 관람 포인트를 찾는 것이…

16시간 ago

군항제 근처 맛집 리스트 Top10: 진해에서 꼭 가봐야 할 핫플레이스

진해 군항제는 대한민국에서 가장 유명한 벚꽃 축제 중 하나로, 매년 수많은 방문객이 찾습니다. 이 시기에…

16시간 ago

경화역 벚꽃 포토존 위치 완벽 가이드 – 사진 명소 추천

진해의 경화역은 매년 봄철마다 아름다운 벚꽃으로 가득 차 관광객들이 몰려드는 명소입니다. 특히 경화역을 배경으로 한…

16시간 ago

경화역 벚꽃 포토존 위치 완벽 가이드 – 사진 명소 추천

진해의 경화역은 매년 봄철마다 아름다운 벚꽃으로 가득 차 관광객들이 몰려드는 명소입니다. 특히 경화역을 배경으로 한…

16시간 ago

진해 1박2일 숙소 잔여객실 실시간 알아보기 및 추천 리스트

진해에서의 1박2일 여행을 계획 중이라면, 현재 잔여 객실 정보를 신속히 확인하는 것이 중요합니다. 특히, 2026…

16시간 ago