หลังจากที่เกริ่นไว้นานแล้วว่าจะเขียน post เกี่ยวกับ python บ้าง มาด้วยหัวข้อแรก ก็เริ่มจากวิเคราะห์ความรู้สึกของลูกค้าเลย disclaimer กันก่อนอันนี้เป็นโปรเจ็คลองทำนะครับ โค้ดอาจจะยึดยาวซักหน่อย จะได้เห็นที่มาที่ไปครับ เป็นยังไงไปดูกัน

จากแนวคิดที่ได้ไปอบรมมา หนุ่มได้เรียน Web scrapping ทั้งที่เป็น Static และ Dynamic ในคอร์สอบรม Geeks ของกรมควบคุมโรค (ป้ายยาของเค้าดีจริงๆ) และก็ได้ลองทำตามบทความ >>link<< ของ อ.ดร.กานต์ ม.รังสิต ในการวิเคราะห์ความรู้สึกของลูกค้าที่คอมเมนต์เข้ามา

หนุ่มก็เลยรวบองค์ความรู้ 2 อันนี้มาประกอบกันดู ซึ่งแบ่งออกเป็น 5 ขั้นตอนหลัก ดังนี้

  1. Library
  2. Login
  3. Collect Comments
  4. Model Sentiment Analysis
  5. My Collected Comments

Library

เป็นโปรเจ็คที่ค่อนข้างเรียกใช้ Library ที่เยอะมากเหมือนกันนะเนี่ย หลักๆ ก็จะเป็น BeautifulSoup, Selenium, Regex, Altair, … มากมายค่อยๆ บอกว่าใช้ทำอะไรในขั้นต่อไปแล้วกันนะครับ —สำหรับ Library ไหนที่เครื่องท่านผู้อ่านยังไม่มี ให้ pip install … เพิ่มก่อนนะครับ ค่อยเรียกใช้งาน

เริ่ม import library กันเลย

import time
from time import sleep

from bs4 import BeautifulSoup
import re

from selenium import webdriver
from selenium.common.exceptions import WebDriverException, TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import altair as alt

import vl_convert as vlc

Login

ขั้นตอนนี้คือเดี๋ยวดีเดี๋ยวร้ายมาก 🤣 เพราะแพลตฟอร์มที่หนุ่มเลือกใช้เป็น TikTok มันเลยมองว่าเป็น bot พอทำเขียนทดสอบหลายๆ ครั้ง มันบล็อกจ้า ไว้หาวิธีเจอว่าจะแก้ไขยังไงได้บ้างจะมาบอกนะคับ T.T เอาโค้ดที่ใช้งานไปก่อนแล้วกัน

# ประกาศหน้าเว็บที่จะ Login, username, password ด้วย
username = "xxx@gmail.com"
password = "xx1234546789xx"
website_url = "https://www.tiktok.com/login/phone-or-email/email"

driver = webdriver.Chrome()

หนุ่มจะใช้ WebDriver ของ Selenium ในการทำออโตแมทควบคุม ChromeDriver ให้เบราว์เซอร์ทำงานตามคำสั่ง เพื่อเปิดหน้า login และใช้ username, password พร้อมกับกดปุ่ม login

try:
    # Navigate to the login page
    driver.get(website_url)

    # Find the input element within the div (หา input username)
    try:
        input_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "#loginContainer > div.tiktok-aa97el-DivLoginContainer.exd0a430 > form > div.tiktok-q83gm2-DivInputContainer.etcs7ny0 > input")) 
        )
    except:
        print("Input element not found within the div.")
        driver.quit()
        exit()
    
    # Enter the username text
    input_element.send_keys(username)

    # Find the input element within the div (หา input password)
    try:
        input_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "#loginContainer > div.tiktok-aa97el-DivLoginContainer.exd0a430 > form > div.tiktok-15iauzg-DivContainer.e1bi0g3c0 > div > input")) 
        )
    except:
        print("Input element not found within the div.")
        driver.quit()
        exit()
    
    # Enter the test text
    input_element.send_keys(password)
    
    # Define the CSS Selector of the button (หาปุ่ม Login และกดปุ่ม)
    button_css_selector = "#loginContainer > div.tiktok-aa97el-DivLoginContainer.exd0a430 > form > button" 
    
    # Find the button element and click it
    try:
        button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, button_css_selector))
        )
        button.click()
    except:
        print("Button not found or not clickable.")

    print("Login successful!")

finally:
    print("Waiting for command...")

จากที่เรียนมาเค้าสอนให้ใช้ presence_of_element_located By.ID ก็คือ ID ของ Input textbox แต่พี่ TikTok เค้าไม่มีก็เลยต้องย้ายมาใช้ By.CSS_SELECTOR แทน ซึ่งวิธีการได้มาก็คือต้องไปคลิกขวา Inspect แล้วมองหาตำแหน่งของ Textbox นั้นๆ แล้วคลิกขวาเลือก copy > Copy selector แล้วเอามาวางแทนที่ และทำแบบเดียวกันกับ password และปุ่ม Login

เอาหล่ะมาถึงพาร์ทที่ต้องลอง login ดู คือหนุ่มทดสอบรันไปหลายครั้งเกินใช่มะมันล็อกอ่า คงต้องรอสัก 2-3 วัน ไปอ่านๆ ดูเค้าบอกต้องรอ 48 ชม. (ตอนแรกๆ มันล็อกอินผ่านจริงๆ สาบาน ✋) แล้วถ้า login ผ่านมันก็จะขึ้นหน้าแรกของ TikTok แล้วมีรูปโปรไฟล์ของเราเลย แบบนี้

และเทคโนโลยีที่พี่ TikTok เค้าเอามากัน bot อีกอันคือ Pattern unlock อันนี้ หากมีผู้เชี่ยวท่านใดกรุณารบกวนอนุเคราะห์หนุ่มตาสีน้ำตาลคนนี้ด้วยนะฮ้าบบบ —โอเค ท่านใดเจอจุดนี้ต้อง Manual ด้วยมือไปก่อนนะครับ

แต่ว่าถึงมันจะ Login ไม่ผ่าน เราก็ยังกวาดคอมเมนต์จาก video ได้อยู่ ไปกันต่อเลย

Collect Comments

สิ่งที่ต้องมีคือ ลิ้งก์คลิปที่ต้องการกวาดคอมเมนต์ขึ้นมาใช้งาน ได้มาจาก URL ด้านบนนั่นแหละครับ คัดลอกมาเลย (หรือจะมาจากปุ่ม share และคัดลอกลิ้งก์มาก็ได้นะคับ)

คือที่ต้องเรียกเป็นสินค้าตัวนี้ เพราะข้อมูลที่ใช้เทรนโมเดลในการวิเคราะห์ความรู้สึกในพาร์ทต่อไป มันเกี่ยวกับสินค้าออนไลน์นะครับ ก็เลยใช้คลิปนี้ ไม่มีเจตนาอื่นใดเลย —ไปดูโค้ดกัน

url = 'https://www.tiktok.com'
driver.get(url)
sleep(5)

max_scroll = 30 # จำนวนครั้งสำหรับ Scroll เม้าส์ลงไปดูคอมเมนต์ ปรับแต่งได้ครับว่าเยอะน้อยยังไง

url = 'https://www.tiktok.com/@massiri1989/video/7426528315403422984?is_from_webapp=1&sender_device=pc&web_id=7459282833639540232' # ลิ้งก์สินค้าที่จะกวาดคอมเมนต์
driver.get(url)
sleep(5)

soup = BeautifulSoup(driver.page_source, "html.parser")

# Scroll down 10 times with a small interval
for _ in range(max_scroll):
    driver.execute_script("window.scrollBy(0, 800);")  # Scroll down by 800 pixels
    time.sleep(0.5)  # Wait for 0.5 seconds

ทำไมถึงมี 2 url น่ะเหรอ คือหนุ่มเอาไว้หลบ Pattern unlock นั่นแหละใช้ได้ผลบ้างในบางครั้ง ถ้าเจอก็ manual เอานะครับ

มาที่ max_scroll เป็นจำนวนครั้งสำหรับ Scroll เม้าส์ลงไปดูคอมเมนต์ TikTok มันจะค่อยโหลดคอมเมนต์มาทีละนิด ก็เลยต้องตั้งค่าให้มันหนุมลงมาทีละนิด

ตรงจุด “window.scrollBy(0, 800);” คือจำนวน pixel ที่จะให้มันหมุนลงแต่ละครั้ง หนุ่มเซ็ตไว้ที่ 800 pixels ท่านผู้อ่านสามารถปรับแต่งได้เองครับว่าเยอะน้อยยังไง

ตรง time.sleep(0.5) อันนี้ก็ค่อนข้างขึ้นอยู่กับความเร็วเน็ตของแต่ละเครื่องเนอะ ว่าหมุนแต่ละครั้งจะรอนานแค่ไหนค่อยหมุนอีกที (ให้คอมเมนต์มันโหลดขึ้นมาทันว่างั้น) ปรับได้ครับจุดนี้

ต่อมาเราก็ใช้ BeautifulSoup ตักซุปออกมา ซึ่งบริเวณที่เราจะตักซุปออกมาก็คือ บริเวณคอมเมนต์นั่นเอง ก็คลิกขวาเลือก copy selector อีกเช่นเคย

แล้วก็เอาไปใส่ในโค้ดบล็อกถัดไป โดยเปิด List ชื่อ paragraphs ว่างขึ้นมา เมื่อตักซุปแต่ละครั้งใน for loop ก็ให้เอามาใส่ใว้ในนี้

soup = BeautifulSoup(driver.page_source, "html.parser")

paragraphs = []

topics = soup.select("#main-content-video_detail > div > div.css-12kupwv-DivContentContainer.ege8lhx2 > div.css-1senhbu-DivLeftContainer.ege8lhx3 > div.css-x4xlc7-DivCommentContainer.ejcng160 > div.css-7whb78-DivCommentListContainer.ezgpko40")

for x in topics:
        paragraphs.append(str(x))
    
print(len(topics))

และก็พบว่ามันตักคอมเมนต์มาได้นะ แต่ได้มาเป็น text ยาวๆๆ เลย เรียกดูความยาว len(paragraphs) มีแค่ 1 เดียว —แงๆ ทำไงดี 🤔

แต่ก่อนอื่นข้อความที่ได้มานั้นมีเครื่องหมายวรรคตอนอย่าง ฟันหนู/Double Quote/เครื่องหมายคำพูด ซึ่งจะให้สกัดเอาคอมเมนต์ออกมาไม่ได้ จึงต้อง Replace มันด้วยค่าว่างก่อน

paragraphs = [i.replace('"', '') for i in paragraphs]

จากนั้นก็สร้างฟังก์ชัน extract_texts สกัดข้อความตาม pattern ที่เราต้องการ หนุ่มจึงเลือกใช้ Regex มาใช้งาน

def extract_texts(message, pattern):
  matches = re.findall(pattern, message)
  return matches

ซึ่ง pattern ที่ได้มานั่นคือต้องสังเกตเอาว่าบริเวณใดที่ Unique พอที่จะเกาะเอาข้อความคอมเมนต์ เป็นรูปแบบซ้ำๆ ที่ใช้แสดงผลข้อความ ตามกฏการใช้งาน Regex เราจะเอาข้อความในวงเล็บ (.+) กี่ตัวอักษรก็ได้ออกมา จะจัดเก็บลงใน list ที่ชื่อ extracted_texts

pattern = r"<p class=TUXText TUXText--tiktok-sans css-1658qcl-StyledTUXText e1vx58lt0 font-size=14 letter-spacing=0.09380000000000001 style=color: inherit; font-size: 14px;>(.+)</p></span>"  
# Matches "keyword"

extracted_texts = extract_texts(paragraphs[0], pattern)
extracted_texts  
# Output: ['keyword1', 'keyword2', 'keyword3']

ลองเรียกผลลัพธ์ออกมาดู ว้าวซ่ามากก ก็จะเห็นว่ามีพวก emoji 😲 ที่พวกเขาแสดงความคิดเห็นออกมาด้วย ซึ่งจะยังไม่จัดการกับมันตอนนี้นะครับ ไว้ถ้ามีเจ้าของร้านค้ามาจ้างให้วิเคราะห์ผลให้หน่อย จะมาแชร์นะครับ

หากใช้ len(extracted_texts) ก็จะเห็นจำนวนคอมเมนต์ที่เราสกัดได้ออกมานะครับ แล้วก็ใช้ driver.close() ปิดเบาวเซอร์ออกไปซะ เพราะเราได้ข้อมูลมาแล้ว

จากนี้เก็บข้อมูลนั้นไว้ก่อนนะครับ ขั้นต่อไปจะเป็นการเทรนด์โมเดลวิเคราะห์ความรู้สึก Sentiment Analysis กันก่อนนะครับ

Model Sentiment Analysis

เช่นเคยนะครับสำหรับการอธิบายโค้ดที่เป็นหลักการ ทั้งในเรื่องวิชาการและความรู้ให้ข้ามไปดูบทความและคลิปของ อ.ดร.กานต์ ได้เลยนะครับที่ลิ้งก์นี้

หนุ่มจะเรียกใช้ไฟล์เทรนด์ของอาจารย์เค้าเลยนะครับ

df = pd.read_csv('Ref/review_shopping.csv', sep='\t', names=['text', 'sentiment'], header=None)
df.head()

ไฟล์ review_shopping.csv เป็นไฟล์กำกับข้อความรีวิวจากเว็บขายของออนไลน์แห่งหนึ่ง เมื่อปี 2561 โดยมี 2 tag คือ pos และ neg กำกับโดย นาย วรรณพงษ์ ภัททิยไพบูลย์ << ขอขอบพระคุณมา ณ ทีนี้ครับ —พอเรียกดูจำนวนก็มีความเห็นเชิง Positive 60 รายการ และ ความเห็นเชิง Negative อีก 68 รายการ

จากนั้นก็จะเข้าสู่การตัดคำที่สื่อความหมายออกไปหรือ stopwords หรือคำที่ไม่ค่อยสื่อความหมาย จากไลบรารี่ PyThaiNLP มาเก็บไว้ที่ตัวแปร thai_stopwords เช่นคำว่า ‘นี่ไง’, ‘หรือยัง’, ‘รับ’, ‘นาง’, ‘เป็นที’ และอื่นๆ อีกมาก

thai_stopwords = list(thai_stopwords())
thai_stopwords[:5]

แล้วก็เขียนเป็นฟังก์ชัน text_process เลยเพื่อลบเครื่องหมายวรรคตอน พวกเครื่องหมายคำถาม จุด ตกใจ คอมม่า ใดๆ → จากนั้นตัดคำภาษาไทย ออกเป็นคำๆ ตามรูปถัดไปจ้า

def text_process(text):
    final = "".join(u for u in text if u not in ("?", ".", ";", ":", "!", '"', "ๆ", "ฯ"))
    final = word_tokenize(final)
    final = " ".join(word for word in final)
    final = " ".join(word for word in final.split() 
                     if word.lower not in thai_stopwords)
    return final

เรียกใช้งานฟังก์ชัน text_process กับคอลัมน์ text จะได้ผลลัพธ์ที่ตัดคำภาษาไทยและลบเครื่องหมายวรรคตอนแล้วดังในคอลัมน์ชื่อ text_tokens

df['text_tokens'] = df['text'].apply(text_process)
df

Word Cloud

หลังจากที่เราตัดคำได้แล้ว จะมาลองสร้าง Word cloud กลุ่มเมฆของคำที่ปรากฏบ่อยๆ ในประโยค โดยกำหนด Regex และฟอนต์ที่จะใช้ก่อนเพื่อความสวย ฮ่าๆๆ

reg = r"[ก-๙a-zA-Z']+"
fp = 'Ref/Mitr/Mitr-Medium.ttf'

บล็อกแรกเป็นข้อความเชิง positive ก่อน ได้ผลลัพธ์ดังนี้

# positive
df_pos = df[df['sentiment'] == 'pos']
pos_word_all = " ".join(text for text in df_pos['text_tokens'])

wordcloud = WordCloud(stopwords=thai_stopwords, background_color = 'white', max_words=2000, 
                      height = 2000, width=4000, font_path=fp, regexp=reg, colormap='ocean').generate(pos_word_all)
plt.figure(figsize = (16,8))
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

ส่วนบล็อกถัดมาเป็นข้อความเชิง Negative ได้ผลลัพธ์ดังนี้

Split text for Train & Test

แบ่งข้อมูลออกเป็นสองส่วนครับ ส่วนแรกใช้ Train model 70% และส่วนสองใช้ Test model 30%

from sklearn.model_selection import train_test_split

X = df[['text_tokens']]
y = df['sentiment']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=101)

Word Vectorizer และ Bag-of-Words (BoW)

เป็นขั้นตอนที่ประมาณว่า การหาความถี่การเกิดของคำนั้นๆ ว่าเกิดกี่ครั้ง และเอาออกมาแสดงผลในรูปของตารางหรือเก็บเอาไว้ในตัวแปรเพื่อใช้งานต่อในการเทรน

from sklearn.feature_extraction.text import CountVectorizer

cvec = CountVectorizer(analyzer=lambda x:x.split(' '))
cvec.fit_transform(X_train['text_tokens'])

cvec.vocabulary_
train_bow = cvec.transform(X_train['text_tokens'])

pd.DataFrame(train_bow.toarray(), columns=cvec.get_feature_names_out(), index=X_train['text_tokens'])

Logistic Regression

สร้างโมเดล Logistic Regression เพื่อจำแนกว่าข้อความนั้นเป็น pos หรือ neg โดยใช้ BoW ที่สร้างขึ้น และ y_train หรือที่เป็น sentiment

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bow, y_train)

หลังจาก Train และ Test เราก็มาหาความแม่นยำของโมเดลกันต่อ แม่นแค่ไหนกันนะ ซึ่งผลลัพธ์คือ

from sklearn.metrics import confusion_matrix,classification_report
test_bow = cvec.transform(X_test['text_tokens'])
test_predictions = lr.predict(test_bow)
print(classification_report(test_predictions, y_test))

Precision และ Recall ก็สูงทั้งคู่นะครับ พอใจแล้วก็ไปต่อ —คือความต้องการของหนุ่มอยากได้ตารางข้อความเทียบกับผลการทำนาย ออกมาด้วยก็เลยทำการรันโค้ดนี้ต่อ

# สร้าง DataFrame จาก NumPy array
predictions_df = pd.DataFrame(test_predictions, columns=['prediction'])

# รวม DataFrame เข้าด้วยกัน
X_test = X_test.reset_index()
result_df = pd.concat([X_test, predictions_df], axis=1)
result_df

My Collected Comments

กลับมายุ่งกับคอมเมนต์สินค้า TikTok ที่เราค้างคากันไว้ จัดการให้มันเป็น DataFrame ก่อน

extracted_texts[:10]

comments = pd.DataFrame(extracted_texts, columns = ['comments'])
comments

ทำ word tokenize โดยใช้ฟังก์ชัน text_process ที่ได้สร้างไว้

comments['text_tokens'] = comments['comments'].apply(text_process)

สร้าง BoW ให้กับชุดข้อความ comments[text_tokens] ของเรา

test_bow = cvec.transform(comments['text_tokens'])

แล้วก็ให้โมเดลที่เราเทรนด์ไว้ มาทำนายข้อความพวกนั้น

test_predictions = lr.predict(test_bow)

แล้วก็เอาออกมาในรูปของตาราง ได้ดังนี้

# สร้าง DataFrame จาก NumPy array
predictions_df = pd.DataFrame(test_predictions, columns=['prediction'])

# รวม DataFrame เข้าด้วยกัน
result_df = pd.concat([comments, predictions_df], axis=1)

แม่นบ้าง ไม่แม่นบ้างเนอะ สำหรับคำไทย “คับ, คัฟ” หลากหลายการพิมพ์เหลือเกิน ไม่เป็นไรผ่านไปก่อน —คราวนี้มานับจำนวนกันว่า Positive หรือ Negative เท่าไหร่กัน

# Count the occurrences of each unique value in the `prediction` column.
prediction_counts = result_df['prediction'].value_counts()
prediction_df = pd.DataFrame({'prediction': prediction_counts.index, 'count': prediction_counts.values})
prediction_df

เสร็จแล้วก็มาลองสร้างเป็น Bar chart ดู คราวนี้หนุ่มจะใช้ Library Altair สร้างดูนะครับ เครื่องมือเค้าดูน่าใช้ดี

prediction_df['prediction'] = prediction_df['prediction'].astype('category')

# Create a bar chart of the `prediction` column.
chart = alt.Chart(prediction_df).mark_bar().encode(
    x=alt.X('prediction').axis(labelAngle=0, labelFontSize=14),
    y=alt.Y('count', title='Count').axis(labelFontSize=14),
    tooltip=['prediction', 'count']
).properties(
    title='Count of Predictions',
    width=800,  # Adjust the width here
    height=500  # Adjust the height here
).interactive()

text = chart.mark_text(
    align="center",
    baseline="bottom",
    size=12
).encode(text="count")

ff = chart + text
ff

แล้วก็ Export เป็นรูปออกมา

# Export Chart Image
chart_json = ff.to_json()

# Save the chart as PNG
png_data = vlc.vegalite_to_png(chart_json)
with open("chart.png", "wb") as f:
    f.write(png_data)

ก็จะเห็นว่าคอมเม้นต์สำหรับโพสต์นี้ไปในทาง Positive 129 รายการ ซึ่งมากกว่า Negative ที่มี 88 รายการเนอะ และไหนๆ ก็มาเป็น ฺBar chart แล้ว WordCloud ก็ต้องมาด้วยแหละ แสดงทั้ง Positive และ Negative เลย

# positive
rdf_pos = result_df[result_df['prediction'] == 'pos']
pos_word_all = " ".join(text for text in rdf_pos['text_tokens'])

wordcloud = WordCloud(stopwords=thai_stopwords, background_color = 'white', max_words=2000, 
                      height = 2000, width=4000, font_path=fp, regexp=reg, colormap='ocean').generate(pos_word_all)
plt.figure(figsize = (16,8))
plt.imshow(wordcloud)
plt.axis('off')
plt.show()
# negative
rdf_neg = result_df[result_df['prediction'] == 'neg']
neg_word_all = " ".join(text for text in rdf_neg['text_tokens'])

wordcloud = WordCloud(stopwords=thai_stopwords, background_color = 'white', max_words=2000, 
                      height = 2000, width=4000, font_path=fp, regexp=reg, colormap='gist_heat').generate(neg_word_all)
plt.figure(figsize = (16,8))
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

Conclusion

ฟิ้ววว ร่ายมายาวมากกก ก็เป็นโปรเจ็คเล็กๆ ที่หนุ่มทำออกมาเนอะ ผิดพลาดยังไงก็ต้องขออภัยนะครับ ส่วนตัวคิดว่าสิ่งที่ต้องปรับปรุงเพิ่มก็น่าจะเป็น …

  • การป้องกันการตรวจจับว่าเราเป็น bot มั้ยในการดึงข้อมูลจาก TikTok
  • น่าจะมีตัวอย่าง Sentiment ที่เป็น Class ของคำถาม เป็นตัวอย่างให้โมเดลเรียนรู้ด้วย
  • การจัดการคำไทยอย่างคำว่า “ครับ, คัรบ, คับ, คัฟ” ที่ต้องตัดออกไปก่อน แต่มันก็ยังมีคำว่า “คับ” ที่หมายถึงในทางลบอย่าง คับ แน่นเกินไป (tight) ที่ใช้งานได้อยู่
  • การจัดการ emoji ออกไปก่อนการวิเคราะห์

ก็เป็นหัวข้อให้เราพัฒนาให้ดีขึ้นต่อไปเน๊อะ สำหรับโค้ดเดี๋ยวแชร์ไว้ให้ในลิ้งก์นะครับ 😆😆


ขอบคุณท่านผู้อ่านที่เปิดเข้ามานะครับ นี่ก็เป็นการเขียนบทความ python 🐍 แรกเลยตื่นเต้น และใช้เวลาร่างและทดสอบนานมา (ทดสอบจนบัค login ไม่ได้เลย) ท่านผู้อ่านหากมีคำถามสงสัย ฝากไว้ในช่อง comment ได้เลยนะครับ

สำหรับผู้ประกอบการร้านค้าที่สนใจอยากวิเคราะห์โพสต์คลิปของท่านเองจ้างได้นะครับ ราคาเป็นกันเอง เป็นกำลังใจ เป็นค่ากาแฟ ☕ ให้หนุ่มซักนิดดดด อิอิ

เขียนดี ไม่ดียังไงติชมได้ครับ + แปลผิดแปลถูก + มี typo ขออภัยครับ 🙏 พยายามแล้วครับ

.

ขอบคุณครับ

Search

About

Feasible เว็บไซต์ที่นำเสนออาชีพปัจจุบันที่เรา (เจ้าของเว็บ) กำลังทำ ไม่ว่าจะเป็น นักวิเคราะห์ข้อมูล นักเรียน นักอ่าน นักฟาร์ม และอีกหลากหลายมุมมอง เรียกได้ว่าเป็น ‘แกงโฮะ’ เลยล่ะ ฮ่าๆๆ ติดตาม Content ที่จะทำออกมาได้เรื่อยๆ นะครับ ขอบคุณที่เข้ามาเยี่ยมกัน 😁✌️

Social Icons

Buy Me a Coffee

😁 ขอบคุณทุกน้ำใจ ทุกการสนับสนุนครับ 👏