สำหรับผู้ที่สนใจเฉพาะส่วนผลการจับลำดับความนิยมของสาวๆ BNK48 (วัดจากจำนวน hashtag ที่เกิดขึ้นในคืนวันวาเลนไทน์, 14-02-2018) ก็อ่านเพียงแค่หัวข้อนี้กับหัวข้อถัดไปและข้ามไปดูส่วน conclusion ที่มี 1. ตัวไฟล์ GIF บันทึกภาพการจัดลำดับความนิยมของสมาชิกวงจากการทำ Twitter streaming (ไฟล์เดียวกับตัวข้างล่างนี้) และ 2. กราฟข้อมูลจำนวนครั้งที่ถูกพูดถึงของ top hashtags แบบเทียบเวลาที่ส่วนล่างของ post นะครับ
post นี้จะไม่ลงรายละเอียดมาก สำหรับผู้เขียน Python เป็นอยู่แล้ว และอยากศึกษาการทำ data streaming แบบง่ายๆ โดยเป็นการแสดงตัวอย่างขั้นตอน ในการใช้ Python
ขั้นตอนส่วนที่ 1 นั้น นำมาจากจากคอร์ส Spark and Python for Big Data with PySpark บน Udemy
ขอบคุณพี่ต้า (Ta Virot Chiraphadhanakul) แห่ง Skoodio ที่ช่วยให้ feedback ของ draft แรก ด้วยครับ
คาดว่าทุกคนรู้จัก Twitter กันอยู่แล้ว ก็ขอเพิ่มเติมเพียงว่า Twitter นี่เป็น platform ที่เหมาะกับผู้ที่อยากฝึกทำ data streaming มากๆ ทั้งเรื่อง API support และ supply of data
จากนั้นผู้เขียนก็มาคิดดูว่า data ชุดไหนที่น่าสนใจจะดึงมาลองเล่นดูและมีผู้พูดถึงอยู่เรื่อยๆ (continuous supply of data) ก็สรุปว่าจะเลือกสำรวจข้อความบน Twitter (tweets) ที่เกี่ยวกับกลุ่มไอดอลหญิงของไทยที่มาแรงและมีกลุ่มผู้ติดตามใน Twitter เป็นจำนวนมาก ซึ่งก็คือ BNK48
เราก็มารู้จักกับสาวๆ BNK48 กันสักเล็กน้อย สั้นๆ คือ – “BNK48 เป็นกลุ่มไอดอลหญิงของประเทศไทย และเป็นวงน้องสาวของกลุ่มไอดอลญี่ปุ่น AKB48 ภายใต้แนวคิดร่วมกันคือ “ไอดอลที่คุณสามารถไปพบได้” (จาก th.wikipedia.org/wiki/บีเอ็นเคโฟร์ตีเอต)
ก่อนเริ่มส่วน technical ก็มาดู workflow กันก่อน สั้นๆ เราก็จะทำการ
environment ที่ใช้
สำหรับผู้ที่ไม่เคยมี account กับ Twitter ก่อนอื่นก็ต้องสร้าง Twitter account ก่อนนะครับ
เมื่อมี Twitter account แล้ว ก็เข้าไปที่นี่ https://apps.twitter.com/ แล้วก็ login ตามกระบวนการปกติครับ จะเห็นหน้าต่างประมาณนี้
จากนั้นก็เลือก Create New App เพื่อสร้างตัว Twitter app ที่เราจะใช้เป็นตัวกลางในการ Streaming ข้อมูล จาก Twitter – เติมแค่ fields ที่จำเป็นต้องเติมก็พอ (ตรง Website ใส่เว็บตัวเองหรือ placeholder เช่น https://www.google.com ไปก็ได้)
เมื่อสร้าง app แล้ว ก็เข้าไปดูใน app นั้น จากนั้นเลือก Keys and Access Tokens โดยในหน้านี้จะมี 4 fields ที่เราจะนำค่าออกมาใช้ผูกกับ code ของเราเพื่อเรียกข้อมูล ประกอบด้วย Consumer Key (API Key), Consumer Secret (API Secret), Access Token, Access Token Secret
ส่วนที่แต่ละคนต้องปรับเองคือ บรรทัดที่ 12-15 โดยเติม consumer_key, consumer_secret, access_token, และ access_secret ของตัวเองลงไปครับ (บรรทัดที่ 53 จะเปลี่ยนหรือไม่เปลี่ยนก็ตามสะดวกครับ, บรรทัดที่ 51 ก็ต้องเปลี่ยนหากไม่ต้องการให้ script ประมวลบน local host ครับ, บรรทัดที่ 44 ตั้งเป็น ‘#bnk48’ ที่เราจะใช้เป็น keyword สำหรับคัดกรอง)
ตอนนี้เราก็ได้ script ที่จะใช้ดึงข้อมูลจาก Twitter แล้วครับ ต่อไปก็เป็นการเตรียม PySpark code สำหรับทำ data streaming ครับ
ต่อไปเราก็ทำ code เพื่อเชื่อมต่อ streaming channel จาก script ก่อนหน้ากับตัว Jupyter Notebook ที่จะใช้จัดการข้อมูลครับ โดยใช้ networking interface ของ Python
ปล. ส่วน technical part 2 และ 3 ใน blog post นี้ใช้ internal Python networking interface – สำหรับผู้สนใจตัวอย่าง code ที่ทำแบบเดียวกันโดยใช้ PySpark (สำหรับทำ distributed computation) ดูได้ที่นี่
หน้าตาของ code ก็มีดังข้างล่าง โดยกรอกค่า “host” และ ค่า “port” ที่เราตั้งไว้ตามบรรทัดที่ 51 และ 53 ใน streamingTwitterTags.py
import socket
# connect to a streaming socket
s = socket.socket()
host = '127.0.0.1' # needs to be in quote
port = 5555
s.connect((host, port))
ทีนี้เราก็พร้อมที่จะทดสอบระบบ โดยเริ่มจากสั่ง run script สำหรับ streaming ก่อน
จากนั้นก็ run code เชื่อมต่อที่เตรียมไว้ บน Jupyter Notebook เท่านี้ code ของเราก็พร้อมเริ่มดึง tweet stream มาจัดการต่อ
การเรียก tweets ที่อยู่บน stream ที่เราเปิดไว้ ณ ขณะนั้นๆ ทำได้โดยการ run คำสั่งชุดต่อไปด้านล่างนี้
ปล 2. ตัว code ส่วนเชื่อมต่อด้านบนนั้นกด run เพียงครั้งเดียว ถ้า run ซ้ำการเชื่อมต่อจะโดนยกเลิก (ต้องเริ่ม run ไฟล์ script ใหม่ด้วย) ส่วน code ที่ใช้ดึง tweets ด้านล่างนั้น เมื่อเชื่อมต่อแล้วสามารถ run ซ้ำได้เรื่อยๆ
ปล 3. เลข 4096 คือ จำกัดขนาดของ byte size ที่เราจะเรียกมาแต่ละครั้ง ถ้าข้อมูลบน stream ของเรา ณ เวลานั้นๆ มีมากกว่าขนาดที่จำกัดไว้ ส่วนที่ไม่ถูกเรียกมาในครั้งนั้นๆ จะถูกเก็บไว้และถูกเรียกมาในครั้งต่อไป – ถ้าไม่คิดมากใส่ 4096 ไว้ก็เพียงพอสำหรับเรื่องที่เราจะทดสอบ
# get tweets
tweets = s.recv(4096).decode("utf-8", errors='ignore')
tweets
‘RT @5k8_ea: ทอล์ก-กะ-เทย Tonight วันที่ 18 ก.พ. \n\n#talkwithtoey #BNK48 \n\nhttps://t.co/ps8L5i6o8o https://t.co/6UPYvWpEDpRT @zi_patpat: ถ่ายโอชิตัวเองมันก็จะประมาณนี้แหล่ะ #bnk48 #KateBNK48 https://t.co/bzMs7QmaYIRT @nlessblogger: อาจารย์\u200bเจษฎ์น\u200bี่\u200bเองเป็นแกนนำ ^_^\n :เปิดใจ ตัวแทนกลุ่มคนรักมิวสิค BNK48 ขั้นตอนทำป้าย VR เบิร์ดเดย์ ส่งกำลังใจถึง มิวสิค…RT @Shimuzik: น้ำใส ไหลเย็น เห็นตัวปลาาาาาาาา ~~~~~~ (ฅΦωΦ)ฅ\n#BNK48 #NamsaiBNK48 https://t.co/ZbEyu22T0sRT @punjennis_th: [IG Jennis] พรุ่งนี้มี 2 งานเลย ไปเจอน้องกันนะคะ😍 #JennisBNK48 #BNK48 https://t.co/vTsQeo0mB7RT @jorhorkor48: ฮิ ฮิ~ 😆 ทำไมนี่ยิ้มตาม #KaewBNK48 #BNK48 https://t.co/cI1QFDQ1njRT @cholthiS: นี่ไอดอลหรือคณะตลก\n ก. มิวสิคมีแปรงสีฟัน แต่จิ้งจกขโมยยาสีฟัน\n ข. ปัญกับเจนนิษส์ไม่มียาสีฟันทั้งคู่\n ค. เฌอมียาสีฟัน แต่ไม่มี…RT @fontaku_: กระจกแถวนั้นแตกหมดยังคะ… \n#BNK48 #OrnBNK48 https://t.co/YSZRF8ncq1RT @jnptt_: โอ้โหยอมแล้ว ตายตายไปแล้ว #OrnBNK48 #BNK48 #BNK48Sweetcall https://t.co/hYGIV3J59GRT @rightmen0w: น้องสาวครับ~ อึ๋ย ไม่ทันได้ตั้งรับเธอก็เข้ามาโจมตี #BNK48 #JennisBNK48 https://t.co/AXzDfhLlmuRT @Teeny_SD: รีแอคชั่นของกันและกันระหว่างน้ำหนึ่งกับตาวานตอนออดิชั่น ต่างคนต่างทึ่งอีกฝ่าย5555555555\n\nนน: ดูสกิลตาวานอ่ะ นี่ถึงขนาดอึ้ง หา…RT @genjii_i: ทำไม ทำไม ทำกับน้องแบบนี้ 55555 เฌอของเกน #BNK48 #cherprangbnk48 https://t.co/rp67s4SAtA’
ทีนี้เราก็เห็น tweets เกี่ยวกับ ‘#bnk48’ ที่เค้ากำลังคุยกันบน Twitter ได้แล้วครับ จากนั้น เราก็กรองเฉพาะ hashtags ออกจากข้อความใน tweets ครับ
# get hashtags from tweets
import re
hashtags = [hashtag.lower() for hashtag in re.split('\s+', tweets) if len(hashtag)>0 and hashtag[0]=="#"]
hashtags
['#talkwithtoey',
'#bnk48',
'#bnk48',
'#katebnk48',
'#bnk48',
'#namsaibnk48',
'#jennisbnk48',
'#bnk48',
'#kaewbnk48',
'#bnk48',
'#bnk48',
'#ornbnk48',
'#ornbnk48',
'#bnk48',
'#bnk48sweetcall',
'#bnk48',
'#jennisbnk48',
'#bnk48',
'#cherprangbnk48']
เมื่อเราดึงข้อมูล tweets/hashtags ได้แล้ว เราก็จะนำข้อมูลมาจัดลำดับครับ โดยทำง่ายๆ ก็คือ วน loop เก็บค่า แล้วทำ chart ออกมา โดย chart ของเราคอย update เรื่อยๆ ตามช่วงเวลาที่เรากำหนดไว้
ตัว code ก็เป็นแบบนี้ครับ
import time
import re
from datetime import datetime, timedelta
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from IPython import display
%matplotlib inline
# record start time and set streaming period
start_time = datetime.now()
stream_period = 60 # in minutes
finish_time = start_time + timedelta(minutes=stream_period)
# create a blank Pandas DataFrame
df = pd.DataFrame([], columns=['hashtag','time_(s)'])
# iterative streaming and plotting
while datetime.now() < finish_time:
# set wait time between iteration
wait_time = 10 # in seconds
time.sleep(wait_time)
stream_time = datetime.now()
# get tweets and hashtags
tweets = s.recv(4096).decode("utf-8", errors='ignore')
hashtags = [hashtag.lower() for hashtag in re.split('\s+', tweets) if len(hashtag)>0 and hashtag[0]=="#"]
# store and count hashtags in Pandas DataFrame
temp_df = pd.DataFrame({'hashtag':hashtags,'time_(s)':(stream_time-start_time).seconds})
df = pd.concat([df, temp_df])
results = df.groupby('hashtag')['hashtag'].count().sort_values(ascending=False).reset_index(name='count').head(11)
# number of '#bnk48'
bnk48_count = results[results['hashtag']=='#bnk48']['count'].values
# create bar chart ranking top ten hashtags related to '#bnk48'
fig, ax = plt.subplots(1,1,figsize=(12,6))
results[results['hashtag']!='#bnk48'].plot(kind='bar', x='hashtag', y='count', legend=False, ax=ax)
ax.set_title("Top 10 hashtags related to #BNK48 (%d counts)" % bnk48_count, fontsize=18)
ax.set_xlabel("Hashtag", fontsize=18)
ax.set_ylabel("Count", fontsize=18)
ax.set_xticklabels(ax.get_xticklabels(), {"fontsize":14}, rotation=30)
ax.yaxis.set_major_locator(MaxNLocator(integer=True)) # show only integer yticks
plt.yticks(fontsize=14)
# clear previous output, print start time and current time, and plot the current chart
display.clear_output(wait=True)
print("start time:", start_time.strftime('%Y-%m-%d %H:%M:%S'))
print("stream time:", stream_time.strftime('%Y-%m-%d %H:%M:%S'))
plt.show()
หลังจากเรามี code ทุกอย่างเรียบร้อยแล้วก็ทำการ run ทุกอย่างตามที่เขียนไว้ในส่วน Technical part 2 และ Technical part 3 ได้เลยครับ โดยก็จะได้ chart ที่ update ตัวเองเรื่อยๆ ตามที่ตั้งไว้
ผมก็ได้ทดสอบ code ที่เราทำขึ้นในคืนวันวาเลนไทน์ ช่วงเวลา 21.15-22.15 นะครับ เรามาดูกันว่าสาวๆ BNK48 คนไหนบ้างที่มีชาว Twitter พูดถึงกันมากที่สุดในช่วงค่ำของวันวาเลนไทน์ (ย่นเวลาแสดงผลเหลือ 30 วินาที)
จากไฟล์ GIF นะครับ ก็จะเห็นว่าในช่วงระยะเวลาหนึ่งชั่วโมงที่เราทำการดึงข้อมูลนั้น มีผู้ใช้ hashtag #bnk48 ทั้งหมด 2489 ครั้ง โดยมี #cherprangbnk48 กับ #punbnk48 ที่ขับเคี่ยวกันมาสองคน แล้วก็มี #musicbnk48 ที่ตามมาห่างๆ เป็นอันดับที่สาม ส่วนอีกสามคนที่ตามมาก็เป็น #jennisbnk48, #mobilebnk48, และ #ornbnk48 แล้วก็มีอีกคนที่ตามมาแบบเว้นระยะหน่อยคือ #janbnk48
เนื่องจาก bar chart ด้านบนเปลี่ยนแปลงเร็ว ถ้าเราอยากเห็นละเอียดขึ้นว่าใครมี hashtag ที่แฟนๆ พูดถึง แซงกันในช่วงไหนบ้างก็คงไม่ทัน เราก็มาดูเป็น line plot ที่ทำเทียบกับเวลาที่เริ่มทำการ stream กันครับ (ใช้ข้อมูลเวลาที่เก็บไว้ใน DataFrame) โดยเลือกเฉพาะเจ็ดสมาชิกที่มีคนพูดถึงเยอะที่สุดที่กล่าวไว้ด้านบน โดยใน line plot ผมเติม scatter plot ลงไปด้วยทุกสิบนาที โดยให้ขนาดของ scatter plot เปลี่ยนไปตามจำนวนที่มีผู้เรียกแต่ละ hashtag จนถึงเวลานั้นๆ
โดยใน line plot นะครับ ก็จะเห็นว่าการขับเคี่ยวระหว่าง #cherprangbnk48 กับ #punbnk48 นั้น มีช่วงที่ #cherprangbnk48 โดนทิ้งห่างไปเป็นระยะเวลานานกว่า 20 นาทีเลยทีเดียว แต่ก็กลับมาสูสีกันอีกครั้งในตอนท้ายๆ นอกจากนี้ก็ยังเห็นข้อสังเกตอื่นๆ เช่น จำนวนคนพูดถึง #musicbnk48 ที่เกิดขี้นในระยะเวลา 60 นาทีนั้น อยู่ในระดับเดียวกับจำนวนคนพูดถึง #cherprangbnk48 กับ #punbnk48 ในเวลาเพียงแค่ 40 นาที
# close the connection
s.close()
ปล. อย่าลืมไปปิดตัว terminal ที่รัน python script (streamingTwitterTags.py) เชื่อมต่อกับ Twitter API ด้วยนะครับ
จบแล้วครับสำหรับการทำ code และตัวอย่างการสำรวจความนิยมของสาวๆ BNK48 โดยใช้ Twitter API + Python
เป็นไงบ้างครับ โอชิใครขึ้นแท่นสมาชิกสุดฮอทของวงบ้าง โอชิใครหลุดลำดับไปก็ชวนเพื่อนๆ ไปช่วยกันทำคะแนนความนิยมเพิ่มได้โดยการไป flood Twitter กันนะครับ XD
หวังว่าตัวอย่างนี้จะพอเป็นประโยชน์กับผู้ที่สนใจจะทำ data streaming ด้วย Python บ้างนะครับ
หากส่วนไหนผิดพลาดหรือมีข้อแนะนำเพิ่มเติมตรงไหนก็ช่วยแจ้งได้เลยนะครับ ขอบคุณครับผม