tkinter, bs4 오픈소스를 이용하여 뉴스 스크래핑 프로그램을 만들어보자.
bs4는 Beautifulsoup이다. 웹 스크래핑 분야에서 selenium과 쌍두마차로 되게 유명한 오픈소스.
bs4는 정적인 사이트에서 정보만 긁어오는 거라 굉장히 빠르지만 스크롤, 클릭 등을 할 수 없고
셀레니움은 그런 동적인 활동을 할 수 있지만 속도가 느리다는 특징이 있다.
그래서 이놈을 통해서 네이버 상단뉴스를 가져와 띄우고, 각각 링크를 만들어줄 프로그램을 tkinter interface에 넣어주도록 하겠다.
기획으로는
1. 버튼을 누르면 스크래핑이 시작될 것. 다시 버튼을 누르면 결과가 누적되지 않고 새로 써질 것.
2. 링크 프레임은 버튼을 누른 뒤에만 보이도록 처리할 것.
3. 오늘의 날씨, 핫 뉴스, 스포츠 뉴스를 가져올 것.
시작해보자.
from tkinter import *
from bs4 import BeautifulSoup
import requests
def create_soup(url):
headers = {크롬으로 접속 후 'my user agent'를 검색해서 나오는 결과 붙여넣기}
res= requests.get(url, headers=headers)
res.raise_for_status()
soup = BeautifulSoup(res.text, 'lxml')
return soup
우선 이렇게 create_soup이라는 함수를 만들어주겠다.
bs4가 soup이라는 객체를 만들고 그를 이용해서 특정 사이트의 html을 다 긁어온 뒤에
검색해서 필요한 자료를 찾는 방식인데,
뉴스를 긁어올 때마다 새로운 soup 객체를 만들어줘야 하기 때문에
반복작업이 필요하지 않도록 함수로 정의해준다.
btn_frame = Frame(root) #버튼이 들어갈 프레임
btn_frame.pack() #pack을 해줘야 화면에 계속 업데이트된다
scrap_btn = Button(btn_frame, text='뉴스 가져오기', width=10, command=scrap)
#버튼
scrap_btn.pack(padx=5, pady=5)
weather_news_frame = LabelFrame(root, text='오늘의 날씨', width=50) #오늘의 날씨가 들어갈 프레임
weather_news_frame.pack(pady=5, padx=5)
weather_news = Listbox(weather_news_frame, width=50) #오늘의 날씨 리스트
weather_news.pack(fill='x', side='top', pady=10, padx=10)
todays_news_frame = LabelFrame(root, width=50, text='오늘의 뉴스') #오늘의 뉴스 프레임
todays_news_frame.pack(side='top',pady=5, padx=5)
todays_news = Listbox(todays_news_frame, width=50)
#오늘의 뉴스 리스트 todays_news.pack(side='left', fill='x', padx=10, pady=10)
sports_news_frame = LabelFrame(root, text='스포츠 뉴스') #스포츠 뉴스가 들어갈 프레임
sports_news_frame.pack(side='top',padx=5, pady=5)
sports_news= Listbox(sports_news_frame, width=50) #스포츠 뉴스 리스트
sports_news.pack(side='left', fill='x', padx=10, pady=10)
이렇게 기본적인 인터페이스를 정의해주자. 여기에서 뉴스의 링크가 들어갈 공간은
버튼을 누르기 전에는 없다 = 버튼을 누르면 생긴다 이므로
함수 내부에 정의할 계획으로 넣지 않았다.
그럼 이제 하나씩 스크래핑을 해보자.
weather_news.delete(0,END)
url = 'https://search.naver.com/search.naver?where=nexearch&sm=top_sug.asiw&fbm=0&acr=1&acq=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8&qdt=0&ie=utf8&acir=1&query=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8'
soup = create_soup(url)
#날씨, 어제보다 00도 높아요
cast = soup.find('p', attrs={'class':'cast_txt'}).get_text()
#현재 00도 (최고, 최저)
cur_temp = soup.find('p', attrs={'class' : 'info_temperature'}).get_text().replace('도씨', '')
max_temp = soup.find('span', attrs={'class':'max'}).get_text()
min_temp = soup.find('span', attrs={'class':'min'}).get_text()
#강수확률
morning_rainrate = soup.find('span', attrs={'class':'point_time morning'}).get_text().strip()
afternoon_rainrate = soup.find('span', attrs={'class':'point_time afternoon'}).get_text().strip()
#미세먼지
dust = soup.find('dl', attrs={'class':'indicator'})
pm10 = dust.find_all('dd')[0].get_text() #미세먼지
pm25 = dust.find_all('dd')[1].get_text() #초미세먼지
네이버에 '오늘 날씨'를 검색해서 나온 결과를 url 변수에 대입해주고, 아까 우리가 만든 create_soup 객체에 넣어준다. 그러면 soup객체는 생성이 되었다. 우리가 필요한 html 정보만 가져오면 된다.
soup 객체는 find, find_all 등을 이용해서 태그를 찾을 수 있고, 태그 내의 속성에 대해서는
soup.find('a')['href'] 식으로 써주면 태그 a 내의 href 속성을 찾아 줘 와 같은 말이다.
개발자 도구의 왼쪽 제일 위에 있는 네모와 마우스 버튼을 클릭하면
해당 요소의 위치, 태그 정보 등을 알 수 있다.
예를 들어 오늘의 온도를 보자. 우선 p 태그 안에 클래스가 있다. 이걸로 찾아주면 되겠다.
만약 동명의 element가 여러 개라면, find_all() 함수를 쓰고 인덱싱을 해주면 된다.
위의 코드에서 미세먼지 항목이 그렇다.
태그를 펼쳐보니 p 태그 아래에 있을 건 다 있다. 그러나 도씨와 C는 같은 뜻이므로 도씨는 replace('도씨', '')를 이용하여 제거해준 상태로 보관하자.
이런 식으로 필요한 모든 요소들을 스크래핑 해오면 된다.
strip()함수는 불필요한 공백을 다 없애준다. 우리는 결과를 출력해야 하므로 정보를 깔끔하게 저장해야 한다!
replace와 strip, 적극 활용하자.
출력 부분은 다 리스트에 때려넣고, for문을 이용해서 오늘의 날씨 리스트박스에 넣어주면 될 듯.
오늘의 날짜를 프린트하기 위해 datetime을 import해주자.
import datetime(코드 제일 위에다)
#출력
lines = [cast,'현재 {} (최저{} / 최고 {})'.format(cur_temp, min_temp, max_temp), '오전 {} / 오후 {}'.format(morning_rainrate, afternoon_rainrate), '미세먼지 {}'.format(pm10), '초미세먼지 {}'.format(pm25)] weather_news.insert(END, str(datetime.datetime.today().strftime("%Y-%m-%d")+': 오늘의 날씨입니다.'))
for line in lines:
weather_news.insert(END, line)
weather_news.insert(END, '')
이런 식으로 함수를 정의해주고, 혹시 오류가 생길 때를 대비해서 try와 except로 감싸주자. 오류가 날 때는 오류명을 출력하기 위해 except Exception으로 넣고 프린트해주면 될 것 같다.
그래서 최종 함수의 형태는
def scrape_weather(): global weather_news try: weather_news.delete(0,END) url = 'https://search.naver.com/search.naver?where=nexearch&sm=top_sug.asiw&fbm=0&acr=1&acq=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8&qdt=0&ie=utf8&acir=1&query=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8' soup = create_soup(url) #날씨, 어제보다 00도 높아요 cast = soup.find('p', attrs={'class':'cast_txt'}).get_text() #현재 00도 (최고, 최저) cur_temp = soup.find('p', attrs={'class' : 'info_temperature'}).get_text().replace('도씨', '') max_temp = soup.find('span', attrs={'class':'max'}).get_text() min_temp = soup.find('span', attrs={'class':'min'}).get_text() #강수확률 morning_rainrate = soup.find('span', attrs={'class':'point_time morning'}).get_text().strip() afternoon_rainrate = soup.find('span', attrs={'class':'point_time afternoon'}).get_text().strip() #미세먼지 dust = soup.find('dl', attrs={'class':'indicator'}) pm10 = dust.find_all('dd')[0].get_text() #미세먼지 pm25 = dust.find_all('dd')[1].get_text() #초미세먼지 #출력 lines = [cast,'현재 {} (최저{} / 최고 {})'.format(cur_temp, min_temp, max_temp), '오전 {} / 오후 {}'.format(morning_rainrate, afternoon_rainrate), '미세먼지 {}'.format(pm10), '초미세먼지 {}'.format(pm25)] weather_news.insert(END, str(datetime.datetime.today().strftime("%Y-%m-%d")+': 오늘의 날씨입니다.')) for line in lines: weather_news.insert(END, line) weather_news.insert(END, '') except Exception : weather_news.insert(END, err) weather_news.insert(END, '오류가 발생하였습니다.')
def scrape_weather():
global weather_news
try:
weather_news.delete(0,END)
url = 'https://search.naver.com/search.naver?where=nexearch&sm=top_sug.asiw&fbm=0&acr=1&acq=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8&qdt=0&ie=utf8&acir=1&query=%EC%84%9C%EC%9A%B8+%EB%82%A0%EC%94%A8'
soup = create_soup(url)
#날씨, 어제보다 00도 높아요
cast = soup.find('p', attrs={'class':'cast_txt'}).get_text()
#현재 00도 (최고, 최저)
cur_temp = soup.find('p', attrs={'class' : 'info_temperature'}).get_text().replace('도씨', '')
max_temp = soup.find('span', attrs={'class':'max'}).get_text()
min_temp = soup.find('span', attrs={'class':'min'}).get_text()
#강수확률
morning_rainrate = soup.find('span', attrs={'class':'point_time morning'}).get_text().strip()
afternoon_rainrate = soup.find('span', attrs={'class':'point_time afternoon'}).get_text().strip()
#미세먼지
dust = soup.find('dl', attrs={'class':'indicator'})
pm10 = dust.find_all('dd')[0].get_text() #미세먼지
pm25 = dust.find_all('dd')[1].get_text() #초미세먼지
#출력
lines = [cast,'현재 {} (최저{} / 최고 {})'.format(cur_temp, min_temp, max_temp), '오전 {} / 오후 {}'.format(morning_rainrate, afternoon_rainrate), '미세먼지 {}'.format(pm10), '초미세먼지 {}'.format(pm25)]
weather_news.insert(END, str(datetime.datetime.today().strftime("%Y-%m-%d")+': 오늘의 날씨입니다.'))
for line in lines:
weather_news.insert(END, line) weather_news.insert(END, '')
except Exception :
weather_news.insert(END, err)
weather_news.insert(END, '오류가 발생하였습니다.')
이렇게 될 것. 이런 식으로 뉴스, 스포츠 뉴스도 스크래핑 해주면 된다.
bs4의 세세한 작동법이 좀 방대해서, 필요한 기능이 생기면 그때그때 구글링해보는 게 최선인 것 같다.
이제 뉴스랑 스포츠 뉴스인데, 이게 링크 때문에 번거로운 게 많아서 따로 포스팅하는 게 낫겠다.
클래스 정의, 프레임 삭제 등은 다음 포스트!
'Programming > Projects' 카테고리의 다른 글
[Python] 화면 녹화 프로그램 1.-opencv, tkinter (0) | 2021.09.30 |
---|---|
[Python] 계산기 만들기 - tkinter (0) | 2021.09.30 |
[Python] 인스타그램 좋아요 매크로 업그레이드- opencv (0) | 2021.09.30 |
[Python] 인스타그램 좋아요 매크로-selenium (1) | 2021.09.30 |
[Python] 뉴스 스크래핑 프로그램 만들기 (2) (0) | 2021.09.19 |
댓글