티스토리 뷰

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

'머신러닝'에 관한 기사 텍스트 80000자로 워드 클라우드를 생성했을 때

 

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')

코스피500.xlsx
0.02MB

 

 

4. 마무리

네이버는 초보자가 크롤링을 연습하기 정말 편하다.

requests, BeautifulSoup에 익숙해지기 좋은 사이트들ㅎㅎ

다음은 동적크롤링 Selenium! 여기서도 네이버를 거치지 않을 수 없었다...

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
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
글 보관함