My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


SSAFY 스타트 캠프 2주차 - (3)

아래 포스트는 SSAFY 스타트 캠프의 수업내용을 정리한 글입니다.

텔레그램 키 재발급하기

​ /revoke -> 봇 선택해서 토큰없애기

​ /token -> 다시 재발급하기

환경변수

code .bashrc

(.은 숨김파일을 만들 때 쓰이는것임)

​ -> 리눅스 커맨드를 통해서 환경변수를 설정할 것임.

export TELEGRAM_TOKEN=#텔레그램 초기설정으로부터 받은 토큰
export NAME="Dong+Chan"
alias d5="cd ~/chatbot"

​ (환경변수는 ALL UPPER CASE로 하는게 관례이다.)

​ -> source .bashrc : 환경설정을 다시 갱신해줌.

​ -> cat .bashrc : 안에 들어간 내용을 출력해줌.

​ -> 파일 있는지 확인하려면 ls -al | grep bashrc

alias는 별칭을 붙여줌 : d5라고하기만 하면 cd ~/chatbot가 실행이 되는것임.

​ (예를 들어 d5 & jupyter notebook 하면 자동으로 주피터나옴.)

git 환경변수 확인

​ cat .gitconfig

echo $TELEGRAM_TOKEN

​ echo는 쉘에서는 print와 다름없음.

​ 환경변수는 보통 $키를 통해서 확인함.

​ 파이썬을 통해서도 환경변수 확인 가능함

import os

print(os.getenv('NAME'))
print(os.getenv('TELEGRAM_TOKEN'))

그외 꿀팁들

requests.get(url).text() 말고

requests.get(url).json() 으로 한다면 자동으로 딕셔너리로 변환되어서 나옴!

vscode : ctrl + shift + p -> select default shell -> git bash 를 vscode에서 사용가능!

UC 버클리 교수님의 말 : 컴퓨터에서 제일 중요한 것은 추상화이다!

​ 이 세상을 어떻게 간단하게 컴퓨터의 언어로 이해할 수 있게끔 요약할 수 있는가???

강의링크 스크래치로 이것도 가능하다

​ 한국에는 ‘엔트리’ : 스크래치 비슷한 툴이 있음.

​ 앞으로 배울 쟝고라는 툴은 추상화가 엄청나게 구성되어있음!!

하나의 함수는 하나의 기능만 쓰도록 작성하라!

함수가 얽히고 얽히는 구조는 크게 좋지 않다

추상화를 잘 따르기 위해서는 모듈을 기능단위로 분류하는게 좋다.

해본 것 : 티몬 사이트에서 [오늘의 추천 상품] 중 가장 잘 팔리는 5개를 뽑아주는 코드

import os
import requests
from pprint import pprint as pp
import json
from bs4 import BeautifulSoup
import re
from selenium import webdriver
import time

# step1
def chat_url(token,method="sendMessage",chat_id=None,text=None):
    if method == "getUpdates":
        return f'https://api.telegram.org/bot{token}/{method}'
    else:
        return f'https://api.telegram.org/bot{token}/{method}?chat_id={my_id}&text={text}'

# step2
token = os.getenv('TELEGRAM_TOKEN')
method = "getUpdates"
url = chat_url(token,method)
res = requests.get(url)
res_dict = res.json()
my_id = res_dict['result'][0]['message']['chat']['id']
# webhook을 달게되면 getUpdates를 쓰지 못함
method = "sendMessage"

# step3
def crawl_hot_items():
    browser = webdriver.Chrome('chromedriver.exe')
    timon_url = 'http://www.ticketmonster.co.kr/home'
    browser.get(timon_url)
    page_source = browser.page_source
    soup = BeautifulSoup(page_source,'html.parser')
    titles = soup.select('.title_name')[:24]
    prices = soup.select('.price_area > .price > .price')[:24]
    buy_counts = soup.select('.buy_count')[:24]
    links = soup.select('.item > .anchor')[:24]
    whole_tags = [titles,prices,buy_counts,links]
    whole_items = [[titles[x].text,prices[x].text,buy_counts[x].text,links[x]['href']]for x in range(len(titles))]
    for x in range(len(whole_items)):
        whole_items[x][0] = re.sub('[/&?]',' ',str(whole_items[x][0]))
        # 실제 url에서 쓰이는 특수문자들을 제거해야 손상없이 전달될 수 있음.
        whole_items[x][1] = ''.join(re.findall('[0-9]',str(whole_items[x][1])))
        whole_items[x][2] = ''.join(re.findall('[0-9]',str(whole_items[x][2])))
    hot_items = sorted(whole_items, key=lambda whole_items : int(whole_items[2]),reverse=True)[:5]
    browser.close()
    return hot_items

hot_items = crawl_hot_items()
print(*hot_items, sep='\n')

def item_to_string(item):
    [title,price,count,link] = item
    return title + "  " + price + "원  " + count +"개 판매  " + link

for item in hot_items:
    url = chat_url(token,chat_id=my_id,text=item_to_string(item))
    requests.get(url)

네이버 얼굴인식(클로바)

​ post 앱 만들기 -> 유명인 사진 -> 클로버 api -> 유명인의 사람을 뽑아냄.

naver develops에서 api등록

api_id secret_key를 찾아와서 환경변수에 등록

유명인 사진을 다운로드하고 네이버 클로바 예제코드에 집어넣어서 예측값 뽑아내기

import os
import sys
import requests
client_id = os.getenv("NAVER_ID")
client_secret = os.getenv("NAVER_SECRET")
# url = "https://openapi.naver.com/v1/vision/face" // 얼굴감지

url = "https://openapi.naver.com/v1/vision/celebrity" #// 유명인 얼굴인식
files = {'image': open('iu.jpg', 'rb')}
headers = {'X-Naver-Client-Id': client_id, 'X-Naver-Client-Secret': client_secret }
response = requests.post(url,  files=files, headers=headers)
rescode = response.status_code

if(rescode==200):
    print (response.json()["faces"][0]["celebrity"]["value"])
else:
    print("Error Code:" + rescode)

플라스크를 통해서 여태껏 서버를 만들어줬음

​ 각각의 클라이언트들이 url을 통해서 요청을 주면 그에 따라서 다른 응답을 줌.

그런데 요청은 두가지 방식이 있음.

  1. get 방식 : 가져오는것.

    1. POST : 게시하다, 작성하다, 데이터를 보내다

    예를 들어 get방식으로 회원가입사이트를 만든다면

    할 때마다 id와 password가 url에 보여지므로 매우 좋지 않다.

    => 데이터를 보여주는게 아니라 서버에 게시하는것이 필요함.

플라스크를 이용해서 POST를 한번 실습해보자.

  1. 기본적으로 플라스크에서 app.route(‘/’)는 GET으로 인지한다

  2. app.route(‘/’, methods=[‘POST’]) 이런식으로 명시해야함.

  3. form 태그에 method를 POST로 바꿔주어야한다.

  4. 그러면 flask에서 post로 날라온 데이터는 어디서 받아서쓰는가?

    ​ : 새로운 route를 만들어야한다.(action에 지정된 사이트)

    ​ request.form.get()를 통해서 받을수있음.

    특이한점

    브라우저에 해당하는 url을 직접 치면 접근할 수 없다.

    1. 해당하는 url을 직접 치는 것은 GET 방식이다(브라우저로는 POST가 불가능)

    2. 하지만, FLASK app에서 POST만 허용했기 때문에 접근하는것은 불가능함.

      ex) https://uni.likelion.org/users/sign_up : form태그에서 어디로 보내주는지 유추가능

      ​ https://www.codecademy.com/ : 여기는 form태그가 철저해서 유추도 못함.

from flask import Flask,render_template,request
import requests

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/signup', methods=['POST'])
def signup():
    email = request.form.get('email')
    password = request.form.get('password')
    
    adminEmail = "qwer@qwer.com"
    adminPassword = "12341234"
    
    if email == adminEmail and password == adminPassword:
        msg = "관리자님 환영합니다"
    elif email == adminEmail:
        msg = "관리자님 비밀번호 틀리셨어요"
    else:
        msg = "관리자님 아니시잖아요"
    
    return render_template('signup.html',msg=msg)

app.run()

postman이라는 어플리케이션을 통해서 post에 접근가능(테스트하기 좋음)

쟝고같은 프레임워크가 csrf공격(?)같은 방법에 안정적이다.

​ -> 검증된 프레임워크를 쓰는 사람이 많아짐.

Interactive한 챗봇

내가 보낸 메시지를 상대방이 실시간으로 알 수 있어야함.

여태까지는 getUpdate를 사용함으로써 수동적으로 텔레그램에 요청해서 알림을 받았는데,

이제부터는 항상 돌아가는 서버(알림이)를 만들고, 자동으로 텔레그램의 알림을 받을 수 있음.

(텔레그램 서버가 webhook을 통해서 c9 서버에 무언가를 보내주면서 실시간으로 볼 수 있는것.)

  1. webhook 서버의 주소를 텔레그램 서버에 알려주어야함

    (webhook 세팅 : 우리가 텔레그램의 요청을 받을 수 있는 라우트 + 텔레그램에 요청할 수 있는 c9서버의 주소를 알려주는 라우트)

    • 텔레그램 서버의 method : setWebhook -> 인자 : 알려줄 곳을 말해줘.
    • 그런데 알려줄 주소를 getWebhookData같이 흔한거로 한다면 누군가가 탈취해서 이상한 응답을 보낼 수도 있음 -> 우리의 토큰을 라우트로 지정하는게 관례임.추천
    • 역시 c9서버에도 환경변수를 지정해줘야함.
    • 텔레그램 서버는 항상 잘 됬다는 신호(200 status)를 json과 함께 받아야함.
    1. 그러면 텔레그램 서버에서 준 json파일을 분석하여 메시지에 따라 각기다른 방법을 보내는 것이다.(POST방식으로 줌)
    • request : 요청을 보낸 사람에 대한 정보 -> json으로 파싱 : request.json()

    • webhook을 걸어놓고 봇의 상태가 바뀌면 “메세지를 받았다”라는 메세지를 주는거임.

    • 그러면 “알았어 잘 받았어” 라는 말과 동치인 200을 내뱉어야 다시 묻지않음.

      -> return render_template(‘telegram.html’), 200

from flask import Flask,render_template,request
import os
import requests
from pprint import pprint as pp

app = Flask(__name__)

token = os.getenv("TOKEN")
base_url = "https://api.hphk.io/telegram"
my_url = "https://webhook-start666.c9users.io"

@app.route('/{}'.format(token),methods=["POST"])
def telegram():
    doc = request.get_json()
    my_id = doc['message']['chat']['id']
    text = doc['message']['text']
    chat_url = "{}/bot{}/sendMessage?chat_id={}&text={}".format(base_url,token,my_id,reply)
    requests.get(chat_url)
    return '',200

@app.route('/setwebhook')
def setwebhook():
    url = "{}/bot{}/setWebhook?url={}/{}".format(base_url,token,my_url,token)
    print(url)
    res = requests.get(url)
    return ('{}'.format(res), 200)

과제 : 이미지가 들어왔을 때 클로바 api를 이용하여 얼굴인식결과를 제공해주기

주의 - 만약 텍스트가 들어오지 않았다면 text변수가 잘 출력이 안됨

​ -> get method를 사용한다면 에러를 잡지 않으므로

​ 자료타입이 동적으로 변하는 경우에 대처할 수 있음.

  1. 내가 텔레그램에 사진을 보냄 -> 텔레그램이 사진을 클라우드에 저장함 -> 일단 텔레그램이 반환한 파일 id를 우리가 받아서 다시 get으로 클라우드에 요청하면 파일이 있는 정확한 주소를 반환함.
  2. 주소를 받고 네이버에 요청을 보내는데 POST로 요청을 하며, 헤더를 반드시 보내야함.
from flask import Flask,render_template,request
import os
import requests
from pprint import pprint as pp
import random

app = Flask(__name__)

token = os.getenv("TOKEN")
naver_id = os.getenv("NAVER_ID")
naver_secret = os.getenv("NAVER_SECRET")

base_url = "https://api.hphk.io/telegram"
my_url = "https://webhook-start666.c9users.io"

@app.route('/{}'.format(token),methods=["POST"])
def telegram():
    doc = request.get_json()
    pp(doc)
    my_id = doc['message']['chat']['id']
    text = doc['message'].get('text')
    img = False
    
    if doc['message'].get('photo') is not None:
        img = True
    
    if img:
        file_id = doc.get('message').get('photo')[-1].get('file_id')
        file = requests.get("{}/bot{}/getFile?file_id={}".format(base_url, token, file_id))
        # getfile 메서드를 통해 파일의 아이디를 얻는다.
        file_url = "{}/file/bot{}/{}".format(base_url, token, file.json().get('result').get('file_path'))
        
        
        # 네이버로 요청
        res = requests.get(file_url, stream=True)
        clova_res = requests.post('https://openapi.naver.com/v1/vision/celebrity',
            headers={
                'X-Naver-Client-Id':naver_id,
                'X-Naver-Client-Secret':naver_secret
            },
            files={
                'image':res.raw.read()
            })
        if clova_res.json().get('info').get('faceCount'):
            print(clova_res.json().get('faces'))
            text = "{}".format(clova_res.json().get('faces')[0].get('celebrity').get('value'))
        else:
            text = "인식된 사람이 없습니다."
    else:
    	# text 처리
    	text = doc['message']['text']

    chat_url = "{}/bot{}/sendMessage?chat_id={}&text={}".format(base_url,token,my_id,text)
    requests.get(chat_url)
    return '',200

IBM 왓슨

​ 자연어처리를 하는 챗봇 : 사람이 가진 intent를 가져올 수 있음.

​ (여러가지 단어군 -> 하나의 intent -> 각 intent에 대해서 이런 답안을 주겠다.

​ 생각해보면 로직자체는 룰베이스와 크게 다르지는 않다고함.)

comments powered by Disqus