Text Processing(1) - 데이터 클리닝(Data Cleaning/Cleansing)

2019. 1. 20. 19:04Udacity Nanodegree/Natural Language Processing

Data/Text Cleaning

일단 request라이브러리를 이용해서 udacity홈페이지를 크롤링 해보자. requests라이브러리를 사용하면 웹페이지의 내용을 그대로 가져올 수 있다.

import requests

# Fetch a web page
r = requests.get("https://www.udacity.com/courses/all")
print(r.text)

결과는 다음과 같다.


정규표현식을 쓰는건 어떨까? 일단 HTML태그의 모양을 정의하고 그 모양에 맞는 패턴이 나온다면 공백으로 바꿔주는 방법을 써보자.

import re

# Remove HTML tags using RegEx(Regular expressions)
pattern = re.compile(r'<.&?>') # tags looks like <...>
print(pattern.sub('', r.text))


이 코드의 결과는 아래와 같다.


뭔가 나아진 결과같은데 Javascript 코드가 여전히 많이 남아있고, 다른 필요없는 부분도 여전히 많다. 그리고 우리가 정의한 태그형식에 대한 부분이 여전히 많이 남아있기도 하다. 아마 태그 안의 태그같이 nested형태로 남아있는 부분이 많았나보다. 아마 라인별로 태그를 풀어헤치거나 해야하나? 음;;


여기서 우리가 필요한건 HTML을 분리하는것이다. (마치 웹브라우저에서 보이듯이!) 그리고 또 하나, HTML과 관련이 높아보이는 부분은 빼버리는 것이다.


이것이 BeautifulSoup 라이브러리가 필요한 이유이다. BeautifulSoup 라이브러리는 정확하게 이 용도를 위해서 개발된 툴이다! 이제 내가 해야할 것은 그냥 웹페이지에서 나온 텍스트를 모두 Soup객체에 던져주고 get_text 메쏘드를 이용해서 일반 텍스트를 추출한 결과를 받는것이다. 이러한 방법은 nested형태의 태그나 여러줄에 걸친 태그, 그 외에 여러 엄청나게 번거로운 HTML 파싱을 해결해준다! 또한 사소한 HTML오류정도는 너그럽게 넘어가준다. (친절도 하셔라...)

일단 코드를 다시 작성해보자.

from bs4 import BeautifulSoup

# Remove HTML tags using Beautiful Soup Library
soup = BeautifulSoup(r.text, "html5lib")
print(soup.get_text())


그리고 결과.


좀 나아진게 보인다!! 아직 Javascript 코드나 상당수의 공백이 남아있기는 하다... 뭔가 다른 방법은 또 없을까? 이번엔 HTML이 어떤 구조로 되어있는가 살펴보자.




여기서 '검사(N)'을 누르면 HTML소스코드를 그대로 볼 수 있다.


(크롬기준으로 메뉴 - 도구 더보기 - 개발자 도구 를 사용해서 같은 동작을 할 수 있다.)


제목부분을 따로 살펴보면 상세 HTML태그를 볼 수 있는데, 시연 영상과는 달리 여기서는 보기가 힘들다...이럴때는 개발자도구 왼쪽 위에 있는 아이콘 (아래그림)을 눌러서 살펴보자.


그렇게 제목을 누르면 아래와 같은 꽤 괜찮은 형태의 태그를 볼 수 있다. 일단 써보자.


일단 내가 사용한 태그는 "card__inner card mb-0"이라는 태그이다.

# Find all course summaries
soup.find_all("div", class_="card__inner card mb-0")


BeautifulSoup는 매우 강력한 툴인데, 이런식으로 아예 지정해서 찾는 식도 가능하다. 일단 여기서 우리는 div부분에서 card__inner card를 찾아달라고 하는 명령어를 사용한다. 일단 나는 이 코드에서 결과를 찾지 못했다. (물론 실습에서 쓰인 course-summary-card를 통해서도..)



찾다보니 그럴싸하고 예시와 비슷한 형태를 발견했다!! 이름은 "course-summary-card ..." 라고 한다. 일단 이걸로 수정해서 한번 더 결과를 보려고 했는데... 다시 안나온다.

또 다시 구글링해보니 Javascript 코드를 찾으려면 Phantom JS라는것을 설치해야 한다고 한다. 일단 설치해보고 다시 진행해보자... (친절한 가이드: https://programmingsummaries.tistory.com/365)

검색해본 결과 새로운 코드를 써서 해야 하는것 같다. 아래와 같은 코드를 새롭게 짜서 시도해봤다.

import requests
from bs4 import BeautifulSoup
response = requests.get("https://www.udacity.com/courses/all")
soup = BeautifulSoup(response.text)
soup.find_all(class_= "course-summary-card")


결과는?


된다!!! 복잡해보이지만 뭔가 결과가 나왔다.


강의에서는 타이틀을 찾는 코드를 쓰는데, 다음 방식은 우리가 가져온 데이터에 똑같이 적용할 수 있다.

summaries[0].select_one("h3 a")


이렇게 작성한 이유는 제목 태그가 h3에 있기 때문이다. 일단 결과는 다음과 같다.



요걸 텍스트만 가져오도록 이렇게 써보자.



아주 좋다! 원래는 양쪽의 공백을 제거해줘야 하는 작업이 있지만 우리의 경우에는 그런거 없으므로 일단 패스하자. (해줘야 하면 .strip() 을 써주면 된다)


가끔 값 자체에 =""과 같은 방식으로 공백인 경우가 있다. 이 경우에 써줄수 있는 방법은 다음과 같다. 일단 태그 이름을 지정해주고, attribute의 이름을 []에 넣어서 검색한다고 한다. 하지만 우리의 경우에는 형식이 달라졌으므로 다음과 같은 방법을 사용했다.


# Extract description
summaries[0].find_all("span", class_="ng-star-inserted")[-1].get_text()


결과는 다음과 같이 나온다.



이 작업을 루프를 통해 반복시키면 텍스트 정보만 뽑을 수 있다.


# Find all course summaries, extract title and description
summaries = soup.find_all("div", class_= "course-summary-card")
for summary in summaries:
   title = summary.select_one("h3 a").get_text().strip()
   description = summary.find_all("span", class_="ng-star-inserted")[-1].get_text()
   print("***", title, "***")
   print(description)


결과를 보면, 잘 나온다!



이 텍스트 데이터를 저장해보자.


# Find all course summaries, extract title and description
courses = []
summaries = soup.find_all("div", class_= "course-summary-card")
for summary in summaries:
   title = summary.select_one("h3 a").get_text().strip()
   description = summary.find_all("span", class_="ng-star-inserted")[-1].get_text()
   courses.append((title, description))
   
print(len(courses), "course summaries found. Sample:")
print(courses[0][0])
print(courses[0][1])


이렇게 하면 course라는 리스트 안에 데이터가 저장된다.


지금까지 우리가 한것은 웹페이지 스크래핑(Scraping webpage)이라고 불리는 작업이다. (또는 크롤링이라고 불리는 작업이다.) 이러한 스크래핑 작업은 매우 흔한 작업이며, 구글 뉴스는 이러한 작업의 좋은 예시이다.