YOGYUI

웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (3) 본문

Data Analysis/Data Engineering

웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (3)

요겨 2022. 1. 8. 19:48
반응형

Get Corporations List Classified by Sectors from DART(fss)

[시리즈]

웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (1)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (2)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (3)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (Final)

7. 업종에 기업이 존재하지 않을 경우에 대한 예외처리

전체 트리노드를 크롤링하다보니 미처 고려하지 못했던 문제를 발견했다

특정 업종의 경우 해당 업종에 속하는 기업이 아예 없는 경우가 있는데, 이 경우 테이블 아래의 페이지 탐색 태그 (<pageSkip>) 자체가 없어서 코드에 예외가 발생했다

 

앞서 javascript에서 함수 두 개를 다음과 같이 변경해서 예외 발생 시 멈추지 않고 계속 진행할 수 있도록 했다

  • try - catch 문 추가
  • 기업 리스트 Empty인 노드의 경우 확인을 위해 콘솔 로그 추가
// 페이지 여러개에 대한 비동기 순차 테이블 조회
async function fnGetCorpNamesFromTableAll(corp_name_arr, delay_ms) {
    var pageInfo = document.getElementsByClassName("pageInfo");
    var pageSkip = document.getElementsByClassName("pageSkip")[0];
    try {
        var li_tags = pageSkip.getElementsByTagName("li");
        var arrIndex = [];
        for (i = 0; i < li_tags.length; i++) {
            arrIndex.push(i + 1);
        }
    
        await arrIndex.reduce((prevTask, currTask) => {
            return prevTask.then(() => fnGetCorpNamesFromTableSearch(currTask, corp_name_arr, delay_ms));
        }, Promise.resolve());
    } catch (error) {
        // 해당 종목에 회사가 아예 없으면 pageSkip 태그가 없다! (TypeError 발생)
        ;
    }
}

// 트리 노드 선택 후 테이블에서 기업 정보 가져오기
async function fnSelectNodeAndGetCorpNamesFromTableAll(leaf_node_dict, treeObj, delay_ms) {
    // 트리 노드 선택 - 딜레이
    var node_id = leaf_node_dict.node_id;
    treeObj.select_node(node_id);
    
    await sleep(delay_ms);

    // 테이블에서 모든 기업 정보 가져오기
    var corp_name_arr = []
    await fnGetCorpNamesFromTableAll(corp_name_arr, delay_ms);
    // 기업 정보를 가져온 뒤 결과를 딕셔너리 내의 어레이에 concat
    if (corp_name_arr.length > 0) {
        leaf_node_dict.corp_names = leaf_node_dict.corp_names.concat(corp_name_arr);
    } else {
        console.log("Empty corp list - ", leaf_node_dict.node_text);
    }
    
    // 트리 노드 선택 해제
    treeObj.deselect_node(node_id);
}

앞서 구현한 python 코드로 크롤링해본 결과 2022년 1월 8일 기준으로 기업이 존재하지 않는 업종은 다음과 같았다

Empty corp list -  기타 작물 재배업 (lv:0, line:105)
Empty corp list -  임산물 채취업 (lv:0, line:105)
Empty corp list -  특수사 및 코드직물 제조업 (lv:0, line:105)
Empty corp list -  기타 산업용 유리제품 제조업 (lv:0, line:105)
Empty corp list -  그 외 기타 유리제품 제조업 (lv:0, line:105)
Empty corp list -  부정형 내화 요업제품 제조업 (lv:0, line:105)
Empty corp list -  점토 벽돌, 블록 및 유사 비내화 요업제품 제조업 (lv:0, line:105)
Empty corp list -  탄소섬유 제조업 (lv:0, line:105)
Empty corp list -  전투용 차량 제조업 (lv:0, line:105)
Empty corp list -  생활용수 공급업 (lv:0, line:105)
Empty corp list -  방사성 폐기물 수집, 운반 및 처리업 (lv:0, line:105)
Empty corp list -  방음, 방진 및 내화 공사업 (lv:0, line:105)
Empty corp list -  모터사이클 및 부품 소매업 (lv:0, line:105)
Empty corp list -  속옷 및 잠옷 도매업 (lv:0, line:105)
Empty corp list -  기타 비전기식 생활용 기기 및 기구 도매업 (lv:0, line:105)
Empty corp list -  건어물 및 젓갈류 소매업 (lv:0, line:105)
Empty corp list -  담배 소매업 (lv:0, line:105)
Empty corp list -  한복 소매업 (lv:0, line:105)
Empty corp list -  악기 소매업 (lv:0, line:105)
Empty corp list -  문구용품 및 회화용품 소매업 (lv:0, line:105)
Empty corp list -  가정용 고체연료 소매업 (lv:0, line:105)
Empty corp list -  사진기 및 사진용품 소매업 (lv:0, line:105)
Empty corp list -  중고 가구 소매업 (lv:0, line:105)
Empty corp list -  중고 가전제품 및 통신장비 소매업 (lv:0, line:105)
Empty corp list -  기타 중고 상품 소매업 (lv:0, line:105)
Empty corp list -  노점 및 유사이동 소매업 (lv:0, line:105)
Empty corp list -  철도 화물 운송업 (lv:0, line:105)
Empty corp list -  민박업 (lv:0, line:105)
Empty corp list -  그 외 기타 숙박업 (lv:0, line:105)
Empty corp list -  한식 면요리 전문점 (lv:0, line:105)
Empty corp list -  한식 해산물요리 전문점 (lv:0, line:105)
Empty corp list -  이동 음식점업 (lv:0, line:105)
Empty corp list -  기타 주점업 (lv:0, line:105)
Empty corp list -  비디오물 감상실 운영업 (lv:0, line:105)
Empty corp list -  건강 보험업 (lv:0, line:105)
Empty corp list -  산업재해 및 기타 사회보장 보험업 (lv:0, line:105)
Empty corp list -  사업 공제업 (lv:0, line:105)
Empty corp list -  법무사업 (lv:0, line:105)
Empty corp list -  탐정 및 조사 서비스업 (lv:0, line:105)
Empty corp list -  음반 및 비디오물 임대업 (lv:0, line:105)
Empty corp list -  입법기관 (lv:0, line:105)
Empty corp list -  기타 일반 공공 행정 (lv:0, line:105)
Empty corp list -  교육 행정 (lv:0, line:105)
Empty corp list -  환경 행정 (lv:0, line:105)
Empty corp list -  기타 사회서비스 관리 행정 (lv:0, line:105)
Empty corp list -  노동 행정 (lv:0, line:105)
Empty corp list -  농림수산 행정 (lv:0, line:105)
Empty corp list -  우편 및 통신행정 (lv:0, line:105)
Empty corp list -  외무 행정 (lv:0, line:105)
Empty corp list -  국방 행정 (lv:0, line:105)
Empty corp list -  법원 (lv:0, line:105)
Empty corp list -  검찰 (lv:0, line:105)
Empty corp list -  교도기관 (lv:0, line:105)
Empty corp list -  경찰 (lv:0, line:105)
Empty corp list -  소방서 (lv:0, line:105)
Empty corp list -  기타 사법 및 공공질서 행정 (lv:0, line:105)
Empty corp list -  사회보장 행정 (lv:0, line:105)
Empty corp list -  중학교 (lv:0, line:105)
Empty corp list -  상업 및 정보산업 특성화 고등학교 (lv:0, line:105)
Empty corp list -  공업 특성화 고등학교 (lv:0, line:105)
Empty corp list -  기타 특성화 고등학교 (lv:0, line:105)
Empty corp list -  대학원 (lv:0, line:105)
Empty corp list -  특수학교 (lv:0, line:105)
Empty corp list -  외국인 학교 (lv:0, line:105)
Empty corp list -  대안학교 (lv:0, line:105)
Empty corp list -  태권도 및 무술 교육기관 (lv:0, line:105)
Empty corp list -  레크리에이션 교육기관 (lv:0, line:105)
Empty corp list -  미술학원 (lv:0, line:105)
Empty corp list -  기타 예술학원 (lv:0, line:105)
Empty corp list -  치과 병원 (lv:0, line:105)
Empty corp list -  한방 병원 (lv:0, line:105)
Empty corp list -  일반 의원 (lv:0, line:105)
Empty corp list -  치과 의원 (lv:0, line:105)
Empty corp list -  한의원 (lv:0, line:105)
Empty corp list -  공중 보건 의료업 (lv:0, line:105)
Empty corp list -  앰뷸런스 서비스업 (lv:0, line:105)
Empty corp list -  유사 의료업 (lv:0, line:105)
Empty corp list -  신체 부자유자 거주 복지시설 운영업 (lv:0, line:105)
Empty corp list -  정신질환, 정신지체 및 약물 중독자 거주 복지시설 운영업 (lv:0, line:105)
Empty corp list -  아동 및 부녀자 거주 복지시설 운영업 (lv:0, line:105)
Empty corp list -  직업재활원 운영업 (lv:0, line:105)
Empty corp list -  사회복지 상담서비스 제공업 (lv:0, line:105)
Empty corp list -  공연 예술가 (lv:0, line:105)
Empty corp list -  당구장 운영업 (lv:0, line:105)
Empty corp list -  낚시장 운영업 (lv:0, line:105)
Empty corp list -  무도장 운영업 (lv:0, line:105)
Empty corp list -  기원 운영업 (lv:0, line:105)
Empty corp list -  불교 단체 (lv:0, line:105)
Empty corp list -  천주교 단체 (lv:0, line:105)
Empty corp list -  민족종교 단체 (lv:0, line:105)
Empty corp list -  정치 단체 (lv:0, line:105)
Empty corp list -  환경운동 단체 (lv:0, line:105)
Empty corp list -  기타 시민운동 단체 (lv:0, line:105)
Empty corp list -  자동차 세차업 (lv:0, line:105)
Empty corp list -  모터사이클 수리업 (lv:0, line:105)
Empty corp list -  의복 및 기타 가정용 직물제품 수리업 (lv:0, line:105)
Empty corp list -  가죽, 가방 및 신발 수리업 (lv:0, line:105)
Empty corp list -  시계, 귀금속 및 악기 수리업 (lv:0, line:105)
Empty corp list -  피부 미용업 (lv:0, line:105)
Empty corp list -  기타 미용업 (lv:0, line:105)
Empty corp list -  점술 및 유사 서비스업 (lv:0, line:105)
Empty corp list -  자가 소비를 위한 가사 생산 활동 (lv:0, line:105)
Empty corp list -  자가 소비를 위한 가사 서비스 활동 (lv:0, line:105)

꽤 많네 ㅎㅎ...

8. 비동기 테이블 페이지 Refresh 오류 수정

글이 점점 지저분해진다 ㅠㅠ 의도했던바가 아닌데

블로그 포스트는 개발 노트처럼 쓰고 싶지 않았는데;;

최종 코드는 깃헙에 올려야겠다

 

어쨌든, 오류 이유는 search 함수 호출 전에 테이블 태그를 가져온게 문제였다 ㅎㅎ

그리고 첫번째 페이지는 무조건 로드가 되어 있는 상태니깐, 1번 페이지에 대한 호출-대기 시퀀스는 무시해서 시간을 단축하도록 간단하게 수정해주자

async function fnGetCorpNamesFromTableSearch(index, corp_name_arr, delay_ms) { 
    // search - delay가 선행되어야 한다, 2번 페이지부터만 호출하면 된다
    if (index > 1) {
        search(index);
        await sleep(delay_ms);
    }
    var corp_name_arr = (corp_name_arr) ? corp_name_arr : [];
    var table = document.getElementsByClassName("tb")[0];
    var tbody = table.getElementsByTagName("tbody")[0];
    var tr_list = tbody.getElementsByTagName("tr");
    
    for (i = 0; i < tr_list.length; i++) {
        tr = tr_list[i];
        td = tr.getElementsByTagName("td")[0];
        span = td.getElementsByTagName("span")[0];
        a = span.getElementsByTagName("a")[0];
        corp_name = a.text;
        corp_name = corp_name.replace(/(\r\n|\n|\r|\t)/gm,"");
        corp_name_arr.push(corp_name);
    }
    
    return corp_name_arr;
}

9. 반환 결과 리스트의 위계 구조 구축

알고리즘을 테스트하기 위해 매번 크롤링해올 수는 없으니 전체를 한번 훑은 뒤에 반환되는 리스트를 직렬화해서 로컬에 저장하는 테스트 코드를 python 코드에 추가했다

class DartCrawlerWindow(QMainWindow):    
    def callbackResult(self, result: object):
        if isinstance(result, list):
            self._editConsole.clear()

            # for test
            import pickle
            with open('result_list.pkl', 'wb') as fp:
                pickle.dump(result, fp)

텍스트밖에 담겨있지 않은데도 용량이 꽤 크다 ㅎㅎ 

직렬화 파일로 작업해보고 싶은 사람을 위해 공유

result_list.pkl
1.25MB

 

이제 불러온 뒤에 열심히 알고리즘을 짜보자

import pickle

with open('./result_list.pkl', 'rb') as fp:
    node_list = pickle.load(fp)
In [1]: len(node_list)
Out[1]: 1195

In [2]: node_list[0].keys()
Out[2]: dict_keys(['corp_names', 'node_id', 'node_text', 'parents'])

In [3]: node_list[0].get('node_text')
Out[3]: '곡물 및 기타 식량작물 재배업'

In [4]: node_list[0].get('parents')
Out[4]: [{'id': 'all', 'text': '전체'}, {'id': 'root0103', 'text': '농업, 임업 및 어업'}, {'id': '01', 'text': '농업'}, {'id': '011', 'text': '작물 재배업'}, {'id': '0111', 'text': '곡물 및 기타 식량작물 재배업'}]

In [5]: node_list[0].get('corp_names')
Out[5]: ['농업회사법인동주물산', '농업회사법인보령스마트에너지', '농업회사법인어석', '농업회사법인위더그린', '농업회사법인장지', '농업회사법인제희', '농업회사법인현대서산농장', '대원영농조합법인', '대한영농영림', '베이스제일차', '신명알앤디', '아라미주팜', '이그린글로벌', '키스비케이제삼차', '현대서산천지인큰농장영농조합', '효원']

위계구조 구현을 위해 핵심사항을 정리해봤다

  • 최하위 노드 및 부모 노드들은 각자 고유의 'id'를 가진다
  • 각 노드는 부모 노드와 자식 노드(들)을 멤버로 가져야한다
  • 각 노드의 부모 노드는 NULL이거나 1개여야 한다 (다중 부모 구현 불필요)
  • 각 노드의 자식 노드(들)은 NULL(leaf node)이거나 1개 이상의 노드들로 구성된다
  • 부모 노드는 자식 노드들의 기업 리스트를 한꺼번에 조회할 수 있어야한다
  • 실제 기업리스트는 최하위 노드에만 존재해야 한다 (메모리 낭비 방지)

이정도로 구현 요구사항을 정리하고 노드에 대한 클래스를 다음과 같이 구성했다

트리는 자료구조 공부하면서 여러 번 다뤄봤는데, 이번에는 구글링이나 도서 참고하지 않고 머리속에서 그려지는 대로 구현해봤다 (따라서 최적화된 구현이 아닐 수 있다는거~)

 

[tree.py]

from typing import List, Union

class TreeNodeBase:
    _corp_names: List[str]
    _ident: str = ''
    _text: str = ''

    def __init__(self, ident: str, text: str, corp_names: List[str] = None):
        self._ident = ident
        self._text = text
        self._corp_names = list()
        if corp_names is not None:
            self._corp_names.extend(corp_names)
            self._corp_names.sort()

    def getIdent(self) -> str:
        return self._ident

    def getText(self) -> str:
        return self._text

    def getCorpNames(self) -> List[str]:
        result = list()
        for child in self.getChildren():
            corp_names = child.getCorpNames()
            result.extend(corp_names)
        result.extend(self._corp_names)
        result.sort()
        return result

    def setParentNode(self, node):
        pass

    def addChildNode(self, node):
        pass

    def hasChildNode(self, node) -> bool:
        return False

    def getChildren(self) -> list:
        return []

    def __repr__(self):
        return f'{self._text}({self._ident})'

class TreeNode(TreeNodeBase):
    _parent: Union[TreeNodeBase, None]
    _children: List[TreeNodeBase]

    def __init__(self, ident: str, text: str, corp_names: List[str] = None):
        super().__init__(ident, text, corp_names)
        self._parent = None
        self._children = list()

    def setParentNode(self, node: TreeNodeBase):
        self._parent = node
        if not self._parent.hasChildNode(self):
            self._parent.addChildNode(self)

    def addChildNode(self, node: TreeNodeBase):
        self._children.append(node)

    def hasChildNode(self, node: TreeNodeBase) -> bool:
        return node in self._children

    def getChildren(self) -> List[TreeNodeBase]:
        return self._children

    def getChildNode(self, ident: int) -> Union[TreeNodeBase, None]:
        filterted = list(filter(lambda x: x.getIdent() == ident, self._children))
        if len(filterted) == 1:
            return filterted[0]
        else:
            return None

    def __eq__(self, other: TreeNodeBase):
        return self._ident == other.getIdent()

class Tree:
    _node_list: List[TreeNode]

    def __init__(self):
        self._node_list = list()  # 여러개의 최상위 노드를 담을 수 있게 일단은 구성

    def add_leaf_node(self, ident: str, text: str, parents: List[dict], corp_names: List[str] = None):
        iter_node: Union[TreeNode, None] = None

        for i, elem in enumerate(parents):
            temp_ident = elem.get('id')
            temp_text = elem.get('text')
            temp_node = TreeNode(temp_ident, temp_text)
            if iter_node is None:  # 최초 iteration
                if temp_node in self._node_list:  # 최상위 노드가 트리의 노드 리스트에 있을 경우
                    temp_node = list(filter(lambda x: x.getIdent() == temp_ident, self._node_list))[0]
                else:  # 최상위 노드가 트리의 노드 리스트에 없을 경우 (추가)
                    self._node_list.append(temp_node)
            else:  # 자식 노드 iteration
                if iter_node.hasChildNode(temp_node):  # 이전 노드의 자식으로 이미 포함되어 있을 경우
                    temp_node = iter_node.getChildNode(temp_ident)
                else:  # 이전 노드의 자식에 없을 경우 (추가)
                    iter_node.addChildNode(temp_node)
            iter_node = temp_node
        node = TreeNode(ident, text, corp_names)
        iter_node.addChildNode(node)

    def add_leaf_node_dict(self, leaf_node_dict: dict):
        ident = leaf_node_dict.get('node_id')
        text = leaf_node_dict.get('node_text')
        parents = leaf_node_dict.get('parents')
        corp_names = leaf_node_dict.get('corp_names')
        self.add_leaf_node(ident, text, parents, corp_names)

    def add_leaf_nodes(self, leaf_node_dict_list: List[dict]):
        [self.add_leaf_node_dict(x) for x in leaf_node_dict_list]

    def getNodeList(self) -> List[TreeNode]:
        return self._node_list

add_leaf_nodes 메서드를 통해 크롤링 결과물 (리스트)를 바로 위계구조를 구축할 수 있도록 구현했다

with open('./result_list.pkl', 'rb') as fp:
    node_list = pickle.load(fp)

tree = Tree()
tree.add_leaf_nodes(node_list)

구현해둔 메서드들로 속성 몇가지를 체크해보자

In [6]: tree.getTotalLeafNodeCount()
Out[6]: 1195

In [7]: tree.getTotalNodeCount()
Out[7]: 2025

잎노드 개수는 크롤링 결과물과 동일하게 1195개, 전체 노드 개수는 2025개로 집계된다

corp_names = list()
for node in tree_node_list:
    corp_names.extend(node.getCorpNames())
corp_name_unique = set()
corp_name_duplicated = list()

for x in corp_names:
    if x in corp_name_unique:
        corp_name_duplicated.append(x)
    else:
        corp_name_unique.add(x)
In [8]: len(corp_names)
Out[8]: 43965
In [9]: len(corp_name_unique)
Out[9]: 41996
In [10]: len(corp_name_duplicated)
Out[10]: 1969

잎노드에 속한 기업명의 총 개수는 43965개인데, 이중에 1969개가 중복된 것으로 나타났다... 읭???

 

DART 기업검색을 통해 알아보니, 예를 들어 '국민은행'은 총 3개가 검색된다...

심지어 코스피에서는 국민은행은 'KB금융', 종목코드 105560으로 거래되고 있다

뭔가 주식의 역사가 고스란히 담긴 거 같은데... 일단 중복되는게 정상적인 결과라는 건 알게 됐다

In [11]: corp_names.count('국민은행')
Out[11]: 3

'건화'라는 기업은 총 8건 ㄷㄷㄷ...

결국 나중에 제대로 된 서비스를 하려면 '종목코드'까지 같이 테이블에서 긁어와야 한다는 결론!

기업명만 문자열로 다룰게 아니라, 기업 클래스를 만들어야 되겠다 (멤버로 기업명, 종목코드, 소속업종 등 정보 포함)

10. 트리 시각화

구현한 Tree 객체를 Qt의 QTreeWidget, QTableWidget을 사용해서 UI를 만들어보자

 

[treeview.py]

from typing import List
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QResizeEvent
from PyQt5.QtWidgets import QWidget, QTreeWidget, QTreeWidgetItem, QTableWidget, QTableWidgetItem
from PyQt5.QtWidgets import QSplitter, QHeaderView
from tree import Tree, TreeNodeBase

class CorpTreeItem(QTreeWidgetItem):
    def __init__(self, node: TreeNodeBase):
        super().__init__()
        self._node = node
        text = node.getText() + ' (' + node.getIdent() + ')'
        self.setText(0, text)

    def getCorpList(self) -> List[str]:
        return self._node.getCorpNames()

class CorpTreeViewWidget(QWidget):
    def __init__(self):
        super().__init__()
        self._tree = QTreeWidget()
        self._table = QTableWidget()
        self._splitter = QSplitter(Qt.Horizontal, self)
        self.initControl()
        self.initLayout()

    def initControl(self):
        self._tree.itemSelectionChanged.connect(self.onItemSelectionChanged)
        self._tree.setHeaderHidden(True)
        self._table.setColumnCount(1)
        self._table.horizontalHeader().hide()
        self._table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    def initLayout(self):
        self._splitter.addWidget(self._tree)
        self._splitter.addWidget(self._table)

    def setCorpTree(self, tree_: Tree):
        self._tree.clear()
        tree_node_list = tree_.getNodeList()
        for top_node in tree_node_list:
            def addTreeItem(tree_node: TreeNodeBase, tree_item: CorpTreeItem = None):
                if tree_item is None:
                    tree_item = CorpTreeItem(tree_node)
                    self._tree.addTopLevelItem(tree_item)
                else:
                    tree_child_item = CorpTreeItem(tree_node)
                    tree_item.addChild(tree_child_item)
                    tree_item = tree_child_item
                for child_node in tree_node.getChildren():
                    addTreeItem(child_node, tree_item)

            addTreeItem(top_node)

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self._splitter.resize(self.width(), self.height())

    def onTreeItemClicked(self, item: CorpTreeItem):
        corp_names = item.getCorpList()
        self._table.setRowCount(len(corp_names))
        for i in range(self._table.rowCount()):
            item = QTableWidgetItem()
            item.setText(corp_names[i])
            self._table.setItem(i, 0, item)

    def onItemSelectionChanged(self):
        selitems = self._tree.selectedItems()
        corp_names = []
        if len(selitems) > 0:
            for elem in selitems:
                if isinstance(elem, CorpTreeItem):
                    corp_names.extend(elem.getCorpList())
        self._table.setRowCount(len(corp_names))
        for i in range(self._table.rowCount()):
            item = QTableWidgetItem()
            item.setText(corp_names[i])
            self._table.setItem(i, 0, item)

실행해보자

if __name__ == "__main__":
    import sys
    import pickle
    from PyQt5.QtWidgets import QApplication

    with open('./result_list.pkl', 'rb') as fp:
        node_list = pickle.load(fp)

    app = QApplication(sys.argv)
    wgt_ = CorpTreeViewWidget()
    wgt_.resize(600, 600)
    wgt_.show()
    corp_tree = Tree()
    corp_tree.add_leaf_nodes(node_list)
    wgt_.setCorpTree(corp_tree)
    app.exec_()

마치 DART 홈페이지를 보는듯 잘 동작한다 ㅎㅎ

여기에 각 기업별 개황정보를 OPENDART API 등을 이용해서 가져오는 코드만 추가하면 완벽한 종목별 기업개황 어플리케이션을 완성할 수 있다


처음엔 간단할 줄 알고 무작정 프로토타입 코드부터 블로그에 작성하기 시작했는데, 막상 실제로 돌려보니 이것저것 고칠게 많아서 결국 개발노트가 되어버렸다... 이런;;

일단 DB (SQL)과 연동하는 작업은 잠시 뒤로 미루고, 크롤링할 때 기업의 객체 정보(기업명, DART 고유코드, 소속 업종 id)를 가져오게 자바스크립트를 수정한 뒤에 깃헙에 올리고 이번 주제 글은 마무리해야겠다

 

[시리즈]

웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (1)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (2)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (3)
웹크롤링 - DART 기업개황 업종별 기업 리스트 가져오기 (Final)

반응형