하먹이
하먹이
🌿 뉴비 파트너
🏡 내집마련 찐친

AI로 경매 도전하기(2) - 법원경매정보 크롤링해서 물건 찾아오기

안녕하세요! 하먹이 입니다 :)

지난번에는 경매 권리분석을 어떻게 하는지에 대해 작성했었는데요.

부린이에게 적합한 경매 투자 방법을 찾기 위해 우선 물건검색부터 시작해야했습니다.

그런데 흔히들 부동산 공부를 하시는 분들은 손품 발품을 많이 팔아야 한다고 하는데,

저는 직장인이라 최대한 손품을 줄여보고 싶었습니다.

그래서, 오늘 개발한 것은 법원경매사이트에서 내가 원하는 경매 물건만 크롤링해서 물건을 찾아오는 방법입니다.

1. 법원경매정보 사이트 크롤링 어떻게 하지? → GPT한테 물어보자

저도 크롤링을 처음 해본지라, 뭐부터 어떻게 해야하는지 몰랐어요.

그래서 냅다 chatGPT에게 물어봤습니다.

단순히 코드만 알려주는게 아니라 뭐부터 어떻게 설치해서 준비하고, 코드를 짜야 하는지

상세하게 알려줘서 좀 좋더라구요.

그런데 저는 python 개발에 익숙한 사람이라 해당 내용을 이해했는데, 어디에서 어떻게 실행하는지 모르시는 분들은 뭐부터 해야하는지 chatGPT에게 물어보는 것도 좋을 것 같아요 ㅎㅎ

위에서 물어본 건 초안일 뿐이라, 하나하나 구체화 하는 과정이 필요해요.

그래서 어떤 버튼을 눌러서 페이지를 넘어가고 싶은데, 어떻게 명령어를 짜야하는지도 GPT한테 물어봐서 점점 코드를 구체화시켜갔습니다.

2. 문제에 봉착했다. 에러는 무서운데… → 이것도 GPT / Cluade 한테 물어보자

근데 문제에 봉착했어요. 에러가 났거든요 ㅋㅋㅋㅋ

근데 에러가 당최 무슨 말인지 모르겠는거에요. 그래서 그것도 GPT한테 물어봤어요. GPT가 이유를 설명해줬죠.

근데 수정하는 방법들을 다 적용했는데도 동일한 에러가 계속 발생하더라구요.

그래서 Cluade로 갈아탔습니다 ㅋㅋㅋㅋㅋ

selenium을 쫌 써봤다하는 동료에게 원인을 물어보며 도와달라했는데 그 친구도 Cluade에게 질문하니 이유까지 알려줬다고 합니다. Cluade가 코드 작성 쪽으로는 GPT보다 월등히 좋다고 하더니, 맞는거 같아요.

Cluade가 말하길, 법원경매사이트는 오래된 웹 페이지 구조를 가지고 있다고 ㅋㅋㅋ 맞아요…

어쨋든 HTML을 주면서 이렇게 물어보니, 어떻게 코드를 작성해야하는지 상세하게 알려줘서 덕분에 문제를 해결했습니다.

3. 결국 9할을 AI에 맡기며 경매 물건 찾아오기 성공!

이래저래 AI한테 물어가면서, 생에 첫 크롤링을 완성했습니다.

그래서 아래와 같이 제가 작성한 대로의 내용으로 크롤링이 완성되었어요!

4. 결론 : 손품의 결과가 좀 줄었으니, 이제 자동화를 해보자.

아직 초안의 단계기 때문에 필터 조건 개수도 좀 적은 편이고, 정리가 완벽하지는 않습니다.

제 최종 목적은 필터링 된 조건에 대해서 매일 업데이트를 해서 저에게 메일로보내는 작업입니다. 그러면 매번 경매 사이트에 들어가서 하나하나 손품을 보는 것에서 좀 공수가 줄어들지 않을까 싶네요! ㅎㅎ

자동화를 위해 Zapier를 쓰려고 했는데, 아뿔사… selenium 패키지를 제공하지 않아서 못한다고 하더라구요ㅠ 그래서 make.com을 배워서 자동화 해보려고 하고 있습니다.

그래서 일단은 실패… streamlit을 써서 웹페이지까지만 만들어보려구요! :)



추가

어제 해당 글을 작성하고 위아래로 streamlit만 붙이면 간편하게 만들 수 있을 것 같아서 만들어서 실행시켜봤습니다.

스트림릿 처음 만들어보는데, 웹페이지로 보니 훨씬 깔끔하고 좋네요!!

이 뒤로는 시각화하는 방법으로 추세를 보는 것 까지 추가해보는 것도 좋을 것 같습니다.

아래는 코드 전문을 공유드려요.

<details>

<summary>코드보기 </summary>

<div markdown="1">

import streamlit as st
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup
import urllib.parse
import pandas as pd
import numpy as np
import re

def setup_webdriver():
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)
    return driver

def navigate_to_search_page(driver):
    driver.get("https://www.courtauction.go.kr/")
    driver.switch_to.frame("indexFrame")
    wait = WebDriverWait(driver, 10)
    search_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[@id='qk_srch_link_1']/a")))
    search_button.click()

def set_search_criteria(driver, input_data, building_codes):
    setCourt = Select(driver.find_element(By.ID, 'idJiwonNm'))
    setCourt.select_by_value(input_data['jiwon'])

    setAPT = Select(driver.find_element(By.NAME, 'lclsUtilCd'))
    setAPT.select_by_value("0000802")
    setAPT = Select(driver.find_element(By.NAME, 'mclsUtilCd'))
    setAPT.select_by_value("000080201")
    setAPT = Select(driver.find_element(By.NAME, 'sclsUtilCd'))
    setAPT.select_by_value(building_codes[input_data['building']])

    time_textbox = driver.find_element(By.NAME, 'termStartDt')
    time_textbox.clear()
    time_textbox.send_keys(input_data['start_date'])
    time_textbox = driver.find_element(By.NAME, 'termEndDt')
    time_textbox.clear()
    time_textbox.send_keys(input_data['end_date'])

    driver.find_element(By.XPATH, '//*[@id="contents"]/form/div[2]/a[1]/img').click()

def change_items_per_page(driver):
    if driver.find_elements(By.ID, 'ipage'):
        setPage = Select(driver.find_element(By.ID, 'ipage'))
        setPage.select_by_value("default40")
    else:
        driver.find_element(By.XPATH, '//*[@id="contents"]/div[4]/form[1]/div/div/a[4]/img').click()

def extract_table_data(driver):
    soup = BeautifulSoup(driver.page_source, 'html.parser')
    table = soup.find('table', attrs={'class': 'Ltbl_list'})
    table_rows = table.find_all('tr')
    row_list = []
    for tr in table_rows:
        td = tr.find_all('td')
        row = [tr.text for tr in td]
        row_list.append(row)
    return pd.DataFrame(row_list).iloc[1:]

def navigate_pages(driver, aution_item):
    page = 1
    while True:
        aution_item = pd.concat([aution_item, extract_table_data(driver)], ignore_index=True)
        page2parent = driver.find_element(By.CLASS_NAME, 'page2')
        children = page2parent.find_elements(By.XPATH, '*')
        if page == 1:
            if len(children) == page:
                break
            else:
                children[page].click()
        elif page <= 10:
            if len(children) - 1 == page:
                break
            else:
                children[page + 1].click()
        else:
            if len(children) - 2 == (page % 10):
                break
            else:
                children[(page % 10) + 2].click()
        page += 1
    driver.find_element(By.XPATH, '//*[@id="contents"]/div[4]/form[1]/div/div/a[4]/img').click()
    return aution_item

def clean_table_data(aution_item):
    aution_item = aution_item.iloc[:, 1:]
    col_list = ['사건번호', '물건번호', '소재지', '비고', '감정평가액', '날짜']
    aution_item.columns = col_list
    for col in col_list:
        aution_item[col] = aution_item[col].str.replace('\t', '')
        aution_item[col] = aution_item[col].apply(lambda x: re.sub(r"\n+", "\n", x))

    aution_item['법원'] = aution_item['사건번호'].str.split('\n').str[1]
    aution_item['사건번호'] = aution_item['사건번호'].str.split('\n').str[2]
    aution_item['용도'] = aution_item['물건번호'].str.split('\n').str[2]
    aution_item['물건번호'] = aution_item['물건번호'].str.split('\n').str[1]
    aution_item['내역'] = aution_item['소재지'].str.split('\n').str[2:].str.join(' ')
    aution_item['소재지'] = aution_item['소재지'].str.split('\n').str[1]
    aution_item['비고'] = aution_item['비고'].str.split('\n').str[1]
    aution_item['최저가격'] = aution_item['감정평가액'].str.split('\n').str[2]
    aution_item['최저비율'] = aution_item['감정평가액'].str.split('\n').str[3].str[1:-1]
    aution_item['감정평가액'] = aution_item['감정평가액'].str.split('\n').str[1]
    aution_item['유찰횟수'] = aution_item['날짜'].str.split('\n').str[3].str.strip()
    aution_item['유찰횟수'] = np.where(aution_item['유찰횟수'].str.len() == 0, '0회', aution_item['유찰횟수'].str.slice(start=2))
    aution_item['날짜'] = aution_item['날짜'].str.split('\n').str[2]

    aution_item = aution_item[['날짜', '법원', '사건번호', '물건번호', '용도', '감정평가액', '최저가격', '최저비율', '유찰횟수', '소재지', '내역', '비고']]
    aution_item = aution_item[~aution_item['비고'].str.contains('지분매각')].reset_index(drop=True)
    return aution_item

def encode_to_euc_kr_url(korean_text):
    euc_kr_encoded = korean_text.encode('euc-kr')
    return urllib.parse.quote(euc_kr_encoded)

def create_url(row):
    court_name_encoded = encode_to_euc_kr_url(row["법원"])
    sa_year, sa_ser = row["사건번호"].split("타경")
    url = f"https://www.courtauction.go.kr/RetrieveRealEstDetailInqSaList.laf?jiwonNm={court_name_encoded}&saYear={sa_year}&saSer={sa_ser}&_CUR_CMD=InitMulSrch.laf&_SRCH_SRNID=PNO102014&_NEXT_CMD=RetrieveRealEstDetailInqSaList.laf"
    return url

def main(input_data, building_codes):
    driver = setup_webdriver()
    navigate_to_search_page(driver)
    set_search_criteria(driver, input_data, building_codes)
    change_items_per_page(driver)
    aution_item = pd.DataFrame()
    aution_item = navigate_pages(driver, aution_item)
    aution_item = clean_table_data(aution_item)
    aution_item["URL"] = aution_item.apply(create_url, axis=1)
    driver.quit()
    return aution_item

# Streamlit UI
st.title('법원 경매 검색')

# Input form
with st.form(key='search_form'):
    jiwon = st.selectbox('지원', ['서울중앙지방법원', '서울동부지방법원', '서울서부지방법원'])
    building = st.selectbox('건물 유형', ["단독주택", "다가구주택", "다중주택", "아파트", "연립주택", "다세대주택", "기숙사", "빌라", "상가주택", "오피스텔", "주상복합"])
    start_date = st.date_input('시작 날짜')
    end_date = st.date_input('종료 날짜')
    submit_button = st.form_submit_button(label='검색')

if submit_button:
    input_data = {
        'jiwon': jiwon,
        'building': building,
        'start_date': start_date.strftime('%Y.%m.%d'),
        'end_date': end_date.strftime('%Y.%m.%d')
    }

    building_codes = {
        "단독주택": "00008020101",
        "다가구주택": "00008020102",
        "다중주택": "00008020103",
        "아파트": "00008020104",
        "연립주택": "00008020105",
        "다세대주택": "00008020106",
        "기숙사": "00008020107",
        "빌라": "00008020108",
        "상가주택": "00008020109",
        "오피스텔": "00008020110",
        "주상복합": "00008020111"
    }

    auction_data = main(input_data, building_codes)
    st.dataframe(auction_data)

</div>

</details>


#11기_내집마련

10
4개의 답글

👉 이 게시글도 읽어보세요