파이썬/라이브러리(API)

파이썬 BeautifulSoup select 사용해서 HTML 웹페이지 크롤링하기

코데방 2024. 3. 22.
728x90

BeautifulSoup select 메소드

 

기존 포스팅에서는 find와 find_all 메소드를 통해 HTML 페이지를 파싱해 원하는 태그를 찾는 방식을 사용했습니다.

 

하지만 select 메소드를 사용하면 좀 더 편하게 태그를 찾을 수 있어서 이번에는 해당 방법으로 네이버 증권의 일별 코스피 지수를 크롤링해보도록 하겠습니다 .

 

 

 

 

BeautifulSoup select 사용해 웹페이지 크롤링하기

 

먼저 HTML의 표 정보를 가져와서 저장하고, 데이터프레임까지 넣을 수 있도록 세 개 라이브러리를 가져오겠습니다.

import requests
from bs4 import BeautifulSoup
import pandas as pd

 

 

 

 

select 메소드를 사용하기 위해 BeautifulSoup의 형식 옵션값으로 'lxml'을 줍니다.

 

url = 'https://finance.naver.com/sise/sise_index_day.naver?code=KOSPI'
resp = requests.get(url)
soup = BeautifulSoup(resp.content, 'lxml')

 

 

 

 

 

select 메소드는 파라미터로 "상위태그.클래스명" 또는 "상위태그.아이디명" 형태로 사용해줍니다.

하위태그의 경우 대괄호[ ] 안에 넣어서 찾아주면 됩니다. 여러모로 find 메소드보다 편리한 것 같습니다.

 

먼저 날짜부터 가져와보겠습니다. 날짜는 <td> 태그 안에 "date"라는 클래스로 구분되어 있습니다.

 

 

 

아래와 같이 select를 하면 깔끔하게 모든 날짜 데이터를 가져올 수 있습니다. 

 

select는 모든 태그를 가져와 리스트 형태로 반환하는데 단 1개만 있더라도 리스트 안에 담아서 줍니다. 따라서 리스트의 길이와 관계 없이 슬라이싱을 해서 사용해야합니다. 

date = soup.select("td.date")

 

 

 

리스트이므로 깔끔하게 for문과 .text를 통해 태그 사이의 문자만 가져와 새로운 리스트에 담아줄 수 있습니다.

date_lst = [d.text for d in date]

 

 

 

 

이번에는 전일비입니다. 마찬가지로 별도 클래스로 구분되어있기 때문에 날짜와 동일하게 가져오면 됩니다.

 

rate_down = soup.select("td.rate_down")
rate_down_lst = [r.text for r in rate_down]

 

 

 

 

양쪽 공백의 메타문자가 포함되었습니다. strip()을 이용해 공백을 지워줍니다.

rate_down = soup.select("td.rate_down")
rate_down_lst = [r.text.strip() for r in rate_down]

 

 

 

 

이제 남은 부분들입니다. 체결가, 등락률, 거래량, 거래대금은 따로 구분자가 없고 "number_1"이라는 클래스명을 공유합니다. 

 

대신 표 형태로 반복되고 있기 때문에 순서의 규칙이 있습니다.

 

soup.select("td.number_1")

 

 

 

 

 

따라서 0번부터 시작해 4 단위로 건너뛰므로 for문 또는 슬라이싱을 잘해서 가져와주면 됩니다. 

위 구문은 새로운 리스트에 텍스트를 따로 옮겨줬지만 어차피 soup.select()에서 반환하는 객체가 리스트이기 때문에 아래와 같이 한 번에 사용해서 담을 수 있습니다. 

prices, rates, volumns, amounts = [], [], [], []

prices = [p.text for p in soup.select("td.number_1")[0::4]] # 체결가
rates = [r.text.strip() for r in soup.select("td.number_1")[1::4]]  # 등락률
volumns = [v.text for v in soup.select("td.number_1")[2::4]] # 거래량
amounts = [a.text for a in soup.select("td.number_1")[3::4]] # 거래대금

for i in range(len(prices)):
    print(prices[i], rates[i], volumns[i], amounts[i])

 

 

 

 

 

게시판 또는 표 마지막 페이지 가져오기

 

마지막 페이지까지 모두 크롤링하기 위해서는 마지막 페이지 정보를 알아야합니다. 

마지막 페이지는 "맨 뒤" 링크에서 가리키는 숫자를 찾아오면 됩니다. 

 

 

 

 

<td class="pgRR">에 마지막 페이지 링크가 들어있고, 링크의 마지막에 페이지 번호가 있습니다. 크롤링해와서 숫자만 따로 가져와줍니다. 

 

하위 태그의 경우 ("상위태그.클래스명 하위태그") 형태로, 공백을 주면 됩니다. 물론 select("").select("") 형태로도 사용이 가능합니다. 

 

그리고 select는 언제나 리스트 객체로 반환하므로 인덱싱 [ ]을 해줘야합니다. 

 

마지막으로 하위 태그 안에 있는 항목을 조회할 때도 대괄호 ["href"] 를 사용해주면 됩니다. 

last_page = int(soup.select("td.pgRR a")[0]["href"].split("=")[-1])

 

 

 

 

이제 마지막 페이지까지 for문을 돌려 같은 작업을 반복하면 됩니다. 

 

물론 마지막 페이지에서 가져올게 더 이상 없을 경우 break를 해주는 if문이나, 색깔에 따라 전일비에 기호를 붙여주는 등 추가 코드가 필요하지만 아래 코드를 작성할 수 있다면 크게 어려운 부분이 없기 때문에 생략합니다. 

 

* 파이썬 append, extend의 차이점

 

참고로 아래 코드에서 append를 사용할 경우 리스트 안에 리스트 형태로 모든 데이터가 들어갑니다. 각 컬럼에 들어갈 값의 리스트이기 때문에 각 리스트는 1차원 리스트가 되어야 하고, 이 때는 append 대신 extend를 사용해주면 됩니다.

 

extend는 append와 달리 리스트를 리스트에 넣더라도 내용만 넣어서 1차원 리스트로 만들어줍니다. 

 

dates, prices, diffs, rates, volumns, amounts = [],[],[],[],[],[]

for i in range(223,225):
    url = f'https://finance.naver.com/sise/sise_index_day.naver?code=KOSPI&page={i}'
    resp = requests.get(url)
    soup = BeautifulSoup(resp.content, 'lxml')
    
    dates.extend([d.text for d in soup.select("td.date")])# 날짜
    prices.extend([p.text for p in soup.select("td.number_1")[0::4]]) # 체결가
    diffs.extend([d.text.strip() for d in soup.select("td.rate_down")]) # 전일비
    rates.extend([p.text.strip() for p in soup.select("td.number_1")[1::4]]) # 체결가
    volumns.extend([v.text for v in soup.select("td.number_1")[2::4]]) # 거래량
    amounts.extend([a.text for a in soup.select("td.number_1")[3::4]]) # 거래대금

df_ksp200 = pd.DataFrame({'날짜' : dates, '체결가' : prices, '전일비' : diffs,
              '등락률' : rates, '거래량' : volumns, '거래대금' : amounts})

 

 

 

 

 

 

판다스 데이터프레임 데이터 날짜, 숫자 타입 바꾸기

 

위의 결과물은 모두 문자열로 되어있기 때문에 그래프를 그리거나 통계를 낼 수 없는 상태입니다. 따라서 판다스에서 제공해주는 기능으로 날짜는 날짜, 숫자는 모두 숫자 타입으로 바꿔주겠습니다.

 

먼저 날짜컬럼을 날짜 타입으로 바꿔줍니다. 

 

df_ksp200['날짜'] = pd.to_datetime(df_ksp200['날짜'])
df_ksp200.info()

 

 

 

 

다음으로 숫자로 바꾸기 위해 등락률에는 %를 없애고 거래량과 거래대금에서는 콤마를 없애줍니다. 

 

주의할 점은,

 

  • df_ksp200["등락률"].reaplce("찾을문자", "바꿀문자")

 

를 사용할 경우 무조건 해당 문자와 일치하는 값을 찾아 바꿉니다. 숫자 사이에 있는 콤마만 바뀌는게 아니라 그냥 콤마만 있는 값만 바뀐다는 의미입니다. 따라서 값의 일부 문자만 바꾸고 싶다면 아래와 같이 srt의 메소드를 사용해줘야합니다.

df_ksp200['등락률'] = df_ksp200['등락률'].str.replace('%', '')
df_ksp200['거래량'] = df_ksp200['거래량'].str.replace(',', '')
df_ksp200['거래대금'] = df_ksp200['거래대금'].str.replace(',', '')

 

 

 

 

이제 모든 데이터를 숫자로 바꿀 수 있도록 만들어줬으므로 형태를 바꿔줍니다. "to_numeric"을 이용해 자동으로 float/int형으로 바꿀 수도 있고, "astype()"을 이용해 원하는 형을 지정할 수도 있습니다.

df_ksp200['날짜'] = pd.to_datetime(df_ksp200['날짜'])
df_ksp200['등락률'] = pd.to_numeric(df_ksp200['등락률'])
df_ksp200['거래량'] = pd.to_numeric(df_ksp200['거래량'])
# df_ksp200['거래대금'] = pd.to_numeric(df_ksp200['거래대금'])
df_ksp200['거래대금'] = df_ksp200['거래대금'].astype(int)
df_ksp200.info()

 

 

 

 

 

728x90

댓글

💲 추천 글