참고 유튜브
https://www.youtube.com/watch?v=V4tjbUQ46Uw&t=185s
코드
CSV 문서로 변환해서 테스트
import requests
import json
import csv
from datetime import datetime
import time
class NaverRealEstateCrawler:
def __init__(self):
self.cookies = {
'NNB': 'REPOEE3TYVIWK',
'ASID': '738bd02a0000018bcaf202af0000004f',
'_ga_451MFZ9CFM': 'GS1.1.1722665147.2.1.1722665162.0.0.0',
'_ga_Q7G1QTKPGB': 'GS1.1.1722665276.1.0.1722665276.0.0.0',
'naverfinancial_CID': '921ffa2916a840f19cdaf450ab333dd2',
'NID_AUT': 'maNJRVfRZfYtBA6kCCElhLtdnseKDPsX63iU2vwuH3Hf+mqhRbIxNRR7/sBrv8c7',
'NID_JKL': 'K7Qu+Lvg6hu9Hprc0Z13eLUbhnpxd11nSlazRlmwP1w=',
'_ga_RCM29786SD': 'GS1.1.1727568316.3.1.1727568333.0.0.0',
'NFS': '2',
'_fbp': 'fb.1.1733047540332.474845353899071337',
'nstore_session': '6s+yUW4sTUs20wfs/tNrBAzc',
'_ga_J5CZVNJNQP': 'GS1.1.1734828968.1.0.1734828972.0.0.0',
'NV_WETR_LOCATION_RGN_M': '"MDk1NDUxMDM="',
'NV_WETR_LAST_ACCESS_RGN_M': '"MDk1NDUxMDM="',
'_fwb': '107itr85p91OlxDKh8vV1vr.1741440649696',
'_ga_EFBDNNF91G': 'GS1.1.1743241835.11.0.1743241840.0.0.0',
'ba.uuid': '5412b756-6be2-481b-bca3-dc9453f8049c',
'_ga_EQP4JZZ5VJ': 'GS1.1.1743255876.2.0.1743255876.0.0.0',
'BNB_FINANCE_HOME_TOOLTIP_STOCK': 'true',
'BNB_FINANCE_HOME_TOOLTIP_ESTATE': 'true',
'_ga': 'GA1.2.1205107697.1700030410',
'_ga_6Z6DP60WFK': 'GS1.2.1745159159.1.0.1745159159.60.0.0',
'nstore_pagesession': 'js12RdqWVOfvplsMV6G-135711',
'ab.storage.userId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Ak0os%7Ce%3Aundefined%7Cc%3A1745742046639%7Cl%3A1745742046640',
'ab.storage.deviceId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Ab7f75e9e-32ee-6352-deb3-9ab73f4459a6%7Ce%3Aundefined%7Cc%3A1745742046641%7Cl%3A1745742046641',
'ab.storage.sessionId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Aef42f330-0fde-f838-d4a0-a8682c56cfee%7Ce%3A1745743846735%7Cc%3A1745742046640%7Cl%3A1745742046735',
'NACT': '1',
'page_uid': 'jvZEowqVOsossPowS9dssssssZV-019315',
'NAC': 'AJnGBQAwjf9VA',
'SRT30': '1748053745',
'NID_SES': 'AAABtn4ilEz+qs+jVZ+/Xd+pKvVD2uFym66rsgUzSAN6GJHJ9+GkR5Kdvb6HAjq0AURhTLVYY2A3ElxUexyElSgmys4lano8tdeviKjlj5qxMAcpY4niXCx3bX9yz2TgZy4Q72dC5qzViJB46S2Refpn0iZYnH89Mj1EGmnBuV39YkW1cflNtuJOYIl5ZYYrAPR+FUPQOOhyScucVxq1DpevdJYFFUAMLvituflO7R9SDvCCh+8bukILobBGVNe3WGH9UIdqE5NRW4OF3K5ZvlNe/8PaE6I+twnTcrnoL4gjdAY4P7n7Mb9dqjDNqt9jTu+r+6FXY02pd5SV5y/dF/4DoYeI0wTiJL8JQTk3TAG03pYmSKcSuwCLbdyLnGJcK1ykKvef2kJAyTwLNFOqwMG46fx7hC1tTwkwsIv5GWbjP1EXeWxisZ35cgZp0LzO3NoBe/r3UmrRqToUzIRlHSHz5d9+aK15z8ZlJl38MuktdZDqRev7HCtcUvA0uJE04kyF2h03LLdtkmiAtAEMvkvIlXKJ9naMJgXiHnSxkkjEcHeStWXJpZxYfiDDxkf3/buzFwaCs2eFUsUeMn4HRp9sZq8=',
'SRT5': '1748055206',
'nhn.realestate.article.rlet_type_cd': 'A01',
'nhn.realestate.article.trade_type_cd': '""',
'nhn.realestate.article.ipaddress_city': '1100000000',
'landHomeFlashUseYn': 'Y',
'realestate.beta.lastclick.cortar': '4100000000',
'REALESTATE': 'Sat%20May%2024%202025%2011%3A53%3A50%20GMT%2B0900%20(Korean%20Standard%20Time)',
'BUC': 'jX8TzKX0jkTs2LTom2dHhzpzdbqMKgShauc7WvKutnw=',
}
self.headers = {
'accept': '*/*',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlJFQUxFU1RBVEUiLCJpYXQiOjE3NDgwNTUyMzAsImV4cCI6MTc0ODA2NjAzMH0.mEmiTcb0P5uTI6spw5k6_EQQSO3sL3yRQPTbD72TYpE',
'priority': 'u=1, i',
'referer': 'https://new.land.naver.com/search?ms=37.7219314,127.0423986,19&a=APT:PRE:ABYG:JGC&e=RETAIL&articleNo=2527822843',
'sec-ch-ua': '"Chromium";v="136", "Google Chrome";v="136", "Not.A/Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
}
def crawl_complex(self, complex_no, start_page=1, end_page=10):
"""특정 아파트 단지의 매물 정보를 크롤링합니다."""
all_articles = []
for page in range(start_page, end_page + 1):
print(f"페이지 {page}/{end_page} 크롤링 중...")
url = f'https://new.land.naver.com/api/articles/complex/{complex_no}?realEstateType=APT%3APRE%3AABYG%3AJGC&tradeType=&tag=%3A%3A%3A%3A%3A%3A%3A%3A&rentPriceMin=0&rentPriceMax=900000000&priceMin=0&priceMax=900000000&areaMin=0&areaMax=900000000&oldBuildYears&recentlyBuildYears&minHouseHoldCount&maxHouseHoldCount&showArticle=false&sameAddressGroup=false&minMaintenanceCost&maxMaintenanceCost&priceType=RETAIL&directions=&page={page}&complexNo={complex_no}&buildingNos=&areaNos=&type=list&order=rank'
try:
response = requests.get(url, cookies=self.cookies, headers=self.headers)
response.raise_for_status()
data = response.json()
articles = data.get('articleList', [])
if not articles:
print(f"페이지 {page}에 더 이상 매물이 없습니다.")
break
all_articles.extend(articles)
# 더 이상 데이터가 없으면 중단
if not data.get('isMoreData', False):
print("모든 매물을 가져왔습니다.")
break
# API 부하 방지를 위한 딜레이 (페이지가 많으므로 시간 증가)
time.sleep(1.5)
except Exception as e:
print(f"페이지 {page} 크롤링 중 오류 발생: {e}")
# 오류 발생해도 계속 진행
continue
print(f"총 {len(all_articles)}개의 매물을 수집했습니다.")
return all_articles
def parse_price(self, price_str):
"""가격 문자열을 원 단위로 변환합니다."""
try:
price_str = price_str.replace(',', '').strip()
price_won = 0
# 억 단위 처리
if '억' in price_str:
parts = price_str.split('억')
eok = int(parts[0].strip())
price_won += eok * 100000000
# 만 단위가 있는 경우
if len(parts) > 1 and parts[1].strip():
man_str = parts[1].strip()
if '만' in man_str:
man = int(man_str.replace('만', '').strip())
price_won += man * 10000
# 만 단위만 있는 경우
elif '만' in price_str:
man = int(price_str.replace('만', '').strip())
price_won += man * 10000
return price_won
except:
return 0
def parse_articles(self, articles):
"""크롤링한 매물 정보를 파싱하여 리스트로 변환합니다."""
parsed_data = []
for article in articles:
# 가격 정보 처리
price_str = article.get('dealOrWarrantPrc', '')
price_won = self.parse_price(price_str)
# 태그 리스트를 문자열로 변환
tags = ', '.join(article.get('tagList', []))
parsed_article = {
'매물번호': article.get('articleNo'),
'아파트명': article.get('articleName'),
'거래유형': article.get('tradeTypeName'),
'가격(원)': price_won,
'가격(표시)': article.get('dealOrWarrantPrc'),
'면적(㎡)': article.get('area1'),
'전용면적(㎡)': article.get('area2'),
'층수': article.get('floorInfo'),
'향': article.get('direction'),
'동': article.get('buildingName'),
'확인일자': article.get('articleConfirmYmd'),
'특징': article.get('articleFeatureDesc'),
'태그': tags,
'중개사': article.get('realtorName'),
'중개업체': article.get('cpName'),
'동일주소매물수': article.get('sameAddrCnt'),
'최고가': article.get('sameAddrMaxPrc'),
'최저가': article.get('sameAddrMinPrc'),
'크롤링시간': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
parsed_data.append(parsed_article)
return parsed_data
def save_to_csv(self, data, filename='naver_real_estate_data.csv'):
"""파싱된 데이터를 CSV 파일로 저장합니다."""
if not data:
print("저장할 데이터가 없습니다.")
return
# 첫 번째 딕셔너리의 키를 헤더로 사용
headers = list(data[0].keys())
# CSV 파일 작성 (UTF-8 BOM으로 한글 깨짐 방지)
with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=headers)
writer.writeheader()
writer.writerows(data)
print(f"{filename} 파일로 저장되었습니다. (총 {len(data)}개 매물)")
# 사용 예시
if __name__ == "__main__":
# 크롤러 인스턴스 생성
crawler = NaverRealEstateCrawler()
# 호원한승미메이드 아파트 (complex_no: 19494) 크롤링
complex_no = 19494
print(f"아파트 단지 번호 {complex_no} 크롤링 시작...")
print("1페이지부터 10페이지까지 크롤링합니다.")
# 매물 정보 크롤링 (1페이지부터 10페이지까지)
articles = crawler.crawl_complex(complex_no, start_page=1, end_page=10)
if articles:
# 데이터 파싱
parsed_data = crawler.parse_articles(articles)
# CSV 파일로 저장
crawler.save_to_csv(parsed_data, f'howon_hanseung_mimade_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv')
# 결과 요약 출력
print("\n=== 크롤링 결과 요약 ===")
print(f"총 매물 수: {len(parsed_data)}")
# 가격 통계 계산 (pandas 없이)
prices = [item['가격(원)'] for item in parsed_data if item['가격(원)'] > 0]
if prices:
avg_price = sum(prices) / len(prices)
max_price = max(prices)
min_price = min(prices)
print(f"평균 가격: {avg_price:,.0f}원")
print(f"최고 가격: {max_price:,.0f}원")
print(f"최저 가격: {min_price:,.0f}원")
# 면적별 매물 수 계산
area_count = {}
for item in parsed_data:
area = item['면적(㎡)']
area_count[area] = area_count.get(area, 0) + 1
print("\n면적별 매물 수:")
for area in sorted(area_count.keys()):
print(f"{area}㎡: {area_count[area]}개")
else:
print("크롤링된 매물이 없습니다.")
네이버부동산.py
import streamlit as st
import requests
import json
import pandas as pd
from datetime import datetime
import time
import plotly.express as px
import plotly.graph_objects as go
# 페이지 설정
st.set_page_config(
page_title="네이버 부동산 매물 조회",
page_icon="🏠",
layout="wide"
)
class NaverRealEstateAPI:
def __init__(self):
self.cookies = {
'NNB': 'REPOEE3TYVIWK',
'ASID': '738bd02a0000018bcaf202af0000004f',
'_ga_451MFZ9CFM': 'GS1.1.1722665147.2.1.1722665162.0.0.0',
'_ga_Q7G1QTKPGB': 'GS1.1.1722665276.1.0.1722665276.0.0.0',
'naverfinancial_CID': '921ffa2916a840f19cdaf450ab333dd2',
'NID_AUT': 'maNJRVfRZfYtBA6kCCElhLtdnseKDPsX63iU2vwuH3Hf+mqhRbIxNRR7/sBrv8c7',
'NID_JKL': 'K7Qu+Lvg6hu9Hprc0Z13eLUbhnpxd11nSlazRlmwP1w=',
'_ga_RCM29786SD': 'GS1.1.1727568316.3.1.1727568333.0.0.0',
'NFS': '2',
'_fbp': 'fb.1.1733047540332.474845353899071337',
'nstore_session': '6s+yUW4sTUs20wfs/tNrBAzc',
'_ga_J5CZVNJNQP': 'GS1.1.1734828968.1.0.1734828972.0.0.0',
'NV_WETR_LOCATION_RGN_M': '"MDk1NDUxMDM="',
'NV_WETR_LAST_ACCESS_RGN_M': '"MDk1NDUxMDM="',
'_fwb': '107itr85p91OlxDKh8vV1vr.1741440649696',
'_ga_EFBDNNF91G': 'GS1.1.1743241835.11.0.1743241840.0.0.0',
'ba.uuid': '5412b756-6be2-481b-bca3-dc9453f8049c',
'_ga_EQP4JZZ5VJ': 'GS1.1.1743255876.2.0.1743255876.0.0.0',
'BNB_FINANCE_HOME_TOOLTIP_STOCK': 'true',
'BNB_FINANCE_HOME_TOOLTIP_ESTATE': 'true',
'_ga': 'GA1.2.1205107697.1700030410',
'_ga_6Z6DP60WFK': 'GS1.2.1745159159.1.0.1745159159.60.0.0',
'nstore_pagesession': 'js12RdqWVOfvplsMV6G-135711',
'ab.storage.userId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Ak0os%7Ce%3Aundefined%7Cc%3A1745742046639%7Cl%3A1745742046640',
'ab.storage.deviceId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Ab7f75e9e-32ee-6352-deb3-9ab73f4459a6%7Ce%3Aundefined%7Cc%3A1745742046641%7Cl%3A1745742046641',
'ab.storage.sessionId.7d7bb94a-f465-48e5-bec1-35db97daf128': 'g%3Aef42f330-0fde-f838-d4a0-a8682c56cfee%7Ce%3A1745743846735%7Cc%3A1745742046640%7Cl%3A1745742046735',
'NACT': '1',
'page_uid': 'jvZEowqVOsossPowS9dssssssZV-019315',
'NAC': 'AJnGBQAwjf9VA',
'SRT30': '1748053745',
'NID_SES': 'AAABtn4ilEz+qs+jVZ+/Xd+pKvVD2uFym66rsgUzSAN6GJHJ9+GkR5Kdvb6HAjq0AURhTLVYY2A3ElxUexyElSgmys4lano8tdeviKjlj5qxMAcpY4niXCx3bX9yz2TgZy4Q72dC5qzViJB46S2Refpn0iZYnH89Mj1EGmnBuV39YkW1cflNtuJOYIl5ZYYrAPR+FUPQOOhyScucVxq1DpevdJYFFUAMLvituflO7R9SDvCCh+8bukILobBGVNe3WGH9UIdqE5NRW4OF3K5ZvlNe/8PaE6I+twnTcrnoL4gjdAY4P7n7Mb9dqjDNqt9jTu+r+6FXY02pd5SV5y/dF/4DoYeI0wTiJL8JQTk3TAG03pYmSKcSuwCLbdyLnGJcK1ykKvef2kJAyTwLNFOqwMG46fx7hC1tTwkwsIv5GWbjP1EXeWxisZ35cgZp0LzO3NoBe/r3UmrRqToUzIRlHSHz5d9+aK15z8ZlJl38MuktdZDqRev7HCtcUvA0uJE04kyF2h03LLdtkmiAtAEMvkvIlXKJ9naMJgXiHnSxkkjEcHeStWXJpZxYfiDDxkf3/buzFwaCs2eFUsUeMn4HRp9sZq8=',
'SRT5': '1748055206',
'nhn.realestate.article.rlet_type_cd': 'A01',
'nhn.realestate.article.trade_type_cd': '""',
'nhn.realestate.article.ipaddress_city': '1100000000',
'landHomeFlashUseYn': 'Y',
'realestate.beta.lastclick.cortar': '4100000000',
'REALESTATE': 'Sat%20May%2024%202025%2011%3A53%3A50%20GMT%2B0900%20(Korean%20Standard%20Time)',
'BUC': 'jX8TzKX0jkTs2LTom2dHhzpzdbqMKgShauc7WvKutnw=',
}
self.headers = {
'accept': '*/*',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IlJFQUxFU1RBVEUiLCJpYXQiOjE3NDgwNTUyMzAsImV4cCI6MTc0ODA2NjAzMH0.mEmiTcb0P5uTI6spw5k6_EQQSO3sL3yRQPTbD72TYpE',
'priority': 'u=1, i',
'referer': 'https://new.land.naver.com/search',
'sec-ch-ua': '"Chromium";v="136", "Google Chrome";v="136", "Not.A/Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
}
def fetch_complex_data(self, complex_no, page=1):
"""특정 아파트 단지의 매물 정보를 API로 가져옵니다."""
url = f'https://new.land.naver.com/api/articles/complex/{complex_no}'
params = {
'realEstateType': 'APT:PRE:ABYG:JGC',
'tradeType': '',
'tag': '::::::::',
'rentPriceMin': 0,
'rentPriceMax': 900000000,
'priceMin': 0,
'priceMax': 900000000,
'areaMin': 0,
'areaMax': 900000000,
'oldBuildYears': '',
'recentlyBuildYears': '',
'minHouseHoldCount': '',
'maxHouseHoldCount': '',
'showArticle': 'false',
'sameAddressGroup': 'false',
'minMaintenanceCost': '',
'maxMaintenanceCost': '',
'priceType': 'RETAIL',
'directions': '',
'page': page,
'complexNo': complex_no,
'buildingNos': '',
'areaNos': '',
'type': 'list',
'order': 'rank'
}
try:
response = requests.get(url, params=params, cookies=self.cookies, headers=self.headers)
response.raise_for_status()
return response.json()
except Exception as e:
st.error(f"API 요청 중 오류 발생: {e}")
return None
def parse_price(self, price_str):
"""가격 문자열을 원 단위로 변환합니다."""
try:
price_str = price_str.replace(',', '').strip()
price_won = 0
if '억' in price_str:
parts = price_str.split('억')
eok = int(parts[0].strip())
price_won += eok * 100000000
if len(parts) > 1 and parts[1].strip():
man_str = parts[1].strip()
if '만' in man_str:
man = int(man_str.replace('만', '').strip())
price_won += man * 10000
elif '만' in price_str:
man = int(price_str.replace('만', '').strip())
price_won += man * 10000
return price_won
except:
return 0
def process_articles(self, articles):
"""API 응답에서 매물 정보를 처리합니다."""
processed_data = []
for article in articles:
price_won = self.parse_price(article.get('dealOrWarrantPrc', ''))
processed_article = {
'매물번호': article.get('articleNo'),
'거래유형': article.get('tradeTypeName'),
'가격': article.get('dealOrWarrantPrc'),
'가격(원)': price_won,
'면적(㎡)': article.get('area1'),
'전용면적(㎡)': article.get('area2'),
'층수': article.get('floorInfo'),
'향': article.get('direction'),
'동': article.get('buildingName'),
'특징': article.get('articleFeatureDesc'),
'중개사': article.get('realtorName'),
'확인일자': article.get('articleConfirmYmd')
}
processed_data.append(processed_article)
return pd.DataFrame(processed_data)
# Streamlit 앱 메인 함수
def main():
st.title("🏠 네이버 부동산 매물 조회 시스템")
st.markdown("---")
# 사이드바 설정
with st.sidebar:
st.header("⚙️ 설정")
# 아파트 단지 정보 입력
complex_no = st.number_input(
"아파트 단지 번호",
min_value=1,
value=19494,
help="네이버 부동산에서 아파트 단지의 고유 번호"
)
# 페이지 범위 설정
col1, col2 = st.columns(2)
with col1:
start_page = st.number_input("시작 페이지", min_value=1, value=1)
with col2:
end_page = st.number_input("종료 페이지", min_value=1, value=10)
# 조회 버튼
search_button = st.button("🔍 매물 조회", type="primary", use_container_width=True)
# API 인스턴스 생성
api = NaverRealEstateAPI()
# 메인 영역
if search_button:
with st.spinner("매물 정보를 가져오는 중..."):
all_data = []
progress_bar = st.progress(0)
status_text = st.empty()
# 각 페이지별로 데이터 가져오기
for page in range(start_page, end_page + 1):
status_text.text(f"페이지 {page}/{end_page} 처리 중...")
data = api.fetch_complex_data(complex_no, page)
if data and 'articleList' in data:
articles = data['articleList']
if articles:
df_page = api.process_articles(articles)
all_data.append(df_page)
# 더 이상 데이터가 없으면 중단
if not data.get('isMoreData', False):
status_text.text(f"페이지 {page}에서 조회 완료")
break
# 진행률 업데이트
progress = (page - start_page + 1) / (end_page - start_page + 1)
progress_bar.progress(progress)
# API 부하 방지
time.sleep(1)
progress_bar.empty()
status_text.empty()
# 결과 표시
if all_data:
df_total = pd.concat(all_data, ignore_index=True)
# 통계 정보 표시
st.header("📊 매물 통계")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("총 매물 수", f"{len(df_total)}개")
with col2:
avg_price = df_total['가격(원)'].mean()
st.metric("평균 가격", f"{avg_price/100000000:.1f}억원")
with col3:
max_price = df_total['가격(원)'].max()
st.metric("최고 가격", f"{max_price/100000000:.1f}억원")
with col4:
min_price = df_total['가격(원)'].min()
st.metric("최저 가격", f"{min_price/100000000:.1f}억원")
# 탭 생성
tab1, tab2, tab3 = st.tabs(["📋 매물 목록", "📈 차트 분석", "💾 데이터 다운로드"])
with tab1:
st.subheader("전체 매물 목록")
# 필터링 옵션
col1, col2, col3 = st.columns(3)
with col1:
selected_area = st.selectbox(
"면적 선택",
["전체"] + sorted(df_total['면적(㎡)'].unique().tolist())
)
with col2:
selected_dong = st.selectbox(
"동 선택",
["전체"] + sorted(df_total['동'].dropna().unique().tolist())
)
with col3:
selected_direction = st.selectbox(
"향 선택",
["전체"] + sorted(df_total['향'].dropna().unique().tolist())
)
# 필터링 적용
filtered_df = df_total.copy()
if selected_area != "전체":
filtered_df = filtered_df[filtered_df['면적(㎡)'] == selected_area]
if selected_dong != "전체":
filtered_df = filtered_df[filtered_df['동'] == selected_dong]
if selected_direction != "전체":
filtered_df = filtered_df[filtered_df['향'] == selected_direction]
# 데이터 표시
st.dataframe(filtered_df, use_container_width=True)
with tab2:
st.subheader("가격 분포 분석")
# 면적별 가격 분포
fig1 = px.box(
df_total,
x='면적(㎡)',
y='가격(원)',
title='면적별 가격 분포',
labels={'가격(원)': '가격 (원)', '면적(㎡)': '면적 (㎡)'}
)
fig1.update_layout(yaxis_tickformat=',.0f')
st.plotly_chart(fig1, use_container_width=True)
# 동별 매물 수
dong_counts = df_total['동'].value_counts()
fig2 = px.bar(
x=dong_counts.index,
y=dong_counts.values,
title='동별 매물 수',
labels={'x': '동', 'y': '매물 수'}
)
st.plotly_chart(fig2, use_container_width=True)
# 층수별 가격 분석
df_floor = df_total.copy()
df_floor['층'] = df_floor['층수'].str.extract(r'(\d+)/')
df_floor['층'] = pd.to_numeric(df_floor['층'], errors='coerce')
fig3 = px.scatter(
df_floor.dropna(subset=['층']),
x='층',
y='가격(원)',
color='면적(㎡)',
title='층수별 가격 분포',
labels={'가격(원)': '가격 (원)', '층': '층수'}
)
fig3.update_layout(yaxis_tickformat=',.0f')
st.plotly_chart(fig3, use_container_width=True)
with tab3:
st.subheader("데이터 다운로드")
# CSV 다운로드
csv = df_total.to_csv(index=False, encoding='utf-8-sig')
st.download_button(
label="📥 CSV 파일 다운로드",
data=csv,
file_name=f"naver_real_estate_{complex_no}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv"
)
# 엑셀 다운로드
try:
import io
buffer = io.BytesIO()
with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
df_total.to_excel(writer, sheet_name='매물목록', index=False)
st.download_button(
label="📥 Excel 파일 다운로드",
data=buffer.getvalue(),
file_name=f"naver_real_estate_{complex_no}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
except:
st.info("Excel 다운로드를 위해서는 xlsxwriter 패키지가 필요합니다.")
else:
st.warning("조회된 매물이 없습니다.")
else:
# 초기 화면
st.info("👈 왼쪽 사이드바에서 아파트 단지 번호와 페이지 범위를 설정한 후 '매물 조회' 버튼을 클릭하세요.")
# 사용 안내
with st.expander("📖 사용 방법"):
st.markdown("""
1. **아파트 단지 번호 입력**: 네이버 부동산에서 조회하고자 하는 아파트의 단지 번호를 입력합니다.
- 예: 호원한승미메이드 = 19494
2. **페이지 범위 설정**: 조회할 페이지의 시작과 끝을 설정합니다.
- 각 페이지당 약 20개의 매물이 표시됩니다.
3. **매물 조회**: 설정을 완료한 후 '매물 조회' 버튼을 클릭합니다.
4. **결과 확인**:
- 📋 매물 목록: 전체 매물을 표로 확인
- 📈 차트 분석: 가격 분포와 통계를 시각화
- 💾 데이터 다운로드: CSV 또는 Excel 파일로 저장
""")
if __name__ == "__main__":
main()
streamlit 실행
streamlit run 네이버부동산.py
결과
참고자료
curl 복사해서 크롤링 데이터 확인
Convert curl commands to code
Privacy We do not transmit or record the curl commands you enter or what they're converted to. This is a static website (hosted on GitHub Pages) and the conversion happens entirely in your browser using JavaScript. There is also a VS Code extension and a c
curlconverter.com
출처 및 참고 유튜브
https://www.youtube.com/watch?v=V4tjbUQ46Uw&t=185s
의견
AI로 너무 빠르게 만들었다. 만든 시간이 1시간 밖에 안걸린 것 같다 ;;
이것저것 깔기 싫어서 영상처럼 주피터를 활용하고 몇가지 방법밖에 안쓴 것 같은데 끝이났다.
진짜 이제 AI로 할 수 있는게 무궁무진해졌다. 너무 재밌다.
근데 이번에 새로나온 클로드 opus4를 써봤는데 생각보다 너무 좋아서 놀랐다
다만 너무 비싸서 테스트용으로 쓸 수 밖에 없었다.
'혼자 고민해보기_ 개발' 카테고리의 다른 글
[Python] Python Advanced (0) | 2024.12.29 |
---|---|
[Python] 환경 설정 (0) | 2024.12.29 |
Test Code -Nest.js (0) | 2023.11.07 |
cypress로 e2e테스트 이해하기 (0) | 2023.11.07 |
Redis 캐시(Cache)적용을 통한 조회 성능 개선 (1) | 2023.11.02 |