DEV Community

Cover image for 웹 테이블에서 Pandas DataFrame까지 30초
circobit
circobit

Posted on

웹 테이블에서 Pandas DataFrame까지 30초

웹사이트에서 완벽한 데이터셋을 찾았습니다. 이제 Pandas에 넣어야 합니다.

전통적인 방법:

import pandas as pd

# 웹사이트 구조가 단순하길 기도하며
tables = pd.read_html('https://example.com/data')

# 어떤 테이블인지 추측
df = tables[0]  # 맞을까? 일단 확인...

# 문제 발견
print(df.dtypes)
# 전부 'object' (문자열)
# 숫자에 쉼표가 있음
# 날짜를 파싱할 수 없음
# 열 이름에 공백이 있음

# 30분 동안 정리...
Enter fullscreen mode Exit fullscreen mode

더 빠른 방법을 보여드리겠습니다.

pd.read_html()의 문제점

Pandas의 read_html()은 편리하지만 한계가 있습니다:

  1. 테이블 선택 불가 — 모든 테이블을 가져옵니다. 어떤 인덱스가 필요한지 추측해야 합니다.

  2. 정제 없음 — "1,234,567" 같은 숫자가 문자열로 남습니다.

  3. CORS 문제 — 많은 사이트가 프로그래밍 방식의 접근을 차단합니다.

  4. JavaScript 렌더링 — 동적 테이블은 원시 HTML에 존재하지 않습니다.

  5. 인증 — 로그인된 콘텐츠에 접근할 수 없습니다.

빠른 스크립트에는 괜찮습니다. 실제 분석에는 더 나은 것이 필요합니다.

30초 워크플로

실제로 제가 하는 방법입니다:

1단계: 브라우저에서 내보내기 (5초)

HTML Table Exporter 사용:

  1. 원하는 테이블 클릭
  2. 확장 프로그램 아이콘 클릭
  3. "For Pandas" 프로필 선택
  4. 확장 프로그램 내에서 강조된 테이블을 CSV로 내보내기

확장 프로그램은 브라우저가 보는 것과 정확히 같은 것을 봅니다—JavaScript로 렌더링된 콘텐츠, 인증된 페이지, 전부 다.

2단계: Pandas에서 로드 (5초)

import pandas as pd

df = pd.read_csv('export.csv')
print(df.dtypes)
Enter fullscreen mode Exit fullscreen mode

끝입니다. 데이터가 이미 깨끗합니다.

"깨끗하다"의 실제 의미

"For Pandas" 프로필로 내보내면 확장 프로그램이 다음을 처리합니다:

숫자 정규화

전: "1.234.567,89" (유럽식 형식)
후:  1234567.89     (float)

전: "$1,234.56"
후:  1234.56
Enter fullscreen mode Exit fullscreen mode

CSV에는 Pandas가 올바르게 파싱하는 정규화된 숫자가 포함됩니다:

# 정제 없이:
df['revenue'].sum()  # TypeError: can only concatenate str

# 정제 후:
df['revenue'].sum()  # 4892341.50 ✓
Enter fullscreen mode Exit fullscreen mode

불리언 변환

전: "Yes", "No", "Y", "N", "True", "False"
후:  true, false
Enter fullscreen mode Exit fullscreen mode
# 필터가 즉시 작동
active_users = df[df['is_active'] == True]
Enter fullscreen mode Exit fullscreen mode

Null 처리

전: "-", "N/A", "n/a", "", "null", "—"
후:  (비어있음, NaN으로 파싱)
Enter fullscreen mode Exit fullscreen mode
# Null 감지가 작동
df['optional_field'].isna().sum()  # 정확한 카운트
Enter fullscreen mode Exit fullscreen mode

Snake Case 헤더

전: "Revenue ($M)", "User Count", "Growth Rate %"
후:  revenue_m, user_count, growth_rate
Enter fullscreen mode Exit fullscreen mode
# 깨끗한 열 접근
df['revenue_m']  # df['Revenue ($M)'] 대신
Enter fullscreen mode Exit fullscreen mode

실제 예시: FBRef 축구 통계

프리미어 리그 선수 통계를 FBRef에서 가져오고 싶다고 합시다.

기존 방법

import pandas as pd
import requests
from bs4 import BeautifulSoup

url = 'https://fbref.com/en/comps/9/stats/Premier-League-Stats'
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')

# 올바른 테이블 찾기 (많이 있음)
table = soup.find('table', {'id': 'stats_standard'})

# 헤더가 복잡해서 수동으로 파싱
# FBRef는 그룹화된 헤더 사용: "Playing Time"이 여러 열에 걸침
# 이것이 pd.read_html()을 깨뜨림

# 45분 후...
Enter fullscreen mode Exit fullscreen mode

새로운 방법

  1. 브라우저에서 FBRef 열기
  2. 확장 프로그램 클릭 → 테이블 선택 → "For Pandas" 프로필로 내보내기
  3. 로드:
df = pd.read_csv('fbref_stats.csv')
print(df.columns.tolist())
# ['player', 'nation', 'squad', 'playing_time_mp', 
#  'playing_time_starts', 'performance_gls', ...]
Enter fullscreen mode Exit fullscreen mode

그룹화된 헤더("Playing Time", "Performance")가 하위 헤더와 자동으로 병합됩니다.

더 이상 작성하지 않는 코드

매번 웹 스크래핑마다 작성하던 정리 코드입니다:

def clean_web_data(df):
    """더 이상 필요 없는 함수."""

    # 숫자 열 수정
    for col in df.select_dtypes(include='object'):
        # 숫자로 변환 시도
        try:
            # 통화 기호 제거
            cleaned = df[col].str.replace(r'[$€£¥]', '', regex=True)
            # 천 단위 구분자 제거
            cleaned = cleaned.str.replace(',', '')
            # 변환
            df[col] = pd.to_numeric(cleaned, errors='ignore')
        except:
            pass

    # 불리언 열 수정
    bool_map = {
        'yes': True, 'no': False,
        'true': True, 'false': False,
        'y': True, 'n': False,
        '1': True, '0': False,
    }
    for col in df.columns:
        if df[col].str.lower().isin(bool_map.keys()).all():
            df[col] = df[col].str.lower().map(bool_map)

    # Null 값 수정
    null_values = ['', '-', 'N/A', 'n/a', 'null', 'NULL', '', '']
    df = df.replace(null_values, np.nan)

    # 열 이름 수정
    df.columns = (df.columns
        .str.lower()
        .str.replace(r'[^a-z0-9]+', '_', regex=True)
        .str.strip('_'))

    return df
Enter fullscreen mode Exit fullscreen mode

이 함수를 모든 데이터셋에 실행했습니다. 이제는 내보내기가 처리합니다.

언제 무엇을 사용할까

시나리오 최적의 접근법
일회성 분석 브라우저 내보내기 → CSV → Pandas
반복적 스크래핑 requests를 사용한 Python 스크립트
JavaScript 중심 사이트 브라우저 내보내기 (렌더링된 콘텐츠를 봄)
인증된 데이터 브라우저 내보내기 (기존 세션 사용)
API 사용 가능 API 직접 사용
간단한 정적 테이블 pd.read_html()로 충분

Pro 팁: 복잡한 데이터에는 JSON

중첩되거나 타입이 지정된 데이터의 경우 JSON으로 내보내세요:

import pandas as pd
import json

with open('export.json') as f:
    data = json.load(f)

df = pd.DataFrame(data)
Enter fullscreen mode Exit fullscreen mode

JSON 내보내기는 타입을 보존합니다:

  • 숫자는 숫자로 (문자열이 아님)
  • 불리언은 불리언으로
  • Null은 null로
print(df.dtypes)
# player         object
# goals          int64   # 이미 숫자!
# is_starter     bool    # 이미 불리언!
# injury_date    object  # datetime으로 파싱 가능
Enter fullscreen mode Exit fullscreen mode

JSON 워크플로에 대한 자세한 내용은 테이블 내보내기를 위한 Chrome 최고의 확장 프로그램 5선을 참조하세요.

워크플로 요약

기존 워크플로 (30분 이상):

  1. 스크래핑 스크립트 작성
  2. CORS/인증 문제 처리
  3. 복잡한 HTML 파싱
  4. 숫자 정제
  5. 불리언 정제
  6. Null 정제
  7. 열 이름 수정
  8. 엣지 케이스 디버깅
  9. 드디어: 분석

새로운 워크플로 (30초):

  1. 확장 프로그램 클릭
  2. 정제 프로필로 내보내기
  3. pd.read_csv()
  4. 분석

사용해 보세요

  1. HTML Table Exporter 설치
  2. 분석하고 싶은 테이블 찾기
  3. 정제 프리셋으로 내보내기
  4. Pandas에서 로드

무료 버전은 기본 내보내기를 처리합니다. PRO는 Pandas에 최적화된 출력을 위한 정제 프리셋과 프로필을 추가합니다.

gauchogrid.com/ko/html-table-exporter에서 자세히 알아보거나 Chrome 웹 스토어에서 사용해 보세요.


웹 데이터를 Pandas로 가져오는 현재 워크플로는 어떻게 되시나요? 정제 단계에 얼마나 시간을 쓰시는지 궁금합니다. 아래 댓글로 알려주세요.

Top comments (0)