การจะทราบถึงปรากฏการณ์ทางสังคมว่าคนในสังคมกำลังสนใจสิ่งเหล่านี้มากน้อยแค่ไหน ทางหนึ่งก็ทำได้จากการวิเคราะห์ปริมาณการปฏิสัมพันธ์ของผู้คนบนสื่อสังคมออนไลน์
ในที่นี้จะกล่าวถึงการเก็บรวบรวมข้อมูลจาก page ของ Facebook และการนำข้อมูลมาวิเคราะห์เบื้องต้น
Facebook นั้นมีบริการที่เรียกว่า graph API ซึ่งเปิดให้ผู้ใช้สามารถดึงข้อมูลต่างๆมาเท่าที่ได้รับอนุญาตได้
การจะดึงข้อมูลนั้นต้องมี access token ซึ่งในเบื้องต้นสามารถได้มาจาก Graph Api Explorer
การดึงข้อมูลจาก Graph API Explorer
กดปุ่ม Get token แล้วระบบจะ generate access token ชั่วคราวมาให้ โดย token นี้จะมีอายุเพียง 1 วันเท่านั้น ถ้า expire ก็กดขอใหม่
นอกจากนี้ Graph Api Explorer ยังเปิดให้ทดลองดึงข้อมูลได้ เช่นในช่อง input ขนาดนี้เป็น /me?fields=id, nameเมื่อกดปุ่ม Submit ระบบก็จะส่ง id และ name ของ Login Facebook ที่ใช้งานอยู่มาให้
แต่การจะทราบถึงกระแสสังคมต่อเรื่องต่างๆนั้น ย่อมเป็นการดีกว่าที่จะดูจากจุดที่เป็นศูนย์รวมต่อเรื่องต่าง นั่นคือ page
ในที่นี้จะแสดงการดึงข้อมูลจาก page official ของ Maysa BNK48 เพราะคนเขียนชอบ
อยากแรกที่ต้องให้เลยคือ url name ของ page นั่นคือสิ่งที่ตามหลัง facebook.com/ ใน url ของ page นั่นเอง ในที่นี้คือ bnk48official.maysa
เมื่อนำมาใส่ใน Graph Api Explorer แล้ว submit ก็จะได้ชื่อและ id ของ page มาดังนี้
ถ้ารู้ id ของ page แล้วก็สามารถใช้ id ของ page (224218921376880) มาใส่แทน “bnk48official.maysa” ก็ query ข้อมูลมาได้เหมือนกัน
เติม /feed หลัง url เพื่อ query feed ต่างๆของ page มา
นอกจากนี้เรายังสามารถกำหนด field ที่ Api return กลับมาให้ได้ด้วย โดยใส่ที่ช่องด้านซ้าย หรือเติมท้าย url
โดยที่แสดงในรูปคือมี create_time, comment, รูปภาพใน post, จำนวนการแชร์ และประเภทของ post
ส่วน reaction ใน post นั้น tricky เล็กน้อย คือหากใส่ reactions ไปใน field ตรงๆเลยจะไม่มีข้อมูลมา ต้องเพิ่ม parameters ตามหลังด้วย
reactions.type(LIKE).limit(0).summary(1).as(like) reactions.type(WOW).limit(0).summary(1).as(wow) reactions.type(SAD).limit(0).summary(1).as(sad) reactions.type(LOVE).limit(0).summary(1).as(love) reactions.type(HAHA).limit(0).summary(1).as(haha) reactions.type(ANGRY).limit(0).summary(1).as(angry)
comment ในโพสเองก็สามารถดึงได้ และ comment ใน comment หรือที่เรียกว่า reply ก็ดึงมาได้เช่นกัน
reaction ใน comment ก็ดึงมาได้ด้วยวิธีเดียวกับของโพส
ใน data ที่ return กลับมานั้น reply จะเป็น comment ที่ nested อยู่ใน comment อีกที
อย่างไรก็ตาม ในการ query 1 ครั้ง จะได้ข้อมูลมาเพียงบางส่วนเท่านั้น ลองเลื่อนช่อง output ดูด้านล่าง post สุดท้ายที่เห็นคือ 2018-01-14
post ที่เก่ากว่านี้จะอยู่อีกหน้าหนึ่ง และ query มาได้โดยใช้ string ในส่วนของ paging ในท้ายของข้อมูล
หรือก็คือเติม parameter after={string} ท้าย url นั่นเอง
หากกด link ตรง next ก็จะเป็นการ query ข้อมูลหน้าถัดไป
อย่างไรก็ดี จะมานั่งกดดูทีละหน้าก็ใช่ที่ ให้โปรแกรมเป็นคนทำงานให้ดีกว่า ในที่นี้จะแสดงตัวอย่างสคริป ruby ที่ใช้เก็บข้อมูล feed ในช่วงหกเดือนก่อนนี้ แล้วเก็บลง mongodb แต่เพื่อความรวบรัดจะขอละส่วนของ comment ไว้ก่อน
เก็บข้อมูลด้วย Ruby script
เหตุที่ใช้ mongodb เพราะสามารถโยน json เข้าไปได้เลย ไม่ต้องสร้าง schema ให้ยุ่งยาก
#!/usr/bin/env ruby require 'json' require 'net/http' require 'mongo' access_token = 'EAACEdEose0cBABAzbaZAnrr9F........ page_name = 'bnk48official.maysa' uri = URI("https://graph.facebook.com/v2.12/#{page_name}") fields = 'id,name,website,picture' params = { :access_token => access_token, :fields => fields } uri.query = URI.encode_www_form(params) res = Net::HTTP.get_response(uri) puts uri page = JSON.parse(res.body) if res.is_a?(Net::HTTPSuccess) puts page
สร้าง uri แล้วใช้ Net::HTTP ไป get respond มา
ส่วนของ feed จะใช้ uri นี้
reaction_str = 'reactions.type(LIKE).limit(0).summary(1).as(like),' + 'reactions.type(WOW).limit(0).summary(1).as(wow),' + 'reactions.type(SAD).limit(0).summary(1).as(sad),' + 'reactions.type(LOVE).limit(0).summary(1).as(love),' + 'reactions.type(HAHA).limit(0).summary(1).as(haha),' + 'reactions.type(ANGRY).limit(0).summary(1).as(angry)' uri = URI("https://graph.facebook.com/v2.12/#{page_name}/feed") feed_fields = "message,created_time,shares,#{reaction_str}" params = { :access_token => access_token, :fields => feed_fields } uri.query = URI.encode_www_form(params) res = Net::HTTP.get_response(uri) puts uri
ต่อ mongodb รอไว้ก่อน ในที่นี้ใช้ชื่อ db เป็น ‘fb_maysa’ และ collection เป็น ‘feed’
client = Mongo::Client.new([ config[‘mongodb_host’] ], :database => ‘fb_maysa’)
coll = client[:feed]
respond ที่ได้มานั้นจะเป็น json string ก็สามารถใช้ JSON.parse ได้เลย โดยแต่ละ post นั้นจะอยู่ใน key ‘data’
และเนื่องจากเราต้องการดึง feed มาเพียง 6 เดือนเท่านั้น เลยต้องมีการเทียบเวลาจาก field created_time ว่าเก่าเกิน 6 เดือนหรือไม่
ส่วนของ reactions นั้น จากตัวอย่างด้านบนจะเห็นว่า respond ส่วนนี้จะมี structure ที่ซับซ้อน จึงต้องแปลงให้อยู่ในรูปง่ายๆก่อน
แล้วใช้ collection.insert_one เพื่อ insert เข้า mongodb
r_j = JSON.parse(res.body) now = Time.now() r_j['data'].each do |f| created_time = DateTime.strptime(f['created_time']).to_time time_diff = (now - created_time) month = 30*24*60*60 pass_month = (time_diff/month).floor puts "passed month: #{pass_month}" break if pass_month > 6 f['shares'] = f['shares']['count'] reaction_hash = {} ['like', 'wow', 'sad', 'angry', 'haha', 'love'].each do |r| count = f[r]['summary']['total_count'] reaction_hash[r] = count f.delete r end f['reactions'] = reaction_hash coll.insert_one(f) end
อย่างที่กล่าวคือการ get_respond 1 ครั้งก็จะได้ข้อมูลมาเพียงบางส่วนเท่านั้น หากขะดึง feed ที่เก่ากว่านี้ก็ดึงได้จากส่วนของ paging
while r_j['paging']['next'] uri = URI(r_j['paging']['next']) res = Net::HTTP.get_response(uri) puts uri pass_month = 0 r_j = JSON.parse(res.body) r_j['data'].each do |f| created_time = DateTime.strptime(f['created_time']).to_time time_diff = (now - created_time) month = 30*24*60*60 pass_month = (time_diff/month).floor puts "passed month: #{pass_month}" break if pass_month > 6 f['shares'] = f['shares']['count'] reaction_hash = {} ['like', 'wow', 'sad', 'angry', 'haha', 'love'].each do |r| count = f[r]['summary']['total_count'] reaction_hash[r] = count f.delete r end f['reactions'] = reaction_hash coll.insert_one(f) end break if pass_month > 6 end
เท่านี้ก็จะได้ feed ของ 6 เดือนย้อนหลังอยู่ใน database ของเราแล้ว จากนี้จะแสดงการนำข้อมูลมาสร้างกราฟง่ายๆโดยใช้ R
การใช้ R เพื่อวิเคราะห์ข้อมูล
ข้อมูลที่เป็นตัวเลขที่เราได้มาก็จะมี จำนวนการแชร์ และจำนวน reaction จึงจะใช้สองส่วนนี้เป็นหลัก
ขั้นแรกเลยก็คือโหลดข้อมูลจาก mongodb มาไว้ที่ R ก่อน
library(ggplot2) library(mongolite) library(chron) library(scales) con <- mongo(collection = "feed", db = "fb_maysa", url = "mongodb://127.0.0.1", verbose = FALSE) mydata <- con$find() stat_data <- c() dtparts <- t(as.data.frame(strsplit(mydata$created_time,'T'))) row.names(dtparts) <- NULL dtparts[,2] <- sapply(dtparts[,2], function(x){ xx <- strsplit(x, "\\+") return(xx[[1]][1]) }) stat_data$time <- chron(dates=dtparts[,1],times=dtparts[,2], format=c('y-m-d','h:m:s'))
ส่วนของ Datetime นั้น R จะเห็นเป็นเพียง string จึงต้องปรับ format และแปลงให้อยู่ในรูปของ “chron” ซึ่งประกอบด้วย Date และ Time ให้ R เข้าใจก่อน
แล้วเรียงข้อมูลใหม่ จากวันที่ (stat_data$time)
stat_data$reaction <- mydata$reaction stat_data$reaction <- stat_data$reaction[order(stat_data$time, decreasing=FALSE),] stat_data$shares <- mydata$shares stat_data$shares <- stat_data$shares[order(stat_data$time, decreasing=FALSE)] stat_data$comment_count <- stat_data$comment_count[order(stat_data$time, decreasing=FALSE)] stat_data$time <- stat_data$time[order(stat_data$time, decreasing=FALSE)] stat_data$sum_reactions <- rowSums(stat_data$reaction)
แปลง structure ของ data ให้เหมาะกับ ggplot2
d <- data.frame(stat_data$time, stat_data$shares, as.Date(stat_data$time)) d <- cbind(d, "Shares") colnames(d) <- c('time', 'sums' ,'date', 'Total') d2 <- data.frame(stat_data$time, stat_data$sum_reactions, as.Date(stat_data$time)) d2 <- cbind(d2, "Reaction") colnames(d2) <- c('time', 'sums' ,'date', 'Total') d <- rbind(d, d2) month <- as.Date(cut(d$date, breaks = "month")) d <- cbind(d, month)
เพื่อให้ดูง่าย จึงควร plot graph โดยสรุปแต่ละเดือน เพื่อการนี้จึงเพิ่ม column month เข้ามา
จะได้ตารางหน้าตาแบบนี้
ผลลัพธ์
จำนวน share และ reaction ทั้งหมดของเพจในรอบ 6 เดือนจะมีดังนี้
“Total shares 8112”
“Total reactions 199453”
หากแยก reaction ตามประเภทก็จะได้ดังนี้
“Total like 152961”
“Total wow 1827”
“Total sad 466”
“Total angry 72”
“Total haha 8112”
“Total love 41394”
plot ด้วย ggplot2 โดยสรุปตามแต่ละเดือน
ggplot(data = d, aes(x=month, y=sums, group=Total)) + scale_color_manual(values=c("red", "blue")) + stat_summary(fun.y = sum, geom = "line", aes(color=Total)) + stat_summary(fun.y = sum, geom = "point", aes(color=Total)) + scale_x_date(labels = date_format("%Y-%m"), date_breaks = "1 month") + theme(legend.position="top")
ก็จะได้กราฟเส้น สีแดงคือจำนวนแชร์ สีน้ำเงินคือจำนวน reaction รวมๆ
Total reactions and shares in each month
จะเห็นว่ากราฟขึ้นสูงในเดือนธันวาคม นั่นคือช่วงที่มีดราม่าภาพหลุด และลดต่ำลงมาในเดือนมกราคม นั่นคือหลังจากถูกพักงานแล้ว
จากข้อมูลที่มีอาจจะ plot กราฟได้อีกหลายแบบ ลองเปรียบเทียบ reaction แต่ละแบบดู
Number of reactions by type per month
reaction ทั้งหมดจำนวนมากที่สุดก็คือ like นั่นเอง แต่ like ก็อาจจะตีความได้หลายความหมาย จึงอาจจะไม่มีประโยชน์เท่าไหร่นัก
ลอง plot อีกรูปโดยตัด like ออกไปดู
Number of reactions by type (exclude like) per month
เมื่อตัด like ออกไปแล้ว จำนวน reaction ที่มากที่สุดคือ love รองลงมาก็คือ haha
ทั้ง haha และ love เพิ่มขึ้นมาในช่วงเดือนธันวาคมซึ่งเป็นช่วงที่มีดราม่า แต่ angry ไม่ได้เพิ่มหรือลดอย่างเห็นได้ชัด ก็อาจจะกล่าวสรุปจากตัวเลขได้ว่าคนไทยก็ไม่ได้โกรธขึ้งน้องจากกรณีดราม่าขนาดนั้น แต่ชอบหาความบันเทิงจากดราม่ามากกว่า
จบแล้วกับบทความนี้ จริงๆแล้วก็อาจจะต่อยอดได้อีกเช่นการพิจารณาข้อมูลของ comment และ reply ร่วมด้วย เนื่องจาก reaction ที่นำมาแสดงให้ดูเป็นเพียง reaction บนตัว post เท่านั้น ไม่ได้รวมถึง reaction ต่อ comment และ reply ด้วย ซึ่งหากนำมารวมด้วยแล้วก็อาจจะได้กราฟในรูปแบบที่ต่างออกไปก็ได้ อาจจะเจอ angry ในสัดส่วนที่มากขึ้นหรือเปล่า เพราะคนอาจจะแสดงความเกรี้ยวกราดลงใน comment มากกว่าก็เป็นไปได้