이 가이드는 Python에서 lxml 패키지를 사용하여 정적 및 동적 콘텐츠를 파싱하고, 일반적인 과제를 극복하며, 데이터 추출 프로세스를 간소화하는 방법을 설명합니다.
웹에서 구조적이고 계층적인 데이터는 HTML과 XML의 두 가지 형식으로 표현될 수 있습니다:
- XML은 사전 구축된 태그와 스타일이 없는 기본 구조입니다. 개발자가 자체 태그를 정의하여 구조를 만듭니다. 태그의 주요 목적은 서로 다른 시스템 간에 이해될 수 있는 표준 데이터 구조를 만드는 것입니다.
- HTML은 미리 정의된 태그를 갖는 웹 마크업 언어입니다. 이러한 태그에는
<h1>태그의font-size또는<img />태그의display와 같은 일부 스타일 속성이 포함됩니다. HTML의 주요 기능은 웹 페이지를 효과적으로 구조화하는 것입니다.
lxml은 HTML과 XML 문서 모두에서 작동합니다.
lxml로 Web스크레이핑을 시작하기 전에, 머신에 몇 가지 라이브러리를 설치해야 합니다:
pip install lxml requests cssselect이 명령은 다음을 설치합니다:
- XML 및 HTML을 파싱하기 위한
lxml - 웹 페이지를 가져오기 위한
requests - CSS 선택자를 사용하여 HTML 요소를 추출하는
cssselect
스크레이핑할 수 있는 웹 콘텐츠는 크게 정적과 동적의 두 가지 유형이 있습니다. 정적 콘텐츠는 웹 페이지가 처음 로드될 때 HTML 문서에 포함되어 있어 스크레이핑이 쉽습니다. 반면 동적 콘텐츠는 초기 페이지 로드 이후 JavaScript에 의해 지속적으로 로드되거나 트리거됩니다.
먼저, 브라우저의 Dev Tools를 사용하여 관련 HTML 요소를 식별합니다. 웹 페이지에서 마우스 오른쪽 버튼을 클릭하고 Inspect 옵션을 선택하거나 Chrome에서 F12를 눌러 Dev Tools를 엽니다.
화면 오른쪽에는 페이지 렌더링을 담당하는 코드가 표시됩니다. 각 책의 데이터를 처리하는 특정 HTML 요소를 찾으려면 hover-to-select 옵션(화면 좌측 상단의 화살표)을 사용하여 코드를 탐색합니다:
Dev Tools에서 다음 코드 스니펫을 확인할 수 있습니다:
<article class="product_pod">
<!-- code omitted -->
<h3><a href="catalogue/a-light-in-the-attic_1000/index.html" title="A Light in the Attic">A Light in the ...</a></h3>
<div class="product_price">
<p class="price_color">£51.77</p>
<!-- code omitted -->
</div>
</article>static_scrape.py라는 새 파일을 만들고 다음 코드를 입력합니다:
import requests
from lxml import html
import json
URL = "https://books.toscrape.com/"
content = requests.get(URL).text다음으로, HTML을 파싱하고 데이터를 추출합니다:
parsed = html.fromstring(content)
all_books = parsed.xpath('//article[@class="product_pod"]')
books = []이 코드는 html.fromstring(content)를 사용하여 parsed 변수를 초기화하며, 이는 HTML 콘텐츠를 계층적 트리 구조로 파싱합니다. all_books 변수는 XPath 선택자를 사용하여 웹 페이지에서 class가 product_pod인 모든 <article> 태그를 가져옵니다. 이 문법은 XPath 표현식에 대해 특히 유효합니다.
다음으로, 책 목록을 반복하면서 제목과 가격을 추출합니다:
for book in all_books:
book_title = book.xpath('.//h3/a/@title')
price = book.cssselect("p.price_color")[0].text_content()
books.append({"title": book_title, "price": price})book_title 변수는 <h3> 태그 내부의 <a> 태그에서 title 속성을 가져오는 XPath 선택자를 사용하여 정의됩니다. XPath 표현식 시작의 점(.)은 기본 시작점이 아니라 <article> 태그부터 검색을 시작하도록 지정합니다.
다음 줄에서는 cssselect 메서드를 사용하여 class가 price_color인 <p> 태그에서 가격을 추출합니다. cssselect는 리스트를 반환하므로 인덱싱([0])을 통해 첫 번째 요소에 접근하며, text_content()는 요소 내부의 텍스트를 가져옵니다.
추출된 각 제목과 가격 쌍은 딕셔너리 형태로 books 리스트에 추가되며, 이는 JSON 파일에 쉽게 저장할 수 있습니다.
이제 추출한 데이터를 JSON 파일로 저장합니다:
with open("books.json", "w", encoding="utf-8") as file:
json.dump(books, file)스크립트를 실행합니다:
python static_scrape.py이 명령은 디렉터리에 다음 출력이 포함된 새 파일을 생성합니다:
이 스크립트의 전체 코드는 GitHub에서 확인할 수 있습니다.
동적 콘텐츠를 스크레이핑하려면 Selenium을 설치합니다:
pip install seleniumYouTube는 JavaScript로 렌더링되는 콘텐츠의 훌륭한 예입니다. 키보드 입력을 에뮬레이션하여 페이지를 스크롤하는 방식으로, freeCodeCamp.org YouTube channel에서 상위 100개 비디오 데이터를 스크레이핑해 보겠습니다.
먼저 Dev Tools로 웹 페이지의 HTML 코드를 검사합니다:
다음 코드는 비디오 제목과 링크를 표시하는 요소를 식별합니다:
<a id="video-title-link" class="yt-simple-endpoint focus-on-expand style-scope ytd-rich-grid-media" href="/watch?v=i740xlsqxEM">
<yt-formatted-string id="video-title" class="style-scope ytd-rich-grid-media">GitHub Advanced Security Certification – Pass the Exam!
</yt-formatted-string></a>비디오 제목은 ID가 video-title인 yt-formatted-string 태그 안에 있으며, 비디오 링크는 ID가 video-title-link인 a 태그의 href 속성에 있습니다.
dynamic_scrape.py를 만들고 필요한 모듈을 import합니다:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from lxml import html
from time import sleep
import json브라우저 드라이버를 정의합니다:
URL = "https://www.youtube.com/@freecodecamp/videos"
videos = []
driver = webdriver.Chrome()
driver.get(URL)
sleep(3)이전 스크립트와 마찬가지로, 스크레이핑할 웹 URL을 담은 URL 변수를 선언하고 모든 데이터를 리스트로 저장할 videos 변수를 선언합니다.
다음으로 브라우저와 상호작용하는 데 사용할 driver 변수(즉, Chrome 인스턴스)를 선언합니다. get() 함수는 브라우저 인스턴스를 열고 지정된 URL로 리クエスト를 보냅니다.
그 후 웹 페이지의 모든 HTML 코드가 브라우저에 로드되었는지 확인하기 위해, 페이지의 어떤 요소에도 접근하기 전에 sleep 함수를 호출하여 3초 동안 대기합니다.
이제 더 많은 비디오를 로드하기 위해 아래로 스크롤을 에뮬레이션합니다:
parent = driver.find_element(By.TAG_NAME, 'html')
for i in range(4):
parent.send_keys(Keys.END)
sleep(3)send_keys 메서드는 END 키를 누르는 동작을 시뮬레이션하여 페이지 하단으로 스크롤하고, 추가 비디오 로드를 트리거합니다. 이 동작은 for 루프 내에서 4번 반복되어 충분한 비디오가 로드되도록 합니다. sleep 함수는 각 스크롤 후 3초 동안 일시 정지하여, 다시 스크롤하기 전에 비디오가 로드될 시간을 확보합니다.
다음으로, 비디오 제목과 링크를 추출합니다:
html_data = html.fromstring(driver.page_source)
videos_html = html_data.cssselect("a#video-title-link")
for video in videos_html:
title = video.text_content()
link = "https://www.youtube.com" + video.get("href")
videos.append( {"title": title, "link": link} )이 코드에서는 driver의 page_source 속성에서 얻은 HTML 콘텐츠를 fromstring 메서드에 전달하여 HTML의 계층적 트리를 구성합니다.
그런 다음 CSS 선택자를 사용하여 ID가 video-title-link인 모든 <a> 태그를 선택하며, 여기서 # 기호는 태그의 ID를 사용해 선택함을 의미합니다. 이 선택은 지정된 조건을 만족하는 요소 리스트를 반환합니다.
이후 각 요소를 반복하여 제목과 링크를 추출합니다. text_content 메서드는 내부 텍스트(비디오 제목)를 가져오고, get 메서드는 href 속성 값(비디오 링크)을 가져옵니다.
마지막으로 데이터는 videos라는 리스트에 저장됩니다.
이제 데이터를 JSON 파일로 저장하고 driver를 닫습니다:
with open('videos.json', 'w') as file:
json.dump(videos, file)
driver.close()스크립트를 실행합니다:
python dynamic_scrape.py스크립트를 실행하면 디렉터리에 videos.json이라는 새 파일이 생성됩니다:
이 스크립트의 전체 코드도 GitHub에서 확인할 수 있습니다.
Web스크레이핑은 안티봇 도구나 속도 제한 같은 문제에 직면할 수 있습니다. 프록시 서버는 사용자의 IP 주소를 마스킹하여 도움을 줍니다. Bright Data는 신뢰할 수 있는 프록시 서비스를 제공합니다.
시작하려면 무료 체험에 가입하여 Bright Data에서 프록시를 얻습니다. Bright Data 계정을 생성한 후에는 다음 대시보드를 확인할 수 있습니다:
My Zones 옵션으로 이동하여 새로운 residential proxy를 생성합니다. 그러면 다음 단계에서 필요한 프록시 사용자 이름, 비밀번호, 호스트가 표시됩니다.
다음으로 URL 변수 아래에 다음 코드를 추가하여 static_scrape.py를 수정합니다:
URL = "https://books.toscrape.com/"
# new
username = ""
password = ""
hostname = ""
proxies = {
"http": f"https://{username}:{password}@{hostname}",
"https": f"https://{username}:{password}@{hostname}",
}
content = requests.get(URL, proxies=proxies).text플레이스홀더를 Bright Data 자격 증명으로 교체하고 스크립트를 실행합니다:
python static_scrape.py이 스크립트를 실행하면 이전 예시에서 받은 것과 유사한 출력을 확인할 수 있습니다.
이 전체 스크립트는 GitHub에서 확인할 수 있습니다.
Python에서 lxml을 사용하면 효율적인 Web스크레이핑이 가능하지만, 시간이 많이 소요될 수 있습니다. Bright Data는 바로 사용할 수 있는 datasets와 Web Scraper API를 통해 효율적인 대안을 제공합니다.
Bright Data를 무료로 사용해 보세요!






