먹고 기도하고 코딩하라

Python Django 카카오톡 학식봇 만들기(3) - 텍스트 파일 파싱해서 메시지로 보내기 본문

개발일지

Python Django 카카오톡 학식봇 만들기(3) - 텍스트 파일 파싱해서 메시지로 보내기

사과먹는사람 2020. 3. 5. 17:44
728x90
728x90

이전 글 보기

 

카카오톡 학식봇 만들기(2) - Amazon EC2에 firefox 웹드라이버 설치, 동적 웹페이지 크롤링하기

이전 글 보기 카카오톡 학식봇 만들기(1) - 동적 웹페이지 크롤링 방법이 정적 웹페이지와 다른 이유 이전 시리즈 : 카카오 i 오픈빌더 챗봇 만들기 파이썬 장고로 카카오 i 오픈빌더 챗봇 만들기

dev-dain.tistory.com

 

 

 

안녕하세요? 이번 포스팅에서는 저번 포스팅에서 다뤘던 크롤링 코드 파일의 완성판을 한 번 같이 보고 실행 결과인 텍스트 파일을 views 에서 잘 가공하는 방법을 다뤄 보겠습니다.

먼저 가상환경을 활성화하시고 위치가 /home/ubuntu 인 것을 확인하십시오. 다음을 입력합니다.

 

$ rm test.py
$ cd [base_name]/[app_name]
$ vi scrapy_page.py

# scrapy_page.py
# Import
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from time import sleep
import os
import sys

# Function
def get_options():
    options = Options()
    options.headless = True
    return options

def get_html(driver, url):
    driver.get(url)
    driver.implicitly_wait(5)
    sleep(3) #sleep이 있어야 dateTime class, dietNoteContent id 콘텐츠를 볼 수 있음

    html = driver.page_source
    return html

def find_table(html):
    soup = BeautifulSoup(html, 'lxml')
    target_table = soup.find(id='schedule-table')
    return target_table

def get_info(target_table):
    info_str = target_table.find_all("th")
    for i in range(len(info_str)):
        info_str[i] = info_str[i].get_text('\n')+'\r\r'
    return info_str

def get_meal(target_table):
    meal_str = target_table.find_all("td")
    for i in range(len(meal_str)):
        meal_str[i] = meal_str[i].get_text('\n')+'\r\r'
    return meal_str

def go_crawl():
    if os.path.exists('week_meal.txt'):
        os.remove('week_meal.txt')
    if os.path.exists('week_info.txt'):
        os.remove('week_info.txt')

    url = 'http://www.duksung.ac.kr/diet/schedule.do?menuId=1151'
    options = get_options()
    driver = webdriver.Firefox(options=options)

    try:
        html = get_html(driver, url)
    except:
        sys.exit()

    target_table = find_table(html)
    info_str = get_info(target_table)
    meal_str = get_meal(target_table)

    meal_fp = open('week_meal.txt', 'w', encoding='utf-8')
    meal_fp.writelines(meal_str)
    meal_fp.close()
    info_fp = open('week_info.txt', 'w', encoding='utf-8')
    info_fp.writelines(info_str)
    info_fp.close()

    driver.close()


# Main
if __name__ == '__main__':
    go_crawl()

 

go_crawl()을 제외한 함수들은 저번 포스팅에서 순서대로 설명했으므로 건너뛰겠습니다.

일단 week_meal.txt 와 week_info.txt 가 있는지 확인하고 있으면 지웁니다. 물론 새로 덮어쓸 수 있긴 한데 저는 매번 깔끔히 지우고 새로 시작하고 싶어서 이렇게 했습니다.

headless option 을 받고 driver 에 headless option 의 웹 드라이버를 할당해줍니다. 그리고 html 에 드라이버와 url 로 웹 소스 코드를 받아주는데요. 만약 여기서 실패하면 그냥 실행을 종료하도록 했습니다.

그런 다음 우리가 찾는 테이블을 찾고 날짜와 요일, 각 날짜의 식단을 따로 저장하도록 했습니다. 파일 포인터를 열어 날짜 정보와 식단 정보를 따로 텍스트 파일로 저장하고 닫아주고 드라이버까지 close 해 줬습니다.

if __name__ == '__main__' 에 대한 자세한 설명이 여기 있습니다. 파이썬 인터프리터가 최초로 실행한 스크립트 파일의 이름은 '__main__'인데요, 만약 이 파일이 import 된 모듈이 아니라 최초 실행된 스크립트 파일이면 if 다음의 문장을 실행하게 해줍니다. 사실 다른 모듈을 import 한 건 아니라서 그냥 go_crawl() 해 줘도 됩니다.

 

웹 크롤링을 하고 필요한 정보를 뽑아내 텍스트 파일까지 만들었으면 이제 views 에서 적절히 사용할 수 있게 해야겠습니다.

저는 여기서 고민을 좀 했는데요. 굳이 텍스트 파일을 만들지 않고 go_crawl() 에서 info_str 과 meal_str 을 return 하도록 해서 직접 views 에서 scrapy_page 를 import 해서 쓰면 되지 않을까 하고요. 하지만 그렇게 해보니 카카오톡에서는 응답이 2분 넘도록 오지 않았고 CLI에서는 실행은 됐지만 시간이 오래 걸렸습니다. 그래서 그냥 scrapy_page 를 따로 실행하도록 했습니다. 게다가 views 에서 매번 무겁게 웹크롤링을 하는 작업을 하면 낭비라서 따로 빼 놨습니다.

 

자 그럼 views 파일을 열어 수정해 봅시다. 다음과 같이 수정합니다.

 

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from time import localtime
from datetime import datetime
import json


def keyboard(request):
    return JsonResponse({
        'type': 'text'
    })

@csrf_exempt
def message(request):
    answer = ((request.body).decode('utf-8'))
    return_json_str = json.loads(answer)
    return_str = return_json_str['userRequest']['utterance']

    wday_arr = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
    go_main_button = '처음으로'
    today_wday = localtime().tm_wday
   
    meal_path = '/home/ubuntu/haksik_project/haksik/week_meal.txt'
    # open에는 full 경로 설정 필수
    meal_fp = open(meal_path, 'r', encoding='utf-8')
    
    temp_str = ''
    for line in meal_fp.readlines():
        temp_str += line
    temp_str = temp_str.strip('\n')
    meal_list = []
    meal_list = temp_str.split('\n\n')
    meal_fp.close()

    info_path = '/home/ubuntu/haksik_project/haksik/week_info.txt'
    info_fp = open(info_path, 'r', encoding='utf-8')

    temp_str = ''
    for line in info_fp.readlines():
        temp_str += line
    temp_str = temp_str.strip('\n')
    info_list = []
    info_list = temp_str.split('\n\n')
    info_list = [tmp for tmp in info_list if tmp]
    info_fp.close()

    for i in range(11):
        if meal_list[i] == '-':
            meal_list[i] = '\n학식이 없는 날이거나 홈페이지에 등록되지 않았습니다.'

    add_text = ''
    for j in range(10):
        if j < 5:
            add_text += '*'
            add_text += info_list[6]
            add_text += '\n'
        else:
            add_text += '\n<택1>\n\n*'
            add_text += info_list[7]
            add_text += '\n'
        add_text += meal_list[j]
        meal_list[j] = add_text
        add_text = ''

    s_day = {'mon': meal_list[0], 'tue': meal_list[1], 'wed': meal_list[2],
            'thu': meal_list[3], 'fri': meal_list[4], 'every': meal_list[10]}
    e_day = {'mon': meal_list[5], 'tue': meal_list[6], 'wed': meal_list[7],
            'thu': meal_list[8], 'fri': meal_list[9]}
    info = {'mon': info_list[1], 'tue': info_list[2], 'wed': info_list[3],
            'thu': info_list[4], 'fri': info_list[5]}

    if return_str == '테스트':
        return JsonResponse({
            'version': "2.0",
            'template': {
                'outputs': [{
                    'simpleText': {
                        'text': info_list[1]+"\n테스트 성공입니다.\n"+meal_list[0]
                    }
                }],
                'quickReplies': [{
                    'label': go_main_button,
                    'action': 'message',
                    'messageText': go_main_button
                }]
            }
        })

 

코드가 좀 많이 길어졌습니다. 하나씩 살펴보겠습니다.

일단 datetime 과 localtime 을 추가로 import 해줬습니다. 사용자의 요청이 들어오는대로 views 가 실행되기 때문에 바로바로 날짜를 잡아내기 위해서입니다.

일단 저는 요일을 나타내는 리스트 wday_arr 을 선언해 날짜를 월~일 순서로 입력해 뒀습니다. 그런 다음 '초기화면'이라는 string 변수를 만들었는데 후에 분기문에서 쓸 것입니다.

week_meal 파일부터 열어 가공해줍니다. 파일 포인터로 open 할 때는 반드시 full path 를 다 적어줘야 하는 것을 잊지 마세요. temp_list 라는 빈 리스트를 만들어 readlines() 하면서 한 줄씩 읽어와 temp_str 에 한꺼번에 다 붙여 버립니다. 그런 다음 '\n'을 토큰으로 strip 을 해주는데요. 원소 앞뒤로 붙은 불필요한 개행을 없앱니다. 그리고 빈 리스트 meal_list 를 만들어 scrapy_page 에서 넣어준 캐리지 리턴 두 개를 기준으로 temp_str 을 split 해서 삽입해줍니다. 그러면 meal_list 에는 총 11개의 원소가 들어가게 됩니다. (월~금 학식, 월~금 교직원식, 비고란의 상시메뉴)

week_info 파일도 마찬가지입니다. 이 때는 리스트 컴프리헨션도 써 봤는데요. 저렇게 쓰면 tmp 가 true, 즉 화이트 스페이스가 아닌 것들만 포함되기 때문에 눈에 보이지 않는 공백들이 리스트에 들어가지 않는다는 장점이 있습니다.

meal_list 에는 학식이 없는 날인 '-'인 원소를 찾아 학식이 없다는 메시지로 바꿔줍니다.

그런 다음 info_list 로 루프를 도는데요. 조금 의아한 구성일 텐데 설명해 드리겠습니다. 일단 loop 를 도는 10은 월~금 학식과 월~금 교직원식 각각 5개 총 10개로 도는 겁니다. 그래서 0~4번째까지는 학식이니 info_list[6]인 '* 학생식당'을 덧붙이는 것이고요. 5~9번째까지는 저희 학교가 학식이 두 갠데 그 중 하나를 선택해서 먹을 수 있어서 <택1>이라고 써준 다음 개행 주고 info_list[7]인 '* 교직원식당'을 덧붙이는 것입니다. 약간 난해한가요? ^^;

그 다음 s_day 부터 info 까지는 학식(상시메뉴 포함)과 교직원식, 날짜와 요일을 가리키는 딕셔너리들입니다. wday_arr 의 리스트 요소들과 같은 이름이죠? 분기문에서 잘 써먹을 겁니다.

 

일단 간단하게 테스트를 해 보겠습니다. 저는 테스트 문장만 일단 바꿔봤습니다. 날짜와 요일이 나오고 학식을 볼 수 있게요. 여기까지 저장해주고 카카오 i 오픈빌더 홈페이지로 가서 다시 배포를 해준 다음 봇과 대화해봅시다. 

 

 

신통하게 잘 나오는 모습입니다 ^^

다음 포스팅에는 views 파일을 조금 더 정교하게 고쳐보고 카카오 i 오픈빌더에서 마저 시나리오를 만들고 블록을 만들어서 스킬을 적용해보고 마무리하도록 하겠습니다.

읽어주셔서 감사합니다 ^^

 

 

다음 글 보기

 

카카오톡 학식봇 만들기(4) - views 완성, 시나리오와 블록에 스킬 적용하기

이번 포스팅에서는 views 파일 완성본으로 어떻게 i 오픈빌더 시나리오와 블록에 적용해야 카카오톡 채널에서 자동응답이 가능한지 살펴보겠습니다. 우선 가상환경을 활성화하시고 app_name 디렉��

dev-dain.tistory.com

 

 

Reference

파이썬 코딩 도장: 45.2 모듈과 시작점 알아보기

 

 

728x90
반응형
Comments