Sitemap

[Python]Selenium으로 웹사이트 크롤링하기(3) — 데이터 파싱

Sarah Na
7 min readMay 4, 2018

우리가 크롤링을 하는 이유는 웹사이트의 데이터를 가져오기 위함이다. 그러므로 웹사이트 내부의 데이터를 파싱하는 일은 매우 중요하다.

HTML의 데이터를 파싱할 때, 주로 beautiful-soup이라는 HTML 파서를 사용한다. 나도 앞편에서 소개한 소방청 화재 데이터를 크롤링할 때 beautiful-soup을 이용해서 소방청 웹사이트에 있는 데이터를 파싱했었다. 그런데 이러한 데이터 파싱은 Selenium에서도 충분히 가능하기도 하다. 그래서 이번 편에서는 beautiful-soup을 사용하여 데이터를 파싱하는 방법과 Selenium을 사용하여 데이터를 파싱하는 방법 두가지 다 포스팅 해보려고 한다.

1) beautiful-soup으로 파싱하기

beautiful-soup은 HTML/ XML 파일을 파싱하기 위한 파이썬 라이브러리이다. 쉽게 말해, 이 라이브러리를 사용해 웹페이지에 있는 내가 원하는 데이터를 가져올 수 있다는 소리이다. 그래서 우리는 전편에 크롤링 하기로 한 소방청 홈페이지의 화재 현황 데이터를 beautiful-soup을 통해 가져와 볼 것이다. 그 전에 beautiful-soup의 메소드의 활용 예제를 살펴보자.

soup = BeautifulSoup(html_doc, 'html.parser')

먼저, html_doc(파싱하고자 하는 HTML의 전체 내용)과 html_parser 인자를 동시에 전달하여HTML의 내용을 파싱할 객체를 선언한다. 그 후에 beautiful-soup에서 제공하는 메소드를 필요에 따라 호출해서 사용하면 된다.

검색 시 (단일/다중 element 검색)

  • soup.find(id=’id명’)
  • soup.find(class=’class명’)
  • soup.find(‘태그 이름’, class_=’class명’)
  • soup.find_all(‘태그 이름’)

위와 같이 find(), find_all() 메소드를 호출하여 해당 id나 class를 가진 데이터를 파싱할 수 있다. find_all() 메소드의 경우에는 태그 이름(ex. a, table..)을 파라미터로 전달하면 해당 태그를 전부 찾아준다. 그러므로 find() 함수는 단일 element 검색 시에, find_all()함수는 다중 element 검색 시에 사용하면 된다. 다음은 find_all() 메소드를 사용해 a 태그 내부의 링크를 파싱하는 코드이다.

for link in soup.find_all('a'):
print(link.get('href'))

해당 웹페이지에서 ‘a’태그를 전부 찾은 뒤, for문을 사용해 찾은 데이터들에서 ‘href’ 속성을 가져와 링크를 뽑아내는 것이다. 여기서 알 수 있는 사실은 find_all()메소드는 다중 element를 검색하므로, 리스트처럼 사용하면 된다는 것이다. 예를 들어, ‘a’ 태그를 전부 찾은 뒤에, 첫번째로 찾아진 데이터에 접근하고자 할 경우에는

  • soup.find_all(‘a’)[0]

과 같이 접근하면 된다. 만약 여기서 태그 내부의 텍스트 데이터를 가지고 오고 싶다면, get_text() 함수를 사용하면 된다.

  • soup.find_all(‘a’)[0].get_text()

와 같이 사용하면 된다. get_text() 함수를 사용하면 태그 안쪽에 있는 텍스트 데이터를 파싱할 수 있다.

이제, 소방청 홈페이지의 화재 현황 데이터를 가져와보자.

파싱하고자 하는 데이터

위의 사진의 왼쪽에 위치하는 것이 소방청 홈페이지에서 파싱하고자 하는 부분이다. 오른쪽에 나와 있는 HTML 코드를 보면, 저 데이터들을 포함하고 있는 태그는 table 태그이다. 한마디로, table 태그를 사용하여 저 데이터들을 삽입해 코드를 짰다는 것이다. 그렇다면 위에서 봤던 것과 같이 find() 메소드에 table 태그의 class 이름이 ‘default’ 라는 것을 파라미터로 전달해서 해당 태그 내부의 데이터를 긁어오면 된다.

table = soup.find(‘table’, class_=”default”)

이렇게 코드를 작성하면, class 이름이 default인 table 태그의 내부 데이터를 파싱해오게 된다. 그런데 위의 코드를 자세히 보면 구분, 계, 건수(건)과 같은 필요없는 데이터는 파싱할 필요가 없다.

필요한 데이터

구분, 계, 건수(건) 등등 표 맨 위에 짙은 초록색으로 표시된 부분을 제외하고 필요한 데이터들만 보면 위와 같다. <tbody> 태그 안에 <tr>, <td>가 위치한 형식이다.

table = soup.find(‘table’, class_=”default”)
tbody = table.tbody
body = str(tbody.get_text())

이럴 때는 tbody 태그 안에 있는 내용만 가져오면 되므로 아까 선언한 table 변수에서 tbody 태그 내용만을 가져오겠다는 table.tbody를 선언해주면 된다. 그리고 get_text()를 이용해 태그 하위 내용을 모두 파싱해오면 된다.

상세 소스코드는 https://github.com/sehwaa/NFDS-Crawler/blob/master/parsing.py 를 참조하면 되겠다.

2) Selenium으로 파싱하기

Selenium도 기본 구조는 beautiful-soup과 비슷하다.

검색 시

  • find_element_by_id(‘id명’)
  • find_element_by_class_name(‘class명’)
  • find_element_by_xpath(‘xpath’)
  • find_element_by_tag_name(‘tag명’)

위에 있는 메소드들을 사용하여 해당 HTML의 id/class/xpath/tag를 찾아간다. beautiful-soup과 차이가 있다면 beautiful-soup은 find()메소드나 find_all()메소드를 호출할 경우 해당 태그 자체의 결과를 가지고 온다. 예를 들어, find(‘a’)와 같이 사용했을 경우, “<a href=”#”>예시</a>”와 같은 형태로 결과가 나오게 된다. 그러나 Selenium은 이렇게 태그를 보여주는게 아니라 <selenium.webdriver.remote.webelement.WebElement (session=”1214952b08ce04ee553094c85299e76a”, element=”0.6626153679501161–13")> 와 같이 결과가 나온다. 그러므로 반드시 내부 텍스트 데이터를 가져와야만 한다.

beautiful-soup

  • soup.get_text()

Selenium

  • driver.text

beautiful-soup에서 get_text()를 호출하여 내부 데이터를 가져왔다면, Selenium에서는 .text로 내부 데이터를 가져올 수 있다. 소방청 홈페이지에서Selenium으로 데이터를 수집하고 싶다면, 다음과 같이 코드를 작성하면 된다.

table = driver.find_element_by_xpath("//table[@class='default']")
table_data = table.text

예를 들기 위해 find_element_by_xpath()를 사용했지만, 여기서는 find_element_by_class_name()을 사용해도 무방하다.

다만, Selenium에서 조금 불편한 점이 있다면, beautiful-soup은 하위 태그의 내용을 가져오기가 쉬웠는데, Selenium에서는 하위 태그의 내용을 가져오는 것이 힘들었다. 위의 예시만 보아도, table 태그 내부에 tbody 안의 내용만 가져오면 조금 더 수월하게 데이터를 가져올 수 있는데, Selenium에서는 table 태그 내부의 tbody 태그에 접근하는 것이 어려웠다. xpath를 사용해서 가져올 수 있다고는 하는데, 대부분 데이터를 가져오지 못했다. 때에 따라 다르겠지만, Selenium과 beautiful-soup을 같이 쓰는 것이 효율적이지 않을까 생각한다.

--

--

Sarah Na
Sarah Na

No responses yet