파이썬으로 HTML 테이블 스크래핑하는 방법

이 단계별 가이드에서 Beautiful Soup, pandas, Requests를 사용한 Python으로 HTML 테이블을 추출하는 방법을 배워보세요.
4 분 읽기
How to Scrape Financial Data blog image

웹 스크래핑은 다양한 도구나 프로그램을 활용해 웹사이트에서 대량의 데이터를 추출하고 수집하는 자동화 기술입니다. 주로 열과 행으로 구성된 데이터를 포함하는 HTML 테이블을 추출하는 데 사용됩니다. 수집된 데이터는 분석이나 연구 목적으로 활용될 수 있습니다. 더 자세한 가이드는HTML 웹 스크래핑 관련 글을 참고하세요.

이 튜토리얼에서는 Python을 사용하여 웹사이트에서 HTML 테이블을 스크래핑하는 방법을 알려드립니다.

필수 조건

이 튜토리얼을 시작하기 전에Python 버전 3.8 이상을 설치하고가상 환경을 생성해야 합니다. Python을 이용한 웹 스크래핑이 처음이라면이 글이유용한 시작점이 될 것입니다.

환경을 생성한 후 다음 Python 패키지를 설치하세요:

  • Requests
  • Beautiful Soup
  • pandas

패키지는 다음 명령어로 설치할 수 있습니다:

pip install requests beautifulsoup4 pandas

웹 페이지 구조 이해

이 튜토리얼에서는Worldometer 웹사이트의 데이터를 스크래핑합니다. 이 웹 페이지는 2024년 기준 전 세계 국가별 인구 수치를 포함한 최신 데이터를 제공합니다:

HTML table on the web page

HTML 테이블 구조를 찾으려면 테이블(위 스크린샷 참조)을 마우스 오른쪽 버튼으로 클릭하고 ‘검사’를 선택하세요. 이 작업은 개발자 도구 패널을 열어 페이지의 HTML 코드를 표시하며, 선택한 요소가 강조 표시됩니다:

Inspect element with selected element highlighted

ID가 example2인 <table> 태그는 테이블 구조의 시작을 정의합니다. 이 테이블은 <th> 태그로 헤더를 가지며, 행은 <tr> 태그로 정의됩니다. 각 <tr> 은 테이블의 새로운 수평 행을 나타냅니다. 각 <tr> 내부에서 <td> 태그는 해당 행 내 개별 셀을 생성하며, 각 셀에 표시될 데이터를 담습니다.

참고: 스크래핑을 수행하기 전에 웹사이트의 개인정보 처리방침 및 이용약관을 검토하고 준수하여 데이터 사용 및 자동화된 접근에 대한 모든 제한 사항을 따르는 것이 중요합니다.

웹 페이지 접근을 위한 HTTP 요청 전송

HTTP 요청을 보내 웹 페이지에 접근하려면 Python 파일(예: html_table_scraper.py)을 생성하고 requests, BeautifulSoup, pandas 패키지를 임포트하세요:

# 패키지 임포트
import requests
from bs4 import BeautifulSoup
import pandas as pd

그런 다음 스크래핑할 웹 페이지의 URL을 정의하고 https://www.worldometers.info/world-population/population-by-country/를 사용하여 해당 웹 페이지에 GET 요청을 전송합니다:

# 웹사이트에 요청을 보내 페이지 콘텐츠 가져오기
url = 'https://www.worldometers.info/world-population/population-by-country/'

응답이 성공적인지 확인하려면 Requests의 get() 메서드를 사용하여 요청을 전송합니다:

# URL의 콘텐츠 가져오기
response = requests.get(url)
 
# 응답 상태 확인
if response.status_code == 200:
    print("요청 성공!")
else:
    print(f"오류: {response.status_code} - {response.text}")

이 코드는 지정된 URL로 GET 요청을 보낸 후 응답 상태를 확인합니다. 200 응답은 요청이 성공했음을 나타냅니다.

터미널에서 Python 스크립트를 실행하려면 다음 명령어를 사용하세요:

python html_table_scraper.py

출력 결과는 다음과 같아야 합니다:

Request was successful!

GET 요청이 성공했으므로 이제 HTML 테이블을 포함한 전체 웹 페이지의 HTML 콘텐츠를 확보했습니다.

Beautiful Soup을 사용한 HTML 파싱

Beautiful Soup은 형식이 불량하거나 손상된 HTML 콘텐츠를 처리할 수 있으며, 이는 웹 페이지 스크래핑 시 흔히 발생합니다. 여기서는 Beautiful Soup 패키지를 사용하여 다음 작업을 수행합니다:

  • 웹 페이지의 HTML 콘텐츠를 파싱하여 인구 데이터를 표시하는 테이블을 찾습니다.
  • 테이블 헤더를 수집합니다.
  • 테이블 행에 표시된 모든 데이터를 수집합니다.

수집한 콘텐츠를 파싱하려면 Beautiful Soup 객체를 생성합니다:

# BeautifulSoup을 사용하여 HTML 콘텐츠 파싱
soup = BeautifulSoup(response.content, 'html.parser')

다음으로, id 속성이 "example2"인 HTML 내 테이블 요소를 찾습니다. 이 테이블에는 2024년 국가별 인구가 포함되어 있습니다:

# 인구 데이터가 포함된 테이블 찾기
table = soup.find('table', attrs={'id': 'example2'})

테이블 헤더 수집

테이블 헤더는 <thead><th> HTML 태그에 위치합니다. Beautiful Soup 패키지의 find() 메서드를 사용하여 <thead> 태그 내 데이터를 추출하고, find_all() 메서드로 모든 헤더를 수집합니다:

# 테이블에서 헤더 수집
headers = []

# <thead> 태그 내 헤더 행 위치 파악
header_row = table.find('thead').find_all('th')

for th in header_row:
    # 헤더 텍스트를 headers 리스트에 추가
    headers.append(th.text.strip())

이 코드는 headers라는 빈 Python 리스트를 생성하고, <thead> HTML 태그를 찾아 <th> HTML 태그 내의 모든 헤더를 찾은 후, 수집된 각 헤더를 headers 리스트에 추가합니다.

테이블 행 데이터 수집

각 행의 데이터를 수집하려면, 스크랩된 데이터를 저장할 빈 Python 리스트 data를 생성합니다:

# 데이터를 저장할 빈 리스트 초기화
data = []

그런 다음 find_all() 메서드를 사용하여 테이블의 각 행 데이터를 추출하고 파이썬 리스트에 추가합니다:

# 테이블의 각 행을 순회합니다(헤더 행은 건너뜁니다)
for tr in table.find_all('tr')[1:]:
    # 현재 행의 데이터 목록 생성
    row = []
    
# 현재 행의 모든 데이터 셀 찾기
    for td in tr.find_all('td'):
        # 셀의 텍스트 콘텐츠 가져오고 불필요한 공백 제거
        cell_data = td.text.strip()
        
        # 정리된 셀 데이터를 행 리스트에 추가
        row.append(cell_data)
        
    # 해당 행의 모든 셀을 수집한 후, 행을 데이터 목록에 추가
    data.append(row)
    
# 수집된 데이터를 편리한 처리를 위해 pandas DataFrame으로 변환
df = pd.DataFrame(data, columns=headers)

# DataFrame 출력하여 행과 열 수 확인
print(df.shape)

이 코드는 테이블 내에서 발견된 모든 <tr> HTML 태그를 두 번째 행부터(헤더 행은 건너뛰고) 순회합니다. 각 행(<tr>)에 대해 해당 행의 셀 데이터를 저장할 빈 목록 row를 생성합니다. row 내부에서 코드는 find_all() 메서드를 사용하여 행의 개별 데이터 셀을 나타내는 모든 <td> HTML 태그를 찾습니다.

<td> HTML 태그에 대해, 코드는 .text 속성을사용하여 텍스트 콘텐츠를 추출하고 .strip() 메서드를 적용하여 텍스트의 선행 또는 후행 공백을 제거합니다. 정리된 셀 데이터는 row 리스트에 추가됩니다. 현재 행의 모든 셀을 처리한 후, 전체 행이 데이터 리스트에 추가됩니다. 마지막으로 수집된 데이터를 헤더 목록으로 정의된 열명을 가진 pandas DataFrame으로 변환하고 데이터의 형상을 표시합니다.

전체 Python 스크립트는 다음과 같습니다:

# 패키지 임포트
import requests
from bs4 import BeautifulSoup
import pandas as pd

# 웹사이트에 요청을 보내 페이지 콘텐츠 가져오기
url = 'https://www.worldometers.info/world-population/population-by-country/'

# URL의 콘텐츠 가져오기
response = requests.get(url)

# 요청 성공 여부 확인
if response.status_code == 200:
    # Beautiful Soup로 HTML 콘텐츠 파싱
    soup = BeautifulSoup(response.content, 'html.parser')

    # 인구 데이터가 포함된 테이블 ID로 찾기
    table = soup.find('table', attrs={'id': 'example2'}) 

    # 테이블에서 헤더 수집
    headers = []

    # <thead> HTML 태그 내 헤더 행 위치 파악
    header_row = table.find('thead').find_all('th')

    for th in header_row:
        # 헤더 텍스트를 headers 리스트에 추가
        headers.append(th.text.strip())

    # 데이터를 저장할 빈 리스트 초기화
    data = []

    # 테이블의 각 행을 순회 (헤더 행 제외)
    for tr in table.find_all('tr')[1:]:

        # 현재 행의 데이터 리스트 생성
        row = []

        # 현재 행의 모든 데이터 셀 찾기
        for td in tr.find_all('td'):
            # 셀의 텍스트 콘텐츠 가져오고 불필요한 공백 제거
            cell_data = td.text.strip()

            # 정리된 셀 데이터를 행 목록에 추가
            row.append(cell_data)

        # 해당 행의 모든 셀을 가져온 후, 행을 데이터 목록에 추가
        data.append(row)

    # 수집된 데이터를 판다스 DataFrame으로 변환하여 처리 용이성 확보
    df = pd.DataFrame(data, columns=headers)

    # 수집된 데이터를 확인하기 위해 DataFrame 출력
    print(df.shape)
else:
    print(f"오류: {response.status_code} - {response.text}")

터미널에서 다음 명령어로 Python 스크립트를 실행하세요:

python html_table_scraper.py

출력 결과는 다음과 같아야 합니다:

(234,12)

지금까지 HTML 테이블에서 234개의 행과 12개의 열을 성공적으로 추출했습니다.

다음으로 pandas의 head() 메서드와 print() 를 사용하여 추출된 데이터의 첫 10개 행을 확인합니다:

print(df.head(10))
Top ten rows from the scraped table

데이터 정리 및 구조화

HTML 테이블에서 데이터를 스크래핑할 때는 분석을 위한 일관성, 정확성 및 적절한 사용성을 보장하기 위해 데이터를 정리하는 것이 중요합니다. HTML 테이블에서 추출한 원시 데이터에는 누락된 값, 서식 문제, 원치 않는 문자 또는 잘못된 데이터 유형과 같은 다양한 문제가 포함될 수 있습니다. 이러한 문제는 부정확한 분석과 신뢰할 수 없는 결과로 이어질 수 있습니다. 적절한 정리는 데이터 세트를 표준화하고 분석을 위한 의도된 구조와 일치하도록 보장합니다.

이 섹션에서는 다음과 같은 데이터 정리 작업을 수행합니다:

  • 열 이름 변경
  • 행 데이터에 나타난 누락된 값 대체
  • 쉼표 제거 및 데이터 형식 올바른 형식으로 변환
  • 퍼센트 기호(%) 제거 및 데이터 유형을 올바른 형식으로 변환
  • 수치형 열의 데이터 유형 변경

컬럼 이름 변경

pandas에는 rename() 이라는 메서드가 있어 특정 열의 이름을 원하는 대로 변경할 수 있습니다. 이 메서드는 열 이름이 설명적이지 않거나 열 이름을 더 쉽게 작업할 수 있도록 변경하고자 할 때 유용합니다.

특정 열의 이름을 변경하려면 columns 매개변수에 사전(dictionary)을 전달합니다. 여기서 키는 현재 열 이름이고 값은 할당하려는 새 이름입니다. 다음 열 이름을 변경하려면 이 메서드를 적용하세요:

  • #순위
  • 연간 변화량을연간 변화율(%)로 변경
  • World ShareWorld Share %
# 열 이름 변경
df.rename(columns={'#': '순위'}, inplace=True)
df.rename(columns={'연간 변화': '연간 변화 %'}, inplace=True)
df.rename(columns={'세계 점유율': '세계 점유율 %'}, inplace=True)

# 첫 5개 행 표시
print(df.head())

이제 열은 다음과 같아야 합니다:

Column names after renaming

누락된 값 대체

데이터의 누락된 값은 평균이나 합계 같은 계산에 영향을 미쳐 부정확한 결과와 잘못된 통찰을 초래할 수 있습니다. 데이터 세트로 계산이나 분석을 수행하기 전에 누락된 값을 제거하거나 대체하거나 특정 값으로 채워야 합니다.

현재 '도시 인구 비율(% )’ 열에는 N.A.로 표시된 누락된 값이 포함되어 있습니다 . pandas의 replace() 메서드를 사용하여 N.A.를 0%로 대체합니다:

# '도시 인구 비율(%)' 열에서 'N.A.'를 '0%'로 대체
df['도시 인구 비율(%)'] = df['도시 인구 비율(%)'].replace('N.A.', '0%')

퍼센트 기호 제거 및 데이터 유형 변환

Yearly Change %, Urban Pop %, World Share % 열에는 숫자 값 뒤에 백분율 기호(예: 37.0%)가 붙어 있습니다. 이로 인해 분석을 위한 평균, 최대값, 표준편차 계산과 같은 수학적 연산을 수행하는 데 방해가 됩니다.

이를 해결하려면 replace() 메서드를 적용하여 % 기호를 제거한 후 astype() 메서드를 적용하여 분석을 위해 부동 소수점 데이터 유형으로 변환할 수 있습니다:

# '%' 기호 제거 및 float 변환
df['연간 변화율 %'] = df['연간 변화율 %'].replace('%', '', regex=True).astype(float)
df['도시 인구 비율 %'] = df['도시 인구 비율 %'].replace('%', '', regex=True).astype(float)
df['World Share %'] = df['World Share %'].replace('%', '', regex=True).astype(float)

# 첫 5개 행 표시
df.head()

이 코드는 정규 표현식을 사용한 replace() 메서드로 Yearly Change %, Urban Pop %, World Share % 열의 값에서 % 기호를 제거합니다. 그런 다음 astype(float)를 사용하여 정리된 값을 float 데이터 유형으로 변환합니다. 마지막으로 df.head()를 사용하여 DataFrame의 첫 5개 행을 표시합니다.

출력 결과는 다음과 같아야 합니다:

Top five rows

쉼표 제거 및 데이터 유형 변환

현재 Population (2024), Net Change, Density (P/Km²), Land Area (Km²), Migrants (net) 열에는 쉼표가 포함된 숫자 값(예: 1,949,236)이 있습니다. 이로 인해 분석을 위한 수학적 연산을 수행할 수 없습니다.

이를 해결하려면 replace()astype() 을 적용하여 쉼표를 제거하고 숫자를 정수 데이터 유형으로 변환할 수 있습니다:

# 쉼표 제거 및 정수형 변환
columns_to_convert = [
    'Population (2024)', 'Net Change', 'Density (P/Km²)', 'Land Area (Km²)',
    'Migrants (net)'
]

for column in columns_to_convert:
    # 열이 문자열로 처리되도록 보장
    df[column] = df[column].astype(str)

    # 쉼표 제거
    df[column] = df[column].str.replace(',', '')

    # 정수로 변환
    df[column] = df[column].astype(int)

이 코드는 처리해야 할 열의 이름을 포함하는 목록 columns_to_convert를 정의합니다. 목록의 각 열에 대해 astype(str)을 사용하여 열 값이 문자열로 처리되도록 합니다. 그런 다음 str.replace(',', '')를 사용하여 값에서 쉼표를 제거하고, astype(int)로 정리된 값을 정수로 변환하여 수학적 연산에 적합한 값으로 만듭니다.

수치형 열의 데이터 유형 변경

Rank, Med. Age, Fert. Rate 열은 객체 데이터 유형으로 저장되었지만 수치 값을 포함하는 데이터를 나타냅니다. 수학적 연산을 가능하게 하려면 이 열의 데이터를 정수형 또는 부동 소수점 데이터 유형으로 변환합니다:

# 정수 또는 부동 소수점 데이터 유형으로 변환

df['Rank'] = df['Rank'].astype(int)
df['Med. Age'] = df['Med. Age'].astype(int)
df['Fert. Rate'] = df['Fert. Rate'].astype(float)

이 코드는 RankMed. Age 열의 값을 정수 데이터 유형으로, Fert. Rate 열의 값을 부동 소수점 데이터 유형으로 변환합니다.

마지막으로 head() 메서드를 사용하여 정리된 데이터의 데이터 유형이 올바른지 확인합니다:

print(df.head(10))

출력 결과는 다음과 같아야 합니다:

Top ten rows of the cleaned data

데이터 정리가 완료되었으므로 이제평균, 최빈값과 같은 수학적 연산과상관관계와 같은 분석적 방법을 적용하여 데이터를 분석할 수 있습니다.

정제된 데이터 CSV로 내보내기

데이터 정제 후에는 향후 사용 및 분석을 위해 정리된 데이터를 저장하는 것이 중요합니다. 정리된 데이터를 CSV 파일로 내보내면 다른 사람과 쉽게 공유하거나 다양한 지원 도구 및 소프트웨어를 사용하여 추가 처리/분석할 수 있습니다.

pandas의 to_csv() 메서드를 사용하면 DataFrame의 데이터를 world_population_by_country.csv라는 CSV 파일로 내보낼 수 있습니다:

# 데이터를 파일로 저장
filename = 'world_population_by_country.csv'
df.to_csv(filename, index=False)

결론

Beautiful Soup 파이썬 패키지를 사용하면 HTML 문서를 파싱하고 HTML 테이블에서 데이터를 추출할 수 있습니다. 이 글에서는 데이터를 스크래핑하고 정리한 후 CSV 파일로 내보내는 방법을 배웠습니다.

이 튜토리얼은 간단했지만, 복잡한 웹사이트에서 데이터를 추출하는 것은 어렵고 시간이 많이 소요될 수 있습니다. 예를 들어, 페이지가 나누어진 HTML 테이블이나 부모 요소와 자식 요소 사이에 데이터가 중첩된 구조를 다루려면 레이아웃을 이해하기 위한 세심한 분석이 필요합니다. 또한 웹사이트 구조는 시간이 지남에 따라 변경될 수 있으므로 코드와 인프라의 지속적인 유지 관리가 필요할 수 있습니다.

시간을 절약하고 작업을 쉽게 하려면Bright Data 웹 스크레이퍼 API 사용을 고려해 보세요. 이 강력한 도구는 사전 구축된 스크래핑 솔루션을 제공하여 최소한의 기술 지식으로도 복잡한 웹사이트에서 데이터를 추출할 수 있게 합니다. API는 데이터 수집을 자동화하며 동적 콘텐츠, 자바스크립트로 렌더링된 페이지, CAPTCHA 인증과 같은 문제를 처리합니다.

지금 가입하여 무료 웹 스크레이퍼 API 체험을 시작하세요!