Py) 크롤링 BS(find) - 02

Py) 크롤링 BS(find) - 02

크롤링한 소스코드를 정제하는 BeatifulSoup를 활용하여 특정 태그에 접근하고자 할 때 사용할 수 있는 .find() 메서드 시리즈를 알아본다.


개요

requests 또는 Selenium 같은 라이브러리로 특정 페이지의 HTML 소스코드를 가져오거나 API로 XML을 받아온 경우 데이터를 추출하기 위해 원하는 요소(element)에 접근이 필요하다. 이 경우에 사용할 수 있는 것이 BeautifulSoup 라이브러리(이하 bs)로 생성한 객체의 메서드 .find()와 관련 메서드 시리즈다.

.find() 및 관련 메서드 시리즈는 HTML 또는 XML의 특정 요소에 접근하기 위해서 CSS 선택자(CSS Selector)를 사용하는 .select()와는 다르게 특정 태그 및 태그 속성을 활용하여 단일 종류의 태그에 접근하는데 사용된다.

즉, 특정 사이트의 HTML 소스코드를 최초에 파싱(parsing)하는데 있어서는 .select() 또는 .select_one()이 더 효율적이지만, 파싱된 객체에서 특정 태그에 접근하고자 할 때는 .find() 및 관련 메서드 시리즈를 사용하는 것이 더 효율적일 수 있다. 왜냐하면 .find() 메서드 시리즈에는 “string” 인자를 사용하여 특정 문자열을 포함하는 태그에 접근할 수 있기 때문이다.

종류

.find() 및 관련 메서드 시리즈는 총 25개로 목록은 다음과 같으며, 이 게시글에서 다룰 메서드는 .findNext(), .findAllNext(), .findNext(), .findAllPrevious()이다. 목록 중에서 기능 중복 때문에 따로 다루지 않는 스네이크 형식으로 명명된 메서드는 ❌로 표시하였다.

find
findAll
findAllNext 👈🏻
findAllPrevious 👈🏻
findChild
findChildren
findNext 👈🏻
findNextSibling
findNextSiblings
findParent
findParents
findPrevious 👈🏻
findPreviousSibling
findPreviousSiblings
find_all ❌
find_all_next ❌
find_all_previous ❌
find_next ❌
find_next_sibling ❌
find_next_siblings ❌
find_parent ❌
find_parents ❌
find_previous ❌
find_previous_sibling ❌
find_previous_siblings ❌

예시

다음과 같이 라이브러리와 샘플 코드를 준비한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from bs4 import BeautifulSoup as bs

source_code = """
<div class="article">
<p class="title"><b>1.</b>Introduction</p>
<p>Content 1</p>
<p data="desc">Content 2</p>
<p data="desc">Content</p>
<p class="title"><b>2.</b>Functions</p>
<p>Content 1</p>
<p data="desc">Content 2</p>
<p data="desc">Content</p>
</div>
"""

bs_source = bs(source_code)

.findNext()

단일 태그 찾기

다음과 같이 .findNext() 메서드를 사용하여 “p” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
bs_source.find("p")
## <p class="title"><b>1.</b>Introduction</p>

bs_source.find("p").findNext("p")
## <p>Content 1</p>

어트리뷰트가 “title”인 p 태그의 다음 태그에 접근하기 때문에 “Content 1”이 있는 p 태그가 반환되는 것을 알 수 있다.

속성 활용

.find() 메서드와 마찬가지로 .findNext() 메서드에도 속성을 활용할 수 있다. 다음과 같이 “data” 속성이 “desc”인 p 태그의 다음 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.find("p").findNext("p", attrs = {"data": "desc"})
## <p data="desc">Content 2</p>

p 태그 중에서 “data”속성이 있고 그 값이 “desc”인 p 태그의 다음 태그에 접근하기 때문에 “Content 2”가 있는 p 태그가 반환되는 것을 알 수 있다.

다음 코드의 결과와 비교해보자.

1
2
bs_source.find("p", attrs = {"data": "desc"}).find_next("p")
## <p data="desc">Content</p>

태그 내부의 문자열 활용

이번에는 “string” 인자를 사용하여 특정 문자열을 포함하는 태그에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
7
8
bs_source.find("p").findNext("p", string = "Content 1")
## <p>Content 1</p>

bs_source.find("p").findNext("p", string = "Content 2")
## <p data="desc">Content 2</p>

bs_source.find("p").findNext("p", string = "Content")
## <p data="desc">Content</p>

.findNext()로 접근할 수 있는 태그는 “string”으로 조건을 추가할 경우 앞에서 선택된 직전 태그와 바로 이어져있지 않아도 되며 최초의 p 태그로부터 탐색을 시작하여 조건에 맞는 첫 번째 태그를 반환하는 것을 알 수 있다.

정규표현식 사용

정규표현식은 .find() 메서드와 마찬가지로 “string”인자를 사용하긴 하나 re 라이브러리의 compile() 함수를 사용하여 정규식을 작성해야 하며, 태그 내부의 문자열이 숫자로 끝나거나 문자로 끝나는 경우의 태그에 접근하고자 할 때 다음과 같이 할 수 있다.

1
2
3
4
5
bs_source.find("p").findNext("p", string = re.compile("[0-9]$"))
## <p>Content 1</p>

bs_source.find("p").findNext("p", string = re.compile("[a-z]$"))
## <p data="desc">Content</p>

복수 태그 찾기

.find() 메서드와 같이 복수의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 할 수 있다.

1
2
bs_source.find("p").findNext(["p", "div"])
## <p>Content 1</p>

.findAllNext()

단일 태그 찾기

다음과 같이 .findAllNext() 메서드를 사용하여 “p” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
7
8
bs_source.find("p").findAllNext("p")
## [<p>Content 1</p>,
## <p data="desc">Content 2</p>,
## <p data="desc">Content</p>,
## <p class="title"><b>2.</b>Functions</p>,
## <p>Content 1</p>,
## <p data="desc">Content 2</p>,
## <p data="desc">Content</p>]

위 코드는 첫 번째 p 태그 이후에 나오는 모든 p 태그를 리스트 형태로 반환한다.

속성 활용

.findAllNext() 메서드에도 속성을 활용할 수 있다. 다음과 같이 “data” 속성의 값이 “desc”인 모든 p 태그에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
bs_source.find("p").findAllNext("p", attrs = {"data": "desc"})
## [<p data="desc">Content 2</p>,
## <p data="desc">Content</p>,
## <p data="desc">Content 2</p>,
## <p data="desc">Content</p>]

위 코드는 첫 번째 p 태그 이후에 나오는 “data” 속성의 값이 “desc”인 모든 p 태그를 반환한다.

태그 내부의 문자열 활용

이번에는 “string” 인자를 사용하여 특정 문자열을 포함하는 모든 태그에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
7
8
bs_source.find("p").findAllNext("p", string = "Content 1")
## [<p>Content 1</p>, <p>Content 1</p>]

bs_source.find("p").findAllNext("p", string = "Content 2")
## [<p data="desc">Content 2</p>, <p data="desc">Content 2</p>]

bs_source.find("p").findAllNext("p", string = "Content")
## [<p data="desc">Content</p>, <p data="desc">Content</p>]

.findAllNext()는 특정 문자열 조건에 맞는 모든 태그를 반환하며, 탐색 대상 태그와 바로 이어져 있지 않아도 조건에 맞는 태그를 모두 반환하는 것을 알 수 있다.

정규표현식 사용

정규표현식은 .findAllNext() 메서드에서도 사용 가능하다. 다음은 정규식을 사용하여 태그 내부의 문자열이 숫자로 끝나거나 영문 소문자로 끝나는 패턴인 태그에 접근하는 코드이다.

1
2
3
4
5
6
7
bs_source.find("p").findAllNext("p", string = re.compile("[0-9]$"))
## [<p>Content 1</p>,
## <p data="desc">Content 2</p>,
## <p>Content 1</p>,
## <p data="desc">Content 2</p>]

bs_source.find("p").findAllNext("p", string = re.compile("[a-z]$"))

복수 태그 찾기

복수의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
bs_source.find("p").findAllNext(["p", "b"])
## [<b>1.</b>,
## <p>Content 1</p>,
## <p data="desc">Content 2</p>,
## <p data="desc">Content</p>,
## <p class="title"><b>2.</b>Functions</p>,
## <b>2.</b>,
## <p>Content 1</p>,
## <p data="desc">Content 2</p>,
## <p data="desc">Content</p>]

대상이 되는 태그가 대상이 되는 태그의 하위 태그일지라도 별도로 뽑혀서 나오게 된다. 이를 인지하지 못한 상태에서 파싱을 하는 경우 자칫 중복된 값을 대량으로 생산해낼 수 있으니 주의하자.

.findPrevious()

단일 태그 찾기

다음과 같이 .findPrevious() 메서드를 사용하여 “p” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
bs_source.find("p").findPrevious("p")
## None

bs_source.find("p", string = "Content 1").findPrevious("p")
## <p class="title"><b>1.</b>Introduction</p>

위 코드는 첫 번째 p 태그 이전에 나오는 p 태그를 반환하는데, 첫 번째 p 태그 이전에 나오는 p 태그가 없기 때문에 None을 반환하고, “Content 1”이 있는 p 태그 이전에 나오는 p 태그를 반환하는 것을 알 수 있다.

속성 활용

.findPrevious() 메서드에도 속성을 활용할 수 있다. 다음과 같이 “data” 속성의 값이 “desc”인 p 태그의 이전 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.find("p", string = "Content 2").findPrevious("p", attrs = {"class": "title"})
## <p class="title"><b>1.</b>Introduction</p>

위 코드는 “Content 2”가 있는 p 태그 이전에 나오는 “title” 클래스를 가진 p 태그를 반환하는 것을 알 수 있다.

태그 내부의 문자열 활용

이번에는 “string” 인자를 사용하여 특정 문자열을 포함하는 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.find("p", string = "Content").findPrevious("p", string = "Content 1")
## <p data="desc">Content 2</p>

위 코드는 “Content”가 있는 p 태그 중 문자열에 “Content 1”이 있는 p 태그의 이전 태그를 반환하는 것을 알 수 있다.

정규표현식 사용

정규표현식은 .findPrevious() 메서드에서도 사용 가능하다. 다음은 정규식을 사용하여 태그 내부의 문자열이 숫자로 끝나는 패턴인 태그에 접근하는 코드이다.

1
2
bs_source.find("p", attrs = {"data": "desc"}).findPrevious("p", string = re.compile("[0-9]$"))
## <p>Content 1</p>

복수 태그 찾기

복수의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 사용할 수 있다.

1
2
bs_source.find("p", attrs = {"data": "desc"}).findPrevious(["p", "b"])
## <p>Content 1</p>

.findAllPrevious()

단일 태그 찾기

다음과 같이 .findAllPrevious() 메서드를 사용하여 “p” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
bs_source.find("p", string = "Content").findAllPrevious("p")
## [<p data="desc">Content 2</p>,
## <p>Content 1</p>,
## <p class="title"><b>1.</b>Introduction</p>]

위 코드는 “Content”가 있는 p 태그 이전에 나오는 모든 p 태그를 리스트 형태로 반환한다.

속성 활용

.findAllPrevious() 메서드에서도 속성을 활용하여 이전 요소를 찾을 수 있다. 다음은 “data” 속성의 값이 “desc”인 모든 p 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.find("p", string = "Content").findAllPrevious("p", attrs = {"data": "desc"})
## [<p data="desc">Content 2</p>]

위 코드는 “Content”가 있는 p 태그 이전에 나오는 “data” 속성의 값이 “desc”인 모든 p 태그를 반환한다.

태그 내부의 문자열 활용

이번에는 “string” 인자를 사용하여 특정 문자열을 포함하는 모든 이전 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.find("p", string = "Content 2").findAllPrevious("p", string = "Content 1")
## [<p data="desc">Content 2</p>]

.findAllPrevious()는 특정 문자열 조건에 맞는 모든 이전 태그를 반환하며, 탐색 대상 태그와 바로 이어져 있지 않아도 조건에 맞는 태그를 모두 반환한다.

정규표현식 사용

정규표현식은 .findAllPrevious() 메서드에서도 사용 가능하다. 다음은 정규식을 사용하여 태그 내부의 문자열이 숫자로 끝나거나 영문 소문자로 끝나는 패턴인 태그에 접근하는 코드이다.

1
2
bs_source.find("p", string = "Content").findAllPrevious("p", string = re.compile("[0-9]$"))
## [<p data="desc">Content 2</p>, <p>Content 1</p>]

복수 태그 찾기

복수의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 사용할 수 있다.

1
2
3
4
5
bs_source.find("p", string = "Content").findAllPrevious(["p", "b"])
## [<p data="desc">Content 2</p>,
## <p>Content 1</p>,
## <b>1.</b>,
## <p class="title"><b>1.</b>Introduction</p>]

위 코드는 “Content”가 있는 p 태그 이전에 나오는 모든 p와 b 태그를 리스트 형태로 반환한다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×