YOGYUI

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

Data Analysis/Data Engineering

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

요겨 2022. 1. 6. 23:54
반응형

Get Corporations List Classified by Sectors from DART(fss)

[시리즈]

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

 

주식 투자할 때, 뉴스나 소셜미디어 혹은 입소문 등으로 유망한 업종(섹터)을 알게되면 해당 업종에서 활동하는 기업들을 리스트 업한 뒤에 성장가능성이 높은 종목을 택해서 투자하는게 아주 일반적인 프로세스다

 

업종별 기업 리스트는 각 증권사 HTS나 MTS 앱을 통해 쉽게 알아볼 수 있고, 포털사이트나 증권 정보 사이트 등에서도 쉽게 알아볼 수 있는데, 금융감독원 전자공시시스템(DART) 홈페이지에서도 업종별 기업 리스트를 확인할 수 있다

네비게이션바 - 기업개황 - 기업개황

 

상위 업종 아래에도 하위 카테고리들이 있는 트리(tree) 구조로 분류가 되어 있으며, 사용자가 탐색할 수 있게 인터페이스가 구현되어 있다

 

예를 들어 기아와 현대자동차는 [제조업 - 자동차 및 트레일러 제조업 - 자동차용 엔진 및 자동차 제조업 - 자동차 제조업 - 승용차 및 기타 여객용 자동차 제조업] 업종에 소속되어 있다


기업을 몇 개 찾아보다보면, "어차피 웹페이지인데, 크롤링해서 나만의 데이터로 가져올 수 있지 않을까?" 하는 생각이 절로 들게 된다 (주식투자하다보면 DART 페이지는 굉장히 빈번하게 들락날락하게 되는데, 반복 작업은 자동화하고 싶은 생각이 드는 건 공돌이에겐 인지상정이지)

 

그래서 시작해본 "DART 업종별 기업 리스트 웹크롤링 프로젝트"!!

1. 페이지 소스 분석

별도의 툴을 사용하지 않는 이상, 웹크롤링의 시작은 역시 페이지 소스 분석

1.1. 유저 인터페이스 분석

(1) 왼쪽 트리의 항목을 클릭하면 우측 테이블에 기업명들이 refresh된다

좌: "제조업" 최상위 노드 선택 시, 우: "전기,가스,증기 및 공기 조절 공급업" 최상위 노드 선택 시

(2) 테이블이 모두 그려지기까지 약간의 딜레이가 있다

(3) 기업수가 여러개일 경우, 페이지 목록이 테이블 아래에 생성된다, 한페이지에 대략 44개 목록이 테이블에 표시된다

1.2. 페이지 소스 분석

크롬의 개발자 도구 (단축키 F12 혹은 Ctrl + Shift + I) 로 코드를 까보자

개발자 도구 오픈

1.2.1. 트리-뷰 

제일 처음으로 보이는 잎노드(leaf node)를 찍어서 소스에서 검색해봤다

(농업, 임업 및 어업 - 농업 - 작물 재배업 - 곡물 및 기타 식량작물 재배업 - 곡물 및 기타 식량작물 재배업)

<div class="panelWrap" style="">
  <ul id="businessTree" class="jstree jstree-1 jstree-default" role="tree" tabindex="0" aria-activedescendant="01110" aria-busy="false">
    <ul class="jstree-container-ul jstree-children jstree-no-icons" role="group">
      <li role="presentation" aria-selected="false" aria-level="1" aria-labelledby="all_anchor" aria-expanded="true" id="all" class="jstree-node jstree-last jstree-open">
        <i class="jstree-icon jstree-ocl" role="presentation"></i>
        <a class="jstree-anchor" href="#" tabindex="-1" role="treeitem" aria-selected="false" aria-level="1" aria-expanded="true" id="all_anchor">
          <i class="jstree-icon jstree-themeicon" role="presentation"></i>
          전체
        </a>
        <ul role="group" class="jstree-children" style="">
          <li role="presentation" aria-selected="false" aria-level="2" aria-labelledby="root0103_anchor" aria-expanded="true" id="root0103" class="jstree-node jstree-open">
            <i class="jstree-icon jstree-ocl" role="presentation"></i>
            <a class="jstree-anchor" href="#" tabindex="-1" role="treeitem" aria-selected="false" aria-level="2" aria-expanded="true" id="root0103_anchor">
              <i class="jstree-icon jstree-themeicon" role="presentation"></i>
              농업, 임업 및 어업
            </a>
            <ul role="group" class="jstree-children" style="">
              <li role="presentation" aria-selected="false" aria-level="3" aria-labelledby="01_anchor" aria-expanded="true" id="01" class="jstree-node jstree-open">
                <i class="jstree-icon jstree-ocl" role="presentation"></i>
                <a class="jstree-anchor" href="#" tabindex="-1" role="treeitem" aria-selected="false" aria-level="3" aria-expanded="true" id="01_anchor">
                  <i class="jstree-icon jstree-themeicon" role="presentation"></i>
                  농업
                </a>
                <ul role="group" class="jstree-children" style="">
                  <li role="presentation" aria-selected="false" aria-level="4" aria-labelledby="011_anchor" aria-expanded="true" id="011" class="jstree-node jstree-open">
                    <i class="jstree-icon jstree-ocl" role="presentation"></i>
                    <a class="jstree-anchor" href="#" tabindex="-1" role="treeitem" aria-selected="false" aria-level="4" aria-expanded="true" id="011_anchor">
                      <i class="jstree-icon jstree-themeicon" role="presentation"></i>
                      작물 재배업
                    </a>
                    <ul role="group" class="jstree-children" style="">
                      <li role="presentation" aria-selected="false" aria-level="5" aria-labelledby="0111_anchor" aria-expanded="true" id="0111" class="jstree-node jstree-open">
                        <i class="jstree-icon jstree-ocl" role="presentation"></i>
                        <a class="jstree-anchor" href="#" tabindex="-1" role="treeitem" aria-selected="false" aria-level="5" aria-expanded="true" id="0111_anchor">
                          <i class="jstree-icon jstree-themeicon" role="presentation"></i>
                          곡물 및 기타 식량작물 재배업
                        </a>
                        <ul role="group" class="jstree-children" style="">
                        <li role="presentation" aria-selected="true" aria-level="6" aria-labelledby="01110_anchor" id="01110" class="jstree-node  jstree-leaf jstree-last">
                          <i class="jstree-icon jstree-ocl" role="presentation"></i>
                            <a class="jstree-anchor jstree-clicked" href="#" tabindex="-1" role="treeitem" aria-selected="true" aria-level="6" id="01110_anchor">
                            <i class="jstree-icon jstree-themeicon" role="presentation"></i>
                            곡물 및 기타 식량작물 재배업
              </a>
  <!-- 중략 -->
</div>

트리에 해당하는 <ul> 태그의 id는 businessTree 이고, 트리 구조답게 <ul> - <li> - <i> - <a> 태그가 하위로 가지치기하는 형식으로 구성되어 있다

 

그런데 가만 살펴보니 트리 노드들의 클래스명에 공통적으로 jstree 문자열이 들어가있다

어? 이거 완전??

웹 개발시 트리-뷰 구현할 때 애용하는 오픈소스 라이브러리 jsTree 아녀? (jQuery 플러그인)

(요즘은 리액트가 대세라 많이 사용되는지는 모르겠다)

https://www.jstree.com/

 

혹시나해서 페이지 소스의 <head> 태그로 가보니...

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko" class=" ext-strict">
  <head>
    <title>전자공시시스템 | 기업개황 </title>
    <meta charset="UTF-8">
    <meta id="Viewport" name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=yes">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="/resource/js/jquery-3.3.1.min.js"></script>

    <link rel="stylesheet" href="/js/jquery-ui/jquery-ui.min.css">
    <script src="/js/jquery-ui/jquery-ui.min.js"></script>

    <!-- 2011.11.01 ext 2.3 -->
    <!--[if lte IE 8]><link rel="stylesheet" type="text/css" href="/js/ext-main/resources/css/ext-all-ie8.css" /><![endif]-->
    <script type="text/javascript" src="/js/ext-main/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="/js/ext-main/ext-all.js"></script>

    <!-- x-xeries js libraries  -->
    <script type="text/javascript" src="/js/xjs.js"></script>

    <!-- application js libraries -->
    <script type="text/javascript" src="/js/jsval.js"></script>

    <!-- 웹표준 추가 스크립트-->
    <script type="text/javascript" src="/js/jquery/gotop.js"></script>
    <script type="text/javascript" src="/js/idart/idart.js"></script><!-- idart용 -->

    <!-- 웹표준화적용사항 위반으로 DSAB001L.jsp에서 헤드로 위치 이동 -->
    <style type="text/css">
      .checked-icon {background-image: url(/images/temp/add.gif);}
      .ui-autocomplete {max-height: 100px; overflow-y: auto; overflow-x: hidden;}
      .ui-menu-item .ui-menu-item-wrapper.ui-state-active {background: #eaeaea !important; border: none; color:#333;}
    </style>
    <!--end -->

    <!-- 추가 되는 js, css 시작 -->
    <link rel="stylesheet" href="/resource/css/bootstrap.min.css">
    <link rel="stylesheet" href="/resource/css/slick.css">
    <script src="/resource/js/bootstrap.bundle.min.js"></script>
    <script src="/resource/js/slick.min.js"></script>
    <link rel="stylesheet" href="/js/jsTree/default/style.css">
    <script src="/js/jsTree/jstree.js"></script>
    
    <!-- 후략 -->

역시나 jstree.js 스크립트를 로드하고 있다

 

개발자 도구에서 Source 탭을 클릭해보면

jsTree 3.3.10 소스코드 스크립트를 그대로 넣어둔 것을 확인할 수 있다

 

잘 알려진 소스를 써주면 크롤링하는 입장에선 굉장히 감사한 일이다 ㅎㅎ

jQuery에 대한 지식이 조금만 있으면 쉽게 트리 탐색이 가능하기 때문!

1.2.2. 테이블

업종별 기업 리스트가 디스플레이되는 테이블 항목도 찾아보자

<div id="listContents">
  <!-- list -->
  <div class="listWrapS">
    <div class="tbLThBg"></div>
    <div class="tbLInner">
      <table class="tb" summary="회사명,업종명 순으로 구성되어 있습니다" id="corpTabel">
        <caption>회사명,업종명 리스트</caption>
        <thead>  
          <tr>
            <th scope="col" width="70%"><div class="thName">회사명</div></th>
            <th scope="col" width="30%"><div class="thName">종목코드</div></th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="tL ellipsis">
              <span class="nobr1" style="max-width:150px;">
              <span class="tagCom_etc" title="기타법인" style="cursor:default">기</span>
                <a href="javascript:select('00530255');" title="농업회사법인동주물산 기업개황 ">
                농업회사법인동주물산
                </a>
              </span>
            </td>
            <td> </td>
          </tr>
          <tr>
            <td class="tL ellipsis">
              <span class="nobr1" style="max-width:150px;">
              <span class="tagCom_etc" title="기타법인" style="cursor:default">기</span>
                <a href="javascript:select('01506477');" title="농업회사법인보령스마트에너지 기업개황 ">
                농업회사법인보령스마트에너지
                </a>
              </span>
            </td>
            <td> </td>
          </tr>
          <tr>
            <td class="tL ellipsis">
              <span class="nobr1" style="max-width:150px;">
              <span class="tagCom_etc" title="기타법인" style="cursor:default">기</span>
                <a href="javascript:select('01247237');" title="농업회사법인어석 기업개황 ">
                농업회사법인어석
                </a>
              </span>
            </td>
            <td> </td>
          </tr>
          <!-- 중략 -->
        </tbody>
      </table>
    </div>
  </div>

테이블은 HTML로 구현되어 있다 (<table> 태그)

의도인지 실수인지는 모르겠지만 테이블 태그의 id가 "corpTabel"로 오타가 보인다 ㅋㅋ 

(흔히 볼 수 있는 오타 중 하나지...)

 

일반적인 <table> - <tr> - <td> 구조를 따르므로 크롤링 난도는 굉장히 낮다 ㅎㅎ

1.2.3. 페이지 탐색기

테이블 바로 아래에 페이지 탐색기도 찾아보자

앞서 살펴봤던 <table>이 속해있는 <div class="listWrapS">를 포함하는 <div class="listContents"> 태그 내에 존재하는 <div class="psWrap">이 바로 페이지 탐색기이다

<div class="listContents">
  <div class="listWrap">...</div>
  <div class="psWrap">
    <div class="pageInfo">[1/3] [총 91건]</div>
    <div class="pageSkip">
      <ul>
        <li class="on">
          <a onclick="javascript:void(0);">1</a>
        </li>
        <li>
          <a href="javascript:search(2);">2</a>
        </li>
        <li>
          <a href="javascript:search(3);">3</a>
        </li>
      </ul>
    </div>
  </div>
</div>

테이블 로드 후 해당 태그를 가져오면 페이지 개수 및 기업 목록 개수를 가져올 수 있다

그리고 <a> (앵커 태그)를 보면 자바스크립트 함수 호출이 하이퍼링크로 걸려있으니 함수를 호출하면서 모든 기업 목록을 손쉽게 크롤링할 수 있을 것으로 기대된다 (역시나 크롤링 난도는 下)

2. 트리 크롤링 (기초)

앞서 살펴봤듯이, 트리를 구현하는 데 사용된 jsTree 라이브러리는 트리 객체에 외부 사용자가 jQuery를 사용해서 굉장히 손쉽게 접근할 수 있다

jsTree API가 궁금한 사람은 공식 페이지를 참고하도록 하자(https://www.jstree.com/api/)

 

jsTree 객체는 다음 javascript로 가져올 수 있다

var tree = $j("#businessTree").jstree(true);

jstree가 제공하는 API로 트리를 제어할 수 있다

예를 들어 모든 노드를 열고 닫으려면 open_all, close_all 메서드를 호출하면 된다

 

노드를 클릭해서 해당 노드의 기업 리스트를 테이블에 그리기 위해서는 jsTree의 select_node 메서드를 호출해야 된다

이 때, 메서드 인자로 노드의 id를 넘겨줘야 한다

예를 들어 "곡물 및 기타 식량작물 재배업" 잎노드의 id는 "01110"이다

(잎노드는 <li> 태그의 클래스에 jstree-leaf 라고 명시된다)

<li role="presentation" aria-selected="true" aria-level="6" aria-labelledby="01110_anchor" id="01110" class="jstree-node  jstree-leaf jstree-last">
  <i class="jstree-icon jstree-ocl" role="presentation"></i>
  <a class="jstree-anchor jstree-clicked" href="#" tabindex="-1" role="treeitem" aria-selected="true" aria-level="6" id="01110_anchor">
    <i class="jstree-icon jstree-themeicon" role="presentation"></i>
	곡물 및 기타 식량작물 재배업
  </a>
</li>

따라서

tree.select_node("01110");

와 같이 호출하면 해당 노드가 선택되면서 기업 목록이 테이블에 refresh된다

 

그러면 모든 노드를 하나하나 열어가면서 id를 가져와야 하는건가?

물론 아니다

 

jsTree 노드들은 children 속성을 호출해 하위에 존재하는 자식 노드들의 정보를 어레이로 가져올 수 있다

예를 들어 첫번째 최상위 노드인 "농업, 임업 및 어업" 노드 (id="01")의 자식노드들은 다음과 같이 조회할 수 있다

node = tree.get_node("01");
node.children

하위 자식 노드들의 id에 해당하는 문자열이 담긴 array를 확인할 수 있다

 

구글링을 통해 재귀함수를 구현한 글이 있길래 참고해서 다음과 같이 함수를 작성해봤다

https://pretagteam.com/question/jstree-how-to-get-all-leaf-nodes-from-jstree

(해당 글에 나온 코드가 오류가 있으니, 내가 작성한 아래 함수를 참고하는게 좋다)

function jstreeIterateNodes(treeObj, node, fnCondition, bRecursive, arrCollector) {
    var children_id_arr = node.children;    // 노드의 자식 노드들의 id 문자열이 담긴 어레이를 가져온다
    var arrCollector = (arrCollector) ? arrCollector : [];
    var bCheckCondition = (typeof fnCondition === "function") ? true : false;

    for (var i = 0; i < children_id_arr.length; ++i) {
        var nodeChild = treeObj.get_node(children_id_arr[i]);    // 트리의 노드 객체를 가져온다
        if (bCheckCondition) {
            if (fnCondition(nodeChild, node)) {
                // 조건함수가 주어질 경우, 조건함수가 참일 경우에만 결과 어레이에 노드와 부모노드 정보를 첨가한다
                arrCollector.push({node: nodeChild, parent: node});
            }
        } else {
            // 조건함수가 주어지지 않을 경우, 결과 어레이에 노드와 부모노드 정보를 첨가한다
            arrCollector.push({node: nodeChild, parent: node});
        }

        if (bRecursive) {
            // 참인 경우, 자식 노드의 자식들에 대해서도 재귀호출
            arrCollector = jstreeIterateNodes(treeObj, nodeChild, fnCondition, bRecursive, arrCollector);
        }
    }

    return arrCollector;
}

위 함수를 이용해서 잎노드(leaf node)만 가져오고 싶으면, jsTree의 is_leaf 메서드만 사용해주면 된다

참고: node.is_leaf()랑 node.children.length==0 이랑 의미는 동일하다

"전체" 노드의 id는 all_anchor 인 것만 기억해두면 다음과 같이 전체 잎노드를 어레이로 가져올 수 있다

var top_node = tree.get_node('all_anchor');
var fnIsLeafNode = function (node, parent_node) {
    if ($j('#businessTree').jstree("is_leaf", node)) {
        return true;
    } else {
        return false;
    }
}

leaf_node_list = jstreeIterateNodes(tree, tree.get_node('all_anchor'), fnIsLeafNode, true);

tree 객체의 함수 호출을 위와 같이 $j(트리아이디).jstree(함수명, 인자) 이런식으로 할 수도 있다는 걸 보여주기 위해 굳이 이렇게 불합리하게 구현해봤다 ㅎㅎ

원하는대로 총 1195개의 최하위 자식 노드 (잎노드, leaf node)들을 가져올 수 있다

3. 테이블 크롤링 (기초)

테이블 크롤링은 사실 DOM과 js만 사용하면 아주 간단하게 10줄 이내로 구현할 수 있다

function fnGetCorpNamesFromTable() {
    table = document.getElementsByClassName("tb")[0];
    tbody = table.getElementsByTagName("tbody")[0];
    tr_list = tbody.getElementsByTagName("tr");
    corp_names = []
	
    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_names.push(corp_name);
    }
	
    return corp_names;
}

 

회사명 문자열에 replace 정규식을 사용한 이유는 문자열 내에 \t, \n이 많이 들어가있기 때문 ^^

javascript로 정규식을 활용해 문자열을 replace All하는 방법은 다음 사이트를 참고했다

https://www.textfixer.com/tutorials/javascript-line-breaks.php

 

만약에 기업 목록이 길어서 페이지가 여러개로 나뉘게 된다면?

<!-- 1번 선택 시-->
<div class="pageSkip">
  <ul>
    <li class="on"><a onclick="javascript:void(0);">1</a></li>
    <li><a href="javascript:search(2);">2</a></li>
    <li><a href="javascript:search(3);">3</a></li>
  </ul>
</div>

<!-- 2번 선택 시-->
<div class="pageSkip">
  <ul>
    <li><a href="javascript:search(1);">1</a></li>
    <li class="on"><a onclick="javascript:void(0);">2</a></li>
    <li><a href="javascript:search(3);">3</a></li>
  </ul>
</div>

<!-- 3번 선택 시-->
<div class="pageSkip">
  <ul>
    <li><a href="javascript:search(1);">1</a></li>
    <li><a href="javascript:search(2);">2</a></li>
    <li class="on"><a onclick="javascript:void(0);">3</a></li>
  </ul>
</div>

하이퍼링크로 걸려있는 search 함수를 호출하면 된다는 걸 알 수 있다

(인자는 1, 2, ..., n, n=페이지 개수)

 

참고로 search함수는 <head><script>에 구현되어 있는 것을 알 수 있다

(php용 ajax 라이브러리 xajax가 사용되었다.. 공기업 웹페이지 소스 까보면 웹개발 테크닉을 꽤 다방면으로 배울 수 있다)

더보기

search 함수 원형

function search(page) {
    popClose('ceoInfo');
    popClose('compInfo');
    var pattern1 = /[0-9]/;
    
    try {
        /*  init function */
        var frm = findForm("searchForm");
        clearSearchCorpText();
        var searchCorpType = $j('#searchCorpType').val();
        //검색종류 회사명
        //유효성 체크 시작
        $j('#bsnRgsNo').val($j('#bsnRgsNo_1').val() + $j('#bsnRgsNo_2').val() + $j('#bsnRgsNo_3').val());
        
        if(frm["textCrpCik"].value == ""){
            if (frm["textCrpNm"].value.trim() == ""
                    && frm["bsnRgsNo"].value.trim() == ""
                    && frm["crpRgsNo"].value.trim() == ""
                    && frm["businessCode"].value.trim() == "all"
                    && frm["corporationType"].value.trim() == "all"
                    && frm["searchIndex"].value.trim() == "") {
                alert("빠른 검색 서비스를 제공하기 위해 검색조건을 제한합니다.");
                return;
            }
        }
        
        if(searchCorpType == '1'){
            if (!checkQueryString(frm.textCrpNm.value)) {
                alert("회사명에 [! % = \" ' -- < > |] 문자를 입력할 수 없습니다.");
                return;
            }
        }
        
        //검색종류 사업자
        if(searchCorpType == '2'){
            if(($j('#bsnRgsNo_1').val().trim() == '' || $j('#bsnRgsNo_2').val().trim() == '' || $j('#bsnRgsNo_3').val().trim() == '') && frm["searchIndex"].value == "" && frm["businessCode"].value == "all" && frm["corporationType"].value == "all"){
                alert("사업자등록 번호를 입력 해 주세요.");
                return;
            } else if($j('#bsnRgsNo_1').val() != '' && $j('#bsnRgsNo_2').val() != '' && $j('#bsnRgsNo_3').val() != '') {
                if( !(pattern1.test($j('#bsnRgsNo_1').val()) 
                        && pattern1.test($j('#bsnRgsNo_2').val()) 
                        && pattern1.test($j('#bsnRgsNo_3').val())) ){
                    alert("사업자등록 번호는 숫자만 입력 가능합니다.");
                    return;
                }
            }
            
            $j('#bsnRgsNo').val($j('#bsnRgsNo_1').val() + $j('#bsnRgsNo_2').val() + $j('#bsnRgsNo_3').val());
        }
        
        //검색종류 법인등록번호
        if(searchCorpType == '3'){
            if($j('#crpRgsNo').val().trim() == '' && frm["searchIndex"].value == "" && frm["businessCode"].value == "all" && frm["corporationType"].value == "all") {
                alert("법인등록번호를 입력해 주세요.");
                return;
            }
            
            if (!checkQueryString(frm.crpRgsNo.value)) {
                alert("법인등록번호에 [! % = \" ' -- < > |] 문자를 입력할 수 없습니다.");
                return;
            }
        }
        
        if ((frm["textCrpNm"].value != "" || frm["bsnRgsNo"].value != "" || frm["crpRgsNo"].value != "") && frm["searchIndex"].value != "") {
            frm["searchIndex"].value = "";
        }
        
       /*  init paging --------------------------------------------------------- */
        if(page=='') page=1;
        frm.currentPage.value = page;
        
      /*    send ajax request & response ---------------------------------------- */
        xajax.initParameter();
        xajax.sendForm("searchForm", "/dsae001/search.ax", function(str) {
            getRef("listContents").innerHTML = str;
            if (getRef("corp")) {
                history(getRef("historyCik").innerHTML);
            }
        });
    } finally {
        blurSearchCorp();    // init 'textCrpNm' field
    }
 }

 

그런데 search 함수 호출 후 테이블이 refresh되기까지는 수십 ms 이상의 시간이 소요되는데, 비동기 호출이 기본인 javascript로 타이밍을 맞추는게 쉽지는 않다 (search함수에 콜백이 있으면 구현하기 쉬웠을텐데 ㅠㅠ)

 

나는 javascript로 비동기 딜레이에 대한 순차호출은 구현해본 적이 없기에 열심히 구글링했다

https://www.daleseo.com/js-sleep/

 

[자바스크립트] 프로그램의 실행을 지연시키기 (sleep)

Engineering Blog by Dale Seo

www.daleseo.com

https://seungtaek-overflow.tistory.com/4

 

[JS] 비동기 작업들의 순차실행과 병렬실행

어떤 결과를 만들기 위해 5개의 비동기 작업을 수행해야 한다고 가정해보자. 5개의 작업이 서로 연관되어 있어서 작업을 한 번에 하나씩 끝내야 한다면, 작업을 순차적(sequential)으로 처리해야한

seungtaek-overflow.tistory.com

흠... 이정도면 구현이 되겠지??

위 블로그 글들을 기반으로 search 함수 호출 후 일정시간 기다린 후 테이블 정보를 긁어오는 테스트 코드를 짜봤다 (잡다한 스킬들이 들어가서 조금은 있어보이는 함수가 되었다)

function sleep(ms) {
    return new Promise((r) => setTimeout(r, ms));
}

var corp_name_arr = []

async function fnGetCorpNamesFromTableSearch(index) {
    table = document.getElementsByClassName("tb")[0];
    tbody = table.getElementsByTagName("tbody")[0];
    tr_list = tbody.getElementsByTagName("tr");
        
    search(index);
    await sleep(500);
    
    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);
    }
}

pageInfo = document.getElementsByClassName("pageInfo");
pageSkip = document.getElementsByClassName("pageSkip")[0];
li_tags = pageSkip.getElementsByTagName("li");
arrIndex = []
for (i = 0; i < li_tags.length; i++) {
    arrIndex.push(i + 1);
}

async function fnGetCorpNamesFromTableAll(arrIndex) {
    await arrIndex.reduce((prevTask, currTask) => {
        return prevTask.then(() => fnGetCorpNamesFromTableSearch(currTask));
    }, Promise.resolve());
}

fnGetCorpNamesFromTableAll(arrIndex);

핵심은 async와 await를 사용한 비동기함수, Promise 및 setTimeout을 통한 비동기 딜레이, 어레이의 reduce를 활용한 비동기 함수의 순차호출을 활용한 것이다

 

500ms 정도로 넉넉하게 딜레이를 주니 

'작물 재배업'의 91개 기업 목록을 제대로 받아볼 수 있다 

(요상하게도 어레이 투입 순서는 페이지 3-1-2가 되었다)


쓰다보니 글이 너무 길어져서 다음 포스팅으로 넘기도록 한다 ㅠㅠ

(블로그 글도 쓰면서 구현도 하려니 거의 반나절이 걸렸다... 허리 좀 펴야지!)

 

다음 글 내용

(1) jstree - 트리 구조로 어레이 가져오기

(2) 페이지 렌더링 완료까지 대기하기 (비동기 waiting)

(3) 모든 최하위 노드들에 대한 기업 리스트 테이블 크롤링 (비동기함수 순차호출)

(4) 파이썬으로 자동화 코드 구현하기 (스케쥴링)

 

[시리즈]

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

 

 

반응형
Comments