뉴스 크롤링부터 시작하는 기업 파산 예측

뉴스 텍스트 마이닝을 통해 어떻게 기업 파산 가능성을 더 정확하게 예측하는지 알아보세요.

본 포스팅은 해시스크래퍼가 기존에 진행했던 연구 중 텍스트 마이닝을 활용한 기업부도예측 모형연구과정에서 다뤘던 항목 중 하나입니다.

0. 개요

유사 주제의 선행 연구들을 기반으로하여, 부도 예측 과정에서 뉴스텍스트와 같은 데이터를 적용 방법에 따라 부도예측의 정확도를 높일 수 있을지, 그리고 인공지능 활용을 통해서 예측 성능이 향상되는지에 대한 연구입니다.
 

1. 로우데이터를 웹상의 언론으로부터 수집

로우데이터는 2010년과 2021년 사이에 송출되어 있는 뉴스로 설정했습니다. 뉴스는 '네이버 뉴스' 플랫폼에 기업명을 검색해서 크롤링하는 방식을 채택했습니다.
단, 한 가지 짚고 넘어가야 할 이슈가 있습니다. 매우 많은 기업들이 우리나라에 등록이 되어있었고, 그 뉴스 수 또한 매우 방대했기 때문에 정해진 기간안에 모든 뉴스 데이터를 수집한다는 것은 매우 어렵고 시간이 걸리는 작업입니다. 그렇기 때문에 최대한 빠르고 정확히 수집하는 것이 중요했습니다.
  • 시간문제
  • 뉴스 데이터 중복 문제
  • 데이터 용량 문제
  • 광고 뉴스 데이터 제외관련 문제
 

2. 뉴스 데이터 수집 진행

Step-1. 다음과 같은 절차로 수집을 설정하기로 했습니다.

notion image
notion image

Step-2. 코드 작성

아래와 같이 코드를 작성했고, 테스트를 통해 검토를 거칩니다.
column_ = ['기업', '기사발행일', '기사제목', '뉴스기사본문'] 뉴스_df = pd.DataFrame(columns=column_) driver = webdriver.Chrome() 회사명_ = 상폐기업['회사명'] 종료연도_ = 상폐기업['종료연도'] 종료월_ = 상폐기업['종료월'] 종료일_ = 상폐기업['종료일'] 시작연도_ = 상폐기업['시작연도'] 시작월_ = 상폐기업['시작월'] 시작일_ = 상폐기업['시작일'] # start = '2010.01.01' # end = '2010.12.31' # start_= '20100101' # end_ = '20101231' for 기업, 종료연도, 종료월, 종료일, 시작연도, 시작월, 시작일 in zip(회사명_, 종료연도_, 종료월_, 종료일_, 시작연도_, 시작월_, 시작일_): start = (시작연도 + '.' + 시작월 + '.' + 시작일) end = (종료연도 + '.' + 종료월 + '.' + 종료일) start_= (시작연도 + 시작월 + 시작일) end_ = (종료연도 + 종료월 + 종료일) # 나중에 뉴스_df와 concat할 임시 df 생성 및 임시 리스트들 생성 column_ = ['기업', '기사발행일', '기사제목', '뉴스기사본문'] 임시_df = pd.DataFrame(columns=column_) # 임시_df에 들어갈 리스트 생성 본문리스트 = [] 날짜리스트 = [] 제목리스트 = [] 기업이름 = [] # while 종료 조건으로 쓸 리스트 생성 newslist = [] datelist = [] page = 1 # 페이지수가 나와있지않으므로 맨끝에 page에 10씩더해서 계속 다음페이지로 이동 # 20페이지 이후로는 광고 등 불필요한 기사가 많아 페이지를 20페이지로 한정한다. while page < 200: url = 'https://search.naver.com/search.naver?where=news&sm=tab_pge&query='+'"'+기업+'"'+'&sort=0&photo=0&field=0&pd=3&ds='+start+'&de='+end+'&cluster_rank=19&mynews=0&office_type=0&office_section_code=0&news_office_checked=&nso=so:r,p:from'+start_+'to'+end_+',a:all&start='+str(page)' driver.get(url) response = requests.get(url) soup = BeautifulSoup(response.text, "lxml") # a태그중에서 class가 info인 것과 span태그에 class가 info 인 것 가져옴 news_titles = soup.select("a.info") dates = soup.select('span.info') # 네이버기사와 신문사기사 둘다 하이퍼링크가 있을경우는 기사당 news가 2개씩 네이버 기사가 없을 경우는 1개씩만 링크가 추출된다 # 어림잡아 news_titles수가 10개 미만일 경우는 기사의 수가 5개에서 10개 미만이라는 뜻으로 해석하여 10개 이상만 링크와 기사날짜를 추출 if len(news_titles) >= 10: for news in news_titles: title = news.attrs['href'] newslist.append(title) for date in dates: news_date = date.text datelist.append(news_date) # 네이버 검색시 page number가 다음페이지로 갈때마다 1, 11, 21 이렇게 10씩 더해지는데 다음페이지가 없을경우 # 마지막 기사만을 포함한 같은 페이지를 계속 반환함. 따라서 기사와 날짜 둘 다 중복되서 저장될경우 종료함 if (newslist[-1]==newslist[-2]) & (datelist[-1]==datelist[-2]): break page += 10 time.sleep(1) # 기사수 5개 미만시 break, 기사없는기업 리스트에 저장함 else: break # sid=101은 네이버 경제기사를 의미하는 듯함. 경제기사만 추출 newslist = [news for news in newslist if 'sid=101' in news] # news_titles 뉴스기사 url 리스트가 존재시 if news_titles: # 뉴스기사 url 자체에서는 text가 안가져와지는 특이사항 발생, 찾아보니 네이버기사는 인터넷에 떠야 페이지가 작동하는? 방식이라함 # 셀레니움을 통해서 뉴스기사 url 주소로 창을 띄움 for news in newslist: url = news driver.get(url) # 뉴스기사 url에서 본문과 제목, 기사작성날짜를 리스트에 저장함 # 데이터프레임에 직접 행을 지정해주기는 번거로움.. try: 날짜 = driver.find_element('xpath', '//*[@id="ct"]/div[1]/div[3]/div[1]/div/span').text 날짜리스트.append(날짜) 제목 = driver.find_element('xpath', '//*[@id="ct"]/div[1]/div[2]/h2').text 제목리스트.append(제목) 본문 = driver.find_element('xpath', '//*[@id="dic_area"]').text 본문리스트.append(본문) time.sleep(1) 기업이름.append(기업) ## 로딩이 안되서 데이터를 못가져올 경우를 대비해 sleep 3초 주고 다시 시도 except: time.sleep(3) try: 날짜 = driver.find_element('xpath', '//*[@id="ct"]/div[1]/div[3]/div[1]/div/span').text 날짜리스트.append(날짜) 제목 = driver.find_element('xpath', '//*[@id="ct"]/div[1]/div[2]/h2').text 제목리스트.append(제목) 본문 = driver.find_element('xpath', '//*[@id="dic_area"]').text 본문리스트.append(본문) time.sleep(1) 기업이름.append(기업) # 그래도 데이터를 가져오지 못하는 경우는 페이지에 문제가 있다고 판단하여 PASS except: pass # 혹시나 뉴스기사 url 리스트가 없을 경우는 pass else: pass # 기업별로 가져온 날짜와 본문, 제목, 기업이름을 임시 데이터프레임으로 저장 임시_df.기사발행일 = 날짜리스트 임시_df.뉴스기사본문 = 본문리스트 임시_df.기사제목 = 제목리스트 임시_df.기업 = 기업이름 # 임시 데이터프레임을 뉴스 데이터프레임에 아래로 결합 뉴스_df = pd.concat([뉴스_df, 임시_df]) # 앞에서 while 종료 조건이 같은 기사 2번저장인데 이럴 경우 중복으로 저장이 되야 종료되기때문에 # 중복기사 행 제거 뉴스_df.drop_duplicates(inplace=True) 뉴스_df.to_csv('../Step3_1_뉴스전처리/news/상폐기업뉴스.csv', index=None)
 

3. 직면하게 되는 문제들

수집 크롤러 개발에 있어 가장 큰 문제는 서버와 속도일 것입니다. 서버와 속도는 개인이 직접 관리하게 될 경우 아래의 문제가 발목을 잡을 수 있으니 주의하셔야 합니다.
  1. 소요시간 딜레이 무한 증식
  1. 한가지 IP를 사용한다면 트래픽이 많이 발생하여 차단이슈가 발생
  1. 채널이 업데이트 될 경우, 변경되는 HTML 구조로 인해 유지보수 공수 발생
여러가지 라이브러리를 사용하기도 하고,VPN을 써서 IP를 바꿔보기도 하셨을 것이라고 생각됩니다. 직접 관리하기 어려운 상황이거나, 단회적으로 빅데이터 구축이 필요할 경우, 시장에 이미 나와있는 다양한 솔루션을 이용해보는 것이 생산성 측면에서는 더욱 효율적일 수 있으니 도입을 고려해보시는것도 추천드립니다.
 

4. 주의해야 할 점

빅데이터를 구축하시려는 가장 큰 목적은 아무래도, 연구 용도가 가장 많은 비율을 차지합니다. 하지만 크롤링은 사람보다는 기계에게 1차적인 데이터의 선별에 대한 판단을 맡기는 기술이기 때문에, 단순한 기술적 문제가 아닌 도덕적, 윤리적 측면도 고려해야 합니다. 특히 개인 정보와 같은 민감한 데이터에 접근하는 것은 많은 주의를 요합니다. 따라서 모든 데이터 수집은 투명하고 공정하게 이루어질 수 있도록 최선을 다해야합니다. 이러한 과정은 누군가가 알아주지는 못하겠지만, 우리의 연구를 더욱 가치있게 만들어줄 수 있습니다. 만약에 크롤링을 하는데에 있어서 어려움을 느끼신다면 저희 블로그글을 참고하시거나 저희 쪽에 문의하셔도 좋습니다.