다음글
(2) word colud : https://nthree.tistory.com/43
(3) 장소어휘 : https://nthree.tistory.com/44
(4) 토픽 모델링 : https://nthree.tistory.com/45
6개월도 안된 따끈따끈한 학부시절 NLP 프로젝트를 곱씹어보려고 한다.
워낙 대중 가요를 좋아하기도 해서 주제를 정했는데
NLP 관련 첫 실습이라 나름 많이 힘들었던 기억이...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
import re
import requests
from bs4 import BeautifulSoup
from time import sleep
import os
# txt파일을 넣을 폴더를 생성
song_folder_path = "./song_folder"
if not os.path.exists(song_folder_path):
os.makedirs(song_folder_path)
headers = {
'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36')
}
age_url = "https://www.melon.com/chart/age/list.htm"
params = {
'idx': '1',
'chartType': 'YE', # 년도별로
'chartGenre': 'KPOP', # 한국가요
'chartDate': '1991', # 검색연도
'moved': 'Y',
}
response = requests.get(age_url, params=params, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
song_list = soup.select('.lst50')
for i, meta in enumerate(song_list,1):
### 순위, 제목
rank = i
try:
title = meta.select('a[href*=playSong]')[0].text
except:
title = meta.select('.wrap_song_info .ellipsis')[0].text
title = title.strip()
print(str(rank )+ '위. ', title)
# 노래 데이터 url의 html 추출
song_id_html = str(meta.select('a[onclick*=SongDetail]'))
matched = re.search(r"\'(\d+)\'", song_id_html)
song_id = matched.group(1)
front_url = 'https://www.melon.com/song/detail.htm?songId='
song_url = front_url + song_id
### 가수, 앨범명, 발매날짜, 장르
response = requests.get(song_url, params=params, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
singer_html = soup.select('.wrap_info .artist a')
### 가수
singer_s = []
if len(singer_html) != 0:
for html in singer_html:
singer_s.append(html['title'])
else:
# url 없는 Various Artists용
singer_html = str(soup.select('.wrap_info .artist')[0])
singer_html = singer_html.replace('\t','').replace('\r','').split('\n')
singer_html = ''.join(singer_html)
matched = re.search(r">(.*)<", singer_html)
singer_s.append(matched.group(1))
# 가수가 여러명일 때 하나의 string으로 표현
singer_s = ', '.join(singer_s)
# 앨범명
album_name_html = str(soup.select('.list dd')[0])
matched = re.search(r">(.*)<", album_name_html)
matched2 = re.search(r">(.*)<", matched.group(1))
album_name = matched2.group(1).strip()
# 발매날짜
song_date_html = str(soup.select('.list dd')[1])
matched = re.search(r">(.*)<", song_date_html)
song_date = matched.group(1)
# 장르
song_genre_html = str(soup.select('.list dd')[2])
matched = re.search(r">(.*)<", song_genre_html)
song_genre = matched.group(1)
print("가수:", singer_s)
print("장르:", song_genre)
### 가사가 있으면 추출
try:
lyric_html = str(soup.select('.section_lyric .wrap_lyric .lyric')[0])
lyric_html = lyric_html.replace('\t','').replace('\r','').split('\n')
lyric_html = ''.join(lyric_html)
matched = re.search(r"-->(.*)<br/>", lyric_html)
lyric = matched.group(1).strip()
lyric = lyric.replace('<br/>', '\n')
#가사 앞뒤 빈칸 제거
lyric_list = []
for line in lyric.split('\n'):
lyric_list.append(line.strip())
lyric = ('\n').join(lyric_list)
except:
lyric = "없음"
### 작사가, 작곡가
song_members = []
for i in range(0,3):
try:
song_member = str(soup.select('.section_prdcr .list_person .entry .artist')[i].text).strip()
member_data = str(soup.select('.section_prdcr .list_person .entry .meta')[i].text).strip()
song_members.append([song_member, member_data])
except:
pass
composer = ""
lyricist = ""
arranger = ""
for data in song_members:
if data[1] == '작사':
composer = data[0]
if data[1] == '작곡':
lyricist = data[0]
print("작사가:",lyricist)
print()
print('(가사)',sep='\n')
print(lyric)
print()
#파일이름 불가 문자제거
title = re.sub(r"[\/\:\*\?\"\<\>\|]", " ", title)
### 노래 txt 추출
file_name = str(params['chartDate']) + '_' + str(rank) + '. ' + singer_s + ' - ' + title + ".txt"
txt_path = song_folder_path + "/" + file_name
#인코딩 판단
try:
f = open(txt_path, 'w')
f.write(lyric)
except:
f = open(txt_path, 'w', encoding='utf-8')
f.write(lyric)
f.close()
# IP 차단 방지용
sleep(1)
|
cs |
(블로그 마루의 공방님 코드 참고)
일단 크롤링을 진행했다.
크롤링에 대한 지식이 전무하여.. 여러 블로그를 서치해서 도움을 받아서 진행했던 기억이 있다.
결국 멜론 사이트에서 년도 별 TOP 노래를 크롤링 하는데 성공하였고
그 중 데이터가 너무 많아 TOP 20까지만 텍스트 파일로 저장하였다.
그 다음은 CSV파일을 만드는 것인데
코딩 실력이 부족하여
txt 파일을 손수 옮겨서 데이터를 생성하였다...ㅎㅎ (시간이 조금 걸렸다)
그렇게 구글 공유문서로 생성한 csv 파일을 불러와서 본격적으로 프로젝트를 시작하였다.
1
2
3
4
5
6
7
8
|
import os
import numpy as np
import pandas as pd
from google.colab import drive
drive.mount('/content/drive/')
df = pd.read_csv('/content/drive/MyDrive/textmining_rawdata.csv')
|
cs |
구글 코랩으로 진행했기에 구글 드라이브에서 파일을 불러왔다.

'lyrics' 칼럼의 개행문자들을 제거하고 필요없는 특수 문자들을 제거해 주었다.
1
2
|
df['lyrics'].replace(r'\s+|\\n', ' ', regex=True, inplace=True)
df.replace('(,<,>,1,2,3,4,5,6,7,8,9)','', regex=True, inplace=True)
|
cs |
1
2
3
4
5
6
7
8
|
df70 = df.iloc[0:96]
df80 = df.iloc[96:196]
df90 = df.iloc[196:296]
df00 = df.iloc[296:436]
df07 = df.iloc[436:556]
df10 = df.iloc[556:636]
df17 = df.iloc[636:716]
df20 = df.iloc[716:756]
|
cs |
그리고 시대별 가사의 특징을 확인하기 위해서
데이터를 분할시켜 주었었는데
70년대
80년대
90년대
00년~06년
07년~10년
11년~16년
17년~20년
20년~
이렇게 년도를 나누어 데이터를 분할 시켰다.
(맞춤법과 띄어쓰기는 음원사이트의 가사 데이터 특성상 이미 완료가 되어있다고 판단하고 진행하지 않았다.
실제로 'hanspell'을 통해 검증해본 결과 큰 차이가 존재하지 않았음)
본격적으로 konlpy를 활용해 전처리를 진행했다.
1
|
pip install konlpy
|
cs |
1
2
3
|
import konlpy
from konlpy.tag import Okt
okt = Okt()
|
cs |
1
2
3
|
with open('/content/drive/MyDrive/stopwords.txt', 'r',encoding='cp949') as f:
list_file = f.readlines()
stopwords = list_file[0].split(",")
|
cs |
필요한 패키지들을 설치해주고 불용어 사전을 다운받아 (전 실습 참고) 리스트로 저장해 두었다.
1
2
3
4
5
6
7
|
okt = Okt()
tokenized = []
for sentence in df70['lyrics']:
tokens = okt.nouns(sentence)
tokenize = " ".join(tokens)
tokenized.append(tokenize)
df70["lyrics_t"] = pd.DataFrame(tokenized)
|
cs |
Okt를 활용해 토크나이징을 진행하여 새로운 column에 저장했다.

이제 빈도 분석을 위해 sklearn에서 TfidfVectorizer을 설치해주고
1
2
3
4
5
6
7
8
9
10
11
12
|
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(stop_words=stopwords)
tfidf_matrix = tfidf.fit_transform(df70['lyrics_t'])
word_count70 = pd.DataFrame({
'단어': tfidf.get_feature_names(),
'tf-idf': tfidf_matrix.sum(axis=0).flat
})
word_count70.sort_values('tf-idf', ascending=False).head(10)
|
cs |
tfidf 모델을 생성한 후 학습을 시킨 다음
새로운 데이터프레임을 만들어 수치별로 정렬해보았다.

이렇게 70년대 부터 20년대 까지 같은 방법으로
분할된 데이터 전부 빈도 분석을 진행했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
okt = Okt()
tokenized = []
for sentence in df20['lyrics']:
tokens = okt.nouns(sentence)
tokenize = " ".join(tokens)
tokenized.append(tokenize)
df20["lyrics_t"] = pd.DataFrame(tokenized)
tfidf = TfidfVectorizer(stop_words=stopwords)
tfidf_matrix = tfidf.fit_transform(df20['lyrics_t'])
print(tfidf_matrix.shape)
word_count20 = pd.DataFrame({
'단어': tfidf.get_feature_names(),
'tf-idf': tfidf_matrix.sum(axis=0).flat
})
word_count20.sort_values('tf-idf', ascending=False).head(10)
|
cs |
마지막 20년대 가사에 대해 tf-idf를 진행 하였고 결과는 다음과 같다.

모든 시대별 데이터를 분석한 결과 공통적으로 사랑, 마음, 사람, 그대, 눈물 등의 단어들이 높은 TF-IDF 값을 가졌다.
또한 1970, 1980년대는 추억, 세월, 슬픔 등의 단어가 주류를 이루었고
2000년대에 들어서 ‘세상’이라는 단어가 높은 TF-IDF값을 가지기 시작했다.
최근으로 올수록 슬픈 감성의 단어 보다는 ‘순간’, ‘사이’ 등 시간적 개념이나 공간적 개념의 단어가
높은 TF-IDF값을 가지는 특성을 보인다는 것을 알 수 있다.
시대별 분석을 해보고 조금 더 인사이트를 도출하기 위해서
장르별로 자주 사용되는 가사의 빈도를 분석해 보았다.
먼저 5개로 장르를 분류해주었다.
1
2
3
4
5
|
df_t = df[df["genre"]=='성인가요/트로트']
df_r = df[df["genre"]=='록/메탈']
df_b = df[df["genre"]=='발라드']
df_d = df[df["genre"]=='댄스']
df_h = df[df["genre"]=='랩/힙합']
|
cs |

장르별로 잘 분류 된 모습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
okt = Okt()
tokenized = []
for sentence in df_d['lyrics']:
tokens = okt.nouns(sentence)
tokenize = " ".join(tokens)
tokenized.append(tokenize)
df_d["lyrics_t"] = pd.DataFrame(tokenized)
tfidf = TfidfVectorizer(stop_words=stopwords)
tfidf_matrix = tfidf.fit_transform(df_d['lyrics_t'])
word_count_d = pd.DataFrame({
'단어': tfidf.get_feature_names(),
'tf-idf': tfidf_matrix.sum(axis=0).flat
})
word_count_d.sort_values('tf-idf', ascending=False).head(10)
|
cs |
장르도 시대별 분석과 같은 방법으로 토크나이징 후 tf-idf분석을 진행하였다.
장르별 분석에서 유의미하게 볼 수 있었던 것은 힙합 장르였다.
힙합 장르의 분석 결과를 보면 다음과 같다.
힙합 장르는 영어 단어가 많아 전처리 과정을 일부 생략하고 진행하였는데
다른 장르와 다르게 압도적으로 영어의 빈도가 높았다.
다른 장르들은 공통적으로 사랑, 그대, 마음과 같은 사랑과 슬픔 관련 단어가 높은 TF-IDF 점수를 가졌고
그 중 트로트의 경우 여인, 바람 등 특유의 장르 단어들이 높은 점수를 얻은 것을 확인할 수 있다
다음 포스팅에서는 토픽 모델링과 장소 분석에 대해 다루어 보려고 한다.
내용을 많이 간추려서 포스팅 하느라 편했지만 그만큼 애먹었다. ㅎㅎ
꿑!
'자연어처리 > 실습' 카테고리의 다른 글
구름 AI 자연어처리 team project bug search (0) | 2022.09.28 |
---|---|
대화 텍스트로 감정 예측하기 대회 실습 (1) (0) | 2022.09.23 |
한국어 토크나이징 아주 간단하게! (복습용) (0) | 2022.09.17 |
LSTM 모델 간단 실습 (0) | 2022.09.11 |
Topic Modeling 및 Crawling 실습 (뉴스 데이터) (1) | 2022.09.09 |
댓글