1 목차

목차(Table of Contents)는 문서를 구성하는 주요 구성항목 중 하나다. 금융감독원 다트(Dart)에서 4대 회계법인 사업보고서를 다운로드 받아 보고서에서 목차를 추출해낸다.

1.1 삼정 KPMG - pdf_toc()

먼저 “삼정회계법인” 감사보고서에서 목차를 추출한다. pdftools 팩키지에서 pdf_toc() 함수를 사용한다.

library(tidyverse)
library(pdftools)

kpmg_pdf <- "data/[삼정회계법인]회계법인사업보고서(2020.06.29).pdf"

kpmg_toc_list <- pdftools::pdf_toc(kpmg_pdf)

kpmg_toc <- kpmg_toc_list %>% 
  pluck("children")  %>% 
  map_chr("title") %>%
  str_trim() %>% 
  str_remove_all(" ")

kpmg_toc
[1] "사업보고서"                                
[2] "대표이사등의확인ㆍ서명"                    
[3] "I.회계법인의개황"                          
[4] "Ⅱ.주요사업의내용"                          
[5] "Ⅲ.감사보고서품질관리관련정보"              
[6] "Ⅳ.인력에관한사항"                          
[7] "Ⅴ.손해배상준비금및손해배상공동기금등의현황"
[8] "Ⅵ.최근3개사업연도의감리결과"               
[9] "Ⅶ.최근3개사업연도의소송현황"               

상기 스크립트를 함수로 제작하여 나머지 다른 회사(삼일, 안진, 한영)에 대한 사업보고서 목차도 추출한다. toc_extract() 함수를 제작하여 앞서 스크립트로 제작한 코드를 최대한 재활용한다.

toc_extract <- function(annual_report) {
  report_toc_list <- pdftools::pdf_toc(annual_report)

  report_toc <- report_toc_list %>% 
    pluck("children")  %>% 
    map_chr("title") %>%
    str_trim() %>% 
    str_remove_all(" ")
  
  return(report_toc)
}

toc_extract(kpmg_pdf)
[1] "사업보고서"                                
[2] "대표이사등의확인ㆍ서명"                    
[3] "I.회계법인의개황"                          
[4] "Ⅱ.주요사업의내용"                          
[5] "Ⅲ.감사보고서품질관리관련정보"              
[6] "Ⅳ.인력에관한사항"                          
[7] "Ⅴ.손해배상준비금및손해배상공동기금등의현황"
[8] "Ⅵ.최근3개사업연도의감리결과"               
[9] "Ⅶ.최근3개사업연도의소송현황"               

이제 나머지 회사에 대해서도 동일한 작업을 하여 사업보고서 목차의 일관성에 대해서 살펴보자.

accounting_firms <- fs::dir_ls(path = "data/", glob = "*(2020*.pdf")

toc_firms_list <- map(accounting_firms, toc_extract)

toc_firms_list %>% 
  enframe() %>% 
  # mutate(firm_name = str_extract_all(name, "[pdf|2020]") ) %>% 
  mutate(firm_name = str_extract(name, pattern = "(?<=\\[).*?(?=\\])")) %>% 
  select(firm_name, value) %>% 
  unnest_auto(value) %>% 
  count(value)
# A tibble: 10 x 2
   value                                          n
   <chr>                                      <int>
 1 I.회계법인의개황                               4
 2 Ⅱ.주요사업의내용                               4
 3 Ⅲ.감사보고서품질관리관련정보                   4
 4 Ⅳ.인력에관한사항                               4
 5 Ⅴ.손해배상준비금및손해배상공동기금등의현황     4
 6 Ⅵ.최근3개사업연도의감리결과                    4
 7 Ⅶ.최근3개사업연도의소송현황                    4
 8 대표이사등의확인ㆍ서명                         5
 9 사업보고서                                     4
10 정정신고(보고)                                 1

1.2 삼정 KPMG - pdf_toc()

pdf_info(filename) %>% pluck("pages") 방식으로 전체 PDF 파일에서 “목차”를 찾아야하지만, 목차는 문서의 앞부분에 위치하는 대체로 관계대로 첫장부터 10장까지 목차가 들어가는 페이지와 “사업보고서” 가 들어가는 장을 찾아 이를 뽑아낸다.

find_toc_page <- function(filename) {
  
  ## PDF file pages
  pdf_file_pages <- pdf_info(filename) %>% pluck("pages")

  ## PDF 페이지별 텍스트 저장 리스트
  pdf_text_pages <- list()

  ## PDF 페이지별 텍스트 저장: 앞쪽 10페이지만 검색
  for( i in 1:10) {
    pdf_pages <- pdftools::pdf_subset(filename, pages= i)
    pdf_text_pages[[i]] <- pdftools::pdf_text(pdf_pages)
  }

  ## PDF 페이지별 목차가 들어간 페이지 확인
  pdf_text_pages_df <- pdf_text_pages %>% 
    map(str_remove_all, pattern = " ") %>%  
    str_match(pattern = "목차|사업보고서") %>% 
    as_tibble()
  
  ## PDF 목차 페이지 추출
  toc_page_start_number <- which(pdf_text_pages_df$V1 == "목차")
  toc_page_end_number <- which(pdf_text_pages_df$V1 == "사업보고서") %>% as.integer()
  
  return(glue::glue("{toc_page_start_number} : {toc_page_end_number - 1}"))
}

find_toc_page_safely <- safely(find_toc_page, otherwise = NA_integer_)

find_toc_page_safely(kpmg_pdf)
$result
1 : 2

$error
NULL

이제 나머지 문서에 대해 목차가 시작되는 페이지와 목차가 끝나는 페이지를 추출한다.

toc_pages_list <- map(accounting_firms, find_toc_page_safely)

toc_pages_df <- toc_pages_list %>% 
  enframe() %>% 
  mutate(firm_name = str_extract(name, pattern = "(?<=\\[).*?(?=\\])")) %>% 
  select(firm_name, value) %>% 
  unnest_auto(value) %>% 
  unnest(result) %>% 
  separate(result, into = c("start", "end"), sep=":", convert = TRUE) %>% 
  group_by(firm_name, start) %>% 
  summarise(end = min(end)) ## 안진 정정신고 사항 반영

toc_pages_df
# A tibble: 4 x 3
# Groups:   firm_name [4]
  firm_name              start   end
  <chr>                  <dbl> <int>
1 삼일회계법인     1     2
2 삼정회계법인     1     2
3 안진회계법인     1     2
4 한영회계법인     1     2

이제 가장 큰 제목에 달린 목차만 추출하는 것이 아니라 목차에 포함된 모든 큰제목, 작은제목에 달린 모든 텍스트를 추출하여 이와 연결된 페이지수도 함께 추출한다.

kpmg_toc_text <- pdftools::pdf_subset(kpmg_pdf, pages= 1:2) %>% 
  pdf_text()

kpmg_toc_list <- kpmg_toc_text %>% 
  str_split(pattern = "\n")

listviewer::jsonedit(kpmg_toc_list)
kpmg_finstr_toc_df <- kpmg_toc_list %>% 
  enframe() %>% 
  unnest(value) %>% 
  mutate(pages = str_extract(value, pattern = "[0-9]+$")) %>% 
  mutate(title = str_remove_all(value, "[0-9]+$")) %>% 
  mutate(title = str_remove_all(title, "\\.")) %>% 
  # mutate(title = str_extract_all(value, pattern = "^.*? \\.")) %>% 
  select(title, pages) %>% 
  unnest(title) %>% 
  mutate(title = str_remove_all(title, " "))

kpmg_finstr_toc_df %>% 
  DT::datatable()

_output.pdf 파일 생성된 결과 삭제하여 다음 작업에도 문제가 없도록 조치를 취해둠.

rm data/*_output.pdf
 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com