ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [데이터수집] 한국부동산원 청약홈/청약Home 분양정보 (2)
    Python/웹스크래핑.데이터수집 2020. 12. 29. 22:41

    한국부동산원 청약홈 페이지의 APT 분양정보 및 경쟁률 조회 페이지에서,

    -조회 조건을 변경해 1페이지부터 끝페이지까지 다운로드 받는다.

    -항상 최신 데이터를 확인할 수 있도록 최신 일자를 갱신한다.

     

    (1) 한국부동산원 청약홈 페이지 1페이지의 테이블 다운로드 / 데이터 beautifulSoup으로 파싱

    (2) 조회 조건 변경하기 - 전체 페이지 대상으로 실행/최신 일자로 실행


    한국부동산원 청약홈 페이지는 조회 조건 선택 -> 조회 형식으로 이루어져있다.

    requests.post()시에 form data 항목에 해당 조건을 넣어서 전송하면, 실제로 마우스로 클릭하지 않더라도 해당 데이터를 얻을 수 있다.

     

    1) 전체 페이지 대상으로 실행: 전체 페이지 수 정보는 어디에 있을까?

     

    지난번에 전송했던 form data는 아래와 같다.

    formData = {
        'beginPd':	"202002",   # 검색시작 연월
        'endPd':	"202101",   # 검색종료 연월
        'houseDetailSecd':	"", # 주택구분 드롭다운
        'suplyAreaCode':	"", # 공급지역 드롭다운
        'houseNm':	"",         # 주택명(건설업체명) 검색창 입력내용
        'chk0':	"",             # 분양/임대 체크박스, 순서대로 chk: "", chk1: "0", chk2: "1", chk3: "2"로 전달된다
                                # 분양주택, 분양전환 가능임대만 체크할 경우 chk1: "0", chk2: "1"에 추가로 rentSecd: "0,1"까지 전송된다.
        'pageIndex':	"1",    # 페이지 번호
        'gvPgmId':	"AIA01M01"  # 청약일정 및 통계 - 분양정보/경쟁률에서 'APT'에 해당하는 코드
    }

     

    여기서 pageIndex 값을 "1" 대신 "34"으로 전달하면, 34페이지의 정보가 출력될 것이다.

    1번째 페이지부터 34페이지까지의 정보를 얻고 싶다면 for문을 넣어서 돌리면 된다.

    전체 페이지수를 얻으려면, 전체 페이지수가 몇 번까지 있는지를 확인 후 해당 값을 넣어주면 된다.

     

    전체 페이지수 정보를 확인하기 위해서 개발자도구-inspector를 이용한다.

     

    한 페이지에 10건씩 출력되는 점을 이용해 총 게시물 정보를 10으로 나눠서 올림을 해도 되고,

     

    보통은 페이지 맨끝으로 이동 버튼이 있으니 해당 버튼을 이용해도 된다.

    (*다만 페이지수가 10페이지가 안 되는 경우, 버튼이 없을 수 있다. 이때는 총 게시물수로 구해야 한다.)

     

    먼저 총게시물 데이터가 있는 페이지를 확인해보니,

    class명이 'txt_r'인 div 태그 > class명 'total_txt'인 p 태그 > span 태그 순으로 정보가 포함되어 있다.

    from bs4 import BeautifulSoup
    from math import ceil
    
    bs = BeautifulSoup(data.text, 'html.parser') # (1)에서 가져왔던 데이터
    
    # div 태그 > p 태그 > span 태그 순으로 검색
    total_txt = bs.find('div', class_='txt_r').find('p', class_='total_txt').span.text
    print(total_txt) # 총게시물 : 634
    
    
    # p태그에 부여된 total_txt class가 다른 데 또 나오지 않는다면 p태그 값만 찾아도 상관없다
    total_txt = bs.find('p', class_='total_txt').span.text
    print(total_txt) # 총게시물 : 634
    
    lastPage = ceil(int(total_txt.replace("총게시물 : ", ""))/10)
    print(lastPage) # 64

     

    맨끝으로 이동 버튼은 a 태그에 클래스명 arrow, arw_next로 구성되어 있고, 해당 태그의 href값에서 페이지 인덱스를 확인할 수 있다.

    from bs4 import BeautifulSoup
    
    bs = BeautifulSoup(data.text, 'html.parser') # (1)에서 가져왔던 데이터
    
    arw_next = bs.find('a', class_=['arw', 'arw_next'])['href']
    print(arw_next) # ?pageIndex=64
    
    lastPage = arw_next.replace("?pageIndex=", "")
    print(lastPage) # 64

     

    이제 마지막 페이지를 찾았으니 반복해서 실행하면 된다.

    getData함수는 이쪽의 전체 코드에서 pageIndex만 입력받게 수정했다.

    더보기

     

    def getData(pageIndex, fileName='sample.txt'):
        userAgent = 'Chrome/55.0.2883.91 Mobile Safari/537.36'
        url = 'https://www.applyhome.co.kr/ai/aia/selectAPTLttotPblancListView.do'
        postHeaders = {
           'User-Agent': userAgent
        }
        formData = {
            'beginPd':	"202002",   # 검색시작 연월
            'endPd':	"202101",   # 검색종료 연월
            'houseDetailSecd':	"", # 주택구분 드롭다운
            'suplyAreaCode':	"", # 공급지역 드롭다운
            'houseNm':	"",         # 주택명(건설업체명) 검색창 입력내용
            'chk0':	"",             # 분양/임대 체크박스, 순서대로 chk: "", chk1: "0", chk2: "1", chk3: "2"로 전달된다
                                    # 분양주택, 분양전환 가능임대만 체크할 경우 chk1: "0", chk2: "1"에 추가로 rentSecd: "0,1"까지 전송된다.
            'pageIndex':	str(pageIndex),    # 페이지번호 수
            'gvPgmId':	"AIA01M01"  # 청약일정 및 통계 - 분양정보/경쟁률에서 'APT'에 해당하는 코드
        }
        data = requests.post(url, formData, headers=postHeaders)
        bs = BeautifulSoup(data.text, 'html.parser')
    
        tbody = bs.find('table', class_='tbl_st').tbody # table 태그의 'tbl_st' class를 검색
        trs = tbody.find_all('tr') # tbody에서 하위 tr 태그를 검색
    
        with open(fileName, "a+", encoding="UTF-8") as sample:
            for tr in trs:
                for td in tr.find_all('td'):
                    txt = td.text.strip()
                    sample.write(txt + "\t")
                sample.write("\n")

     

    import requests
    import time
    import random
    from bs4 import BeautifulSoup
    
    lastPage = int(lastPage)
    for i in range(1, lastPage+1):
        getData(i, 'sample23.txt')
        time.sleep(random.random()+3) # 혹시 모를 아이피밴 방지

     

    데이터를 확인해보니 634건 모두 의도대로 잘 들어왔다.

    # sample23.txt
    '''
    경기	국민	분양주택	고양지축 A-2블록 신혼희망타운(공공분양)	㈜금호산업	☎ 02-381-9481	2020-12-29	2021-01-12 ~ 2021-01-13	2021-01-20	사업주체문의	사업주체문의	
    경기	국민	분양주택	장항지구 A-5블록 신혼희망타운	(주)케이씨씨건설	☎ 1600-1004	2020-12-29	2021-01-12 ~ 2021-01-14	2021-01-22	사업주체문의	사업주체문의	
    경기	국민	분양주택	장항지구 A-4블록 신혼희망타운	쌍용건설(주)	☎ 1600-1004	2020-12-29	2021-01-12 ~ 2021-01-14	2021-01-21	사업주체문의	사업주체문의	
    제주	국민	분양전환 불가임대	의귀리 국민임대주택	헤르지아건설(주)	☎ 064-780-3592	2020-12-29	2021-01-11 ~ 2021-01-12	2021-04-29	사업주체문의	사업주체문의	
    경기	국민	분양주택	위례자이 더 시티	지에스건설(주)	☎ 1644-3260	2020-12-29	2021-01-11 ~ 2021-01-13	2021-01-19	신청현황	경쟁률	
    
    ...
    
    2020-02-12 ~ 2020-03-25	2020-06-10	사업주체문의	사업주체문의	
    경기	민영	분양주택	의왕 오전 동아루미체	(주)동아토건	☎ 031-452-7750	2020-02-05	2020-02-17 ~ 2020-02-19	2020-02-25	신청현황	경쟁률	
    제주	국민	분양전환 불가임대	제주첨단과학기술단지A23블록행복주택		☎ 064-797-5500	2020-02-04	2020-02-14 ~ 2020-02-18	2020-05-19	사업주체문의	사업주체문의	
    울산	민영	분양주택	학성동 동남하이빌아파트	일위종합건설(주)	☎ 052-268-1866	2020-02-04	2020-02-14 ~ 2020-02-17	2020-02-21	사업주체문의	경쟁률	
    경북	국민	분양전환 가능임대	경북도청신도시 코오롱하늘채	코오롱글로벌(주)	☎ 054-843-1500	2020-02-03	2020-02-21 ~ 2020-02-25	2020-03-02	신청현황	경쟁률	
    대구	국민	분양전환 불가임대	대구연경A-2블록국민임대		☎ 1600-1004	2020-02-03	2020-02-17 ~ 2020-02-18	2020-05-07	사업주체문의	사업주체문의	
    '''

     

    그런데 받아보니 데이터가 필요 이상으로 과하게 많다.

    제공된 데이터는 2020년 02월 ~ 2021년 01월 분량인데, 지나간 월 데이터는 필요하지 않다.

    넉넉하게 전월부터, 확인 가능한 최신 일자까지의 데이터까지면 충분하다.

     

     

    2) 최신 일자로 실행: 최신 일자 정보는 어디에 있을까?

    검색가능한 일자 정보는 조회 옵션 선택 드롭다운에서 확인할 수 있다.

     

    마찬가지로 개발자도구 - inspector에 들어가서 해당 정보를 뜯어본다.

     

    검색 가능한 시작년도는 2020년 12월 ~ 2018년 12월 형태로 제공된다.

    div > div > form > div > div > label > select 순으로 복잡하게 구성되어있지만,

    다행히도 select태그에 유일정보인 id가 있으니 이걸로 찾으면 된다.

    from bs4 import BeautifulSoup
    
    bs = BeautifulSoup(data.text, 'html.parser') # (1)에서 가져왔던 데이터
    
    start_years = bs.find('select', id="start_year")
    print(start_years)
    '''
    <select id="start_year" name="beginPd">
    <option value="202012">2020년 12월</option>
    <option value="202011">2020년 11월</option>
    <option value="202010">2020년 10월</option>
    <option value="202009">2020년 09월</option>
    ...
    <option value="201903">2019년 03월</option>
    <option value="201902">2019년 02월</option>
    <option value="201901">2019년 01월</option>
    <option value="201812">2018년 12월</option>
    </select>

    여기서 다시 find_All을 하면 된다.

    yearsList = start_years.find_all('option')
    print(yearsList)
    '''
    [<option value="202012">2020년 12월</option>, <option value="202011">2020년 11월</option>, <option value="20
    2010">2020년 10월</option>, <option value="202009">2020년 09월</option>, <option value="202008">2020년 08월<
    /option>, <option value="202007">2020년 07월</option>, <option value="202006">2020년 06월</option>, <option
    value="202005">2020년 05월</option>, <option value="202004">2020년 04월</option>, <option value="202003">202
    0년 03월</option>, <option selected="selected" value="202002">2020년 02월</option>, <option value="202001">2
    020년 01월</option>, <option value="201912">2019년 12월</option>, <option value="201911">2019년 11월</option
    >, <option value="201910">2019년 10월</option>, <option value="201909">2019년 09월</option>, <option value="
    201908">2019년 08월</option>, <option value="201907">2019년 07월</option>, <option value="201906">2019년 06
    월</option>, <option value="201905">2019년 05월</option>, <option value="201904">2019년 04월</option>, <opti
    on value="201903">2019년 03월</option>, <option value="201902">2019년 02월</option>, <option value="201901">
    2019년 01월</option>, <option value="201812">2018년 12월</option>]'''

    혹은 select를 이용해도 된다. select_one을 안 썼더니 같은 데이터가 두 번 나와서 select('#start_year > option')를 쓸 수는 없었다.

    yearsList = bs.select_one('#start_year').select('option')

    이제 value를 추출한다. 위의 form data의 beginPd가 202002 형태로 년도4자리 + 월2자리 값이다.

    start_year의 값은 <option value="202012">2020년 12월</option> 형태로 구성되어 있어서, ['value']로 선택할 수 있다.

    beginPds = [year['value'] for year in yearsList]
    print(beginPds)
    '''
    ['202012', '202011', '202010', '202009', '202008', '202007', '202006', '202005', '202004', '202003', '202002
    ', '202001', '201912', '201911', '201910', '201909', '201908', '201907', '201906', '201905', '201904', '2019
    03', '201902', '201901', '201812']
    '''

    endPds도 마찬가지로 구하면 된다.

    beginPds = [k['value'] for k in bs.select_one('#start_year').select('option')]
    endPds = [k['value'] for k in bs.select_one('#end_year').select('option')]

    이제 시작월 - 종료월을 선택하면 된다.

    시작일자는 전월부터로 하기로 했으니, 먼저 현재 기준 전월 값을 구하고, 해당 월이 beginPds 중에 있는지 확인한다.

    전월 구하기는 이쪽을 참조했다: stackoverflow.com/questions/9724906/python-date-of-the-previous-month

    import datetime
    
    def getLastMonth():
        today = datetime.date.today()
        first = today.replace(day=1)
        lastMonth = first - datetime.timedelta(days=1)
        return lastMonth.strftime("%Y%m")
    
    
    def VaildBeginPd(beginPds):
        lastMonth = getLastMonth()
        if lastMonth in beginPds:
            return lastMonth
        else:
            return beginPds[0]
    
    print(VaildBeginPd(beginPds)) # 202011
    

     

    이제 form data에 beginPd, endPd 값을 넣으면 해당 데이터를 확인할 수 있다.

    해당 옵션에 대해서 전체 페이지 범위의 데이터를 확인하고 싶다면, lastPage를 다시 확인해주어야 한다.

     

    (1) 먼저 홈페이지에 접속(form data 없이 get)해서 beginPd, endPd 값을 획득

    (2) 1에서 획득한 beginPd, endPd 기준으로 1페이지에 접속해서 해당 옵션의 lastPage 획득

    (3) 2에서 획득한 beginPd, endPd, lastPage 기준으로 for문 실행, 최종 데이터 획득

     

     

    (1) ~ (3) 단계를 처리할 함수와, 각각의 획득 부분을 처리할 함수로 나누었다. 먼저 main()에서 (1) ~ (3)을 처리하고

    def main():
        # (1) 홈페이지에 접속(form data 없이 get)해서 beginPd, endPd 값을 획득
        tempLast, beginPd, endPd = getFormData()
        time.sleep(random.random()+2)
        
        # (2) 위에서 획득한 beginPd, endPd 기준으로 1페이지 접속해서 lastPage 획득
        lastPage, beginPd, endPd = getFormData(1, beginPd, endPd)
        time.sleep(random.random()+2)
    
        # (3) 위에서 획득한 beginPd, endPd, lastPage 기준으로 for문 실행
        for i in range(1, int(lastPage)+1):
            getData(lastPage, beginPd, endPd, fileName='sample555.txt')
            time.sleep(random.random()+3)  # 혹시 모를 아이피밴 방지

    위에서 (1), (2)는 form data에 들어갈 조건 데이터만 파싱해오는 것이기 때문에, 함수 하나에서 처리하기로 한다.

    데이터를 가져올 getFormData를 위 목적에 맞게 두 경우로 나누었다.

    def getFormData(pageIndex=0, beginPd=None, endPd=None):
        # 입력값에 따라 request 요청 보내서 lastPage, beginPd, endPd 리턴
        if pageIndex == 0:     # (1) 시도에서 아무것도 입력되지 않았을 경우, 기본 get으로 가져옴
            url, headers, formData = makeForm()
            response = requests.get(url, headers=headers)
        else:                  # (2) 값이 입력된 경우, 해당 데이터를 넣고 post로 가져옴
            url, headers, formData = makeForm(pageIndex, beginPd, endPd)
            response = requests.post(url, formData, headers=headers)
        lastPage, beginPd, endPd = getIndexNPds(response.text)
        return lastPage, beginPd, endPd
    

     

    # 전체 코드

    import requests
    from bs4 import BeautifulSoup
    from math import ceil
    import random
    import time
    import datetime
    
    
    def getLastMonth():      # 지난달 구하기
        today = datetime.date.today()
        first = today.replace(day=1)
        lastMonth = first - datetime.timedelta(days=1)
        return lastMonth.strftime("%Y%m")
    
    
    def VaildBeginPd(beginPds):
        # 조회 가능 목록에 지난달이 있으면 지난달 리턴, 없으면 첫번째 리턴
        lastMonth = getLastMonth()
        if lastMonth in beginPds:
            return lastMonth
        else:
            return beginPds[0]
    
    
    def makeForm(pageIndex=1, beginPd=getLastMonth(), endPd=getLastMonth()):
        # request용 변수들 생성용 함수
        userAgent = 'Chrome/55.0.2883.91 Mobile Safari/537.36'
        url = 'https://www.applyhome.co.kr/ai/aia/selectAPTLttotPblancListView.do'
        headers = {
           'User-Agent': userAgent
        }
        formData = {
            'beginPd':	beginPd,   # 검색시작 연월
            'endPd':	endPd,   # 검색종료 연월
            'houseDetailSecd':	"",  # 주택구분 드롭다운
            'suplyAreaCode':	"",  # 공급지역 드롭다운
            'houseNm':	"",         # 주택명(건설업체명) 검색창 입력내용
            'chk0':	"",             # 분양/임대 체크박스
            'pageIndex':	str(pageIndex),    # 페이지번호 수
            'gvPgmId':	"AIA01M01"  # 청약일정 및 통계 - 분양정보/경쟁률에서 'APT'에 해당하는 코드
        }
        return url, headers, formData
    
    
    def getIndexNPds(html):
        # response.text에서 lastPage, beginPd, endPd 파싱해서 리턴
        bs = BeautifulSoup(html, 'html.parser')
        try:
            arw_next = bs.find('a', class_=['arw', 'arw_next'])['href']
            lastPage = arw_next.replace("?pageIndex=", "")
        except TypeError:  # 페이지수가 10페이지가 안 되면 마지막 버튼이 없어서 올림으로 구함
            total_txt = bs.find('p', class_='total_txt').span.text
            lastPage = ceil(int(total_txt.replace("총게시물 : ", ""))/10)
        beginPds = [k['value'] for k in bs.select_one('#start_year').select('option')]
        endPds = [k['value'] for k in bs.select_one('#end_year').select('option')]
        return lastPage, VaildBeginPd(beginPds), endPds[0]
    
    
    def getFormData(pageIndex=0, beginPd=None, endPd=None):
        # 입력값에 따라 request 요청 보내서 lastPage, beginPd, endPd 리턴
        if pageIndex == 0:     # (1) 시도에서 아무것도 입력되지 않았을 경우, 기본 get으로 가져옴
            url, headers, formData = makeForm()
            response = requests.get(url, headers=headers)
        else:                  # (2) 값이 입력된 경우, 해당 데이터를 넣고 post로 가져옴
            url, headers, formData = makeForm(pageIndex, beginPd, endPd)
            response = requests.post(url, formData, headers=headers)
        lastPage, beginPd, endPd = getIndexNPds(response.text)
        return lastPage, beginPd, endPd
    
    
    def getData(pageIndex, beginPd, endPd, fileName='sample.txt'):
        url, headers, formData = makeForm(pageIndex, beginPd, endPd)
        data = requests.post(url, formData, headers=headers)
        bs = BeautifulSoup(data.text, 'html.parser')
    
        tbody = bs.find('table', class_='tbl_st').tbody  # table 태그의 'tbl_st' class
        trs = tbody.find_all('tr')  # tbody에서 하위 tr 태그를 검색
    
        with open(fileName, "a+", encoding="UTF-8") as sample:
            for tr in trs:
                for td in tr.find_all('td'):
                    txt = td.text.strip()
                    sample.write(txt + "\t")
                sample.write("\n")
    
    
    def main():
        # (1) 홈페이지에 접속(form data 없이 get)해서 beginPd, endPd 값을 획득
        tempLast, beginPd, endPd = getFormData()
        time.sleep(random.random()+2)
        
        # (2) 위에서 획득한 beginPd, endPd 기준으로 1페이지 접속해서 lastPage 획득
        lastPage, beginPd, endPd = getFormData(1, beginPd, endPd)
        
        # (3) 위에서 획득한 beginPd, endPd, lastPage 기준으로 for문 실행
        for i in range(1, int(lastPage)+1):
            getData(lastPage, beginPd, endPd, fileName='sample555.txt')
            time.sleep(random.random()+3)  # 혹시 모를 아이피밴 방지
    
    if __name__ == "__main__":
        main()
    

    댓글

Designed by Tistory.