ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 법정동코드 불러와서 DB 저장
    개발 Life 2023. 5. 1. 14:57

    지번코드(PNU)를 사용하기 위해서는 법정동 코드를 반드시 알아야 한다.

    한국 행정구역에 대한 코드는 행정동코드와 법정동코드 두 가지 가 있다.

     

    코드를 얻을 수 있는 곳은 아래와  같이 여러곳이 존재한다.

     

    이번에는 법정동코드를 불러와 보자

    법정동코드 전체자료를 다운받으면 법정동코드, 법정동명, 폐지여부 세가지만 확인 가능하다

    근데 화면에서는 총 11개의 정보를 조회 가능함.

    법정동코드 법정동명 폐지구분 상위지역코드 서열 생성일 폐지일 최종작업일 최하지역명 법정동코드(주민) 법정동코드(지적)

     

    그래서 파일 다운로드해서 넣기보다 화면을 크롤링해서 전체 데이터를 넣기로 했다.

     

    def get_region_code(self):
        page = 1
        while True:
            data = {
                'cPage': page,
                'regionCd_pk': None,
                'chkWantCnt': '0',
                'reqSggCd': '*',
                'reqUmdCd': '*',
                'reqRiCd': '*',
                'searchOk': '0',
                'codeseId': '00002',
                'pageSize': '100',
                'regionCd': None,
                'locataddNm': None,
                'sidoCd': '*',
                'sggCd': '*',
                'umdCd': '*',
                'riCd': '*',
                'disuseAt': 'ALL',
                'stdate': None,
                'enddate': None,
                'chkHigh': '0',
                'chkOrder': '0',
                'chkCrtDt': '0',
                'chkClsDt': '0',
                'chkLocatDt': '0',
                'chkLow': '0',
                'chkJumin': '0',
                'chkJijuk': '0'
            }
            print('page : ', page)
            response = requests.post('https://www.code.go.kr/stdcode/regCodeL.do', data)
            soup = BeautifulSoup(response.content, "html.parser")

    다행히 코드관리사이트는 헤더 같은 것을 관리하지 않아 파라미터로 바로 호출만하면 결과를 받아올 수 있었다.

    코드가 많아서 페이지당 10개로 호출하면 시간이 너무 많이 걸려서 100개씩으로 변경해서 호출했다.

     

    문제는 홈페이지 구축을 얼마나 심혈을 기울여서 했는지, 태그 구성이 엉망이다 tr을 열었으면 닫아야되는데 안닫고 다시 tr을 연다던지해서 크롬같은 브라우저에서는 똑똑해서 알아서 잘 해석하지만, BeautifulSoup은 정신을 못차린다.

    table.table로 찾으면 한번에 찾을 수 있는걸 돌아돌아 찾아야하는 불상사가 생겼다. 테이블을 찾아도 tbody가 찾아지지 않는다.

     

            # table이 한번에 선택이 안되서 전체 선택 후 처리
            tables = soup.select('table')
            # tbody가 선택이 안되서 tr로 처리
            trs = tables[3].select('tr')
            if len(trs) == 1:
                break

    여러 시도중에 table로 찾아서 idx가 3이고 tr을 바로찾는데 첫번째 tr은 헤더다.

    페이지를 넘어가면서 헤더만 있으면 모든 크롤링이 종료.

     

            for i, tr in enumerate(trs):
                if i == 0:
                    continue
                tds = tr.select('td')
                region_cd = tds[0].get_text()
                region_nm = tds[1].get_text()
                status = 'OPEN' if tds[2].get_text() else 'CLOSE'
                upper_region_cd = tds[3].get_text() if tds[3].get_text() != u'\xa0' else None
                order = tds[4].get_text()
                create_dt = tds[5].get_text() if tds[5].get_text() != u'\xa0' and tds[5].get_text() != '' else None
                create_dt = datetime.strptime(create_dt, '%Y%m%d').strftime("%Y-%m-%d") if create_dt is not None else create_dt
                close_dt = tds[6].get_text() if tds[6].get_text() != u'\xa0' and tds[6].get_text() != '' else None
                close_dt = datetime.strptime(close_dt, '%Y%m%d').strftime("%Y-%m-%d") if close_dt is not None else close_dt
                modify_dt = tds[7].get_text() if tds[7].get_text() != u'\xa0' and tds[7].get_text() != '' else None
                modify_dt = datetime.strptime(modify_dt, '%Y%m%d').strftime("%Y-%m-%d") if modify_dt is not None else modify_dt
                lowest_region_nm = tds[8].get_text() if tds[8].get_text() != u'\xa0' else None
                regident_region_cd = tds[9].get_text()
                map_region_cd = tds[10].get_text()

    2번째 줄부터가 데이터인데 코드는 OPEN, CLOSE로 치환했다.

    빈값의 경우 ''가 아닌 \xa0값이 나와서 None으로 대체했다.

    날짜의 경우 YYYYMMDD를 DB에 넣기 위해 YYYY-MM-DD로 변경했다.

     

    전체코드를 보자면 다음과 같다.

    def get_region_code(self):
        page = 1
        while True:
            data = {
                'cPage': page,
                'regionCd_pk': None,
                'chkWantCnt': '0',
                'reqSggCd': '*',
                'reqUmdCd': '*',
                'reqRiCd': '*',
                'searchOk': '0',
                'codeseId': '00002',
                'pageSize': '100',
                'regionCd': None,
                'locataddNm': None,
                'sidoCd': '*',
                'sggCd': '*',
                'umdCd': '*',
                'riCd': '*',
                'disuseAt': 'ALL',
                'stdate': None,
                'enddate': None,
                'chkHigh': '0',
                'chkOrder': '0',
                'chkCrtDt': '0',
                'chkClsDt': '0',
                'chkLocatDt': '0',
                'chkLow': '0',
                'chkJumin': '0',
                'chkJijuk': '0'
            }
            print('page : ', page)
            response = requests.post('https://www.code.go.kr/stdcode/regCodeL.do', data)
            soup = BeautifulSoup(response.content, "html.parser")
            # table이 한번에 선택이 안되서 전체 선택 후 처리
            tables = soup.select('table')
            # tbody가 선택이 안되서 tr로 처리
            trs = tables[3].select('tr')
            if len(trs) == 1:
                break
            regions = []
            for i, tr in enumerate(trs):
                if i == 0:
                    continue
                tds = tr.select('td')
                region_cd = tds[0].get_text()
                region_nm = tds[1].get_text()
                status = 'OPEN' if tds[2].get_text() else 'CLOSE'
                upper_region_cd = tds[3].get_text() if tds[3].get_text() != u'\xa0' else None
                order = tds[4].get_text()
                create_dt = tds[5].get_text() if tds[5].get_text() != u'\xa0' and tds[5].get_text() != '' else None
                create_dt = datetime.strptime(create_dt, '%Y%m%d').strftime("%Y-%m-%d") if create_dt is not None else create_dt
                close_dt = tds[6].get_text() if tds[6].get_text() != u'\xa0' and tds[6].get_text() != '' else None
                close_dt = datetime.strptime(close_dt, '%Y%m%d').strftime("%Y-%m-%d") if close_dt is not None else close_dt
                modify_dt = tds[7].get_text() if tds[7].get_text() != u'\xa0' and tds[7].get_text() != '' else None
                modify_dt = datetime.strptime(modify_dt, '%Y%m%d').strftime("%Y-%m-%d") if modify_dt is not None else modify_dt
                lowest_region_nm = tds[8].get_text() if tds[8].get_text() != u'\xa0' else None
                regident_region_cd = tds[9].get_text()
                map_region_cd = tds[10].get_text()
    
                region = {
                    'region_cd': region_cd,
                    'region_nm': region_nm,
                    'status': status,
                    'upper_region_cd': upper_region_cd,
                    'order': order,
                    'create_dt': create_dt,
                    'close_dt': close_dt,
                    'modify_dt': modify_dt,
                    'lowest_region_nm': lowest_region_nm,
                    'regident_region_cd': regident_region_cd,
                    'map_region_cd': map_region_cd
                }
            db.insert_regions(regions)
            page = page + 1

    총 데이터 건은 23.05.01 기준으로 46,304건으로 크롤링하는 시간도 꽤 걸렸다.

    법정동코드는 일년에 3건정도만 바뀌니까 분기별로 한번씩만 돌리면 될것 같다.

    댓글

Designed by Tistory.