Crawling

BeautifulSoup4 매뉴얼 정리

bluebamus 2025. 2. 6.

1. BeautifulSoup4 개요

   - BeautifulSoup4(이하 BS4)는 파이썬에서 HTML과 XML을 쉽게 파싱할 수 있도록 도와주는 라이브러리이다. 웹 스크래핑, 데이터 추출, HTML 분석 등에 자주 사용된다.

 

2. 설치 방법

pip install beautifulsoup4

   

   - 추가적으로 lxml 또는 html5lib 파서를 사용할 수도 있다.

pip install lxml html5lib

 

3. 기본 사용법

   1) HTML 파싱하기

      - 파서 옵션으로는 'html.parser', 'lxml', 'html5lib' 등이 있으며 각각의 특성과 성능이 다르다.

from bs4 import BeautifulSoup

html = """<html><head><title>Test Page</title></head>
<body><p class='content'>Hello, World!</p></body></html>"""

soup = BeautifulSoup(html, 'html.parser')
print(soup.title.text)  # 출력: Test Page
print(soup.p.text)  # 출력: Hello, World!

 

   2) XML 파싱하기

xml = """<data>
    <item id='1'>First</item>
    <item id='2'>Second</item>
</data>"""

soup = BeautifulSoup(xml, 'xml')
print(soup.find('item', {'id': '2'}).text)  # 출력: Second

 

4. 기본 탐색 메서드

   1) find

      - 조건에 맞는 첫 번째 태그를 반환한다.

# div 태그 중 class가 'content'인 첫 번째 태그를 반환
tag = soup.find('div', class_='content')
print(tag)

 

   2) find_all

      - 조건에 맞는 모든 태그를 리스트로 반환한다.

 

      - 매개변수 설명:

         - name: 태그 이름 (예: 'div', 'a', 'p' 등)
         - attrs: 속성 딕셔너리 (예: {'class': 'header'})
         - recursive: 하위 태그까지 검색 여부 (기본값 True)
         - string: 문자열 매칭
         - 추가 키워드 인자: HTML 속성과 동일한 이름을 사용하되, 파이썬 예약어와 충돌 시 밑줄(_) 추가 (예: class_='some-class')

# 모든 a 태그 중 href 속성이 있는 태그를 리스트로 반환
tags = soup.find_all('a', href=True)
for t in tags:
    print(t)

 

 

5. CSS 선택자 사용

   1) select(css_selector):

      - 조건에 맞는 모든 태그를 리스트로 반환한다.

# 예제 1: 태그 이름으로 선택
soup = BeautifulSoup(html, 'html.parser')
print(soup.select('p'))  # 모든 <p> 태그 선택

# 예제 2: 클래스명으로 선택
print(soup.select('.intro'))  # 클래스가 'intro'인 모든 태그 선택

# 예제 3: 상위태그 > 하위태그 > 하위태그
print(soup.select('div > p > span'))  # div 안에 있는 p, 그 안의 span 태그

# 예제 4: 상위태그.클래스이름 > 하위태그.클래스이름
print(soup.select('p.intro > span.number'))

# 예제 5: 아이디로 선택
print(soup.select('#phone'))

# 예제 6: 아이디 > 태그명.클래스명
print(soup.select('#phone > span.number'))

# 예제 7: 태그명[속성=값] 형태 (속성이 존재하는 태그 선택)
print(soup.select('a[href]'))

 

   2) select_one(css_selector):

      - 조건에 맞는 첫 번째 태그를 반환한다.

item = soup.select_one('p.intro')
print(item)

 

6. 부모/자식/형제 탐색

   1) 태그 탐색

soup.body  # <body> 태그 전체 반환
soup.body.p  # <body> 내부의 첫 번째 <p> 태그 반환

 

   2) 자식 태그 탐색 (내부 탐색):

# 현재 태그의 첫 번째 자식 <section> 탐색
section = tag.find('section')

# 현재 태그 내부의 모든 a 태그 탐색
links = tag.find_all('a')

 

   3) 부모 태그 탐색:

# 첫 번째 부모 태그 (예: div) 탐색
parent_div = tag.find_parent('div')

# 모든 상위 태그를 리스트로 반환
all_parents = tag.find_parents()

 

   4) 형제 태그 탐색 / 문서 내 순서 기반 탐색:

# 바로 다음 형제 태그 탐색
next_item = tag.find_next_sibling('li')

# 바로 이전 형제 태그 탐색
prev_item = tag.find_previous_sibling('li')

# 모든 다음 형제 태그 탐색
next_siblings = tag.find_next_siblings()

# 모든 이전 형제 태그 탐색
prev_siblings = tag.find_previous_siblings()

 

7. 네비게이션 속성

   1) .contents

      - 문서 내 순서 기반 탐색:해당 태그의 직접 자식들을 리스트로 반환한다.

children = tag.contents
print(children)

 

   2) .children

      - 직접 자식들에 대한 반복자(iterator)를 제공한다.

for child in tag.children:
    print(child)

 

   3) .descendants

      - 태그의 모든 후손(자식, 자식의 자식 등)에 대한 반복자이다.

for descendant in tag.descendants:
    print(descendant)

 

   4) .parent

      - 바로 상위 태그(부모)를 반환한다.

print(tag.parent)

 

   5) .next_sibling / .previous_sibling

      - 바로 옆의 형제 요소를 반환한다.

print(tag.next_sibling)
print(tag.previous_sibling)

 

   6) .string

      - 태그 내의 유일한 자식이 문자열일 경우 그 문자열 반환한다 (여러 자식이 있을 경우 None).

print(tag.string)

 

 

   7) stripped_strings

      - 태그 내의 모든 문자열(공백 제거된 상태)을 이터레이터로 반환한다.

for string in tag.stripped_strings:
    print(repr(string))

 

8. 요소 내용 추출 및 수정

   1) 텍스트 추출

      1. get_text(separator, strip)

         - 태그 내부의 모든 텍스트를 하나의 문자열로 추출한다.

text = soup.get_text(separator='\n', strip=True)
print(text)

 

   2) 수정 메서드

      1. append(tag_or_string)

         - 현재 태그의 자식으로 새로운 태그나 문자열을 추가한다.

new_tag = soup.new_tag('span')
new_tag.string = "추가된 내용"
tag.append(new_tag)

 

   3) insert(index, tag_or_string)

      1. 특정 위치에 새로운 태그나 문자열을 삽입한다.

tag.insert(0, "시작에 삽입된 텍스트")

 

       2. insert_before(tag_or_string)

         - 현재 태그 바로 앞에 새로운 태그나 문자열을 삽입한다.

tag.insert_before("앞에 삽입된 내용")

 

       3. replace_with(new_tag_or_string)

         - 현재 태그를 새로운 태그나 문자열로 교체

tag.replace_with("교체된 내용")

 

       4. extend(iterable)

         - 여러 태그나 문자열을 자식으로 추가

tag.extend(["내용1", "내용2"])

 

       5. clear()

         - 현재 태그 내부의 모든 내용을 제거

tag.clear()

 

       6. extract()

         - 현재 태그를 문서 트리에서 제거하고, 제거한 태그를 반환

extracted = tag.extract()

 

       7. decompose()

         - 현재 태그와 그 자손들을 문서에서 완전히 제거하여 메모리에서도 해제한다.

tag.decompose()

 

       8. unwrap()

         - 현재 태그를 제거하고, 해당 태그의 자식들을 상위 노드에 직접 포함한다.

tag.unwrap()

 

9. NavigableString 및 관련 클래스

   1) NavigableString

      - 문서 내의 텍스트 데이터를 나타내며, 태그의 .string 속성을 통해 접근할 수 있다.

      - 특수 문자열 클래스
         - Comment: HTML 주석
         - CData: CDATA 섹션
         - ProcessingInstruction: 처리 지시문
         - 이들은 모두 NavigableString을 상속받아 구현된다.

from bs4 import BeautifulSoup
html = "<p>Hello, <b>World!</b></p>"
soup = BeautifulSoup(html, 'html.parser')
text = soup.p.string  # 단, p 태그의 자식이 여러 개이면 None 반환
print(text)

 

10. 기타 유틸리티

   1) BeautifulSoupUnicodeDammit

      - 입력 문자열의 인코딩을 감지하고 적절한 디코딩을 수행하는 클래스이다. 보통 내부적으로 사용되지만, 인코딩 문제를 다룰 때 참고할 수 있다.

      - BeautifulSoup4의 도구로, 알려지지 않은 인코딩을 알아보는 데 도움을 줄 수 있다. 제공된 마크업을 Unicode로 변환하는 클래스를 구현하고 있다.

 

      - 주요 특징
         - 인코딩 감지: HTML/XML 문서에 명시된 인코딩 정보(예: meta 태그)가 없거나 신뢰할 수 없는 경우, 여러 인코딩 후보를 시도하여 올바른 인코딩을 감지한다.
         - 유니코드 변환: 감지된 인코딩을 기반으로 바이트 데이터를 유니코드 문자열로 변환한다.
         - BOM(Byte Order Mark) 처리: BOM이 포함된 경우 이를 올바르게 처리한다.

      - 이 클래스는 Beautiful Soup 내부에서 파싱 전에 문서의 인코딩 문제를 처리할 때 주로 사용되지만, 사용자가 직접 인코딩을 처리해야 하는 상황에서도 유용하게 사용할 수 있다.

 

      -  예시 코드

         - 다음은 UnicodeDammit를 사용하여 바이트 문자열을 유니코드 문자열로 변환하는 예제이다.

 

         - 코드 설명
            1.바이트 데이터 준비:
               - raw_data 변수에 UTF-8 BOM이 포함된 바이트 문자열을 준비한다.

                  (여기서는 "Hello, world! ✓" 문구를 예제로 사용)
            2. UnicodeDammit 객체 생성:
               - 생성자에 바이트 데이터를 전달하면, 내부적으로 여러 인코딩 후보를 시도하여 적절한 인코딩을 감지하고
    unicode_markup 속성에 유니코드 문자열을 저장한다.
            3. 결과 출력:
               - dammit.original_encoding: 감지된 인코딩 정보를 보여준다.
               - dammit.unicode_markup: 변환된 유니코드 문자열을 출력한다.

from bs4 import UnicodeDammit

# 예제 바이트 데이터 (UTF-8 BOM이 포함된 문자열)
raw_data = b'\xef\xbb\xbfHello, world! \xe2\x9c\x93'  # "Hello, world! ✓" (UTF-8)

# UnicodeDammit를 사용하여 인코딩 감지 및 유니코드 변환
dammit = UnicodeDammit(raw_data)

# 감지된 원래 인코딩 출력
print("Detected encoding:", dammit.original_encoding)

# 변환된 유니코드 문자열 출력
print("Unicode markup:", dammit.unicode_markup)

 

   2) SoupStrainer

      - 문서를 파싱할 때 특정 태그만 대상으로 제한할 수 있다.

from bs4 import SoupStrainer
only_a_tags = SoupStrainer("a")
soup = BeautifulSoup(html, "html.parser", parse_only=only_a_tags)

 

11. 참고사항

   1) 매개변수:

      - 대부분의 메서드들은 name, attrs, recursive, string 등의 매개변수를 받아 검색 조건을 상세하게 지정할 수 있다.
         - 예: soup.find_all('div', class_='header')는 <div class="header">인 모든 태그를 찾는다.

 

   2) 키워드 인자 사용:

      - HTML 속성 이름과 동일한 이름의 키워드 인자를 전달할 수 있으며, 파이썬 예약어와 충돌하는 경우 뒤에 밑줄(_)을 붙여 사용한다.
         - 예: class_='some-class'

 

   3) 검색 방식 선택:

      - CSS 선택자 (select / select_one)와 전통적인 메서드 (find, find_all) 방식을 상황에 맞게 적절히 선택하여 사용한다.

 

12. 설정 옵션 및 설명

   1) 파서 종류 선택

      - BeautifulSoup은 다양한 파서를 지원

soup = BeautifulSoup(html, 'html.parser')  # 기본 파서
soup = BeautifulSoup(html, 'lxml')  # 빠르고 강력한 파서
soup = BeautifulSoup(html, 'html5lib')  # 가장 관대한 파서

 

   2) prettify(): HTML 예쁘게 출력

print(soup.prettify())

 

 

13. 실무 활용 예제

   1) 웹페이지에서 특정 데이터 크롤링

import requests
from bs4 import BeautifulSoup

url = "https://example.com"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

# 특정 클래스의 데이터 추출
data = soup.find('div', class_='target-class').text
print(data)

 

   2) 테이블 데이터 추출

html = """<table>
<tr><th>이름</th><th>나이</th></tr>
<tr><td>홍길동</td><td>30</td></tr>
<tr><td>이몽룡</td><td>25</td></tr>
</table>"""

soup = BeautifulSoup(html, 'html.parser')
rows = soup.find_all('tr')[1:]  # 첫 번째 행(헤더) 제외

for row in rows:
    cols = row.find_all('td')
    name, age = cols[0].text, cols[1].text
    print(f"이름: {name}, 나이: {age}")

 

   3) 뉴스 기사 제목 크롤링

url = "https://news.example.com"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

articles = soup.find_all('h2', class_='news-title')
for article in articles:
    print(article.text)

 

 - reference : 

https://beautiful-soup-4.readthedocs.io/en/latest/

https://tedboy.github.io/bs4_doc/

 

Beautiful Soup Documentation

 

tedboy.github.io

 

댓글