카테고리 없음

[학식왕 김인하] 인하대학교 학식 메뉴 알리미 제작기(2/2)

인생 걸고 삽질 2023. 11. 5. 19:57

 

  이전 글: https://eemune.tistory.com/54

 

[학식왕 김인하] 인하대학교 학식 메뉴 알리미 제작기(feat. AWS lambda, PDF 파일 파싱)

개발 배경 인하대학교에는 학생식당, 교직원식당, 그리고 기숙사식당이 있다. 각 식당은 매일 서로 다른 메뉴를 제공하는데, 어떤 식당에서 학식을 먹을지 결정하려면 학생식당/교직원식당/기

eemune.tistory.com

 

 

2. 데이터 파싱

🔨삽질4🔨 PDF 파일에서 table data 파싱하기(PDF to dataframe)

 고작 100줄정도 되는 코드를 이틀 밤낮으로 붙잡고 있었다. 버티다 버티다 내일 수업을 위해 잠자리에 누웠는데도 해결이 안 되니 도통 잠이 안 왔다. 너무 답답했다. 내 눈에는 이렇게 형태가 잘 보이는데 왜 이걸 파싱만 하면 형태를 못 잡는 거지? 하지만 답답하다고 해서 '기숙사 식당 메뉴는 지원되지 않습니다~ 죄송 ㅎㅋㅠ'라고 퉁치고 넘어가는 건 너무 무책임했다. 물론 아무도 나한테 하라고 한 사람은 없지만...

 다음은 왜 나는 잘 보이는 데 얘는 형태를 못 잡지? 에서 시작한 분석이다.

 




1. 내가 식단표를 잘 인식할 수 있는 이유는 이 data가 table이고, 각 column을 각각의 요일로 인식하면 된다는 사실을 알고 있기 때문이다.

2. 내가 이 파일을 table로 인식하는 이유는 검은색 line이 그려져 있고, 이 형태는 table이라는 사실을 경험적으로 알고 있기 때문이다.

3. column 중간중간에 '요청', 'new' 등의 text가 있음에도 이를 하나의 column이라고 생각하지 않는 이유는 각 텍스트가 다른 형태(하트 등)로 이루어져 있고, 검은 선 위에 있기 때문이다. column 밖에 있으므로 해당 column이라고 인식하지 않는 것이다.




 위의 사실을 분석하고 나니 그제서야 실마리가 잡혔다.


1. 컴퓨터가 이 data가 table인지 아닌지 알 수 없었을 것이다. table을 parsing하는 module을 사용하여 미리 이 data가 table이라는 언질을 주면 될 것이다.

2. 내 눈에는 검은색 선이 보이니까 이 데이터를 table로 인식하지만, 컴퓨터 입장에서는 text를 위주로 layout을 분석하기 때문에 중간중간 있는 '요청'등의 text들도 그냥 하나의 row를 차지하는 column이라고 생각했을 것이다. 선을 중심으로 column을 나누면 된다고 말해 주면 될 것이다.





 분명히 선을 중심으로 table을 parsing하는 기능이 있을 것이다. 그런 생각으로 tabula module의 공식 문서를 막 뒤졌다. 공식 문서: https://tabula-py.readthedocs.io/en/latest/tabula.html

 

tabula — tabula-py documentation

Set pandas options. Note With multiple_tables=True (default), pandas_options is passed to pandas.DataFrame, otherwise it is passed to pandas.read_csv. Those two functions are different for accept options like dtype.

tabula-py.readthedocs.io

 

 

 

역시... 없을 리가 없었다. tabula module에서 지원하는 lattice 모드는 line을 기반으로 table을 나누는 기능이라고 한다.

 

어이 없을 정도로 간단한 몇 줄의 코드로 이틀 간의 고민이 해결됐다. column들 사이에 불필요한 noise가 있는 경우 해당 pdf로부터 table을 parsing 하는 코드는 다음과 같다. 

 

def pdf_to_df(pdf_path):
            dfs = tabula.read_pdf(pdf_path,
                                  pages="all", encoding='utf-8', lattice=True)
            dataframes = [pd.DataFrame(table) for table in dfs]
            merged_df = pd.concat(dataframes, axis=1)
            return merged_df

 

 

 

 

 

 

3. 데이터 전송

 매주 server에서 금주의 학식 정보 데이터를 파싱하고 저장하는 기능은 완성되었다. 이제 매일 해당 데이터를 가져와서 학식 메뉴를 전송하는 코드만 작성하는 일만 남았다.

 

 slack bot은 '해커네컷'을 개발하면서 충분히 씹고 뜯고 맛보고 즐겼으니 구현에 크게 어려운 부분은 없었다. AWS의 EC2 서비스를 이용해 서버를 구축하였고 lambda service를 이용하여 매주 데이터를 update하고 매일 data를 받아 와서 학식 메뉴를 전송하도록 했다.

 

 lambda를 이용하여 매일 아침마다 slack message를 전송하는 데에 있어서 주의할 점은 두 가지가 있다.

 

🔨삽질1🔨 Lambda에서 외부 라이브러리 사용하기

 

 lambda 함수를 굴릴 때 'No module named...' 오류가 발생하는 경우가 있는데, 이는 해당 라이브러리를 lambda측에서 찾을 수 없기 때문에 발생하는 오류이다. lambda에서는 python의 기본 제공 라이브러리들은 제공하지만 flask, pandas 등의 다른 라이브러리는 제공하지 않는다고 한다.

 

 이를 해결하기 위해서는 필요한 modules들을 직접 zip file로 올려 주면 된다. 방법은 다음과 같다.

 

1. module -> zip파일로 만들기

 

!!주의: 이 때 모든 modules들은 'python'이라는 이름의 file 안에 들어 있어야 한다!!

 

 

2. Layer 생성하기

 

'Create layer'를 클릭하여 zip파일로 만들어 둔 라이브러리를 올린다. 이 때 주의할 점은 runtime이 lambda 함수의 runtime과 일치해야 한다는 것이다.

 

 

3. layer 등록하기

 

 'Add a layer'를 클릭하여 아까 만들어 둔 layer를 등록한다.

 

 

 

🔨삽질2🔨 Event bridge를 이용하여 scheduling하기

 lambda function의 trigger로 event bridge를 설정할 수 있다. 이렇게 하면 매일 지정된 시간에 해당 함수를 자동으로 해당 함수를 실행하는 등 함수를 스케쥴링할 수 있다.

 

하지만 이 때 주의해야 할 점이 있다. 바로 시차이다.

 

 

AWS의 시간대는 기본적으로 UTC timezone을 따르기 때문에 우리가 그리니치 천문대에 살고 있지 않다면 원하는 시간대를 UTC로 맞춰 줘야 한다.

해당 문제는 EC2에서도 똑같이 발생하는데, 이 때는 server의 local 표준 시간대를 변경하면 된다. 코드는 다음과 같다.

 

$ date
Sat Nov  4 06:16:46 UTC 2023
$ sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
$ date
Sat Nov  4 15:17:48 KST 2023

 

 

 

이렇게 하면 매일 정해진 시간에 학식 메뉴가 전송되는 것을 확인할 수 있다.

 

 

 

 

 

 후기 및 향후 계획

아주 가벼운 프로젝트일 줄 알았는데, PDF를 크롤링하는 데에서 상당히 애를 많이 먹게 됐다. API는 완성했으니 해당 data를 이용해 slack 기반의 학식 알리미를 운영하는 데에서 더 나아가 인하대학교 학식 어플을 만드는 등의 방향을 고려 중에 있다. 다들 먹고 살자고 하는 일인데... 녹록지 않은 세상이지만 맛있는 밥 먹고 밥심으로 살아 보자!