Skip to content

Python, Beautiful Soup, pandas, Requests를 사용하여 HTML 테이블 データ를 추출, 정리, 내보내어 원활한 データ 분석을 수행합니다.

Notifications You must be signed in to change notification settings

bright-kr/scrape-html-tables-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

Scraping HTML Tables with Python

Promo

이 가이드는 Python에서 Beautiful Soup, pandas, Requests를 사용하여 HTML 테이블을 스크레이핑하는 방법을 설명합니다.

Prerequisites

Python 3.8 이상이 설치되어 있는지 확인하고, virtual environment를 생성한 다음, 다음 Python 패키지를 설치합니다:

  • Requests: 웹 서비스 및 API와 상호작용하기 위해 HTTP 요청를 전송하는 라이브러리로, 데이터 검색 및 제출을 가능하게 합니다.
  • Beautiful Soup: HTML 문서를 파싱하는 도구로, 웹 페이지에서 구조화된 탐색, 검색 및 데이터 추출을 가능하게 합니다.
  • pandas: 스크레이핑된 데이터를 분석, 정리 및 조직화하는 라이브러리로, CSV 또는 XLSX 같은 형식으로 내보내기를 지원합니다.
pip install requests beautifulsoup4 pandas

Understanding the Web Page Structure

이 튜토리얼에서는 2024년 최신 국가별 인구 수치를 제공하는 Worldometer website에서 데이터를 스크레이핑합니다.

HTML table on the web page

HTML 테이블을 확인하려면 테이블을 마우스 오른쪽 버튼으로 클릭하고 Inspect를 선택합니다. 그러면 Developer Tools 패널이 열리고, 해당하는 HTML 코드가 강조 표시됩니다:

Inspect element with selected element highlighted

테이블 구조는 <table> 태그(ID example2)로 시작하며, <th> 태그로 정의된 헤더 셀과 <tr> 태그로 정의된 행을 포함합니다. 각 행 안에서 <td> 태그가 데이터를 담는 개별 셀을 생성합니다.

Note:

 스크레이핑 전에 웹사이트의 개인정보 처리방침 및 서비스 약관을 검토하고 준수하여, 모든 데이터 사용 및 자동화된 접근 제한을 준수하고 있는지 확인하시기 바랍니다.

Sending an HTTP Request to Access the Web Page

HTTP 요청를 전송하여 웹 페이지에 접근하려면 Python 파일(eg html_table_scraper.py)을 생성하고 requests, BeautifulSoup, pandas 패키지를 import합니다:

# import packages
import requests
from bs4 import BeautifulSoup
import pandas as pd

그런 다음 스크레이핑하려는 웹 페이지의 URL을 정의하고 https://www.worldometers.info/world-population/population-by-country/를 사용하여 해당 웹 페이지로 GET 요청를 보냅니다:

# Send a request to the website to get the page content
url = 'https://www.worldometers.info/world-population/population-by-country/'

Requests의 get() 메서드를 사용하여 요청를 보내고 응답가 성공적인지 확인합니다:

# Get the content of the URL
response = requests.get(url)
 
# Check the status of the response.
if response.status_code == 200:
    print("Request was successful!")
else:
    print(f"Error: {response.status_code} - {response.text}")

이 코드는 지정된 URL로 GET 요청를 전송한 다음 응답의 상태를 확인합니다. 200 응답는 요청가 성공했음을 의미합니다.

Python 스크립트를 실행합니다:

python html_table_scraper.py

출력은 다음과 같이 표시되어야 합니다:

Request was successful!

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

Parsing the HTML Using Beautiful Soup

Beautiful Soup는 Web스크레이핑 시 흔히 발생하는 지저분하거나 깨진 HTML을 처리하도록 설계되었습니다. 이를 통해 다음을 수행할 수 있습니다:

  • HTML을 파싱하여 인구 데이터 테이블을 찾습니다.
  • 테이블 헤더를 추출합니다.
  • 각 테이블 행에서 데이터를 수집합니다.

파싱을 시작하려면 Beautiful Soup 객체를 생성합니다:

# Parse the HTML content using BeautifulSoup
soup = BeautifulSoup(response.content, 'html.parser')

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

# Find the table containing population data
table = soup.find('table', attrs={'id': 'example2'})

Collecting Table Headers

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

# Collect the headers from the table
headers = []

# Locate the header row within the <thead> tag
header_row = table.find('thead').find_all('th')

for th in header_row:
    # Add header text to the headers list
    headers.append(th.text.strip())

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

Collecting Table Row Data

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

# Initialize an empty list to store our data
data = []

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

# Loop through each row in the table (skipping the header row)
for tr in table.find_all('tr')[1:]:
    # Create a list of the current row's data
    row = []
    
# Find all data cells in the current row
    for td in tr.find_all('td'):
        # Get the text content of the cell and remove extra spaces
        cell_data = td.text.strip()
        
        # Add the cleaned cell data to the row list
        row.append(cell_data)
        
    # After getting all cells for this row, add the row to our data list
    data.append(row)
    
# Convert the collected data into a pandas DataFrame for easier handling
df = pd.DataFrame(data, columns=headers)

# Print the DataFrame to see the number of rows and columns
print(df.shape)

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

<td> HTML 태그에 대해 .text 속성으로 텍스트 콘텐츠를 추출하고, .strip() 메서드를 적용하여 텍스트의 앞뒤 공백을 제거합니다. 정리된 셀 데이터는 row 리스트에 추가됩니다. 현재 행의 모든 셀 처리가 끝나면, 전체 행이 data 리스트에 추가됩니다. 마지막으로 수집된 데이터를 headers 리스트로 정의된 열 이름과 함께 pandas DataFrame으로 변환하고, 데이터의 shape를 출력합니다.

전체 Python 스크립트는 다음과 같아야 합니다:

# Import packages
import requests
from bs4 import BeautifulSoup
import pandas as pd

# Send a request to the website to get the page content
url = 'https://www.worldometers.info/world-population/population-by-country/'

# Get the content of the URL
response = requests.get(url)

# Check if the request was successful
if response.status_code == 200:
    # Parse the HTML content using Beautiful Soup
    soup = BeautifulSoup(response.content, 'html.parser')

    # Find the table containing population data by its ID
    table = soup.find('table', attrs={'id': 'example2'}) 

    # Collect the headers from the table
    headers = []

    # Locate the header row within the <thead> HTML tag
    header_row = table.find('thead').find_all('th')

    for th in header_row:
        # Add header text to the headers list
        headers.append(th.text.strip())

    # Initialize an empty list to store our data
    data = []

    # Loop through each row in the table (skipping the header row)
    for tr in table.find_all('tr')[1:]:

        # Create a list of the current row's data
        row = []

        # Find all data cells in the current row
        for td in tr.find_all('td'):
            # Get the text content of the cell and remove extra spaces
            cell_data = td.text.strip()

            # Add the cleaned cell data to the row list
            row.append(cell_data)

        # After getting all cells for this row, add the row to our data list
        data.append(row)

    # Convert the collected data into a pandas DataFrame for easier handling
    df = pd.DataFrame(data, columns=headers)

    # Print the DataFrame to see the collected data
    print(df.shape)
else:
    print(f"Error: {response.status_code} - {response.text}")

터미널에서 다음 명령으로 Python 스크립트를 실행합니다:

python html_table_scraper.py

출력은 다음과 같이 표시되어야 합니다:

(234,12)

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

print(df.head(10))

Top ten rows from the scraped table

Cleaning and Structuring the Data

HTML 테이블에서 스크레이핑된 데이터를 정리하는 작업은 분석에서의 일관성, 정확성, 사용성을 위해 매우 중요합니다. 원시 데이터에는 누락 값, 서식 오류, 원치 않는 문자, 잘못된 데이터 타입 등이 포함될 수 있으며, 이는 모두 신뢰할 수 없는 결과로 이어질 수 있습니다. 적절한 정리는 데이터셋을 표준화하고 분석을 위해 의도한 구조에 맞추는 데 도움이 됩니다.

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

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

Renaming Column Names

pandas는 열 이름을 업데이트하기 위한 rename() 메서드를 제공하여, 더 설명적이거나 다루기 쉽게 만들 수 있습니다. columns 매개변수에 딕셔너리를 전달하면 되며, 키는 현재 이름, 값은 새 이름을 나타냅니다. 이 메서드를 사용하여 다음 열 이름을 업데이트합니다:

  • #Rank
  • Yearly changeYearly change %
  • World ShareWorld Share %
# Rename columns
df.rename(columns={'#': 'Rank'}, inplace=True)
df.rename(columns={'Yearly Change': 'Yearly Change %'}, inplace=True)
df.rename(columns={'World Share': 'World Share %'}, inplace=True)

# Show the first 5 rows
print(df.head())

이제 열은 다음과 같이 표시되어야 합니다:

Column names after renaming

Replacing Missing Values

누락 값은 평균이나 합계 같은 계산을 왜곡하여 부정확한 인사이트로 이어질 수 있습니다. 분석을 수행하기 전에 이러한 공백을 적절한 값으로 제거, 대체 또는 채워야 합니다.

Urban Pop % 열에는 현재 N.A.로 표시된 누락 값이 포함되어 있습니다. pandas의 replace() 메서드를 사용하여 N.A.0%로 다음과 같이 대체합니다:

# Replace 'N.A.' with '0%' in the 'Urban Pop %' column
df['Urban Pop %'] = df['Urban Pop %'].replace('N.A.', '0%')

Removing Percentage Signs and Convert Data Types

Yearly Change %, Urban Pop %, World Share % 열에는 % 기호가 포함된 숫자(예: 37.0%)가 들어 있어 평균이나 표준편차 계산과 같은 직접적인 수학 연산이 불가능합니다. 이를 해결하기 위해 replace() 메서드로 %를 제거한 다음, astype()를 사용하여 값을 float로 변환합니다.

# Remove the '%' sign and convert to float
df['Yearly Change %'] = df['Yearly Change %'].replace('%', '', regex=True).astype(float)
df['Urban Pop %'] = df['Urban Pop %'].replace('%', '', regex=True).astype(float)
df['World Share %'] = df['World Share %'].replace('%', '', regex=True).astype(float)

# Show the first 5 rows
df.head()

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

출력은 다음과 같이 표시되어야 합니다:

Top five rows

Removing Commas and Convert Data Types

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

이를 해결하기 위해 replace()astype()를 적용하여 쉼표를 제거하고 숫자를 정수 데이터 타입으로 변환할 수 있습니다:

# Remove commas and convert to integers
columns_to_convert = [
    'Population (2024)', 'Net Change', 'Density (P/Km²)', 'Land Area (Km²)',
    'Migrants (net)'
]

for column in columns_to_convert:
    # Ensure the column is treated as a string first
    df[column] = df[column].astype(str)

    # Remove commas
    df[column] = df[column].str.replace(',', '')

    # Convert to integers
    df[column] = df[column].astype(int)

이 코드는 처리할 열에 대해 columns_to_convert라는 리스트를 생성합니다. 각 열에 대해 먼저 astype(str)로 값을 문자열로 변환하고, str.replace(',', '')를 사용해 쉼표를 제거한 다음, astype(int)로 정수로 변환하여 수학 연산을 수행할 수 있도록 데이터를 준비합니다.

Changing Data Types for Numerical Columns

Rank, Med. Age, Fert. Rate 열은 숫자를 포함하고 있음에도 object로 저장되어 있습니다. 수학 연산을 가능하게 하려면 이러한 열을 정수 또는 부동소수점으로 변환합니다:

# Convert to integer or float data types and integers

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 열의 값을 float 데이터 타입으로 변환합니다.

마지막으로 head() 메서드를 사용하여 정리된 데이터가 올바른 데이터 타입을 갖는지 확인합니다:

print(df.head(10))

출력은 다음과 같이 표시되어야 합니다:

Top ten rows of the cleaned data

Exporting Cleaned Data to CSV

데이터를 정리한 후에는 CSV 파일로 내보내어 향후 분석 및 공유를 위해 저장합니다. pandas의 to_csv() 메서드를 사용하여 DataFrame을 world_population_by_country.csv라는 파일로 내보냅니다:

# Save the data to a file
filename = 'world_population_by_country.csv'
df.to_csv(filename, index=False)

Conclusion

복잡한 웹사이트에서 데이터를 추출하는 작업은 어렵고 시간이 많이 소요될 수 있습니다. 시간을 절약하고 작업을 더 쉽게 하기 위해 Bright Data Web Scraper API 사용을 고려해 보시기 바랍니다. 이 강력한 도구는 사전 구축된 스크레이핑 솔루션을 제공하여, 최소한의 기술적 지식만으로도 복잡한 웹사이트에서 데이터를 추출할 수 있도록 합니다.

가입하고 무료 Web Scraper API 체험을 시작해 보시기 바랍니다!

About

Python, Beautiful Soup, pandas, Requests를 사용하여 HTML 테이블 データ를 추출, 정리, 내보내어 원활한 データ 분석을 수행합니다.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published