티스토리 뷰
INDEX
1. 정적 크롤링(Requests + BeautifulSoup)
2. Requests와 BeautifulSoup을 이용한 네이버 뉴스 크롤링
3. 네이버 금융 크롤링후 엑셀에 저장하기
4. 마무리
1. 정적크롤링
웹 크롤링
웹 크롤링이란 웹 페이지에서 원하는 정보를 추출하는 것으로 3단계로 이루어진다.
1. 웹 페이지의 HTML 문서를 가져온다.
2. 가져온 HTML 문서를 파싱한다.
3. 파싱한 내용에서 원하는 정보만을 추출한다.
정적 크롤링
정적 웹 페이지는 웹 서버에 미리 저장된 파일이 그대로 전달되는 웹 페이지를 말하며 URL 주소만으로 모든 정보를 가져올 수 있다.
이 정적 웹 페이지를 크롤링하는 것을 정적 크롤링이라고하며 주로 Requests와 BeautifulSoup 라이브러리를 사용한다.
과정은 똑같이 3단계로 위와 동일하다.
1. requests를 사용하여 웹 페이지의 HTML을 가져한다.
2. BeautifulSoup으로 가져온 HTML을 파싱한다.
3.파싱된 내용 중 원하는 정보를 추출한다.
정적크롤링은 HTML을 요청하여 원하는 정보를 빠르게 추출한다는 장점이 있다.
물론 동적으로 변하는 데이터는 Selenium으로 동적 크롤링을 해야하기에 두가지 모두 알고는 있어야하나,
Requests를 이용한 크롤링을 중심으로 예제들을 다루었다.
2. Requests와 BeautifulSoup을 이용한 네이버 뉴스 크롤링
(일반적인)뉴스 크롤링하기
네이버 뉴스에서 삼성전자 검색후 title(기사제목), link(URL) 불러오기
import requests
from bs4 import BeautifulSoup
# Protocol://Domain/Path?Parameter
resp = requests.get("<https://search.naver.com/search.naver?where=news&sm=tab_jum&query=%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90>")
html = resp.text
soup = BeautifulSoup(html,'html.parser')
links = soup.select(".news_tit")
# print(links)
for link in links:
title = link.text # 태크안에 텍스트요소 가져옴
url = link.attrs['href'] # href속성 가져옴
print(title, url)
검색어 입력받고 1~10 page의 title과 link 불러오기
import requests
from bs4 import BeautifulSoup
import pyautogui
num = 1
# keyword = input('검색어를 입력하세요>>')
keyword = pyautogui.prompt('검색어를 입력하세요.')
for i in range(1,101,10):
print(f"{num}page입니다@@@@@@@@@@@@@@@@@@@@")
resp = requests.get(f"<https://search.naver.com/search.naver?where=news&sm=tab_jum&query={keyword}&start{i}>")
html = resp.text
soup = BeautifulSoup(html,'html.parser')
links = soup.select(".news_tit")
# print(links)
for link in links:
title = link.text # 태크안에 텍스트요소 가져온뒤
url = link.attrs['href'] # href속성을 가져온다
print(title, url)
num += 1
검색어 입력받고, 해당 뉴스본문들 출력하기
import requests
from bs4 import BeautifulSoup
import time
# 사용자로부터 검색어를 입력받습니다.
query = input("검색어를 입력하세요: ")
base_url = '<https://search.naver.com/search.naver>'
params = {
'where': 'news',
'sm': 'tab_jum',
'query': query
}
resp = requests.get(base_url, params=params)
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info") # a tag에 클래스는 info
if len(links) >= 2:
url = links[1].attrs['href']
resp = requests.get(url, headers={'User-agent': 'Mozilla/5.0'})
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
print(url)
contents = soup.select_one('#dic_area')
if contents:
print(contents.text)
time.sleep(0.5)
연예뉴스/스포츠뉴스
연예 뉴스와 스포츠뉴스는 각각 HTML이 조금씩 다르기때문에
다른 CSS선택자를 사용해야한다.
if 연예뉴스 URL
연예뉴스 사이트의 CSS 선택자 사용
elif 스포츠뉴스 URL
스포츠 뉴스 사이트의 CSS 선택자 사용
else
일반 뉴스 사이트의 CSS 선택자 사용
검색어 입력받고 해당 (네이버)뉴스 본문들 출력하기
import requests
from bs4 import BeautifulSoup
import pyautogui
keyword = pyautogui.prompt('검색어를 입력하세요.')
resp = requests.get(
f'<https://search.naver.com/search.naver?sm=tab_hty.top&where=news&query={keyword}>')
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info") # a tag에 클래스는 info
if len(links) >= 2:
url = links[1].attrs['href']
# print(url)
resp = requests.get(
url, headers={'User-agent': 'Mozila/5.0'}) # 각링크들 받아온후
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
print(url)
# 만약 연예 뉴스라면? redirection이 발생하므로 url이 아닌 resp.url
if "entertain" in resp.url:
title = soup.select_one(".end_tit") # 연예 뉴스 타이틀
contents = soup.select_one("#articeBody") # 연예 뉴스 본문
elif "sports" in resp.url:
title = soup.select_one("h4.title")
contents = soup.select_one('#newsEndContents')
# contents중에서 불필요한 부분(기자명, 다른광고) div 삭제하기
divs = contents.select('div')
for div in divs:
div.decompose() # 삭제
paragraphs = contents.select('p')
for p in paragraphs:
p.decompose()
else:
title = soup.select_one(".media_end_head_title") # 일반 뉴스 타이틀
contents = soup.select_one('#dic_area') # 일반 뉴스 본문
print("==========링크===========\\n", url)
print("==========제목===========\\n", title.text.strip())
print("==========본문===========\\n", contents.text.strip())
time.sleep(0.5) # 필수!!!!!
사용자로부터 페이지 수 입력받고 그 페이지 수만큼 뉴스 본문 크롤링하기
import requests
from bs4 import BeautifulSoup
import pyautogui
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?")) # int변환 필요
page_num = 1
for i in range(1, lastpage * 10, 10):
print(f"{page_num} 페이지 크롤링 중 입니다.======================================")
resp = requests.get(f'<https://search.naver.com/search.naver?sm=tab_hty.top&where=news&query={keyword}start={i}>')
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info") # a tag에 클래스는 info
if len(links) >= 2:
url = links[1].attrs['href']
# print(url)
resp = requests.get(url, headers={'User-agent': 'Mozila/5.0'}) # 각링크들 받아온후
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
print(url)
# 만약 연예 뉴스라면? redirection이 발생하므로 url이 아닌 resp.url
if "entertain" in resp.url:
title = soup.select_one(".end_tit") # 연예 뉴스 타이틀
contents = soup.select_one("#articeBody") # 연예 뉴스 본문
elif "sports" in resp.url:
title = soup.select_one("h4.title")
contents = soup.select_one('#newsEndContents')
# contents중에서 불필요한 부분(기자명, 다른광고) div 삭제하기
divs = contents.select('div')
for div in divs:
div.decompose() # 삭제
paragraphs = contents.select('p')
for p in paragraphs:
p.decompose()
else:
title = soup.select_one(".media_end_head_title") # 일반 뉴스 타이틀
contents = soup.select_one('#dic_area') # 일반 뉴스 본문
print("==========링크===========\\n",url)
print("==========제목===========\\n",title.text.strip())
print("==========본문===========\\n",contents.text.strip())
time.sleep(0.5) # 필수!!!!!
page_num += 1
워드 문서에 저장하기
워드 문서 다루는 방법(참고)
from docx import Document
#1. 워드 생성하기
document = Document()
#2. 워드 데이터 추가하기
document.add_heading('기사제목', level=0)
document.add_paragraph('기사 링크')
document.add_paragraph('기사 본문')
#3. 워드 저장하기
document.save("text.docx")
크롤링 결과를 워드파일에 저장해보기
파일 생성 위치, 데이터 입력 위치, 파일 저장 위치 고려하기
import requests
from bs4 import BeautifulSoup
import pyautogui
from docx import Document
document = Document()
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?")) # int변환 필요
page_num = 1
for i in range(1, lastpage * 10, 10):
print(f"{page_num} 페이지 크롤링 중...")
resp = requests.get(f'<https://search.naver.com/search.naver?where=news&sm=tab_jum&query={keyword}start={i}>')
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info") # a tag에 클래스는 info
if len(links) >= 2:
url = links[1].attrs['href']
# print(url)
resp = requests.get(url, headers={'User-agent': 'Mozila/5.0'}) # 각링크들 받아온후
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
# 만약 연예 뉴스라면? redirection이 발생하므로 url이 아닌 resp.url
if "entertain" in resp.url:
title = soup.select_one(".end_tit") # 연예 뉴스 타이틀
contents = soup.select_one("#articeBody") # 연예 뉴스 본문
elif "sports" in resp.url:
title = soup.select_one("h4.title")
contents = soup.select_one('#newsEndContents')
# contents중에서 불필요한 부분(기자명, 다른광고) div 삭제하기
divs = contents.select('div')
for div in divs:
div.decompose() # 삭제
paragraphs = contents.select('p')
for p in paragraphs:
p.decompose()
else:
title = soup.select_one(".media_end_head_title") # 일반 뉴스 타이틀
contents = soup.select_one('#dic_area') # 일반 뉴스 본문
document.add_heading(title.text.strip(), level=0)
document.add_paragraph(url)
document.add_paragraph(contents.text.strip())
time.sleep(0.5) # 필수!!!!!
page_num += 1
document.save(f"{keyword}_result.docx")
엑셀 문서 다루는 방법(참고)
from openpyxl import Workbook
# 엑샐 생성하기
wb = Workbook()
# 엑셀 시트 생성하기
ws = wb.create_sheet("이도형")
ws['A1'] = '이도형'
wb.save('DHL.xlsx')
크롤링 결과를 엑셀 파일에 저장해보기
셀 너비 설정하여 가독성 고려하기(60,60,120)
import requests
from bs4 import BeautifulSoup
import pyautogui
from openpyxl import Workbook
from openpyxl.styles import Alignment
import time
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?")) # int변환 필요
wb = Workbook()
ws = wb.create_sheet(keyword)
# 너비 설정
ws.column_dimensions['A'].width = 60
ws.column_dimensions['B'].width = 60
ws.column_dimensions['C'].width = 120
page_num = 1
row = 1
for i in range(1, lastpage * 10, 10):
print(f"{page_num} 페이지 크롤링 중...")
resp = requests.get(f"<https://search.naver.com/search.naver?where=news&sm=tab_jum&query={keyword}&start={i}>")
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info") # a tag에 클래스는 info
if len(links) >= 2:
url = links[1].attrs['href']
# print(url)
resp = requests.get(url, headers={'User-agent': 'Mozila/5.0'}) # 에러 방지
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
# 만약 연예 뉴스라면? redirection이 발생하므로 url이 아닌 resp.url
if "entertain" in resp.url:
title = soup.select_one(".end_tit") # 연예 뉴스 타이틀
contents = soup.select_one("#articeBody") # 연예 뉴스 본문
elif "sports" in resp.url:
title = soup.select_one("h4.title")
contents = soup.select_one('#newsEndContents')
# contents중에서 불필요한 부분(기자명, 다른광고) div 삭제하기
divs = contents.select('div')
for div in divs:
div.decompose() # 삭제
paragraphs = contents.select('p')
for p in paragraphs:
p.decompose()
else:
title = soup.select_one(".media_end_head_title") # 일반 뉴스 타이틀
contents = soup.select_one('#dic_area') # 일반 뉴스 본문
ws[f'A{row}'] = url
ws[f'B{row}'] = title.text.strip()
ws[f'C{row}'] = contents.text.strip()
ws[f"C{row}"].alignment = Alignment(wrap_text=True)
row += 1
time.sleep(0.4)
page_num += 1
wb.save(f"{keyword}_result.xlsx")
여러페이지의 본문 텍스트들을 합쳐 워드 클라우드 만들어보기
import requests
from bs4 import BeautifulSoup
import time
import pyautogui
import pyperclip
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?")) # int변환 필요
page_num = 1
total_content = ""
article_num = 0
for i in range(1, lastpage * 10, 10):
print(f"{page_num} page...")
response = requests.get(f"<https://search.naver.com/search.naver?where=news&sm=tab_jum&query={keyword}&start={i}>")
html = response.text
soup = BeautifulSoup(html, "html.parser")
articles = soup.select("div.info_group")
for article in articles:
links = article.select("a.info")
if len(links) >= 2:
url = links[1].attrs["href"]
response = requests.get(url, headers={'User-agent': 'Mozila/5.0'})
html = response.text
soup_sub = BeautifulSoup(html, "html.parser")
# separation
if "entertain" in response.url:
content = soup_sub.select_one("#articeBody")
elif "sports" in response.url:
content = soup_sub.select_one("#newsEndContents")
# delete unnecessary elements
divs = content.select("div")
for div in divs:
div.decompose()
paragraphs = content.select("p")
for p in paragraphs:
p.decompose()
else:
content = soup_sub.select_one("#dic_area")
print("========BODY========\\n", content.text.strip())
total_content += content.text.strip()
article_num += 1
time.sleep(0.3)
is_last_page = soup.select_one("a.btn_next").attrs["aria-disabled"]
if is_last_page == "true":
print("last page")
break
page_num += 1
print(f"{article_num} articles 크롤링 완료")
pyperclip.copy(total_content)
pyautogui.alert('클립보드에 복사되었습니다.')
클립보드에 복사된 텍스트를 아래의 링크를 통해 중요한 단어를 더 잘보이게 할 수 있다.
Create word clouds – WordItOut
Create word clouds – WordItOut
Create a new Word listOriginal source Generating from a new source will not change your current word cloud settings.However, changes made to individual words will be lost because a new word list will be created.
worditout.com
3. 네이버 금융 크롤링한 결과를 엑셀에 저장하기
아래 4가지 재무지표를 기준으로 정해 크롤링(참고)
PER
- 주가수익비율
- 주가가 회사 1주당 수익의 몇배가 되는지 나타내는 지표
- 낮을수록 저평가
- 보통 10이하가 좋다(물론 -는 적자)
ROE
- 자기자본이익률
- 순자산대비 얼마나 많은돈을 벌고 있는지 나타내는 지표
- 15% 이상이 좋다
PBR
- 주가순자산비율
- 낮을수록 저평가
- 1이하가 좋다
유보율
- 잉여금/자본금
- 유보율이 높다 == 잉여금이 많다(재무가 탄탄하다)
- 높을수록 좋다
조건: requests만 사용(Selenium X)
고려사항 - nth-child 사용, 데이터 전처리 - 결측치 제거!! (N/A값을 제외)
1. 데이터 수집하기
import requests
from bs4 import BeautifulSoup
# decoded url
url = '<https://finance.naver.com/sise/field_submit.naver?menu=market_sum&returnUrl=http://finance.naver.com/sise/sise_market_sum.naver?&page=&fieldIds=per&fieldIds=roe&fieldIds=pbr&fieldIds=reserve_ratio>'
resp = requests.get(url)
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
table_rows = soup.select("table.type_2 > tbody > tr[onmouseover=\\"mouseOver(this)\\"]")
for tr in table_rows:
# nth-child 사용하기
name = tr.select_one('td:nth-child(2)').text
per = tr.select_one('td:nth-child(7)').text
roe = tr.select_one('td:nth-child(8)').text
pbr = tr.select_one('td:nth-child(9)').text
reverse_ratio = tr.select_one('td:nth-child(10)').text
#데이터 전처리하기
#N/A 값이 아닐 경우에만
if per != 'N/A' and roe != 'N/A' and roe != 'N/A' and pbr != 'N/A' and reverse_ratio != 'N/A':
per = float(per.replace(',', ''))
roe = float(roe.replace(',', ''))
pbr = float(pbr.replace(',', ''))
reverse_ratio = float(reverse_ratio.replace(',', ''))
print(name, per,roe, pbr, reverse_ratio)
2. 여러 페이지 크롤링하기
import requests
from bs4 import BeautifulSoup
import pyautogui
lastPage = int(pyautogui.prompt('page? (1page = 50)'))
for curPage in range(1,lastPage + 1):
# decoded url(HTTP 302)
url = f'<https://finance.naver.com/sise/field_submit.naver?menu=market_sum&returnUrl=http://finance.naver.com/sise/sise_market_sum.naver?page={curPage}&fieldIds=per&fieldIds=roe&fieldIds=pbr&fieldIds=reserve_ratio>'
resp = requests.get(url)
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
table_rows = soup.select("table.type_2 > tbody > tr[onmouseover=\\"mouseOver(this)\\"]")
for tr in table_rows:
# nth-child 사용하기
name = tr.select_one('td:nth-child(2)').text
per = tr.select_one('td:nth-child(7)').text
roe = tr.select_one('td:nth-child(8)').text
pbr = tr.select_one('td:nth-child(9)').text
reverse_ratio = tr.select_one('td:nth-child(10)').text
#데이터 전처리하기
#N/A 값이 아닐 경우에만
if per != 'N/A' and roe != 'N/A' and roe != 'N/A' and pbr != 'N/A' and reverse_ratio != 'N/A':
per = float(per.replace(',', ''))
roe = float(roe.replace(',', ''))
pbr = float(pbr.replace(',', ''))
reverse_ratio = float(reverse_ratio.replace(',', ''))
print(name, per,roe, pbr, reverse_ratio)
3. 데이터 엑셀파일로 저장하기
import requests
from bs4 import BeautifulSoup
import pyautogui
import openpyxl
wb = openpyxl.Workbook()
ws = wb.create_sheet('코스피')
ws.append(["종목명", "PER", "ROE", "PBR", "유보율"])
lastPage = int(pyautogui.prompt('page? (1page = 50)'))
for curPage in range(1,lastPage + 1):
# decoded url(HTTP 302)
url = f'<https://finance.naver.com/sise/field_submit.naver?menu=market_sum&returnUrl=http://finance.naver.com/sise/sise_market_sum.naver?page={curPage}&fieldIds=per&fieldIds=roe&fieldIds=pbr&fieldIds=reserve_ratio>'
resp = requests.get(url)
html = resp.text
soup = BeautifulSoup(html, 'html.parser')
table_rows = soup.select("table.type_2 > tbody > tr[onmouseover=\\"mouseOver(this)\\"]")
for tr in table_rows:
# nth-child 사용하기
name = tr.select_one('td:nth-child(2)').text
per = tr.select_one('td:nth-child(7)').text
roe = tr.select_one('td:nth-child(8)').text
pbr = tr.select_one('td:nth-child(9)').text
reverse_ratio = tr.select_one('td:nth-child(10)').text
#데이터 전처리하기
#N/A 값이 아닐 경우에만
if per != 'N/A' and roe != 'N/A' and roe != 'N/A' and pbr != 'N/A' and reverse_ratio != 'N/A':
per = float(per.replace(',', ''))
roe = float(roe.replace(',', ''))
pbr = float(pbr.replace(',', ''))
reverse_ratio = float(reverse_ratio.replace(',', ''))
print(name, per,roe, pbr, reverse_ratio)
# 엑셀 행 추가
ws.append([name, per,roe, pbr, reverse_ratio])
wb.save('코스피500.xlsx')
4. 마무리
네이버는 초보자가 크롤링을 연습하기 정말 편하다.
requests, BeautifulSoup에 익숙해지기 좋은 사이트들ㅎㅎ
다음은 동적크롤링 Selenium! 여기서도 네이버를 거치지 않을 수 없었다...
'Machine Learning' 카테고리의 다른 글
[머신러닝 완벽가이드 정리] 사이킷런(scikit-learn) (0) | 2023.08.27 |
---|