데이터분석종합반 4일차
스파르타코딩클럽 데이터분석종합반
학습일자: 2022/06/07
강의: 데이터분석종합반
진도: 2-7 ~ 2-15

============================
2-7. 네이버 영화 줄거리로 워드 클라우드 만들기 (1)

import requests # requests라는 패키지를 임포트
import pandas as pd # pandas라는 패키지를 임포트하는데 앞으로 pd로 부르겠음
from bs4 import BeautifulSoup # bs4라는 패키지로부터 BeautifulSoup라는 모듈을 임포트

# 네이버의 크롤링 방지 장치를 우회하기 위함
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'}

movie_code = 191559 # 211097까지 존재, 191559는 앞서 봤던 영화 '듄'의 코드
movies = requests.get('https://movie.naver.com/movie/bi/mi/basic.naver?code='+str(movie_code), headers=headers)

# html 분석
soup = BeautifulSoup(movies.content, 'html.parser')
movie_title = soup.select('#content > div.article > div.mv_info_area > div.mv_info > h3 > a')
movie_genre = soup.select('#content > div.article > div.mv_info_area > div.mv_info > dl > dd:nth-of-type(1) > p > span:nth-of-type(1) > a')
movie_content = soup.select('#content > div.article > div.section_group.section_group_frst > div:nth-of-type(1) > div > div > p')
print(movie_title, movie_genre, movie_content)

위 코드를 실행하면

이렇게 나오는데, 여기서 텍스트만 뽑아내야한다.

제목 코드
movie_title = movie_title[0].text
print(movie_title)

[0]은 왜 적는가?
soup.select()는 ()안에 있는 모든 선택자들을 다 불러온다.
print(soup.select())를 통해 보이는 요소는 첫번째 인덱스만 대표적으로 보여주는 것 뿐
그러므로 변수에 데이터를 담을 땐 [0]을 쓰고 그 뒤에 .text를 넣어줘야함

장르 코드
movie_genre_list = []
for genre in movie_genre:
  movie_genre_list.append(genre.text)

print(movie_genre_list)

위 코드를 실행하면

장르는 왜 빈 리스트를 선언하고 그 안에 넣는가?
예시로 든 듄을 봐도 장르가 여러개임.
movie_genre라는 변수에 soup.select('...')를 통해 데이터를 전부 담았고, 그중에 text를 뽑아서 리스트에 넣고, 그걸 최종데이터로 결정

줄거리 코드
movie_content = movie_content[0].text
print(movie_content)

줄거리는 제목과 흡사하므로 그대로 진행

영화 한개를 코드를 통해 크롤링하는 함수 구현
# 영화 한개 크롤링 함수
def crawl(code):
    headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'}

    movie_code = code # 211097까지 존재
    movies = requests.get('https://movie.naver.com/movie/bi/mi/basic.naver?code='+str(movie_code), headers=headers)

    soup = BeautifulSoup(movies.content, 'html.parser')
    movie_title = soup.select('#content > div.article > div.mv_info_area > div.mv_info > h3 > a:nth-of-type(1)')
    movie_genre = soup.select('#content > div.article > div.mv_info_area > div.mv_info > dl > dd:nth-of-type(1) > p > span:nth-of-type(1) > a')
    movie_content = soup.select('#content > div.article > div.section_group.section_group_frst > div:nth-of-type(1) > div > div > p')

    movie_title = movie_title[0].text
    movie_genre_list = []
    for genre in movie_genre:
      movie_genre_list.append(genre.text)
    movie_content = movie_content[0].text

    movie_list = [movie_title, movie_genre_list, movie_content]

    return movie_list # 크롤링한 내용을 다른 변수에 저장할 수 있도록 리턴!


\xa0는 뭐지? 슬랙에 문의한 상태.

============================
2-8. 네이버 영화 줄거리로 워드 클라우드 만들기 (2)

네이버 영화 만개를 크롤링 할 것이다!

크롤링 코드
def crawl_all():
    movie_list_all = [] # 전체 영화 내용을 저장하기 위한 리스트
    for i in range(10000, 20001): # 영화 코드 10000 부터 영화 코드 20000 까지 크롤링!
        try: # 혹시나 크롤링이 안되더라도 다음 영화 코드로 계속 진행하라는 의미의 try ~ except
            movie_list = crawl(i)
            movie_list_all.append(movie_list) # 크롤링해온 내용을 리스트에 저장
        except:
            continue

    return movie_list_all # 전체 영화 내용이 담긴 리스트를 리턴

그 후 csv파일로 만들 것이다!

movie_list_all = crawl_all()
df = pd.DataFrame(movie_list_all, columns=['Title', 'Genre', 'Content'])
print(df)
df.to_csv('naver_movies.csv')

크롤링 돌리는데만 2시간 30분 걸림

============================
2-9. 네이버 영화 줄거리로 워드 클라우드 만들기 (3)

어제 크롤링 통해서 만든 csv파일을 토대로 수업 진행

import pandas as pd
movies = pd.read_table('naver_movies.csv', sep=',')

movies.dropna(inplace=True)
content_list = movies['Content'].values
print(content_list)

joined_data = ' '.join(content_list)

프린트를 제외한 아래 3줄
movies.dropna(inplace=True)
movies.dropna: movies라는 데이터프레임에서 결측치(값이 없는것)를 제외한 값만 가져온다.
inplace: 전에 나왔지만 원본데이터에 영향을 미칠것인가 하는 물음. T면 영향을 주고, F면 안줌(다른 변수에 담을수 있게 해줄 뿐)
movies['Content'].values
1. movies라는 이름을 가진 데이터프레임 변수에서
2. Content라는 컬럼을 가진 행에서
3. value값만 쫙 뽑아서 리스트화한다.
joined_data = ' '.join(content_list)
1. content_list라는 변수 안에 있는 요소들을
2. ' '로 연결하면서 join한다. 리스트 안의 수많은 요소들을 문자열로 만드는것.
3. 왜? 워드클라우드를 만들려면 리스트 형태를 하나의 문자열로 만들어야 하기 때문.


joind_data는 엄청나게 긴 문자열이기 때문에 0번째부터 10번째까지만 시험 삼아 출력해봄. 잘 되네요.
하나의 문자열로 합쳐졌다! 이게 중요한거.

워드 클라우드 코드
from wordcloud import WordCloud
import matplotlib.pyplot as plt # 한글폰트 세팅할 때 불러왔었던 패키지!

# 영화 줄거리의 워드클라우드
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
plt.figure(figsize = (15,15))
wc = WordCloud(max_words = 10000 , width = 1600 , height = 800, font_path = fontpath).generate(joined_data)
plt.imshow(wc, interpolation = 'bilinear')

기억이 잘 안나니까 지난 강의 복습 해야겠다.

워드클라우드 만들때 반드시 필요한 기본 코드(한글폰트 등등)
import matplotlib as mpl
import matplotlib.pyplot as plt
 
%config InlineBackend.figure_format = 'retina'
 
!apt -qq -y install fonts-nanum
 
import matplotlib.font_manager as fm
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
plt.rc('font', family='NanumBarunGothic') 
mpl.font_manager._rebuild()
from wordcloud import WordCloud
import matplotlib.pyplot as plt # 한글폰트 세팅할 때 불러왔었던 패키지!

# 영화 줄거리의 워드클라우드
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
plt.figure(figsize = (15,15))
wc = WordCloud(max_words = 10000 , width = 1600 , height = 800, font_path = fontpath).generate(joined_data)
plt.imshow(wc, interpolation = 'bilinear')


짜잔. 워드클라우드가 나왔다.
텍스트 마이닝(명사단위만 남기거나, 불용어를 제거하는) 작업이 없어서 별 의미없는게 제일 많이 나온다.

명사만 뽑아오는 텍스트 마이닝 코드
!pip install konlpy # konlpy 패키지 설치
from konlpy.tag import Okt # Okt 모듈 불러오기

tokenizer = Okt() # tokenizer 라는 이름으로 Okt 모듈 사용!
불용어 제거, 토큰화 작업
stop_words = ['자신의', '그는', '그러나', '결국', '한편', '그들은', '그들의', '함께', '자신', '그', '그리고', '그들', '그녀', '다시', '위해', '아버지', '사람', '때문'] # 나만의 불용어 정의
movies['Content'] = movies['Content'].apply(tokenizer.nouns) # 토큰화 진행 (명사 단위로 나누어 놓아야 불용어인지 아닌지 판별이 가능)
movies['Content'] = movies['Content'].apply(lambda x: [item for item in x if item not in stop_words and len(item) > 1]) # 불용어 제거

apply함수는 인자로 들어있는 함수를 각각의 행마다 실행하는 것이다.


에러가 뜬다....뭐지....

이 부분부터 재실행하니까 잘 된다.

데이터 마이닝 한 자료.

데이터마이닝한 자료를 hstack을 통해 하나로 합친다.

import numpy as np
print(movies['Content'].values)
content_list = np.hstack(movies['Content'].values)


합치기 전과 합친 후의 데이터 비교. 각각의 리스트들이 합쳐졌다.
이제 리스트의 각 요소들을 하나로 합쳐서 문자열 한개로 만든다.

joined_data = ' '.join(content_list)

워드클라우드를 다시 생성해준다.

from wordcloud import WordCloud
import matplotlib.pyplot as plt # 한글폰트 세팅할 때 불러왔었던 패키지!

# 영화 줄거리의 워드클라우드
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
plt.figure(figsize = (15,15))
wc = WordCloud(max_words = 10000 , width = 1600 , height = 800, font_path = fontpath).generate(joined_data)
plt.imshow(wc, interpolation = 'bilinear')


출력된 워드클라우드

============================
2-10. 영화 줄거리를 이용해서 장르 분류해보기 (1) - 기초 개념
기초 개념 설명인데...솔직히 뭐라는지 모르겠다. 패스!

============================
2-11. 영화 줄거리를 이용해서 장르 분류해보기 (2) - 데이터
데이터가 어떻게 이뤄져있는지 탐색하는 시간

캐글IMDb장르분류데이터 (test)
캐글IMDb장르분류데이터 (train)
두개를 다운받아 코랩에 업로드 후, 코드 작성

import pandas as pd
train = pd.read_table('train_data.text', sep=":::", names=['Index', 'Title', 'Genre', 'Content'])
test = pd.read_table('test_data_solution.text', sep=":::", names=['Index', 'Title', 'Genre', 'Content'])
train.head(3) # train의 상위 3개만 출력


잘 처리된걸 볼 수 있다.

각각의 데이터가 몇개의 행으로 이뤄졌는지 확인

train.info()
test.info()


train은 54214, test는 54200개의 행으로 이뤄져있음

장르가 총 몇개 있나?

train['Genre'].unique()


꽤 다양하군..총 27개

줄거리가 뭐냐에 따라서 장르가 변경된다
-> 줄거리 = 독립변수, 장르 = 종속변수

#훈련데이터의 독립 변수 x는 줄거리
#훈련데이터의 종속 변수 y는 장르

y_train = train['Genre']
x_train = train['Content']

#테스트데이터의 독립 변수 x는 줄거리
#테스트데이터의 종속 변수 y는 장르
#테스트데이터의 장르는 안정해졌음
x_test = test['Content']

장르는 숫자화, 줄거리는 벡터화가 필요

mapping = {' drama ':1, ' thriller ':2, ' adult ':3, ' documentary ':4, ' comedy ':5,
       ' crime ':6, ' reality-tv ':7, ' horror ':8, ' sport ':9, ' animation ':10,
       ' action ':11, ' fantasy ':12, ' short ':13, ' sci-fi ':14, ' music ':15,
       ' adventure ':16, ' talk-show ':17, ' western ':18, ' family ':19, ' mystery ':20,
       ' history ':21, ' news ':22, ' biography ':23, ' romance ':24, ' game-show ':25,
       ' musical ':26, ' war ':27}
y_train = y_train.replace(mapping)
y_test = y_test.replace(mapping)

y_train.head(3)을 입력하면

이렇게 나온다.

============================
2-12. 영화 줄거리를 이용해서 장르 분류해보기 (3) - 벡터화

행렬 = 벡터와 유사한 의미로 사용

# DTM, TFIDF 생성 코드

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

corpus = [
    'you know I want your love',
    'I like you',
    'what should I do ',    
]

vector = CountVectorizer() # DTM 벡터화를 위한 객체 생성
x_train_dtm = vector.fit_transform(corpus) # 해당 단어들을 벡터화 진행
print(x_train_dtm.toarray()) # 벡터가 어떻게 생겼는지 확인

tfidf_transformer = TfidfTransformer() # tfidf 벡터화를 위한 객체 생성
tfidfv = tfidf_transformer.fit_transform(x_train_dtm) # x_train_dtm에 대해서 벡터화 진행
print(tfidfv.toarray()) # 벡터가 어떻게 생겼는지 확인

여기서 잠깐
transform() 함수란?
벡터값을 저장하지 않음. 학습하지 않는다. (테스트데이터에 사용)
fit_transfom() 함수란?
데이터를 계속 저장하면서 벡터화 진행 (훈련데이터에 사용)

============================
2-13. 영화 줄거리를 이용해서 장르 분류해보기 (4) - 머신러닝
40분짜리 영상.... 나 죽어

# 예측모델에 필요한 모듈 임포트
from sklearn.naive_bayes import MultinomialNB #다항분포 나이브 베이즈 모델
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score #정확도 계산
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

============================
2-13. 영화 줄거리를 이용해서 장르 분류해보기 (5) - 번외

# 테스트용 내가 만든 줄거리 입력해보기
x_test_dtm = dtmvector.transform(['(내가 만든 줄거리)']) #테스트 데이터를 DTM으로 변환
tfidfv_test = tfidf_transformer.transform(x_test_dtm) #DTM을 TF-IDF 행렬로 변환

predicted = lr.predict(tfidfv_test) #테스트 데이터에 대한 예측
print(predicted)
# 불용어 제거한 후 예측 진행하기
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

# dtm, tfidf 벡터 생성을 위한 객체 생성
dtmvector = CountVectorizer(stop_words="english") # 영어 스탑워드를 제거해달라는 뜻!
tfidf_transformer = TfidfTransformer()

# x_train에 대해서 dtm, tfidf 벡터 생성
x_train_dtm = dtmvector.fit_transform(x_train)
tfidfv = tfidf_transformer.fit_transform(x_train_dtm)

# 나이브 베이즈 분류기로 학습 진행
mod = MultinomialNB()
mod.fit(tfidfv, y_train)

# x_test에 대해서 dtm, tfidf 벡터 생성
x_test_dtm = dtmvector.transform(x_test) #테스트 데이터를 DTM으로 변환
tfidfv_test = tfidf_transformer.transform(x_test_dtm) #DTM을 TF-IDF 행렬로 변환

predicted = mod.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

지금까지는 맛보기였다! 다음주부터 복습도 하고 판다스 함수도 배운다

============================
2-15. 2주차 끝 & 숙제 설명

# 숙제 코드
import pandas as pd 
import numpy as np 
import matplotlib.pyplot as plt 

df = pd.read_table('https://raw.githubusercontent.com/bab2min/corpus/master/sentiment/naver_shopping.txt', names=['ratings', 'reviews'])

print(df['reviews'].nunique())

# 중복 샘플 제거
df.drop_duplicates(subset=['reviews'], inplace=True)

df['ratings'].value_counts().plot(kind = 'bar')
print(df.groupby('ratings').size().reset_index(name = 'count'))

###########################

!pip install konlpy
from konlpy.tag import Okt
tokenizer = Okt()
df['tokenized'] = df['reviews'].apply(tokenizer.nouns)

# 리뷰 점수가 4~5점이면 1, 리뷰 점수가 1~2면 0의 값을 줍니다.
df['label'] = np.select([df.ratings > 3], [1], default=0)
df.head()

positive_reviews = np.hstack(df[df['label']==1]['tokenized'].values) #개수 셀 때, 워드클라우드 만들 때 사용
negative_reviews = np.hstack(df[df['label']==0]['tokenized'].values)

############################
#숙제코드 이어서
from collections import Counter

positive_reviews_word_count = Counter(positive_reviews)
print(positive_reviews_word_count.most_common(20))

negative_reviews_word_count = Counter(negative_reviews)
print(negative_reviews_word_count.most_common(20))

from wordcloud import WordCloud

#긍정 리뷰의 워드 클라우드
plt.figure(figsize = (15,15))
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
temp_data = ' '.join(positive_reviews)
wc = WordCloud(max_words = 2000, width = 1600, height = 800, font_path = fontpath).generate(temp_data)
plt.imshow(wc, interpolation = 'bilinear')

#부정 리뷰의 워드 클라우드
plt.figure(figsize = (15,15))
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
temp_data = ' '.join(negative_reviews)
wc = WordCloud(max_words = 2000, width = 1600, height = 800, font_path = fontpath).generate(temp_data)
plt.imshow(wc, interpolation = 'bilinear')


긍정리뷰 워드클라우드


부정리뷰 워드클라우드

profile
코딩 연습장. 발전하고 싶습니다. 모든 방향에서의 비판 부탁드립니다.

0개의 댓글