Tự phân tích những gì bạn viết trên WordPress bằng Python

Bạn có bao giờ tò mò là trong quá trình viết blog xem là tần suất sử dụng từ của mình như thế nào không? Bạn viết gì nhiều nhất? Bài viết này sẽ hướng dẫn các bạn cách dùng kiến thức data science để trích xuất và xử lý dữ liệu bài viết trên wordpress. Sau đó chúng ta sẽ vẽ 3 biểu đồ: [bar chart] biểu thị tần suất các từ xuất hiện nhiều nhất trên blog, [word cloud] để visualize chúng thành tạo một đám mây chữ và [histogram] để xem sự phân bố của tần suất các chữ được sử dụng. Ngôn ngữ chúng ta sử dụng là Python, và sẽ có một số bước xử lý ngôn ngữ tự nhiên (NPL).

Bước 1: Dữ liệu

Đầu tiên, chúng ta cần lấy dữ liệu đã. Blog của mình mở hàng từ năm 2014, cho đến nay mình đã viết được ngót nghét 8 năm rồi với khoảng gần 300 bài viết nên dữ liệu cũng có khá nhiều. Đầu tiên, giống như ở bài viết [Cách chạy trốn/backup khỏi WordPress.com], các bạn vào trang wp-admin của mình và chọn:

Tools > Export > Posts 

Và download về một file .xml. Bên trong đó chứa đầy đủ toàn bộ nội dung bạn viết. Tuy nhiên, bước quan trọng hơn cả là làm sạch dữ liệu thì mới phân tích được.

Các library sẽ dùng trong bài:

import numpy as np
import matplotlib.pyplot as plt 
import pandas as pd
import json
import bleach

Bước 2: Clean

Ở đây có 2 nhiệm vụ chính: (1) là convert định dạng xml sang json thì dễ làm việc hơn với pandas, (2) là chúng ta phải khử đi các kí tự không cần thiết (bởi vì file content trong xml được wordpress lưu dưới dạng html nên có rất nhiều tags), chỉ lấy về chữ thôi.

(1) Để convert từ xml sang json, chúng ta dùng thư viện pandas và json. Các bạn dùng lệnh sau:

import pandas as pd 
import pandas_read_xml as pdx
import json
#convert xml to json
file = 'xmlfilepath'
data = pdx.read_xml(file, encoding='utf-8')
result = data.to_json()
parsed = json.loads(result)

Vì chúng ta dùng tiếng Việt nên các bạn nhớ chọn encoding ‘utf-8’. Sau khi có được file json (tên là parsed) rồi thì các bạn cần tìm xem là mục content nằm ở cây nào của file json. Để làm điều đó dễ dàng hơn, các bạn có thể upload file json của mình vào trang: [jsonviewer]. Sau khi xác định xong thì chuẩn hoá dữ liệu:

#normalize results
df = pd.json_normalize(parsed['rss']['0']['channel']['item'])

File content của mình nằm ở rss -> 0 -> channel -> item. Chắc của các bạn cũng vậy đó.

(2) Tiếp theo, quan trọng hơn cả là clean dữ liệu. Nếu các bạn print dataframe ra thì có thể sẽ thấy như sau. Mỗi hàng tương ứng với 1 post.

Phần nội dung của chúng ta thực chất nằm ở cột content:encoded. Nhưng các bạn sẽ thấy là ở cột này có rất nhiều giá trị None (nội dung rỗng), khiến cho số lượng post (rows) lên tận 814. Chúng ta phải loại bỏ chúng ra. Cái này thì đơn giản:

df.dropna(subset=['content:encoded'], axis=0, inplace=True)

Kết quả đã được thanh lọc hơn:

Vấn đề thứ hai như các bạn thấy mình đã khoanh đỏ, đó là nó xuất hiện một số kí tự lạ nằm trong tag < >. Mấy cái này không giúp ích gì cho công cuộc phân tích của chúng ta nên cũng cần phải loại bỏ. Để loại bỏ hết HTML, CSS tags ra khỏi dữ liệu, chúng ta dùng library bleach và regex. Ai chưa cài chúng thì vào shelll/terminal và cài đặt như sau:

pip install bleach
pip install regex 

Để minh hoạ mục tiêu cần làm, các bạn có thể coi một cái content chưa qua xử lý (mình lấy bài viết gần đây nhất). Rất nhiều thứ linh tinh cần bỏ, ví dụ như link url, hoặc các tags của wordpress.

Quá trình loại bỏ được thực hiện qua các dòng lệnh sau:

# clear all html and css tags with bleach 
df['content:encoded'] = df['content:encoded'].apply(lambda x: bleach.clean(x, tags=[], attributes=[], protocols=[], strip_comments=True, strip=True))
#remove '\n', '\t' and '\td' from the text
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'\n|\t|\r', ' ', x))
#remove all 'caption'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'caption', '', x))
#remove all 'figure'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'figure', '', x))
#remove all words containing 'align'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'align', '', x))
#remove all words containing 'center'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'center', '', x))
#remove all words containing 'class'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'class', '', x))
#remove all words containing 'style'
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'style', '', x))
#remove all width 
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'width', '', x))
#remove all aligncenter
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'aligncenter', '', x))
#remove all id
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'id', '', x))
#remove all nbsp
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'nbsp', '', x))
#remove all http
df['content:encoded'] = df['content:encoded'].apply(lambda x: re.sub(r'http\S+', '', x))

Một số phần mình buộc phải làm khá thủ công, nhưng mà chấp nhận vậy. Kết quả như thế này:

trước/sau

Nhìn sạch hơn rất nhiều. Bây giờ chúng ta sẽ dùng dữ liệu đã được làm sạch để làm nhiều điều thú vị. Nhưng trước hết…

Bước 3: Xử lý ngôn ngữ tự nhiên (NPL)

Ở phần trên thì mới tách được những thứ không cần thiết thôi. Ở phần tiếp theo thì chúng ta cần tách lấy các từ trong các câu trên ra. Ở đây thì chúng ta sẽ dùng library [underthesea] chuyên dụng để xử lý tiếng Việt.

from underthesea import word_tokenize
df['split'] = df['content:encoded'].apply(word_tokenize)
#convert all split words to lowercase
df['split'] = df['split'].apply(lambda x: [i.lower() for i in x])

Ở đây thì mình sẽ tạo thêm 1 cột split chứa từ đã được tách ra. Chưa hết, chúng ta cần loại bỏ dấu câu và chữ số nữa. Phần loại bỏ dấu câu khá phức tạp vì tiếng Việt là unicode và cần dấu cách giữa các từ. Mình đã thử nhiều cách nhưng liệt kê chay các dấu cần bỏ vẫn cho kết quả vừa ý nhất. Nếu bạn nào biết code nào tối ưu hơn thì comment nhé.

#convert all split to lowercase
df['split'] = df['split'].apply(lambda x: [i.lower() for i in x])
#remove all numbers
df['split'] = df['split'].apply(lambda x: [i for i in x if not i.isdigit()])
#remove all brackets, colons, commas, dots, exclamation marks, question marks, semicolons, slashes, and underscores
df['split'] = df['split'].apply(lambda x: [i for i in x if not i in ['[ =','(','\"','\'', ')', ':', ',', '.', '!', '?', ';', '/','-','[',']', '_', '“', '”', '‘', '’','...','=','+','*','%','$','#','@','^','&','|','~','`','<','>','\\','{','}']])
#remove all commas and dots 
df['split'] = df['split'].apply(lambda x: [i.replace('.', '') for i in x])
#remove all space at the beginning and the end
df['split'] = df['split'].apply(lambda x: [i.strip() for i in x])
#remove beginning with [ =
df['split'] = df['split'].apply(lambda x: [i.replace('[ =', '') for i in x])
#remove all blanks
df['split'] = df['split'].apply(lambda x: [i for i in x if i != ''])

Tại sao phải đổi hết sang lower case, bởi vì sẽ tiện hơn khi lọc ra các từ không phải stop words (ví dụ như: “như”, “nhưng”, v.v..).

Tiếp theo, chúng ta sẽ loại bỏ tiếp những stop words. Vì trong bài của mình có cả tiếng Anh và tiếng Việt, nên mình sẽ lấy 2 lists chứa stop words của cả 2 tiếng và lọc theo list này. Cái stop list tiếng Việt mình lấy từ đây [vietnamese-stopwords], nhưng sau khi vẽ biểu đồ phát hiện ra là một số từ phổ biến như “người ta”, “mặc dù”, “ta” vẫn chưa được lọc đi. Vì thế mình đã fork và add thêm.

import collections 
word_list = []
for i in range(0, df.shape[0]):
    word_list.extend(df.iloc[i]['split'])

#filter stop words
stop_words_vn = pd.read_csv('https://raw.githubusercontent.com/thanhqtran/vietnamese-stopwords/master/vietnamese-stopwords.txt', header=None)
stop_words_en = pd.read_csv('https://raw.githubusercontent.com/stopwords-iso/stopwords-en/master/stopwords-en.txt', header=None)
stop_words_en_list = stop_words_en[0].tolist()
stop_words_vn_list = stop_words_vn[0].tolist()
stop_words = stop_words_en_list + stop_words_vn_list

# remove stop words
word_list_no_stop = [word for word in word_list if word not in stop_words]

Tiếp theo, mình sẽ tạo một dữ liệu con chứa top 500 từ được sử dụng nhiều nhất và sort theo thứ tự giảm dần.

# collector
word_freq = collections.Counter(word_list_no_stop)
# collect top 500 words
word_freq_hist = word_freq.most_common(500)
# make a dataframe
word_freq_df = pd.DataFrame(word_freq_hist, columns=['word', 'freq'])
# sort the dataframe
word_freq_df = word_freq_df.sort_values(by=['freq'], ascending=False)

Bước khó khăn nhất đã qua, bây giờ chúng ta sẽ vẽ biểu đồ minh hoạ trực quan.

Bước 4: Visualization

Biểu đồ cột giảm dần

Chú ý là các bạn cần có câu lệnh cuối để biểu đồ đi theo dạng từ cao xuống thấp.

# make a bar chart
import matplotlib.pyplot as plt

top50 = word_freq_df.head(50)
plt.figure(figsize=(7,15))
plt.barh(top50['word'], top50['freq'])
plt.xticks(rotation=90, fontsize=15)
plt.yticks(fontsize=15)
plt.xlabel('Frequency', fontsize=15)
plt.ylabel('Word', fontsize=15)
plt.title('nipponkiyoshi.com (top 50 most used words)', fontsize=15)
plt.gca().invert_yaxis()

Blog về Nhật Bản và ngôn ngữ nên biểu đồ này phản ánh cũng khá chính xác đấy chứ. Kinh tế các thứ cũng đứng cao.

Word cloud

Tiếp theo, mình tạo một đám mây các chữ theo tần suất. Chữ nào xuất hiện càng nhiều thì chữ đó càng to. Ở đây, các bạn sẽ dùng thư viện wordcloud.

import wordcloud
wc = wordcloud.WordCloud(background_color='white', width=500, height=300, max_words=250, collocations=False, max_font_size=100, random_state=30)
wc=wc.generate(" ".join(word_list_no_stop).lower())
plt.figure(figsize=(15,10))
plt.imshow(wc, interpolation='bilinear')
plt.axis("off")

Nhìn trực quan hơn hẳn.

Histogram

Cuối cùng, mình sẽ vẽ biểu đồ histogram để xem xem là viết lách có được phong phú hay không.

from matplotlib.ticker import PercentFormatter

plt.figure(figsize=(8,10))
freq_list = list(word_freq_df['freq'])
plt.hist(freq_list, weights=np.ones(len(freq_list)) / len(freq_list), bins=50, facecolor='blue', edgecolor='black', alpha=0.5)
plt.xlabel('Frequency', fontsize=15)
plt.ylabel('Percent of Words with that frequency', fontsize=15)
plt.title('Frequency Distribution', fontsize=15)
plt.gca().yaxis.set_major_formatter(PercentFormatter(1))
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)

Ở đây chú ý là cái dataframe word_freq_df là một dictionary. Chúng ta chỉ cần lấy values ‘freq’ của nó là vẽ được histogram rồi. Ngoài ra thì chúng ta không dùng density nhé.

Như vậy có thể thấy có đến gần 50% các từ sử dụng với tần suất khoảng 30 lần gì đó. Các từ mình dùng với tần suất cao thì chỉ chiếm rất rất nhỏ. Chứng tỏ dùng từ cũng khá đa dạng đấy chứ.

Đọc thêm

Cách trích xuất dữ liệu WordPress Stats bằng Python

Cách convert mọi bài viết trên WordPress sang Markdown

Cách trích xuất dữ liệu Apple Health bằng Python

7 thoughts on “Tự phân tích những gì bạn viết trên WordPress bằng Python

Để lại bình luận

Điền thông tin vào ô dưới đây hoặc nhấn vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s

Trang web này sử dụng Akismet để lọc thư rác. Tìm hiểu cách xử lý bình luận của bạn.