Python 웹 크롤링 (1) 기본 사용법

파일 읽기

with 문을 사용하여 파일을 읽을 수 있습니다. 다음은 HTML 파일을 읽어와서 변수에 저장하는 예제입니다.

# './data/sample.html'는 파일 경로, 'r'는 읽기 모드, encoding='UTF-8'은 인코딩 지정
with open('../data/sample.html', 'r', encoding='utf-8') as file:
    content = file.read()

파일 쓰기

with 문을 사용하여 파일에 데이터를 쓸 수 있습니다.

with open('../output/result.html', mode='w', encoding="utf-8") as file:
    file.write(html_content[0])

태그 객체를 문자열로 변환

현재 내용의 타입이 bs4.element.Tag인 경우, 파일에 쓰려면 문자열로 변환해야 합니다 str().

print(type(soup.find('a', class_="thumbnail").img))
with open('save.txt', 'w') as f:
    f.write(str(soup.find('a', class_="thumbnail").img))
    
# 실행 결과
<class 'bs4.element.Tag'>

BeautifulSoup으로 HTML 파싱

bs4의 BeautifulSoup 라이브러리를 사용하여 HTML 내용을 파싱합니다. 'lxml'은 XML과 HTML을 빠르게 파싱할 수 있는 라이브러리입니다.

from bs4 import BeautifulSoup
with open('./data/page.html', 'r', encoding='UTF-8') as f:
    content = f.read()
soup = BeautifulSoup(content, 'lxml')

CSV 파일 생성

open 함수로 CSV 파일을 생성할 때, newline='' 파라미터는 행 끝 문자의 추가 처리를 방지합니다. Windows 환경에서 불필요한 빈 줄이 발생하는 것을 막습니다.

import csv
with open('output.csv', 'w', newline='', encoding='utf-8') as csvfile:
    csvwriter = csv.writer(csvfile)

태그 속성 조회

div 태그를 찾습니다 (첫 번째 div 태그만 반환).

print(soup.div)

.attrs를 사용하여 태그의 속성을 조회합니다.

print(soup.div.attrs)    # div의 모든 속성값 확인
# {'id': 'main-header', 'class': ['header']}
print(soup.find('div', attrs={"class": "content-area"}))
# 첫 번째 일치하는 <div class="content-area">의 전체 내용
print(soup.div.attrs['id'])  # div의 id 속성값获取
# main-header
print(soup.div.attrs['class'])  # div의 class 속성값获取
# ['header']

.find 메서드 사용

.find는 조건에 맞는 첫 번째 요소만 반환합니다.

# span 태그 중 class가 "small-text gray"인 요소 찾기
print(soup.find('span', class_="small-text gray"))

# span 태그 중 class가 "left-link", id가 "contact"인 요소 찾기
print(soup.find('span', class_="left-link", id="contact"))

중요한 점:

  • python에서 class는 키워드이므로 class_로 특수 처리해야 합니다.
  • class_="left-link gray"는 "left-link" 또는 "gray" 중 하나만 일치해도 되고, 둘 다 있을 때도 동작합니다 (AND 조건).

실행 결과 1:

<span class="small-text gray">점수 9.7</span>

실행 결과 2:

<span id="contact" class="left-link gray">
    <p>© 2005-2017 example.com, all rights reserved 회사명</p>
</span>

.find 체이닝

.find와 속성 접근을 조합하여 사용합니다.

print(soup.find('a', class_="thumbnail"))
print(soup.find('a', class_="thumbnail").img)  # img 태그获取
print(soup.find('a', class_="thumbnail").img.attrs)  # img의 속성들
print(soup.find('a', class_="thumbnail").img.attrs['src'])  # src 속성값
print(soup.find('a', class_="thumbnail").img['src'])  # 간단한 접근
print(soup.find('a', class_="thumbnail").find('img'))  #另一种方式

print(soup.find('a', class_="thumbnail").img)  # img 태그
print(type(soup.find('a', class_="thumbnail").img))  # 타입 확인

계층적으로 데이터 가져오기

HTML 구조 예:

<div class="post">
    <h2>소설류  · · · · · · </h2>
    - [![](https://img.example.com/cover1.jpg)](https://book.example.com/123456/)<div class="info-box"> [떠나간 사람들](https://book.example.com/123456/) 
    ---------------------------------------------
    
     </div>

계층적으로 태그를 찾아갑니다.

print(soup.find("div", class_="post").ul.h2)

두 번의 find 호출로 찾기:

result = soup.find('ul', class_='item-list clearfix').find('div', class_="info-box").h2.a
print(result)

# 실행 결과
<a href="https://book.example.com/123456/">떠나간 사람들</a>

텍스트 내용 가져오기

태그 내부의 텍스트만 가져옵니다 - .string, .text, get_text()

print(soup.find('h2', class_="left-title").text)
print(soup.find('h2', class_="left-title").get_text())    # text와 동일
print(soup.find('h2', class_="left-title").string)    # 중첩된 태그가 있으면 None 반환
print(soup.find('h2', class_="left-title").strings)    # 모든 텍스트를 생성자로 반환
print(list(soup.find('h2', class_="left-title").strings))  # 리스트로 변환
print(list(soup.find('h2', class_="left-title").stripped_strings))  # 공백 제거

# 중첩된 구조
print(soup.find('div', class_="post").h2.a.string)  # 중첩 있으면 None
print(soup.find('div', class_="info-box").h2.a.text)
print(soup.find('div', class_="info-box").h2.a.get_text())  # text와 동일


# CSS 선택자 사용
result = soup.select('.info-box h2 a').text
print(result)

repr()로 텍스트 원본 형식 확인

repr()은 객체의 공식 문자열 표현을 반환하는 내장 함수입니다.

print(repr(soup.find('div', class_="post").text))

repr()는 특수 문자를 정확히 표현하여 문자열의 원본 내용을 확인하는 데 유용합니다. 예를 들어, 텍스트가 "Hello\nWorld"라면 "'Hello\nWorld'"를 반환합니다.

CSS 선택자 select()

print(soup.select('h2'))
print(soup.select('ul[class="item-list clearfix"]'))
print(soup.select('ul[class="item-list clearfix"] > li'))  # li 자식 요소만
print(soup.select('ul[class="item-list clearfix"] > li > a'))
print(soup.select('ul[class="item-list clearfix"] > li > a > img'))  # 계층 구조
print(soup.select('ul[class="item-list clearfix"] img'))  # 모든 후손 img 태그
img_list = soup.select('ul[class="item-list clearfix"] img')
for img in img_list:
    print(img['src'])

select 선택자는 다양한 데이터를柔軟하게 가져올 수 있어서强力합니다.

result = soup.select('ul.item-list.clearfix div.info-box h2 a')
for item in result:
    print(item.text)
    print(item.attrs['href'])
    
'''
떠나간 사람들
https://book.example.com/subject/123456/
리바이어던
https://book.example.com/subject/234567/
시대의 아이들
https://book.example.com/subject/345678/
황무지
https://book.example.com/subject/456789/
마법사
https://book.example.com/subject/567890/
'''

soup.select('ul.item-list.clearfix div.info-box h2 a'):

  • select 메서드는 CSS 선택자 문법을 사용하여 일치하는 모든 요소를 찾습니다.
  • 선택자 'ul.item-list.clearfix div.info-box h2 a'는 클래스 "item-list clearfix"를 가진 ul 요소 아래의 "info-box" 클래스를 가진 div 내부의 h2 태그 내의 모든 a 요소를 선택합니다.

결과는 모든 일치하는 요소의 리스트로 반환됩니다.

사용자 정의 필터 함수

필터 함수를 정의하여 특정 조건을 만족하는 td 요소만 선택할 수 있습니다. 다음은 span 클래스를 포함하지 않는 td 요소를 필터링하는 예제입니다.

# td 요소를 필터링하는 함수 정의 - span 클래스를 포함하지 않는 요소만
def valid_cell(tag):
    return tag.name == 'td' and 'span' not in tag.get('class', [])

# CSS 선택자로 모든 테이블 행 선택
rows = soup.select('div.table-wrapper tr')

# 각 행 순회
for row in rows:
    # 필터 함수를 사용하여 td 요소 찾기
    cells = row.find_all(valid_cell)
  1. def valid_cell(tag):: BeautifulSoup 태그 객체를 매개변수로 받는 함수를 정의합니다.
  2. tag.name == 'td': HTML 태그 이름이 'td'(테이블 셀)인지 확인합니다.
  3. 'span' not in tag.get('class', []): 'span' 클래스가 클래스 목록에 없는지 확인합니다. - tag.get('class', \[\]): 'class' 속성이 없으면 빈 리스트를 반환합니다. - 'span' not in ...: 'span'이 클래스 목록에 없는지 확인합니다.
  4. return: 두 조건이 모두 만족하면 True, 아니면 False를 반환합니다.

이 함수는 'span' 클래스가 없는 td 태그에 대해 True를 반환하고, find_all에서 필터 조건으로 사용됩니다.

전체 예제: HTML 테이블을 CSV로 변환

from bs4 import BeautifulSoup
import csv

# HTML 파일을 열고 BeautifulSoup으로 파싱
with open('../data/table.html', 'r', encoding='utf-8') as f:
    soup = BeautifulSoup(f.read(), 'lxml')

# CSS 선택자로 모든 테이블 행 선택
rows = soup.select('div.table-wrapper tr')


# td 요소를 필터링하는 함수 - span 클래스 제외
def valid_cell(tag):
    return tag.name == 'td' and 'span' not in tag.get('class', [])


# CSV 파일 생성 및 데이터 쓰기 준비
with open('result.csv', 'w', newline='', encoding='utf-8') as csvfile:
    csvwriter = csv.writer(csvfile)

    # 각 행을 순회
    for row in rows:
        # 필터 함수를 사용하여 td 요소 선택
        cells = row.find_all(valid_cell)

        # 행 데이터를 저장할 리스트
        row_data = []

        # 각 셀 순회
        for cell in cells:
            # 텍스트 내용 가져오기 (공백 제거)
            cell_text = cell.get_text(strip=True)
            row_data.append(cell_text)

        # CSV 파일에 한 행씩 쓰기
        csvwriter.writerow(row_data)

6월 3일 23:10에 게시됨