HTML 웹 스크래핑 튜토리얼

웹사이트에서 데이터를 효율적으로 수집하는 HTML 웹 스크래핑 기술을 마스터하여 프로젝트와 전략을 강화하는 완벽한 방법을 알아보세요.
4 분 읽기
HTML web scraping main blog image

웹 스크래핑에 관심이 있다면 HTML 이해가 핵심입니다. 모든 웹사이트가 HTML로 구축되기 때문입니다. 웹 스크래핑은 다양한 시나리오에 활용될 수 있으며, API 없이 웹사이트에서 데이터를 수집하거나, 제품 가격을 모니터링하거나, 리드 목록을 구축하거나, 학술 연구를 수행하는 데 도움이 될 수 있습니다.

이 글에서는 HTML의 기초와 Python을 사용한 데이터 추출, 파싱, 처리 방법을 배울 수 있습니다.

심층적인 파이썬 웹 스크래핑 가이드가 궁금하신가요? 여기를 클릭하세요.

웹사이트 스크래핑 및 HTML 추출 방법

본 튜토리얼을 시작하기 전에 HTML의 핵심 구성 요소를 잠시 복습해 보겠습니다.

HTML 소개

HTML은 웹사이트의 구조와 요소에 대해 브라우저에 알려주는 태그 모음입니다. 예를 들어,<h1> 텍스트 </h1>는태그 뒤에 오는 텍스트가 제목임을 브라우저에 알리고,<a href=""> 링크 </a>는 하이퍼링크를 식별합니다.

HTML 속성은 태그에 대한 추가 정보를 제공합니다. 예를 들어,<a> </a>태그의href속성은 해당 속성이 가리키는 페이지의 URL 정보를 알려줍니다.

클래스와 ID는페이지의 요소를 정확히 식별하는 데 중요한 속성입니다. 클래스는 유사한 요소를 그룹화하여 CSS로 일관된 스타일을 적용하거나 JavaScript로 균일하게 조작할 수 있게 합니다. 클래스는.class-name 형식으로 지정됩니다.

W3Schools 웹사이트에서 클래스 그룹은 다음과 같습니다:

<div class="city">
  <h2>런던</h2>
  <p>런던은 잉글랜드의 수도입니다.</p>
</div>

<div class="city">
  <h2>파리</h2>
  <p>파리는 프랑스의 수도입니다.</p>
</div>

<div class="city">
  <h2>도쿄</h2>
  <p>도쿄는 일본의 수도입니다.</p>
</div>

각 제목과 도시 블록이 동일한 city 클래스를 가진 div로 감싸져 있는 것을 확인할 수 있습니다.

반면 ID는 각 요소에 고유합니다(즉, 두 요소가 동일한 ID를 가질 수 없음). 예를 들어, 다음 H1들은 고유한 ID를 가지며 개별적으로 스타일링/조작할 수 있습니다:

<h1 id="header1">Hello World!</h1>

<h1 id="header2">Lorem Ipsum Dolor</h1>

ID로 요소를 지정하는 구문은 #id-name입니다.

HTML 기본을 익혔으니 이제 웹 스크래핑을 시작해 보겠습니다.

스크래핑 환경 설정

이 튜토리얼은 다양한 HTML 스크래핑 라이브러리를 제공하고 배우기 쉬운 언어인 Python을 사용합니다. 컴퓨터에 Python이 설치되어 있는지 확인하려면 PowerShell(Windows) 또는 터미널(macOS)에서 다음 명령을 실행하세요:

python3

Python이 설치되어 있으면 버전 번호가 표시되고, 설치되어 있지 않으면 오류가 발생합니다. 아직 설치하지 않았다면Python을 설치하세요.

다음으로WebScraper라는폴더를 생성하고,WebScraper폴더 안에scraper.py라는 파일을 만드세요. 그런 다음 선택한 통합 개발 환경(IDE)에서 파일을 엽니다. 여기서는Visual Studio Code를사용합니다:

An image showing where VSC is used

IDE는 프로그래머가 코드 작성, 디버깅, 프로그램 테스트, 자동화 구축 등을 할 수 있게 해주는 다목적 애플리케이션입니다. 여기서는 HTML 스크레이퍼 코딩에 사용할 것입니다.

다음으로 가상 환경을 생성하여 파이썬의 글로벌 설치 환경과 스크래핑 프로젝트를 분리해야 합니다. 이는 의존성 충돌을 방지하고 전체 애플리케이션을 체계적으로 관리하는 데 도움이 됩니다.

이를 위해 다음 명령어로 virtualenv 라이브러리를 설치하세요:

pip3 install virtualenv

프로젝트 폴더로 이동합니다:

cd WebScraper

그런 다음 가상 환경을 생성하세요:

 python<버전> -m venv <가상환경이름>

이 명령어는 프로젝트 폴더 내에 모든 패키지와 스크립트를 위한 폴더를 생성합니다:

Command to create a folder for all the packages and scripts

이제 다음 명령어 중 하나를 사용하여 가상 환경을 활성화해야 합니다(사용 중인 플랫폼에 따라 다름):

source <가상환경이름>/bin/activate #MacOS 및 Linux에서

<가상환경이름>/Scripts/activate.bat #CMD에서

<가상환경이름>/Scripts/Activate.ps1 #Powershell에서

활성화가 성공하면 화면 왼쪽에 가상 환경 이름이 표시됩니다:

The name of your virtual environment

가상 환경이 활성화되었으므로 웹 스크래핑 라이브러리를 설치해야 합니다. Playwright, Selenium, Beautiful Soup, Scrapy 등 다양한 옵션이 있습니다. 여기서는 사용이 간편하고, 여러 브라우저를 지원하며, 동적 콘텐츠를 처리할 수 있고, 헤드리스 모드(GUI 없이 스크래핑)를 제공하는 Playwright를 사용합니다.

Playwright 설치: pip install pytest-playwright 실행 필요한 브라우저 설치: playwright install 실행

Playwright 설치가 완료되면 웹 스크래핑을 시작할 준비가 된 것입니다.

웹사이트에서 HTML 추출하기

모든 웹 스크래핑 프로젝트의 첫 단계는 스크래핑할 웹사이트를 식별하는 것입니다. 여기서는 이 테스트 사이트를 활용합니다.

다음으로 페이지에서 스크래핑할 정보를 식별해야 합니다. 이 경우 페이지의 전체 HTML 콘텐츠입니다.

스크래핑할 정보를 확인했다면 스크래퍼 코딩을 시작할 수 있습니다. Python에서는 먼저Playwright에 필요한 라이브러리를 임포트해야 합니다. Playwright는sync와 async 두 가지 유형의 API를 제공합니다. async 라이브러리는 비동기 코드 작성 시에만 사용되므로, 다음 명령어로 sync 라이브러리를 임포트합니다:

from playwright.sync_api import sync_playwright

동기식 라이브러리를 임포트한 후에는 다음 코드 스니펫을 사용하여 Python 함수를 선언해야 합니다:

def main():
    # 나머지 코드는 이 함수 안에 작성됩니다

앞서 언급한 바와 같이, 웹 스크래핑 코드는 이 함수 안에 작성합니다.

일반적으로 웹사이트에서 정보를 얻으려면 웹 브라우저를 열고 새 탭을 생성한 후 해당 사이트를 방문합니다. 사이트를 스크래핑하려면 이러한 동작을 코드로 변환해야 하며, 이를 위해 Playwright를 사용하게 됩니다. Playwright문서에따르면, 앞서 임포트한sync_api를호출하고 다음 코드 조각으로 브라우저를 열 수 있습니다:

with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)

괄호 안에 headless=False를 추가함으로써 웹사이트 콘텐츠를 볼 수 있습니다.

브라우저를 연 후 새 탭을 열고 대상 URL을 방문합니다:

page = browser.new_page()
try:
  page.goto("https://webscraper.io/test-sites/e-commerce/static")
except:
  print("Error")

참고: 위 코드는 브라우저를 실행한 이전 줄 아래에 추가해야 합니다. 모든 코드는 main 함수 내부, 하나의 파일에 포함됩니다.

이 코드 조각은 더 나은 오류 처리를 위해goto()함수를try-except 블록으로감쌉니다.

검색창에 사이트 URL을 입력하면 로딩을 기다려야 합니다. 이를 코드로 구현하려면 다음을 사용하세요:

page.wait_for_timeout(7000) #괄호 안의 값은 밀리초 단위

참고: 위 줄들은 앞의 줄들 아래에 추가해야 합니다.

마지막으로, 다음 코드 줄을 사용하여 페이지의 모든 HTML 콘텐츠를 추출합니다:

print(page.content())

페이지 HTML을 추출하는 전체 코드는 다음과 같습니다:


from playwright.sync_api import sync_playwright

def main():
  with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)   
        page = browser.new_page()
        try:
            page.goto("https://webscraper.io/test-sites/e-commerce/static")
        except:
            print("Error")

        page.wait_for_timeout(7000)
        print(page.content())

main()

Visual Studio Code에서 추출된 HTML은 다음과 같습니다:

The extracted HTML in VSC

특정 속성을 사용하여 HTML 추출하기

이전에는 웹 스크레이퍼 웹 페이지의 모든 요소를 추출했습니다. 그러나 필요한 정보만 추출하도록 제한하지 않으면 웹 스크래핑은 유용하지 않습니다. 이 섹션에서는 웹사이트 첫 페이지에 있는 모든 노트북의 제목만 추출합니다:

Showing the titles we are going to extract from the target website

특정 요소를 추출하려면 대상 웹사이트의 구조를 이해해야 합니다. 마우스 오른쪽 버튼을 클릭하고 다음과 같이‘검사’옵션을 선택하여 확인할 수 있습니다:

Click on inspect on the target website

또는 다음 키보드 단축키를 사용할 수 있습니다:

  • macOS의 경우Cmd + Option + I
  • Windows의 경우Control + Shift + C

대상 페이지의 구조는 다음과 같습니다:

The HTML structure of the target website

검사창 좌측 상단의 선택 도구를 사용해 페이지 내 특정 항목의 코드를 확인할 수 있습니다:

How to inspect specific items in the source code

검사창에서 노트북 제목 중 하나를 선택하세요:

Inspecting one of the titles we want to scrape

제목이 <a> </a> 태그 안에 있으며, 이 태그는 h4 태그로 감싸져 있고 링크에는 title 클래스가 지정되어 있음을 확인할 수 있습니다. 즉, title 클래스를 가진 <h4> 태그 안에 있는 < a href> 태그(URL)를 찾아야 합니다.

이러한 요소를 정확히 타겟팅하는 스크래핑 프로그램을 만들려면 라이브러리를 임포트하여 Python 함수를 생성하고, 브라우저를 실행한 후 대상 웹사이트로 이동해야 합니다:

from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        try:
            page.goto("https://webscraper.io/test-sites/e-commerce/static/computers/laptops")

        except:
            print("Error")

        page.wait_for_timeout(7000)

page.goto() 함수 내부의 대상 URL이 노트북 목록이 포함된 첫 페이지로 업데이트되었음을 확인하세요.

스크래핑 프로그램을 작성한 후에는 웹사이트 구조 분석을 바탕으로 대상 요소를 찾아야 합니다. Playwright에는 다음과 같은 다양한 속성을 기반으로 페이지의 요소를 찾을 수 있는‘로케이터(locator)‘라는 도구가 있습니다:

  • get_by_label()은 요소에 연결된 레이블을 사용하여 대상 요소를 찾습니다.
  • get_by_text()는 요소가 포함하는 텍스트를 사용하여 대상 요소를 찾습니다.
  • get_by_alt_text()는 대체 텍스트를 사용하여 대상 요소를 찾고 이미지에 대한 작업을 수행합니다.
  • get_by_test_id()는 요소의 테스트 ID를 사용하여 대상 요소를 찾습니다.

요소 찾기 관련 추가 메서드는공식 문서를참조하세요.

모든 노트북 제목을 스크래핑하려면 노트북 제목을 감싸는 <h4> 태그를 찾아야 합니다. get_by_role() 로케이터를 사용하면 버튼, 체크박스, 헤딩 등 요소의 기능에 따라 요소를 찾을 수 있습니다. 즉, 페이지의 모든 헤딩을 찾으려면 다음과 같이 작성해야 합니다:

titles = page.get_by_role("heading").all()

그런 다음 콘솔에 출력하려면 다음 코드를 사용하세요:

print(titles)

출력 후 다음과 같은 요소 배열이 반환됨을 확인할 수 있습니다:

Array of elements after printing the titles

이 출력에는 제목이 포함되지 않지만, 선택기 조건에 맞는 요소들을 참조합니다. 제목 클래스( title )를 가진 <a> 태그와 그 안의 텍스트를 찾으려면 이 요소들을 순회해야 합니다.

요소의 경로와 클래스를 기반으로 요소를 찾으려면 CSS 로케이터를 사용하는 것이 권장되며, 다음과 같이 all_inner_texts() 함수를 사용하여 요소의 내부 텍스트를 추출할 수 있습니다:

for title in titles:
  laptop = title.locator("a.title").all_inner_texts()

이 코드를 실행하면 다음과 같은 결과가 출력됩니다:

An image of how the output should look like

값이 없는 배열을 거부하려면 다음을 작성하세요:

if len(laptop) == 1:
  print(laptop[0])

값이 없는 배열을 제외하면 특정 요소만 추출하는 스크래핑 프로그램을 성공적으로 구축한 것입니다.

다음은 이 스크레이퍼의 전체 코드입니다:

from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        try:
            page.goto("https://webscraper.io/test-sites/e-commerce/static/computers/laptops")

        except:
            print("Error")

        page.wait_for_timeout(7000)

        titles = page.get_by_role("heading").all()

        for title in titles:
            laptop = title.locator("a.title").all_inner_texts()
            if len(laptop) == 1:
                print(laptop[0])

main()

요소와의 상호작용

이제 한 단계 더 나아가 노트북이 포함된 첫 페이지의 제목을 추출하고, 두 번째 페이지로 이동하여 그곳의 제목도 추출하는 프로그램을 만들어 보겠습니다.

페이지에서 제목을 추출하는 방법은 이미 알고 있으므로, 노트북의 다음 페이지로 이동하는 방법만 알아내면 됩니다.

현재 보고 있는 페이지에페이지네이션버튼이 있다는 것을 이미 눈치챘을 것입니다.

스크래핑 프로그램을 사용해2를찾아 클릭해야 합니다. 페이지를 검사해 보면 필요한 요소가 목록 요소(<li>태그)이며 내부 텍스트가2임을 확인할 수 있습니다:

The required element has an inner text of 2

즉, 목록 항목을 찾으려면 get_by_role() 선택자를 사용하고, 텍스트가 '2'인 요소를 찾으려면 get_by_text() 선택자를 사용할 수 있습니다.

파일에서 코딩하는 방법은 다음과 같습니다:

page.get_by_role("listitem").get_by_text("2", exact=True)

이 코드는 두 조건을 모두 충족하는 요소를 찾습니다: 첫째, 목록 항목이어야 하며, 둘째, 텍스트가 2여야 합니다.

exact=True는 지정된 텍스트를 가진 요소를 찾기 위한 함수 인자입니다.

버튼을 클릭하려면 이전 코드를 다음과 같이 수정하세요:

       page.get_by_role("listitem").get_by_text("2", exact=True).click()

이 코드에서 click() 함수는 지정된 요소를 클릭합니다.

페이지 로드를 기다린 후 모든 제목을 다시 추출합니다:

       page.wait_for_timeout(5000)

        titles = page.get_by_role("heading").all()

        for title in titles:
            laptop = title.locator("a.title").all_inner_texts()
            if len(laptop) == 1:
                print(laptop[0])

완성된 코드 블록은 다음과 같아야 합니다:

from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        try:
            page.goto("https://webscraper.io/test-sites/e-commerce/static/computers/laptops")

        except:
            print("Error")

        page.wait_for_timeout(7000)

        titles = page.get_by_role("heading").all()

        for title in titles:
            laptop = title.locator("a.title").all_inner_texts()
            if len(laptop) == 1:
                print(laptop[0])
        
        page.get_by_role("listitem").get_by_text("2", exact=True).click()

        page.wait_for_timeout(5000)

        titles = page.get_by_role("heading").all()

        for title in titles:
            laptop = title.locator("a.title").all_inner_texts()
            if len(laptop) == 1:
                print(laptop[0])

main()

HTML 추출 및 CSV로 기록

스크래핑한 데이터를 저장하고 분석하지 않으면 무용지물입니다. 이 섹션에서는 스크래핑할 노트북 페이지 수를 사용자 입력으로 받고, 제목을 추출하여 프로젝트 폴더에 CSV 파일로 저장하는 고급 프로그램을 작성합니다.

이 프로그램에는 사전 설치된 CSV 라이브러리가 필요하며, 다음 명령어로 임포트할 수 있습니다:

import csv

CSV 라이브러리를 설치한 후에는 사용자의 입력에 따라 가변적인 페이지 수를 방문하는 방법을 고려해야 합니다.

웹사이트의 URL 구조를 살펴보면, 노트북 제품 페이지마다 URL 매개변수로 표시된다는 점을 알 수 있습니다. 예를 들어 노트북 디렉토리의 두 번째 페이지 URL은 https://webscraper.io/test-sites/e-commerce/static/computers/laptops?page=2 입니다.

URL 매개변수 ?page=2를 다른 숫자 값으로 변경하여 다른 페이지를 방문할 수 있습니다. 즉, 스크래핑할 페이지 수를 사용자에게 입력하도록 요청해야 합니다. 다음 명령어를 사용하세요:

pages = int(input("스크랩할 페이지 수를 입력하세요: "))

사용자가 입력한 페이지 수만큼 1부터 순차적으로 각 페이지를 방문하려면 다음과 같은 for 루프를 사용합니다:

for i in range(1, pages+1):

range 함수 내부에서는 루프의 시작점과 종료점을 나타내기 위해 1과 pages+1을 함수 인자로 사용합니다. 두 번째 함수 인자는 루프에서 제외됩니다. 예를 들어 range(1,5) 함수를 사용하면 프로그램은 1부터 4까지만 반복합니다.

다음으로, 반복 과정에서i값을 URL 매개변수로 입력하여 각 페이지를 방문해야 합니다.Python f-string을 사용하면 문자열에 변수를 추가할 수 있습니다.

문자열을 출력할 때 따옴표 앞에 f를 붙여 f-string임을 표시합니다. 따옴표 안에서는 중괄호를 사용하여 변수를 지정할 수 있습니다.

f-string을 사용하여 문자열과 함께 변수를 출력하는 예시는 다음과 같습니다:

print(f"변수의 값은 {variable_name_goes_here}입니다")

스크레이퍼로 돌아가서, 파일 내에 다음과 같은 코드 블록을 작성하여 f-string을 활용할 수 있습니다:

    try:
             page.goto(f"https://webscraper.io/test-sites/e-commerce/static/computers/laptops?page={i}")

     except:
            print("Error")

타임아웃 함수로 페이지 로드를 기다린 후 다음 코드로 제목을 추출하세요:

           page.wait_for_timeout(7000)

            titles = page.get_by_role("heading").all()

모든 제목 요소를 확보한 후에는 CSV 파일을 열고, 각 제목을 순회하며 필요한 텍스트를 추출하여 파일에 기록해야 합니다.

CSV 파일을 열려면 다음 구문을 사용하세요:

with open("laptops.csv", "a") as csvfile:

여기서는laptops.csv파일을 추가(a) 모드로 엽니다. 파일을 열 때마다 기존 데이터를 잃지 않으려면 추가 모드를 사용해야 합니다. 파일이 존재하지 않으면 라이브러리가 프로젝트 폴더에 파일을 생성합니다.CSV는파일을 열 때 다음과 같은여러 모드를 제공합니다:

  • r: 아무것도 지정하지 않을 때 기본으로 사용되는 옵션입니다. 파일을 읽기 전용으로 엽니다.
  • w는쓰기 전용으로 파일을 엽니다. 파일을 열 때마다 기존 데이터가 덮어쓰힙니다.
  • a는데이터를 추가하기 위해 파일을 엽니다. 기존 데이터는 덮어쓰지 않습니다.
  • r+은읽기 및 쓰기 모드로 파일을 엽니다.
  • x는새 파일을 생성합니다.

이전 코드 아래에서 CSV 파일을 조작할 수 있는writer객체를 선언해야합니다:

writer = csv.writer(csvfile)

다음으로 각 title 요소를 순회하며 텍스트를 추출합니다:

               for title in titles:
                    laptop = title.locator("a.title").all_inner_texts()

이렇게 하면 각 배열에 개별 노트북의 제목이 포함된 여러 배열을 얻을 수 있습니다. 빈 배열을 제외하려면 다음 조건부 코드를 CSV 파일에 작성하세요:

              if len(laptop) == 1:
                        writer.writerow([laptop[0]])

writerow 함수는 CSV 파일에 새 행을 작성할 수 있게 합니다.

프로그램 전체 코드는 다음과 같습니다:

from playwright.sync_api import sync_playwright
import csv

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        pages = int(input("스크랩할 페이지 수를 입력하세요: "))

        for i in range(1, pages+1):
            try:
                page.goto(f"https://webscraper.io/test-sites/e-commerce/static/computers/laptops?page={i}")

            except:
                print("오류")

            page.wait_for_timeout(7000)

            titles = page.get_by_role("heading").all()

            with open("laptops.csv", "a") as csvfile:
                writer = csv.writer(csvfile)
                for title in titles:
                    laptop = title.locator("a.title").all_inner_texts()
                    if len(laptop) == 1:
                        writer.writerow([laptop[0]])

        browser.close()             


main()

이 코드를 실행한 후 CSV 파일은 다음과 같아야 합니다:

Example of how the CSV file should look like

결론

이 글에서는 Python을 사용하여 HTML을 추출, 파싱 및 저장하는 방법을 배웠습니다.

이 튜토리얼은 비교적 간단했지만, 실제 상황에서는 스크래핑 과정에서 CAPTCHA, 속도 제한, 웹사이트 레이아웃 변경 또는 규제 요건 등 다양한 장애물에 부딪힐 수 있습니다. 다행히Bright Data가도움을 드릴 수 있습니다. Bright Data는고급 주거용 프록시와같은 스크래핑 성능 향상 도구, 대규모 스크래퍼 구축을 위한스크래퍼 IDE, 공개 웹사이트 차단 해제 및 CAPTCHA 해결 기능을 제공하는웹 언블로커를제공합니다. 이러한 도구들은 대규모로 정확한 데이터를 수집하고 장애물을 극복하는 데 도움이 됩니다. 또한 Bright Data의 윤리적 스크래핑 원칙은 웹사이트 이용 약관 및 법적 규정을 준수하도록 보장합니다.

Bright Data의 풍부한 기능 플랫폼을 통해 복잡한 웹 스크래핑 작업은 뒤로 한 채 필요한 가치 있는 데이터 추출에만 집중하세요. 지금 바로 무료 체험을 시작하세요!