[Python]Selenium으로 웹사이트 크롤링하기(3) — 데이터 파싱
우리가 크롤링을 하는 이유는 웹사이트의 데이터를 가져오기 위함이다. 그러므로 웹사이트 내부의 데이터를 파싱하는 일은 매우 중요하다.
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을 같이 쓰는 것이 효율적이지 않을까 생각한다.