Py) 크롤링 BS(find) - 01

Py) 크롤링 BS(find) - 01

크롤링한 소스코드를 정제하는 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개로 목록은 다음과 같으며, 이 게시글에서 다룰 메서드는 .find().findAll()이다. 목록 중에서 기능 중복 때문에 따로 다루지 않는 스네이크 형식으로 명명된 메서드는 ❌로 표시하였다.

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
17
from bs4 import BeautifulSoup as bs
import re

source_code = """
<head></head>
<body>
<div data='d1'>aaa</div>
<div>bbb</div>
<div data='d2'>
<p>
bbb
</p>
</div>
</body>
"""

bs_source = bs(source_code)

.find()

단일 태그 찾기

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

1
2
bs_source.find("div")
## <div data="d1">aaa</div>

결과는 단일 BS 객체로 반환되며 해당 객체는 “div” 태그와 해당 태그의 하위 내용을 포함한다. 별도로 조작하고자 한다면 다음과 같이 할 수 있다.

1
2
3
bs_source_sub = bs_source.find("div")
bs_source_sub.text
### 'aaa'

속성 활용

.find() 메서드는 “attr” 인자를 사용하여 찾고자 하는 태그의 속성을 지정할 수 있다. 다음과 같이 “data” 속성이 “d2”인 “div” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
7
bs_source_sub = bs_source.find("div", attrs = {"data": "d2"})
bs_source_sub
## <div data="d2">
## <p>
## bbb
## </p>
## </div>

그리고 “div” 태그 요소의 하위 태그인 “p” 태그 요소에 접근하는 코드와 그 결과는 다음과 같다.

1
2
3
4
bs_source_sub.find("p")
## <p>
## bbb
## </p>

태그 내부의 문자열 활용

태그 내부의 문자열을 활용하여 태그에 접근하기 위해서는 “string”인자를 활용하여 다음과 같이 할 수 있다.

1
2
bs_source.find("div", string = "bbb")
## <div>bbb</div>

정규표현식 사용

전체 문자열이 아닌 일부 문자열과 매칭이 되는 경우는 기본적으로 지원하지 않으며, 대안으로 정규표현식을 사용할 수 있다. 그리고 여기서는 “string”인자를 사용하긴 하나 re 라이브러리의 compile() 함수를 사용하여 정규식을 작성해야 하며, 다음과 같이 “b” 문자열을 포함하는 태그에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
bs_source.find("div", string = "b")
## None

import re
bs_source.find("div", string = re.compile("b"))
## <div>bbb</div>

복수 태그 찾기

특정 태그 요소에 접근할 때 두 종류 이상의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 할 수 있다.

1
2
bs_source.find(["div", "p"])
## <div data="d1">aaa</div>

.findAll()

단일 태그 찾기

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

1
2
3
4
5
6
7
8
bs_source.findAll("div")
## [<div data="d1">aaa</div>,
## <div>bbb</div>,
## <div data="d2">
## <p>
## bbb
## </p>
## </div>]

결과는 리스트로 반환되며 해당 리스트의 각 원소는 BS이며, “div” 태그와 해당 태그의 하위 내용을 포함한다. 별도로 조작하고자 한다면 다음과 같이 할 수 있다.

1
2
3
4
5
6
bs_source_sub = bs_source.findAll("div")
bs_source_sub[0]
## <div data="d1">aaa</div>

bs_source_sub[0].text
## 'aaa'

속성 활용

.findAll() 메서드도 “attr” 인자를 사용하여 찾고자 하는 태그의 속성을 지정할 수 있다. 다음과 같이 “data” 속성이 “d2”인 “div” 태그 요소에 접근하는 코드와 그 결과를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
bs_source_sub = bs_source.findAll("div", attrs = {"data": "d2"})
bs_source_sub
## [<div data="d2">
## <p>
## bbb
## </p>
## </div>]

bs_source_sub[0].find("p")
## <p>
## bbb
## </p>

태그 내부의 문자열 활용

.find()와 마찬가지로 태그 내부의 문자열을 활용하여 태그에 접근하기 위해서는 “string”인자를 활용하여 다음과 같이 할 수 있다.

1
2
bs_source.findAll("div", string = "bbb")
## [<div>bbb</div>]

정규표현식 사용

.find()와 마찬가지로 정규표현식을 사용할 수 있다. 다음과 같이 “b” 문자열을 포함하는 태그에 접근하는 코드와 그 결과를 보자.

1
2
bs_source.findAll("div", string = re.compile("b"))
## [<div>bbb</div>]

복수 태그 찾기

특정 태그 요소에 접근할 때 두 종류 이상의 태그를 찾고자 할 때는 리스트로 묶어서 다음과 같이 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
bs_source.findAll(["div", "p"])
## [<div data="d1">aaa</div>,
## <div>bbb</div>,
## <div data="d2">
## <p>
## bbb
## </p>
## </div>,
## <p>
## bbb
## </p>]

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

Your browser is out-of-date!

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

×