이 가이드에서는 파이썬의 Parsel을 활용한 웹 스크래핑에 대해 다음과 같이 배웁니다:
- Parsel이란 무엇인가
- 웹 스크래핑에 Parsel을 사용하는 이유
- Parsel을 활용한 웹 스크래핑 단계별 튜토리얼
- 파이썬에서 Parsel을 활용한 고급 스크래핑 시나리오
자, 시작해 보겠습니다!
Parsel이란?
Parsel은 HTML, XML 및 JSON 문서에서 데이터를 파싱하고 추출하기 위한 Python 라이브러리입니다. lxml을 기반으로 구축되어 웹 스크래핑을 위한 더 높은 수준의 사용자 친화적인 인터페이스를 제공합니다. 구체적으로, HTML 및 XML 문서에서 데이터를 추출하는 과정을 단순화하는 직관적인 API를 제공합니다.
웹 스크래핑에 Parsel을 사용해야 하는 이유
Parsel은 다음과 같은 웹 스크래핑을 위한 흥미로운 기능을 제공합니다:
- XPath 및 CSS 선택기 지원: HTML 또는 XML 문서 내 요소를 찾기 위해 XPath 또는 CSS 선택기를 사용하세요. 웹 스크래핑을 위한 XPath 대 CSS 선택기 가이드에서 자세히 알아보세요.
- 데이터 추출: 선택한 요소에서 텍스트, 속성 또는 기타 콘텐츠를 가져옵니다.
- 셀렉터 연결: 여러 셀렉터를 연결하여 데이터 추출을 정교하게 다듬을 수 있습니다.
- 확장성: 이 라이브러리는 소규모 및 대규모 스크래핑 프로젝트 모두에서 원활하게 작동합니다.
이 라이브러리는 Scrapy에 긴밀하게 통합되어 웹 페이지에서 데이터를 파싱하고 추출하는 데 사용됩니다. 그러나 Parsel은 독립형 라이브러리로도 활용할 수 있습니다.
웹 스크래핑을 위한 파이썬에서 Parsel 사용 방법: 단계별 튜토리얼
이 섹션에서는 파이썬의 Parsel을 사용해 웹을 스크래핑하는 과정을 안내합니다. 대상 사이트는 “하키 팀: 양식, 검색 및 페이지네이션“입니다:

Parsel 스크레이퍼는 위 표의 모든 데이터를 추출합니다. 아래 단계를 따라 구축 방법을 확인하세요!
필수 조건 및 종속성
이 튜토리얼을 재현하려면 컴퓨터에 Python 3.10.1 이상이 설치되어 있어야 합니다. 특히 Parsel은 최근 Python 3.8 지원을 중단했음을 유의하세요.
프로젝트의 메인 폴더를 parsel_scraping/이라고 가정합니다 . 이 단계가 완료되면 폴더 구조는 다음과 같아집니다:
parsel_scraping/
├── parsel_scraper.py
└── venv/
여기서:
parsel_scraper.py는스크래핑 로직을 포함하는 Python 파일입니다.venv/에는가상 환경이 포함됩니다.
다음과 같이 venv/ 가상 환경 디렉터리를 생성할 수 있습니다:
python -m venv venv
활성화하려면 Windows에서 다음을 실행하세요:
venvScriptsactivate
macOS 및 Linux에서는 다음과 같이 실행합니다:
source venv/bin/activate
활성화된 가상 환경에서 종속성을 설치하려면 다음을 실행하세요:
pip install parsel requests
이 두 가지 종속성은 다음과 같습니다:
parsel: HTML을 파싱하고 데이터를 추출하는 라이브러리입니다.requests:parsel은HTML 파서만 제공하므로 웹 스크래핑을 수행하려면 Requests와 같은 HTTP 클라이언트가 필요합니다. 이를 통해 스크래핑 대상 페이지의 HTML 문서를 가져올 수 있습니다.
훌륭합니다! 이제 파이썬에서 Parsel을 사용해 웹 스크래핑을 수행하는 데 필요한 모든 것을 갖추셨습니다.
1단계: 대상 URL 정의 및 콘텐츠 파싱
이 튜토리얼의 첫 단계로 라이브러리를 임포트해야 합니다:
import requests
from parsel import Selector
그런 다음 대상 웹페이지를 정의하고 Requests로 콘텐츠를 가져온 후 Parsel로 파싱합니다:
url = "https://www.scrapethissite.com/pages/forms/"
response = requests.get(url)
selector = Selector(text=response.text)
위 코드 조각은 Parsel의 Selector() 클래스를 인스턴스화합니다. 이 클래스는 get()으로 수행된 HTTP 요청의 응답에서 읽은 HTML을 파싱합니다.
2단계: 테이블에서 모든 행 추출
브라우저에서 대상 웹 페이지의 테이블을 검사하면 다음과 같은 HTML을 확인할 수 있습니다:

테이블에 여러 행이 포함되어 있으므로, 스크랩된 데이터를 저장할 배열을 초기화합니다:
data = []
HTML 테이블에는 .table 클래스가 적용되어 있습니다. 테이블의 모든 행을 선택하려면 아래 코드 줄을 사용하세요:
rows = selector.css("table.table tr.team")
이 코드는 css() 메서드를 사용하여 파싱된 HTML 구조에 CSS 선택자를 적용합니다.
선택된 행을 반복 처리하며 데이터를 추출할 차례입니다!
3단계: 행 반복 처리
이전과 마찬가지로 테이블 내 행을 살펴보면:

각 행은 전용 열에 다음과 같은 정보를 포함하고 있음을 알 수 있습니다:
- 팀 이름 →
.name요소 내부 - 시즌 연도 →
.year요소 내부 - 승리 횟수 →
.wins요소 내부 - 패배 수 →
.losses요소 내부 - 연장전 패배 →
.ot-losses요소 내부 - 승률 →
.pct요소 내부 - 득점 (득점 – GF) →
.gf요소 내부 - 실점 (실점 – GA) →
.ga요소 내부 - 골 득실차 →
.diff요소 내부
다음 로직으로 해당 정보를 모두 추출할 수 있습니다:
for row in rows:
# 각 열에서 데이터 추출
name = row.css("td.name::text").get()
year = row.css("td.year::text").get()
wins = row.css("td.wins::text").get()
losses = row.css("td.losses::text").get()
ot_losses = row.css("td.ot-losses::text").get()
pct = row.css("td.pct::text").get()
gf = row.css("td.gf::text").get()
ga = row.css("td.ga::text").get()
diff = row.css("td.diff::text").get()
# 추출된 데이터 추가
data.append({
"name": name.strip(),
"year": year.strip(),
"wins": wins.strip(),
"losses": losses.strip(),
"연장전 패배": ot_losses.strip(),
"승률": pct.strip(),
"홈 승률": gf.strip(),
"원정 승률": ga.strip(),
"차점": diff.strip()
})
위 코드의 동작은 다음과 같습니다:
get()메서드는 CSS3 의사 요소(pseudo-elements)를 사용하여 텍스트 노드를 선택합니다.strip()메서드는 앞뒤 공백을 제거합니다.append()메서드는 내용을데이터목록에 추가합니다.
좋습니다! 파서 데이터 스크래핑 로직 완료.
4단계: 데이터 출력 및 프로그램 실행
마지막 단계로 CLI에서 스크랩한 데이터를 출력합니다:
# 추출된 데이터 출력
print("페이지에서 가져온 데이터:")
for entry in data:
print(entry)
프로그램 실행:
python parsel_scraper.py
예상 결과는 다음과 같습니다:
대단하네요! 페이지의 데이터가 구조화된 형식으로 정확히 출력되었습니다.
5단계: 페이지네이션 처리
이전 단계까지는 대상 URL의 메인 페이지에서 데이터를 가져왔습니다. 이제 모든 페이지를 가져오고 싶다면 어떻게 해야 할까요? 이를 위해서는 코드를 수정하여 페이지 매김을 관리해야 합니다.
먼저, 이전 코드를 다음과 같은 함수로 캡슐화해야 합니다:
def scrape_page(url):
# 페이지 콘텐츠 가져오기
response = requests.get(url)
# HTML 콘텐츠 파싱
selector = Selector(text=response.text)
# 스크래핑 로직...
return data
이제 페이지네이션을 관리하는 HTML 요소를 살펴보세요:
여기에는 모든 페이지 목록이 포함되어 있으며, 각 페이지의 URL은 <a> 요소에 삽입되어 있습니다. 모든 페이지네이션 URL을 가져오는 로직을 함수로 캡슐화하세요:
def get_all_page_urls(base_url="https://www.scrapethissite.com/pages/forms/"):
# 페이지네이션 링크 추출을 위한 첫 페이지 가져오기
response = requests.get(base_url)
# 페이지 파싱
selector = Selector(text=response.text)
# 페이지네이션 영역에서 모든 페이지 링크 추출
page_links = selector.css("ul.pagination li a::attr(href)").getall() # HTML 구조에 따라 선택자 조정
unique_links = list(set(page_links)) # 중복 링크 제거
# 모든 페이지의 완전한 URL 생성
full_urls = [urljoin(base_url, link) for link in unique_links]
return full_urls
이 함수는 다음을 수행합니다:
getall()메서드는 모든 페이지네이션 링크를 가져옵니다.list(set())메서드는 중복을 제거하여 동일한 페이지를 두 번 방문하는 것을 방지합니다.urljoin()메서드(urllib.parse라이브러리)는 모든 상대 URL을 절대 URL로 변환하여 추가 HTTP 요청에 사용할 수 있게 합니다.
위 코드가 작동하려면 Python 표준 라이브러리에서 urljoin을 임포트해야 합니다:
from urllib.parse import urljoin
이제 다음 코드로 모든 페이지를 스크래핑할 수 있습니다:
# 스크랩된 데이터 저장 위치
data = []
# 모든 페이지 URL 가져오기
page_urls = get_all_page_urls()
# 반복하며 스크래핑 로직 적용
for url in page_urls:
# 현재 페이지 스크래핑
page_data = scrape_page(url)
# 스크래핑된 데이터를 리스트에 추가
data.extend(page_data)
# 추출된 데이터 출력
print("모든 페이지의 데이터:")
for entry in data:
print(entry)
위의 코드 조각:
get_all_page_urls()함수를 호출하여 모든 페이지 URL을 가져옵니다.scrape_page()함수를 호출하여 각 페이지의 데이터를 스크랩합니다. 그런 다음extend()메서드로 결과를 통합합니다.- 스크랩된 데이터를 출력합니다.
훌륭합니다! Parsel 페이지네이션 로직이 이제 구현되었습니다.
6단계: 모든 것을 통합하기
아래는 parsel_scraper.py 파일에 포함되어야 할 내용입니다:
import requests
from parsel import Selector
from urllib.parse import urljoin
def scrape_page(url):
# 페이지 콘텐츠 가져오기
response = requests.get(url)
# HTML 콘텐츠 파싱
selector = Selector(text=response.text)
# 스크랩된 데이터 저장 위치
data = []
# 테이블 본문의 모든 행 선택
rows = selector.css("table.table tr.team")
# 각 행을 반복하며 데이터 추출
for row in rows:
# 각 열에서 데이터 추출
name = row.css("td.name::text").get()
year = row.css("td.year::text").get()
승 = row.css("td.wins::text").get()
패 = row.css("td.losses::text").get()
연장전패 = row.css("td.ot-losses::text").get()
승률 = row.css("td.pct::text").get()
gf = row.css("td.gf::text").get()
ga = row.css("td.ga::text").get()
diff = row.css("td.diff::text").get()
# 추출된 데이터를 리스트에 추가
data.append({
"name": name.strip(),
"year": year.strip(),
"wins": wins.strip(),
"losses": losses.strip(),
"ot_losses": ot_losses.strip(),
"pct": pct.strip(),
"gf": gf.strip(),
"ga": ga.strip(),
"diff": diff.strip(),
})
return data
def get_all_page_urls(base_url="https://www.scrapethissite.com/pages/forms/"):
# 페이지네이션 링크 추출을 위한 첫 페이지 가져오기
response = requests.get(base_url)
# 페이지 파싱
selector = Selector(text=response.text)
# 페이지네이션 영역에서 모든 페이지 링크 추출
page_links = selector.css("ul.pagination li a::attr(href)").getall() # HTML 구조에 따라 선택자 조정
unique_links = list(set(page_links)) # 중복 링크 제거
# 모든 페이지의 전체 URL 생성
full_urls = [urljoin(base_url, link) for link in unique_links]
return full_urls
# 스크래핑된 데이터 저장 위치
data = []
# 모든 페이지 URL 가져오기
page_urls = get_all_page_urls()
# 반복 처리하며 스크래핑 로직 적용
for url in page_urls:
# 현재 페이지 스크래핑
page_data = scrape_page(url)
# 추출된 데이터를 리스트에 추가
data.extend(page_data)
# 추출된 데이터 출력
print("모든 페이지의 데이터:")
for entry in data:
print(entry)
아주 좋습니다! Parsel을 사용한 첫 번째 스크래핑 프로젝트를 완료했습니다.
파이썬에서 Parsel을 활용한 고급 웹 스크래핑 시나리오
이전 섹션에서는 파이썬의 Parsel을 사용하여 CSS 선택자로 대상 웹 페이지의 데이터를 추출하는 방법을 배웠습니다. 이제 좀 더 고급 시나리오를 살펴볼 시간입니다!
텍스트로 요소 선택하기
Parsel은 XPath를 사용하여 HTML에서 텍스트를 검색하는 다양한 쿼리 메서드를 제공합니다. 이 경우 text() 함수를 사용하여 요소의 텍스트 콘텐츠를 추출합니다.
다음과 같은 HTML 코드가 있다고 가정해 보세요:
<html>
<body>
<h1>Parsel에 오신 것을 환영합니다</h1>
<p>이것은 한 단락입니다.</p>
<p>또 다른 단락입니다.</p>
</body>
</html>
다음과 같이 모든 텍스트를 가져올 수 있습니다:
from parsel import Selector
html = """
<html>
<body>
<h1>Welcome to Parsel</h1>
<p>This is a paragraph.</p>
<p>Another paragraph.</p>
</body>
</html>
"""
selector = Selector(text=html)
# <h1> 태그에서 텍스트 추출
h1_text = selector.xpath("//h1/text()").get()
print("H1 텍스트:", h1_text)
# 모든 <p> 태그에서 텍스트 추출
p_texts = selector.xpath("//p/text()").getall()
print("단락 텍스트 노드:", p_texts)
이 코드 조각은 <p> 및 <h1> 태그를 찾아 text()를 사용하여 텍스트를 추출합니다. 결과는 다음과 같습니다:
H1 텍스트: Welcome to Parsel
단락 텍스트 노드: ['This is a paragraph.', 'Another paragraph.']
또 다른 유용한 함수는 contains()로, 특정 텍스트를 포함하는 요소를 일치시키는 데 사용할 수 있습니다. 예를 들어, 다음과 같은 HTML 코드가 있다고 가정해 보겠습니다:
<html>
<body>
<p>This is a test paragraph.</p>
<p>Another test paragraph.</p>
<p>Unrelated content.</p>
</body>
</html>
이제 “test”라는 단어만 포함된 단락의 텍스트를 추출하고 싶습니다. 다음 코드로 이를 수행할 수 있습니다:
from parsel import Selector
# html = """..."""
selector = Selector(text=html)
# "test" 단어가 포함된 단락 추출
test_paragraphs = selector.xpath("//p[contains(text(), 'test')]/text()").getall()
print("'test'가 포함된 단락:", test_paragraphs)
Xpath p[contains(text(), 'test')]/text() 는 “test”만 포함하는 단락을 쿼리합니다. 결과는 다음과 같습니다:
'test'를 포함하는 단락: ['This is a test paragraph.', 'Another test paragraph.']
특정 문자열 값으로 시작하는 텍스트를 가로채고 싶다면 어떻게 할까요? starts-with() 함수를 사용하면 됩니다! 다음 HTML을 살펴보세요:
<html>
<body>
<p>여기서 시작합니다.</p>
<p>다시 시작합니다.</p>
<p>여기서 끝납니다.</p>
</body>
</html>
“start”라는 단어로 시작하는 단락의 텍스트를 가져오려면 다음과 같이 p[starts-with(text(), 'Start')]/text() 를 사용하세요:
from parsel import Selector
# html = """..."""
selector = Selector(text=html)
# 텍스트가 "Start"로 시작하는 단락 추출
start_paragraphs = selector.xpath("//p[starts-with(text(), 'Start')]/text()").getall()
print("'Start'로 시작하는 단락:", start_paragraphs)
위의 코드 조각은 다음과 같은 결과를 생성합니다:
'Start'로 시작하는 단락: ['Start here.', 'Start again.']
CSS와 XPath 선택자에 대해 자세히 알아보세요.
정규 표현식 사용
Parsel은 re:test() 함수와 함께 정규 표현식을 사용하여 고급 조건에 맞는 텍스트를 추출할 수 있습니다.
다음 HTML을 고려해 보세요:
<html>
<body>
<p>항목 12345</p>
<p>항목 ABCDE</p>
<p>한 단락</p>
<p>2026년은 현재 연도입니다</p>
</body>
</html>
숫자 값만 포함된 단락의 텍스트를 추출하려면 다음과 같이 re:test() 를 사용할 수 있습니다:
from parsel import Selector
# html = """..."""
selector = Selector(text=html)
# 숫자 패턴과 일치하는 텍스트를 가진 단락 추출
numeric_items = selector.xpath("//p[re:test(text(), 'd+')]/text()").getall()
print("숫자 항목:", numeric_items)
결과는 다음과 같습니다:
숫자 항목: ['Item 12345', '2026 is the current year']
정규 표현식의 또 다른 일반적인 용도는 이메일 주소를 가로채는 것입니다. 이는 이메일 주소만 포함된 단락에서 텍스트를 추출하는 데 사용할 수 있습니다. 예를 들어, 다음 HTML을 고려해 보세요:
<html>
<body>
<p>문의는 [email protected]으로 연락주세요</p>
<p>이메일은 [email protected]으로 보내주세요</p>
<p>여기에는 이메일이 없습니다.</p>
</body>
</html>
다음은 re:test() 를 사용하여 이메일 주소를 포함하는 노드를 선택하는 방법입니다:
from parsel import Selector
selector = Selector(text=html)
# 이메일 주소가 포함된 단락 추출
emails = selector.xpath("//p[re:test(text(), '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}')]/text()").getall()
print("일치하는 이메일:", emails)
결과:
Email Matches: ['Contact us at [email protected]', 'Send an email to [email protected]']
HTML 트리 탐색
Parsel은 XPath를 사용하여 아무리 중첩된 HTML 트리라도 탐색할 수 있게 해줍니다.
다음 HTML을 살펴보겠습니다:
<html>
<body>
<div>
<h1>제목</h1>
<p>첫 번째 단락</p>
</div>
</body>
</html>
다음과 같이 <p> 노드의 모든 상위 요소를 가져올 수 있습니다:
from parsel import Selector
selector = Selector(text=html)
# <p> 태그의 부모 요소 선택
parent_of_p = selector.xpath("//p/parent::*").get()
print("<p>의 부모:", parent_of_p)
결과:
Parent of <p>: <div>
<h1>Title</h1>
<p>First paragraph</p>
</div>
마찬가지로 형제 요소도 관리할 수 있습니다. 다음과 같은 HTML 코드가 있다고 가정해 보겠습니다:
<html>
<body>
<ul>
<li>항목 1</li>
<li>항목 2</li>
<li>항목 3</li>
</ul>
</body>
</html>
다음 형제 노드를 가져오려면 following-sibling을 다음과 같이 사용할 수 있습니다:
from parsel import Selector
selector = Selector(text=html)
# 첫 번째 <li> 요소의 다음 형제 요소 선택
next_sibling = selector.xpath("//li[1]/following-sibling::li[1]/text()").get()
print("첫 번째 <li>의 다음 형제 요소:", next_sibling)
# 첫 번째 <li> 요소의 모든 형제 요소 선택
all_siblings = selector.xpath("//li[1]/following-sibling::li/text()").getall()
print("첫 번째 <li>의 모든 형제 요소:", all_siblings)
결과:
첫 번째 <li>의 다음 형제 요소: Item 2
첫 번째 <li>의 모든 형제 요소: ['Item 2', 'Item 3']
파이썬에서 HTML 파싱을 위한 Parsel 대안
Parsel은 웹 스크래핑을 위한 Python 라이브러리 중 하나이지만 유일한 것은 아닙니다. 아래는 다른 잘 알려져 있고 널리 사용되는 라이브러리들입니다:
- Beautiful Soup: 웹 페이지에서 정보를 쉽게 스크래핑할 수 있게 해주는 Python 라이브러리입니다. Beautiful Soup을 활용한 웹 스크래핑 가이드에서 사용법을 배워보세요.
lxml:libxml2및libxslt라이브러리를 위한 파이썬 스타일 바인딩입니다. 웹 데이터 파싱을 위한 lxml 튜토리얼에서 실제 사용 사례를 확인하세요.- PyQuery: XML 문서에 jQuery 쿼리를 수행할 수 있게 해주는 라이브러리입니다. 이로 인해 최고의 파이썬 HTML 파서 5종 중 하나로 꼽힙니다.
- Scrapy: 웹사이트에서 필요한 데이터를 추출하기 위한 오픈소스 협업 프레임워크입니다. 웹 스크래핑을 위한 Scrapy 사용법을 확인하세요.
html.parser: 텍스트 HTML 및 XTHML 콘텐츠를 파싱하기 위한 클래스를 제공하는 Python 표준 라이브러리의 모듈입니다.html5-parser: Python에서 HTML 5를 빠르게 구현한 라이브러리입니다.
결론
이 글에서는 파이썬의 Parsel과 이를 활용한 웹 스크래핑 방법에 대해 알아보았습니다. 기본 개념부터 시작하여 더 복잡한 시나리오까지 탐구했습니다.
어떤 Python 스크래핑 라이브러리를 사용하든 가장 큰 장애물은 대부분의 웹사이트가 봇 방지 및 스크래핑 방지 조치로 데이터를 보호한다는 점입니다. 이러한 방어 체계는 자동화된 요청을 식별하고 차단하여 기존 스크래핑 기법을 무력화시킵니다.
다행히 Bright Data는 모든 문제를 해결할 수 있는 솔루션 제품군을 제공합니다:
- 웹 언락커(Web Unlocker): 최소한의 노력으로 모든 웹페이지의 안티 스크래핑 보호를 우회하고 깨끗한 HTML을 제공하는 API입니다.
- 스크래핑 브라우저: 자바스크립트 렌더링이 가능한 클라우드 기반 제어형 브라우저입니다. CAPTCHA, 브라우저 지문 인식, 재시도 등을 자동으로 처리해 줍니다. 팬서(Panther) 또는 셀레니움 PHP(Selenium PHP)와 원활하게 통합됩니다.
- 웹 스크레이퍼 API: 수십 개의 인기 도메인에서 구조화된 웹 데이터에 프로그래밍 방식으로 접근할 수 있는 엔드포인트입니다.
웹 스크래핑은 다루기 싫지만 온라인 데이터에는 여전히 관심이 있으신가요? 바로 사용 가능한 데이터셋을 살펴보세요!
지금 Bright Data에 가입하고 무료 체험을 시작하여 스크래핑 솔루션을 테스트해 보세요.