특정 웹사이트에서 HTML문서를 받아온 다음 가장 자주하는 작업은 특정 태그에 접근해서 태그 안의 문자열을 가지고 오는 일일입니다. 여기서는 BeautifulSoup을 이용해서 어떻게 특정 태그의 자식 요소 혹은 후손 요소에 접근할 수 있는지 살펴보겠습니다.
| 자식 요소와 후손 요소의 차이는 뭐야?
<html>
<head>
<title>현명한 쥐 이야기</title>
</head>
<body>
<div>
<p> 내용 </p>
</div>
</body>
</html>
자식과 후손은 의미가 겹치는 유사 단어입니다. 둘 다 부모 요소를 기준으로 그 태그에 속하는 요소를 가리키고 있습니다. 하지만 자식 요소는 바로 안 단계 아래의 태그만 가르키는데 반해 후손 요소는 부모 태그를 기준으로 그 아래에 포함되어 있는 요소를 모두 가리킵니다.
위 HTML 태그를 기준으로 이야기를 하면 <body>라는 태그의 자식 요소라고 하면 <div>태그를 말합니다. 하지만 후손 요소라면 <div>와 <p>태그가 모두 포함이 됩니다.
| HTML 문서는 어디서 가져와?
html문서는 원하는 사이트에 요청을 해서 가지고 오면 됩니다. 이번에는 네이버 금융에서 가지고 오겠습니다.
받아온 response를 BeautifulSoup으로 파싱합니다.
※파싱이란 데이터를 분해하고 재구성한다는 의미입니다.
from bs4 import BeautifulSoup
import requests
from fake_useragent import UserAgent
ua = UserAgent()
header = {'user-agent': ua.chrome}
finance_page = requests.get('https://finance.naver.com/', headers = header)
soup = BeautifulSoup(finance_page.content, 'lxml')
코드의 자세한 내용이 궁금하신 분은 아래 링크를 확인해 주세요.
웹사이트 html문서 가지고 오기(웹 스크래핑 기초, 금융 분석 자동화)
| 자식 태그에는 어떻게 접근해?
네이버 금융의 html문서는 매우 방대하기 때문에 차이를 쉽게 확인하기 위해서 비교적 내용이 적은 head 태그를 살펴보려고 했는데 head 태그에도 내용이 적지 않네요. 그 중에서도 가장 중요한 요소 중 하나인 <title>태그를 통해서 차이를 확인해 보겠습니다.
여기서 키워드가 되는 것은 head.contents 입니다. contents를 통해서 자식 태그에 접근할 수 있게 됩니다. 리스트 컴프리핸션을 사용해서 '\n'의 경우 포함시키지 않도록 하고 있습니다.
children = [child for child in head.contents if child != '\n']
print(children)
print로 출력한 내용을 살펴보면 <title> 태그를 포함한 매우 다양한 태그가 표시됩니다.
[<title>네이버 금융</title>, <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>, <meta content="text/javascript" http-equiv="Content-Script-Type"/>, <meta content="text/css" http-equiv="Content-Style-Type"/>, <meta content="네이버 금융" property="og:title"/>, <meta content="https://ssl.pstatic.net/static/m/stock/im/2016/08/og_stock-200.png" property="og:image"/>, <meta content="https://finance.naver.com" property="og:url"/>, <meta content="국내 해외 증시 지수, 시장지표, 펀드, 뉴스, 증권사 리서치 등 제공" property="og:description"/>, <meta content="article" property="og:type"/>, <meta content="" property="og:article:thumbnailUrl"/>, <meta content="네이버금융" property="og:article:author"/>, <meta content="http://FINANCE.NAVER.COM" property="og:article:author:url"/>, <link href="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/css/finance_header.css" rel="stylesheet" type="text/css"/>, <link href="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/css/finance.css" rel="stylesheet" type="text/css"/>, <link href="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/css/newstock3.css" rel="stylesheet" type="text/css"/>, <script src="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/js/jindo.min.ns.1.5.3.euckr.js" type="text/javascript"></script>, <script src="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/js/release/common.js" type="text/javascript"></script>, <script src="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/js/jindoComponent/jindo.Component.1.0.3.js" type="text/javascript"></script>, <script src="https://ssl.pstatic.net/imgstock/static.pc/20211029233853/js/nhn.autocomplete.stock.js" type="text/javascript"></script>, ' smart channel 광고 ', <script async="" src="https://ssl.pstatic.net/tveta/libs/glad/prod/gfp-core.js">
</script>, <script type="text/javascript">
...
...
※ contents 대신 children을 사용할 수도 있습니다. contents는 리스트를 반환하지만 children은 이터레이터를 반환한다는 점이 차이가 납니다. 따라서 만약 접근하려는 html 트리가 크다면 contents를 사용하는 편이 효율적입니다.
children = [child for child in head.children if child != '\n']
print(children)
| 자손 태그에는 어떻게 접근해?
마찬가지로 <head> 태그를 대상으로 자손 태그에 접근을 해보겠습니다. descendants는 generator 타입을 반환하기 때문에 for문으로 접근을 하겠습니다.
for index, child in enumerate(soup.head.descendants):
print(index)
print(child if child != '\n' else '\\n' )
어떤 요소가 순서대로 나왔는지 확인하기 쉽도록 index도 함께 출력해 주었습니다.
0
\n
1
<title>네이버 금융</title>
2
네이버 금융
3
\n
4
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
5
\n
6
...
...
아까 이야기한 것처럼 차이를 쉽게 확인하기 위해서 <title>태그를 중심으로 보겠습니다. contents.로 접근했을 때는 아래처럼 태그와 내용만이 포함되어 있었던 것에 비해서
<title>네이버 금융</title>
descendants로 접근을 했을 때는 2번째와 3번 째 인덱스의 내용으로 출력된 것이 아래 처럼 두 가지였습니다. 즉 자식 요소였던 <title>태그와 그 안의 내용만을 contents로 접근할 수 있었던데 반해서 descendants로는 자식 요소인 <title>태그 뿐만 아니라 title 태그 안의 요소인 문자열 역시 접근할 수 있다는 것을 확인할 수 있습니다.
1
<title>네이버 금융</title>
2
네이버 금융
| 전체 코드
from bs4 import BeautifulSoup
import requests
from fake_useragent import UserAgent
ua = UserAgent()
header = {'user-agent': ua.chrome}
finance_page = requests.get('https://finance.naver.com/', headers = header)
soup = BeautifulSoup(finance_page.content, 'lxml')
head = soup.head
children = [child for child in head.contents if child != '\n']
#print(children)
for index, child in enumerate(soup.head.descendants):
print(index)
print(child if child != '\n' else '\\n' )
| 왜 contents나 descendants 모두 알고 있어야 해?
목적에 따라서 구별해서 사용할 수 있기 때문입니다. 예를 들어 특정 태그 바로 아래 단계에 있는 경우만 가지고 오고 싶은 경우도 있을 것입니다. 이 때는 contents를 사용할 수 있습니다.
하지만 때로는 특정 태그 아래에 있는 모든 <a> 태그를 가지고 오고 싶은 경우도 있을 것입니다. 이 때는 descendants를 사용하는 것이 훨씬 편할 것입니다.
| 같이 보면 좋은 자료 모음