본문 바로가기

콩's AI

AI로 네이버 부동산 매물 조회 프로그램 만들기 (3/3)

반응형

나만의 네이버 부동산 매물 조회 프로그램 (3/3)

어느덧 시리즈의 마지막 편입니다!
1편에서 기본 구조를 세우고, 2편에서 상세 정보와 매물 목록을 불러오는 기능까지 완성했죠.
이번 3편에서는 여기서 만족하지 않고, '빌라/주택' 탭처럼 여러 페이지에 걸쳐 있는 방대한 양의 데이터를 모두 가져오는 고급 기법과 함께 사용자 경험(UX)을 개선하는 팁까지 다루며 프로젝트를 완성해 보겠습니다.

1단계: 새로운 API 탐험 - 지역명으로 좌표 얻기 🗺️

아파트/오피스텔 검색은 '동 코드'라는 명확한 값으로 시작했지만, 빌라/주택 검색은 다릅니다. 네이버 부동산의 모바일 API를 활용해야 하는데, 여기서는 '동 코드' 대신 지리적 좌표(위도, 경도)를 사용합니다. 따라서 사용자가 '잠실동'이라고 입력하면, 이를 API가 알아들을 수 있는 좌표 값으로 변환하는 과정이 먼저 필요합니다.

이를 위해 약간의 트릭을 사용합니다. 네이버 모바일 부동산에서 지역명을 검색하면, 해당 지역의 좌표가 포함된 URL로 이동(리다이렉트)되는 점을 이용하는 것입니다.

핵심 코드: app.py의 지역 검색 엔드포인트

백엔드에 /api/search-region 이라는 엔드포인트를 만들어 이 과정을 처리합니다.

# backend/app.py

@app.route('/api/search-region', methods=['GET'])
def search_region():
    region_name = request.args.get('regionName') # 예: '잠실동'
    
    # 네이버 모바일 부동산에 지역명으로 검색 요청
    search_url = f'https://m.land.naver.com/search/result/{region_name}'
    search_response = requests.get(search_url, headers=headers)
    
    # 최종적으로 이동된 URL (좌표가 포함되어 있음)
    redirected_url = search_response.url 
    # redirected_url => https://m.land.naver.com/map/37.51...:127.10...:14:1171010100/...

    # URL에서 위도, 경도, 확대 레벨(z), 동 코드(cortarNo) 추출
    map_params = redirected_url.split('/map/')[1].split('/')[0]
    parts = map_params.split(':')
    lat, lon, z, cortar_no = parts[0], parts[1], parts[2], parts[3]
    
    # 추출한 정보를 담아 프론트엔드에 반환
    return jsonify({
        'searchParams': { 'lat': lat, 'lon': lon, 'z': z, 'cortarNo': cortar_no }
    })
  • 이 엔드포인트 덕분에 프론트엔드에서는 단순히 '잠실동'이라는 텍스트만 보내면, API 요청에 필요한 모든 지리 정보를 한 번에 얻을 수 있게 됩니다.

2단계: 끝까지 간다! - 모든 매물 페이지 크롤링하기 🔄

빌라/주택처럼 매물이 수백, 수천 개에 달하는 경우, API는 보통 한 번에 20개씩 페이징(Paging) 처리를 해서 데이터를 나눠줍니다. 따라서 우리는 더 이상 가져올 데이터가 없을 때까지 반복적으로 API를 호출해서 모든 데이터를 수집해야 합니다.

이 과정은 프론트엔드에서 직접 처리하도록 구현했습니다. 먼저 매물이 총 몇 개인지 알아낸 뒤, 필요한 페이지 수만큼 반복해서 요청을 보내는 방식입니다.

핵심 코드: RealEstateSearch.vue의 순차적 데이터 로딩

사용자가 '빌라/주택' 탭에서 검색 버튼을 누르면 다음과 같은 과정이 순차적으로 실행됩니다.

// frontend/src/views/RealEstateSearch.vue

async searchVillaHouseByRegion() {
  this.isVillaHouseLoading = true;
  this.villaHouseSearchResult = [];
  
  try {
    // 1. (1단계) 지역명으로 좌표 정보 얻기
    const searchResponse = await villaHouseService.searchRegion({ regionName: this.villaHouseSearchParams.regionName });
    const geoInfo = searchResponse.data.searchParams;
    this.villaHouseSearchInfo = { ...geoInfo }; // 좌표 정보 저장

    // 2. 해당 지역의 전체 매물 개수(totCnt) 파악하기
    const countResponse = await villaHouseService.getVillaHouseCount(this.villaHouseSearchInfo);
    const totCnt = countResponse.data.totCnt || 0;
    if (totCnt === 0) {
      alert('해당 조건에 맞는 매물이 없습니다.');
      return;
    }

    // 3. (핵심) 전체 페이지 수 계산 후, 모든 페이지 순차적으로 호출
    await this.loadAllVillaHouseArticlesSequential(totCnt);

  } catch (error) {
    // ... 에러 처리 ...
  } finally {
    this.isVillaHouseLoading = false;
  }
},

async loadAllVillaHouseArticlesSequential(totCnt) {
  const itemsPerPage = 20; // 네이버 API는 보통 한 페이지에 20개
  const totalPages = Math.ceil(totCnt / itemsPerPage);
  let allResults = [];

  for (let page = 1; page <= totalPages; page++) {
    const params = { ...this.villaHouseSearchInfo, page: page };
    const res = await villaHouseService.getVillaHouseByLocation(params);
    allResults.push(...res.data.articleList); // 결과를 계속 이어붙임
  }
  
  // 중복 제거 후 최종 결과 저장
  const unique = {};
  for (const item of allResults) {
      if (item && item.atclNo) unique[item.atclNo] = item;
  }
  this.villaHouseSearchResult = Object.values(unique);
}
  • 이제 사용자는 검색 한 번으로 해당 지역의 모든 빌라/주택 매물을 한눈에 볼 수 있게 되었습니다.
    데이터가 많을수록 이 기능의 진가가 드러나죠.

3단계: 기다림의 미학 - 로딩 상태 시각화하기 ⏳

수십 페이지의 데이터를 가져오는 데는 당연히 시간이 걸립니다. 이때 사용자가 아무런 피드백 없이 하얀 화면만 보고 있다면, 프로그램이 멈췄다고 생각하고 떠나버릴 수 있습니다. 따라서 '지금 열심히 데이터를 가져오고 있어요!'라는 시각적 신호를 보내주는 것이 매우 중요합니다.

이를 위해 '스켈레톤(Skeleton) UI'를 도입했습니다. 실제 데이터가 표시될 테이블과 비슷한 모양의 회색 상자를 미리 보여주어, 사용자가 앞으로 보게 될 내용의 구조를 예측하고 안정감을 느끼게 해주는 기법입니다.

로딩중에 기다림에 지친이들에게 조그만한 안식처가 될 스켈레톤 UI 기법

핵심 코드: RealEstateSearch.vue & SkeletonTable.vue

데이터 로딩 상태를 나타내는 isVillaHouseLoading 변수를 이용해, 로딩 중일 때는 스켈레톤 UI를, 로딩이 끝나면 실제 데이터 테이블을 보여주도록 분기 처리합니다.

<!-- frontend/src/views/RealEstateSearch.vue -->
<div class="right-panel">
  <!-- 로딩 중일 때 SkeletonTable 컴포넌트를 보여줌 -->
  <div v-if="isVillaHouseLoading">
    <SkeletonTable 
      :rows="10" 
      :columns="8" 
      :estimatedTime="getEstimatedRemainingTime()" />
  </div>
  
  <!-- 로딩이 끝나면 실제 결과 테이블을 보여줌 -->
  <template v-else>
    <table class="table villa-house-table">
      <!-- ... 실제 데이터 v-for ... -->
    </table>
  </template>
</div>
  • SkeletonTable.vue는 단순히 회색 박스로 테이블 형태를 흉내 내는 컴포넌트입니다.
  • 여기에 추가로 estimatedTime(예상 남은 시간) 같은 정보를 표시해주면, 사용자는 막연한 기다림에서 벗어나 훨씬 편안하게 서비스를 이용할 수 있습니다.

 

프로젝트를 마치며

총 3편에 걸쳐 네이버 부동산 매물 조회 프로그램을 만드는 여정을 함께했습니다. 간단한 단지 목록 조회에서 시작해, 상세 정보 모달을 띄우고, 수십 페이지의 데이터를 모두 크롤링하는 고급 기능까지 구현해 보았습니다. 이 프로젝트는 웹 크롤링, 백엔드와 프론트엔드의 데이터 통신, 상태 관리, 사용자 경험 개선 등 웹 개발의 핵심적인 요소들을 두루 경험할 수 있는 좋은 기회였다고 생각합니다.

여기서 멈추지 않고 가격 필터링, 면적별 차트 시각화 등 자신만의 기능을 추가하며 프로그램을 더욱 발전시켜보는 것은 어떨까요? 긴 글 읽어주셔서 감사합니다!

반응형

⚠️ 광고 차단 프로그램 감지

애드블록, 유니콘 등 광고 차단 확장 프로그램을 해제하거나
화이트리스트에 추가해주세요.