저번시간 까지는 웹 크롤링을 활용하여 다양한 정보들을 추출했는데,

이번시간에는 공공데이터API를 활용하여 실시간 버스 도착정보를 얻어보겠습니다.

저는 제가 주로 이용하는 한 버스 정류소의 실시간 버스 도착 정보를 얻기 위해 사용했습니다. 

마을 버스 한 대만 통행하는 일부 정류소는 버스 도착 정보가 잘 조회되지 않는 것 같아서 최소 4대가 통행하는 정류소를 선정했습니다.

 

*누구나 겪을 수 있는 시행착오에 대한 부분도 포함되어 있기 때문에 한번 쭉 읽고나서 따라하실 것을 권장합니다.

 

공공데이터 포털에 접속하여 '서울특별시_버스도착정보조회 서비스'를 신청합니다.

https://www.data.go.kr/

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

 

서비스 신청 후 API키가 발급되는데, 서비스를 바로 이용할 수 있는 것은 아니고 어느정도 시간이 소요된 후 이용할 수 있습니다. 

서비스 종류에 따라 짧게는 1~2시간에서 길게는 하루 넘게 걸리는 서비스도 있다고합니다.

 

서비스가 아직 서버에서 승인되지 않아서 발생하는 오류와 잘못된 데이터를 기입해서 발생되는 오류는 다르기 때문에 이를 헷갈리시면 안됩니다.

간혹, 서비스는 정상적으로이용할 수 있는데 본인이 잘못된 데이터를 입력하여 발생한 오류를 API키의 발급이 느려져서 발생하는 오류라고 오해하시는 분들이 계셨습니다.

 

제가 포스팅 한 글을 참고하시면 좋을 것 같습니다.

https://jow1025.tistory.com/318 

 

공공데이터 api 신청 후 서비스 호출 에러

실시간 도착 버스 정보 조회 API를 신청하고나서 짧게는 1시간 길게는 2일 정도 사용허가를 해준다 그랬는데 신청하고 20분후에 아래 처럼 승인이 나 있었습니다. 되게 빨리 됐다 싶어서 바로 사

jow1025.tistory.com

 

이제 발급받은 키를 활용하여 openAPI를 활용해보겠습니다.

 

 

2,4번은 구현할 서비스에서 딱히 참고할 만 한 게 없고 3번 서비스가 우리가 구현할 목적에 적합하므로 이를 이용합니다.

테스트를 위해 아래 샘플데이터에 자기가 조회할 버스정류장의 ID를 적어주면됩니다. stID는 정류소ID를 의미합니다.

 

정류소 ID를 알아내기위해  버스정보 사이트에서 개발자 도구로 어떤 정보를 얻을 수 있는지 확인해봅니다.

서울버스정보 조회 사이트에 접속하고 조회할 정류소 이름을 검색합니다.

https://bus.go.kr/searchResult6.jsp

 

버스도착정보

 

bus.go.kr

조회하면 오른쪽 지도에 아래처럼 버스 아이콘이 뜨는데,

개발자도구(F12)를 킨 다음 이 아이콘을 클릭하면 개발자도구 상단 Network탭에 아래 3가지 .jsp 목록이 나타납니다.

getStationByUid.jsp를 클릭해보면, 

아래와 같이 특정 버스(6630버스)의 정보를 알 수 있는데, (정류소 고유번호, 노선 이름, 노선ID, 등)  

우리가 공공데이터 API를 사용하여 얻고자 하는 데이터는 아래 목록 중 rtid(노선번호,=버스이름),arrmsg1,arrmsg2(첫번째, 두번째 버스 도착 예정 시간)입니다. 

 

아까 위에서 API활용 3번 째 서비스를 이용하려면 정류소 ID가 필요하다고 했는데 그것까진 출력되지 않는 것 같습니다.

 

 

++++++++++

다른 사이트를 참고해볼까요?

++++++++++++

 

아래 사이트에서 조회하니깐 버스정류장 id를 포함한 더 자세한 정보들까지 출력되는 것 같으니 위 사이트보다  아래 사이트를 이용하시는 것을 강추합니다.

https://topis.seoul.go.kr/map/openBusMap.do

 

버스정보 조회 | 서울시 교통정보 시스템 - TOPIS

 

topis.seoul.go.kr

 

버스 id까지 다 나오는데, 이를 이용하면됩니다. 

------------------------------------------------------------------------------------------------

두번째 버스정보사이트에서는 정류소 ID까지 출력되므로 정류소 id정보를 얻기위한 아래 과정은 생략해도됩니다.

저는 첫번째 버스정보사이트에서 정류소 ID를 못찾았다는 것을 가정하고 진행하겠습니다.

------------------------------------------------------------------------------------------------

 

그럼 어떻게 정류소 ID를 얻을까?

 

다시, 아까 위에서 본 서비스 목록 중 1번을 펼쳐보면 노선번호(busRouteID)를 적어서 기능을 테스트해 볼 수 있음을 확인할 수 있습니다. 

위에서 개발자 도구로 살펴본 정류소 정보를 보면 6630번 버스의 Id가 100100307임을 알 수 있었는데 100100307을 입력하고 미리보기 버튼을 클릭합니다.

그러면 아래처럼  되게 많은 데이터들이 출력됩니다.

서비스 설명에 나와있듯 "경유노선 정류소 도착예정 정보" 가 출력됩니다. 설명이 약간 헷갈릴 수 있는데,

 

방금 입력한 id값 100100307(6630번버스)가 경유하는 모든 버스 정류소와 그 정류소들에 도착할 6630번 버스의 정보가 출력되는 것입니다.

 

마이페이지에서  openAPI상세 페이지를 클릭하여 변수들을 살펴보면,

 

우리가 알아내야할 정류소ID가 stID라는 변수명으로 매칭되어있고, 아까 개발자 도구로 살펴본 버스정보에 나온 변수와똑같거나 비슷한 이름의 변수들이 보입니다. 

 

이제 위의 XML파일에서 ctrl+f로 정류소이름을 검색한 뒤 해당 정류소의 stid를 찾아냅니다.

 이 stid를 3번 서비스에 입력하고 미리보기 버튼을 누르면 

보여지는 xml데이터가 바로 버스 정류장에 도착하는 버스 정보가 되는 것입니다.

 

순서를 정리하자면,

1. 버스정보사이트에서 개발자 도구로 본인이 검색할 정류소를 경유하는 버스의 id 알아내기

2. open API 1번 서비스에 1번에서 알아낸 버스 id를 입력 후 경유하는 모든 정류소 목록 중 본인이 검색하고싶은 버스 정류소 ID를 알아내기

 

* 2-1) 아래 사이트에서 개발자도구로  2번 내용을 쉽게 확인가능

https://topis.seoul.go.kr/map/openBusMap.do

 

버스정보 조회 | 서울시 교통정보 시스템 - TOPIS

 

topis.seoul.go.kr

 

3. 이 id를 이용, open API 3번에 입력하여 정류소를 경유하는 모든 버스의 도착 예정시간 얻기

 

이 순서대로 진행하여 3번 서비스 xml파일에서 해당 정류소를 경유하는 모든 버스 리스트를 조회할 수 있고 도착 정보를 확인할 수 있습니다.

열린 xml파일에서 각 버스의 arrmsg1 변수(첫번 째 도착예정시간)가 잘 출력되시나요?

 

저는 일부 버스 상태가  "출발대기"밖에 안뜨고 가끔 도착예정 정보가 뜨는데 서비스가 안정적이진 않은 것 같습니다.

다른분들은 도착예정시간이 잘 뜨는지 궁금합니다.

 

3번 서비스 설명에 적힌 "저상버스"의 의미가 정말 사전적인 의미의 "低床버스, 영어: low-floor bus)는 바닥이 낮고 출입구에 계단이 없는 버스"(출처: 위키백과) 여서 일반 버스의 경우 조회가 안되는건지, 아니면 서비스 자체가 불안정해서 그런건지는 잘 모르겠습니다. 

 

이제 이 내용을 파이썬 코드에서 필요한 부분(버스이름, 도착예정시간)만 추출할 수 있도록 코딩해야합니다.

일단, 방금 확인한 xml파일의 주소이름을 보면, 

http://ws.bus.go.kr/api/rest/arrive/getLowArrInfoByStId?serviceKey=api키&stId=정류장id 형태로 되어있습니다. 

대충 api키와 정류장id만 인자로 넣어주면 서비스 받을 수 있다는 것을 예상할 수 있는데, 실제로 확인해봅니다.

서비스 페이지에서 활용가이드 docs를 열어봅니다.

 

초반부분을 보시면 서비스 유형은 REST, 데이터 포맷은 XML, 교환 유형은 Request-Response라고 나옵니다.

 

 

페이지를 내리다보면 우리가 사용할 기능의 사용 방법이 잘 나와있습니다.

api키와 정류소id만 인자로 넣어주면 서비스를 이용할 수 있음을 확인할 수 있습니다. 좀 더 내려보면 결과 예시 창이 나오는데, 우리가 아까 open한 xml파일처럼 출력된다고 설명되어있는 것입니다.

 

 

이제 코드를 작성해보겠습니다.

기본적인 챗봇 부분은 아래와 같이 설정했습니다.

1
2
3
4
elif(user_text=="버스"):
       bus_info=bus_crawling()
        bot.send_message(chat_id=id,text=bus_info)
        bot.sendMessage(chat_id=id,text=info_message)
cs

 

이제 구현한 함수를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def bus_crawling():
    serviceKey='api키'
    
    # 정류소 id
    #stationId="115000302"
    stationId="115000116"
    #버스 ID
    
    url="http://ws.bus.go.kr/api/rest/arrive/getLowArrInfoByStId?serviceKey={}&stId={}".format(serviceKey,stationId)
    #get으로 요청함
    response=requests.get(url).content
    #xml파일을 dict로 파싱하여 사용
    dict=xmltodict.parse(response)
 
    #원하는 데이터가 ServiceResult 내부 msgBody 내부 itemList내부에 있음 
    #다시 dict로 받은 값을 Json로 변환 
    jsonString=json.dumps(dict['ServiceResult']['msgBody']['itemList'],ensure_ascii=False)
    #json을 형태로 받은 데이터를 Python의 객체로 변환 (dict)
    jsonObj=json.loads(jsonString)
 
    msg=''
    for i in range(len(jsonObj)):
        msg+='{}\n 첫번째: {}\n 두번째: {}\n'.format(jsonObj[i]['rtNm'],jsonObj[i]['arrmsg1'],jsonObj[i]['arrmsg2'])
    return msg
 
cs

 

2행~9행은 사이트 이름 설정이고 11행에서 get형식으로 url을 받아옵니다.

13행은 xml을 dict형태로 파싱 해주는 코드인데

dict=xmltodict.parse(response)

 

이를 사용하기 위해 아래 명령어로 패키지를 설치해주고,

pip install xmltodict

 

맨 상단에 import json,xmltodict를 추가해줍니다.

아까 오픈한 xml파일을 보면, 구성이 아래와 같이 되어있었습니다.

ServiceResult 내부 msgBody내부 itemList 내부에 데이터가 쌓여있는데, 이 구조를 그대로 이용합니다.

17행 코드를 통해 dict를 json으로 덤프(변환)합니다.

jsonString=json.dumps(dict['ServiceResult']['msgBody']['itemList'],ensure_ascii=False)

 

19행에서 이 json형식을 다시 파이썬 객체(dict)로 로드함으로써 우리는 이제 버스이름, 도착예정시간을 쉽게 얻어올 수 있습니다.

jsonObj=json.loads(jsonString)

 

결과적으로 xml로 되어있는 url페이지를 dict로 파싱하겠다고 선언한 후 우리가 추출할 부분만 다시 json으로 변환한 뒤 이를 다시 dict으로 변환함으로써 배열인덱스 형태로 데이터를 얻어오는 것입니다.

 

 

실행결과는 앞서 살펴본 xml파일의 rtNM과 각 버스의 arrmsg1, arrmsg2가 그대로 똑같이 잘 전달됨을 확인할 수 있습니다. N65는 실제 운행이 종료된 버스가 맞는데, 652,6657버스는 xml파일에서도 그렇고 출발대기로만 뜨는게 이상하긴하지만 나머지 데이터는 잘 출력되었습니다.

 

 

 

이번시간에는 본인이 살고있는 동네의 날씨를 크롤링 해와서 챗봇에 서비스 기능을 추가해 주겠습니다.

보통, GPS를 킨 상태에서 핸드폰으로 네이버에 날씨를 검색하면 본인 동네의 날씨가 나오게 되는데, 이를 이용하겠습니다.

이번에는 간단하게 현재 동네 온도 정보를 받아오는 실습이기 때문에 어려운게 없습니다.

 

저는 휴대폰으로 네이버에 "날씨"를 검색하면 "발산 1동"의 날씨가 받아와 지므로,  네이버 검색창에 "발산1동 날씨"라고 검색한 뒤 그 페이지를 크롤링 하겠습니다.

https://search.naver.com/search.naver?sm=tab_hty.top&where=nexearch&query=%EB%B0%9C%EC%82%B01%EB%8F%99+%EB%82%A0%EC%94%A8&oquery=%EB%B0%9C%EC%82%B01%EB%8F%99&tqi=hgpo3dprvxssseUUrFdssssst6Z-291650 

 

발산1동 날씨 : 네이버 통합검색

'발산1동 날씨'의 네이버 통합검색 결과입니다.

search.naver.com

 

계속 실습하다보니 대충 어떻게 데이터를 크롤링 해올지 감이 오지않나요? 

이번시간에는 구체적인 설명은 생략하겠습니다. 저는 아래 사진에서 박스친 부분의 데이터를 받아왔습니다.

 

날씨 정보를 크롤링 해오는 코드는 아래와 같습니다. 3가지 정보를 얻어왔습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def n_weather_crawling():
    url="https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EB%B0%9C%EC%82%B01%EB%8F%99%EB%82%A0%EC%94%A8"
    r=requests.get(url)
    soup=BeautifulSoup(r.text,'html.parser')
    weather_info=soup.select("div.today_area>div.main_info")
    if len(weather_info) >0:
        temperature=soup.select("span.todaytemp")
        cast_text=soup.select("p.cast_txt")
        indicator=soup.select("span.indicator")
        if len(temperature) >0 and len(cast_text)>0 and len(indicator)>0:
            temperature=temperature[0].text.strip()
            indicator=indicator[0].text.strip()
            txt=cast_text[0].text.strip()
            weather="{}도\r\n{}\r\n{}".format(temperature,indicator,txt)
        return weather
cs

 

챗봇 코드는 아래와 같습니다.

1
2
3
4
5
elif(user_text=="동네날씨"):
        #n: neighbor
        n_weather=n_weather_crawling()
        bot.send_message(chat_id=id,text=n_weather)
        bot.sendMessage(chat_id=id,text=info_message)      
cs

 

최종 결과는 아래와 같습니다.

위에서 살펴본 사이트에서 본인이 원하는 정보(미세먼지, 오존지수 등)를 입맛대로 추가할 수 있습니다.

 

 

 

 

 

 

 

이번시간에는 실시간 멜론차트 1~10순위 곡-가수 정보를 받아와 보겠습니다.

구현하면 할수록 크롤링을 어떻게 해야할지 쉽게 감이 오고 실력도 많이 느는 것 같습니다.

특히 굳이 앱을 킬 필요도 없이 텔레그램에서 코로나정보/차트순위/영화 정보를 서비스 받을 수 있다는 게 정말 흥미롭지 않나요??

라즈베리파이 서버/아마존 Ec2 같은 시스템을 이용한다면 지금처럼 pc를 켠 상태가 아니어도 서비스를 이용가능합니다.

 

멜론 차트 순위는 아래 링크에서 확인할 수 있습니다.

멜론차트>TOP100>멜론 (melon.com)

 

Melon

음악이 필요한 순간, 멜론

www.melon.com

 

실시간 음원차트 순위는 곡 순위를 확인하는 게 제일 큰 목적이므로

곡 순위/가수/곡 이름 정보만 크롤링하였습니다.

보시면 ellipsis클래스에서 rank01이 제목, rank02가 가수 이름임을 알 수 있습니다. 

 

 

더 늘려보면 아래처럼 span 태그안에 곡 이름/가수 이름이 링크로 연결되어있음을 확인할 수 있습니다.

주의할 점은 곡명은 하나이고 가수는 한명 이상이기 때문에 크롤링 할 때 가수이름 목록과 곡 제목을 긁어오는 코드 구조를 똑같이 구성하면 안된다는 것입니다.

가령, 위 사진에서 1위 곡 가수가 2명으로 되어있는데 코드를 긁어오는 방법에 따라 두번째 인자인 Justin Bieber가 2위 곡인 신호등과 매칭될 수 있다는 것입니다. 

 

코드를 살펴 보겠습니다.

주의할 것은 7,8행인데 8행을 7행처럼 'span >a' 라고 작성한다면 출력할 때 "신호등-Justin Beiber"가 출력될 것입니다.

가수는 한명 이상이고 제목은 하나이기 때문에 'span>a'로 작성하면 한명의 가수이름만 출력됩니다. 저스틴 비버가 짤리게되죠.

가수명(8행)은 span 태그만 사용하여 모든 가수를 불러오도록 해야합니다.

 

또한 11행, 17행 반복문 (index, value)형식으로 출력하는데, 출력해보면 곡 명 앞에 01,02 처럼 순위가 같이 딸려오게됩니다. (ex. 02신호등-이무진)

30행 str(titles[i][2:])을 보면, 같이 출력되는 '01', '02' 등을 건너 뛰고 제목부터 출력되게끔 조절하고 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def melon_chart_crawling():
    addr = 'https://www.melon.com/chart/index.htm'
 
    driver.get(addr)
    melon = driver.page_source
    soup = BeautifulSoup(melon, 'html.parser')
    title = soup.select('#frm > div div.ellipsis.rank01 > span > a')
    artist = soup.select('#frm > div div.ellipsis.rank02 > span')
 
    titles = []
    for i,j in enumerate(title):
        if i < 10:
            tts = str(i) + ' ' + j.get_text()
            titles.append(tts)
 
    artists = []
    for i, j in enumerate(artist):
        if i < 10:
            tts = j.get_text()
            artists.append(tts)
 
    key_val = [titles, artists]
    # rank_text = dict(zip(*key_val))
    
    #titles, artists 는 .text필드 없음, str로 파싱 후 title은 앞자리2번째부터
    output=" "
    #10개. 더 구하고싶으면 더 조절
    for i in range (0,10):
        
        output+=str(i+1)+'위: '+str(titles[i][2:])+"-"+str(artists[i])+'\n'
            
    return output
 
cs

 

메세지 송수신 부분 코드는 아래와 같습니다.

1
2
3
4
5
elif( user_text=="멜론"):
        bot.send_message(chat_id=id, text="조회 중 입니다...")
        melon_chart=melon_chart_crawling()
        bot.send_message(chat_id=id, text=melon_chart)
        bot.sendMessage(chat_id=id, text=info_message)
cs

 

실행을하고 "멜론"을 보내면 아래처럼 곡 정보가 잘 받아와 지는 것을 확인할 수 있습니다.

 

오늘은 저번시간에 실습한 코로나 정보 챗봇 기능에 추가하여 네이버 영화순위 정보를 추가해보겠습니다.

 

앞으로도 모든 실습은 기본 코드에 추가하는 식으로 구현하겠습니다.

 

네이버 영화순위에 나오는 1~5순위 영화 정보를 하나씩 출력하는 식으로 구현할텐데,

한번에 1~5순위 까지 모든 영화정보를 하나의 메시지로 출력하면 최초 영화에 대한 프리뷰 화면만 나오고 

나머지 2~5순위 영화는 영화 이름, 링크만 나오게 되어서 저는 하나의 메시지가 아닌 5개 메세지를 전송받아서 각 영화에 대한 짤막한 정보를 얻게 구현했습니다.

 

아래 링크에서 역시나 F12를 눌러서 우리가 얻어올 영화 이름, 프리뷰, 사진 정보 컴포넌트를 조사합니다.

http://movie.naver.com/movie/running/current.nhn

오른쪽 lst_sdc 클래스를 펼쳐보면 왼쪽 영화 사진/이름/정보 들이 쭉 나열이 되어있습니다.

이 정보를 크롤링하여 각 영화에 대한 정보를 얻어올 수 있습니다.

 

추가한 영화 크롤링 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def movie_chart_crawling():
    session=requests.Session()
    #영화 크롤링 사이트
    addr='http://movie.naver.com/movie/running/current.nhn'
    req=session.get(addr)
    soup=BeautifulSoup(req.text,'html.parser')
    titles=soup.find_all('dl',class_='lst_dsc')
    cnt=1
    output=" "
 
    # 영화제목+ 링크가 순서대로 5개 출력되고 각 영화별 설명이 짤막하게 들어가고 + 출력까지 
    for title in titles:
        output+=str(cnt)+'위: '+title.find('a').text+'\n'+addr+title.find('a')['href']+'\n'
        #여기서 푸쉬해서 5개 각 저옵가 메세지로 출력되게끔
        bot.send_message(chat_id=id,text=output)
        output="" 
        cnt+=1
        if cnt==6:
            break
    #return output 
  
cs

 

추가한 챗봇 송수신 코드입니다.

전에 실습한 코로나 관련 기능 구현은 함수에서 값을 리턴받은 뒤 그 내용을 출력하였지만

이번에는 각 영화에 대한 정보를 하나씩 출력하기 위해 함수 내부에서 크롤링 하고 출력까지 진행하였습니다.

1
2
3
4
5
6
elif(user_text=="영화"):
        bot.send_message(chat_id=id, text="조회 중 입니다...")
        movie_chart=movie_chart_crawling()
        #출력은 위의 함수 내부에서 한다.
        #bot.send_message(chat_id=id,text=movie_chart)
        bot.sendMessage(chat_id=id,text=info_message)
cs

 

결과는 아래와 같습니다. 각 영화정보가 하나의 메세지별로 출력됨을 알 수 있습니다.

 

이번시간부터 본격적으로 본인이 원하는 기능을 서비스받을 수 있는 챗봇을 구현해보겠습니다.

첫 시간이고 앞으로 많은 단계를 거쳐 웹 정보를 크롤링 할 것이므로 패키지들 부터 다 설치해주겠습니다.

저는 vscode에서 python으로 코딩하였습니다. 터미널에서 아래 명령어로 모든 패키지를 설치합니다.

 

pip install python-telegram-bot

pip install telegram

pip install selenium

pip install beautifulsoup4

pip install requests

 

Selenium은 webdriver라는 것을 통해 디바이스에 설치된 브라우저들을 제어할 수 있습니다.

주로 chrome을 많이 사용하므로 chromedriver를 설치합니다.

https://sites.google.com/a/chromium.org/chromedriver/downloads

 

Downloads - ChromeDriver - WebDriver for Chrome

WebDriver for Chrome

sites.google.com

 

주의 해야 할 것은 설치할 크롬 드라이버 버전과 자신의 크롬 버전이 일치해야한다는 것입니다.

버전이 서로 다르면 크롤링이 되지 않습니다.

자신의 크롬 버전 확인: 크롬실행-> 우상단 점3개 아이콘 클릭-> 도움말-> chrome 정보 클릭 하여 확인

설치, 압축 해제 해줍니다. 나중에 이 드라이버를 불러봐야 하기 때문에 저장 위치(경로)를 잘 기억해 둡니다.

 

=================================================

여기까지가 앞으로 몇단계에 걸쳐 진행할 실습을 위한 사전작업 단계입니다.

 

이제 실습 진행해보겠습니다. 

이번시간에 웹의 정보를 크롤링하여 정보를 수집하고, 얻는 정보는 다음과 같습니다.

<웹: 네이버>

1. 코로나 일일 확진자 수

2. 코로나 관련 최근 10개의 이미지

3. 코로나 관련 최근 3개 기사 타이틀, 링크

 

모두 네이버에 검색한 내용을 크롤링하여 정보를 수집하였습니다.

 

 

네이버에 "확진자"를 검색 하면 아래 창이 나타나고, 개발자도구(F12)를 눌러서 확진자 수 데이터를 확인합니다.

 

status_info 영역 내 클래스 안에 em(글꼴 강조) 클래스 안에 2049데이터가 있습니다.

이 2049 데이터를 나중에 select함수 내부에 '2049데이터 위치' 를 넣어서 그 부분만 정보를 추출할 수 있습니다.

div.status_info 안에 em 클래스안에 2049가 있네요.

 

이런식으로 하나씩 정보를 크롤링하여 원하는 정보만 추출할 수 있습니다.

 

크롤링 방법을 다루는 실습은 아니므로 구체적인 설명은 생략하겠습니다.

함수 사용은 거의 동일하고 필요한 부분은 주석으로 대체했습니다.

 

이제 전체 코드를 확인하겠습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import telegram
import requests
from telegram.ext import Updater
from telegram.ext import MessageHandler, Filters
from bs4 import BeautifulSoup 
from selenium import webdriver
import urllib.request as req
import os
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
API_key='knI%2FsEhuhoIf37FOmsc8uCq6qdcCXaJU9%2BKHEwgtLzMWGJ7A7LtC3w3Z3JvKzcE4cSrxn6reCcJi2FzIcKvKAQ%3D%3D'
 
options = webdriver.ChromeOptions()
#크롬창을 키지 않고 연결
options.add_argument('headless')
#사이즈 
options.add_argument('window-size=1920x1080')
#GPU설정 X
options.add_argument("disable-gpu")
# 혹은 options.add_argument("--disable-gpu")
driver  = webdriver.Chrome("./chromedriver.exe", options = options) 
#확진자 검색 후 f12로 코로나 확진자 수 정보 컴포넌트 위치 파악 후 크롤링
def covid_num_crawling():
    code = req.urlopen("https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%ED%99%95%EC%A7%84%EC%9E%90")
    #html 방식으로 파싱
    soup = BeautifulSoup(code, "html.parser")
    #정보 get
    info_num = soup.select("div.status_info em")
    result = info_num[0].string #=> 확진자
    return result
 
def covid_news_crawling():
    code = req.urlopen("https://search.naver.com/search.naver?where=news&sm=tab_jum&query=%EC%BD%94%EB%A1%9C%EB%82%98")
    soup = BeautifulSoup(code, "html.parser")
    title_list = soup.select("a.news_tit")
    output_result = ""
    for i in title_list:
        title = i.text
        news_url = i.attrs["href"]
        output_result += title + "\n" + news_url + "\n\n"
        if title_list.index(i) == 2:
            break
    return output_result
 
def covid_image_crawling(image_num=5):
    if not os.path.exists("./코로나이미지"):
        os.mkdir("./코로나이미지")
 
    browser = webdriver.Chrome("./chromedriver")
    browser.implicitly_wait(3)
    wait = WebDriverWait(browser, 10)
 
    browser.get("https://search.naver.com/search.naver?where=image&section=image&query=%EC%BD%94%EB%A1%9C%EB%82%98&res_fr=0&res_to=0&sm=tab_opt&color=&ccl=0&nso=so%3Ar%2Cp%3A1d%2Ca%3Aall&datetype=1&startdate=&enddate=&gif=0&optStr=d&nq=&dq=&rq=&tq=")
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.photo_group._listGrid div.thumb img")))
    img = browser.find_elements_by_css_selector("div.photo_group._listGrid div.thumb img")
    for i in img:
        img_url = i.get_attribute("src")
        req.urlretrieve(img_url, "./코로나이미지/{}.png".format(img.index(i)))
        if img.index(i) == image_num-1:
            break
    browser.close()
 
 
 
#토큰 넘버
token = "토큰"
id = "id값"
 
bot = telegram.Bot(token)
info_message = '''- 오늘 확진자 수 확인 : "코로나" 입력
- 코로나 관련 뉴스 : "뉴스" 입력
- 코로나 관련 이미지 : "이미지" 입력 '''
bot.sendMessage(chat_id=id, text=info_message)
 
updater = Updater(token=token, use_context=True)
dispatcher = updater.dispatcher
updater.start_polling()
 
### 챗봇 답장
def handler(update, context):
    user_text = update.message.text # 사용자가 보낸 메세지를 user_text 변수에 저장
    # 오늘 확진자 수 답장
    if (user_text == "코로나"):
        covid_num = covid_num_crawling()
        bot.send_message(chat_id=id, text="오늘 확진자 수 : {} 명".format(covid_num))
        bot.sendMessage(chat_id=id, text=info_message)
    # 코로나 관련 뉴스 답장
    elif (user_text == "뉴스"):
        covid_news = covid_news_crawling()
        bot.send_message(chat_id=id, text=covid_news)
        bot.sendMessage(chat_id=id, text=info_message)
    # 코로나 관련 이미지 답장
    elif (user_text == "이미지"):
        bot.send_message(chat_id=id, text="최신 이미지 크롤링 중...")
        covid_image_crawling(image_num=10)
        # 이미지 한장만 보내기
        # bot.send_photo(chat_id=id, photo=open("./코로나이미지/0.png", 'rb'))
        # 이미지 여러장 묶어서 보내기
        photo_list = []
        for i in range(len(os.walk("./코로나이미지").__next__()[2])): # 이미지 파일 개수만큼 for문 돌리기
            photo_list.append(telegram.InputMediaPhoto(open("./코로나이미지/{}.png".format(i), "rb")))
        bot.sendMediaGroup(chat_id=id, media=photo_list)
        bot.sendMessage(chat_id=id, text=info_message)
      
    
        
echo_handler = MessageHandler(Filters.text, handler)
dispatcher.add_handler(echo_handler)
cs

 

결과 화면입니다.

 

 

실시간 도착 버스 정보 조회 API를 신청하고나서 

짧게는 1시간 길게는 2일 정도 사용허가를 해준다 그랬는데 신청하고 20분후에 아래 처럼 승인이 나 있었습니다.

 

되게 빨리 됐다 싶어서 바로 사용해보려고 상세기능의 샘플코드로 테스트해보기 위해 busRouteId를 입력했더니

아래처럼 오류가 났습니다.

처음엔 잘 몰랐어서 오류를 보고나서 전용 API키를 발급받아야되나? 내가 어떤 잘못을 했나? 싶어서 다시 시도해봤는데

계속 오류가 떳습니다..

 

오류를 검색 했더니 이는 공공기관 서버 쪽에서 아직 처리가 안된거라고 합니다.

어느정도 시간이 지나서(야식을 먹고 새벽에..) 다시 시도해보니.

 

아래처럼 오류문구가 바꼈습니다. 이제는 인증실패가 아니라 결과가 없다고 나오네요.

처음에는 새벽에 버스가 운행을 하지않기 때문에 데이터가 없다고 출력됐다고 생각했는데,

이는 입력 데이터가 잘못된 것입니다. 즉, 승인 허가는 났고 이제 잘못된 데이터를 넣어서 오류가 난것이죠.

 

 

올바른 값을 넣으면 아래처럼 잘 나옵니다.

저는 api사용 신청 후 실제 사용 허가 까지 2시간 정도 걸린 것 같습니다.

 

 

 

최근에 크롤링에 대해서 공부했는데 매우 쉽고 금방 써먹을 수 있어서 이를 어떻게 응용해볼까 생각하다가 텔레그램을 활용한 챗봇을 만들어보고자 본 튜토리얼을 진행하게되었습니다.

저는 VScode에서 python으로 진행했습니다.

 

순서는 다음과 같습니다.

 

1. 텔레그램 설치(토큰 값 저장)

2. 메세지 보내고 응답받기

 

 

1. 텔레그램 설치

구글 스토어에서 텔레그램을 설치합니다.

돋보기 클릭 후 'botfather' 이라고 검색한 뒤 클릭합니다.

 

'/newbot' 입력 후 새 봇 이름을 설정해줍니다. (채팅방 이름)

이제 채팅방에 접속하기 위한 봇 이름(계정 이름 같은?) 을 설정합니다 . 끝자리는 무조건 'bot'이 들어가야하며 중복 에러 발생 시 새 이름을 지어야합니다.

메세지 중간 부분에 토큰 아이디가 나오는데 이를 복사 해 두도록 합니다. 이 정보를 이용해 챗봇과 연결할 수 있습니다.

이제 나가서 다시 돋보기로 봇이름을 검색해봅니다. 아래와 같이 나올것이며 접속 후 시작 버튼을 눌러줍니다.

 

2. 메세지 보내고 응답받기

채팅방에 접속하여 아무런 메세지나 입력해도 아무 반응이 없습니다.(정상입니다)

메세지를 보내기 위해 우리는 token값과 id정보를 이용해야 하는데 token값은 알고 있으니 id정보를 얻을차례입니다.

 

이제 vscode에서 터미널에 아래 명령어로 패키지를 깔아줍니다.

 

pip install python-telegram-bot

 

그리고 아래 코드를 입력 후, 실행해봅니다.

1
2
3
4
5
6
7
import telegram
 
token = '토큰 id'
bot = telegram.Bot(token=token)
updates = bot.getUpdates()
for u in updates:
    print(u.message)
cs

에러가 날 경우 채팅방에 몇마디 입력한 뒤 다시 실행합니다. 아래와 같은 정보들이 터미널에 뜰 텐데 id값을 복사하여 역시 복사해둡니다. 제 id는 1954654082네요.

 

이제 텔레그램 봇이 자동으로 나에게 하나의 메세지를 전달해보도록 하겠습니다.

아래 코드를 입력 후 실행합니다. 

1
2
3
4
5
6
7
import telegram
 
token = "토큰 id"
id = "터미널에서 확인한 id값"
 
bot = telegram.Bot(token)
bot.sendMessage(chat_id=id, text="테스트 코드")
cs

 

텔레그램에서 "테스트 코드"라는 메세지가 도착했을겁니다.

 

이제 우리가 메세지를 보내면 응답을 받는 코드를 작성해보겠습니다.

updater, start_polling, handler함수 등이 있는데 handler함수를 조절하여 메세지 핸들러를 생성한 후 이를 디스패처 변수의 add_handler함수를 이용하면 메세지가 푸쉬되는것입니다. 

handler함수를 조정하여 간단하게 테스트해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import telegram
from telegram.ext import Updater
from telegram.ext import MessageHandler, Filters
 
token = "토큰 값"
id = "id 값"
 
bot = telegram.Bot(token)
 
 
# updater
updater = Updater(token=token, use_context=True)
dispatcher = updater.dispatcher
#봇 
updater.start_polling()
 
def handler(update, context):
    user_text = update.message.text 
    if user_text == "ㅋㅋ"#ㅋㅋ라고 보내면 왜웃냐고 답장
        bot.send_message(chat_id=id, text="왜 웃냐"# 답장 보내기
    elif user_text == "웃겨서"
        bot.send_message(chat_id=id, text="뭐가 웃긴데?"# 답장 보내기
 
echo_handler = MessageHandler(Filters.text, handler)
dispatcher.add_handler(echo_handler)
cs

 

결과는 아래와 같습니다. 

 

매우간단하죠?

 

오늘 간단한 챗봇 기능까지 구현하였고 다음시간부터 데이터 크롤링 + 오픈API를 사용하여 다양한 기능들을 추가해보겠습니다.

+ Recent posts