基本上是透過 Facebook Graph API 去取得粉絲頁的資料,但是使用 Facebook Graph API 還需要取得權限,有兩種方法 :
第一種是取得 Access Token
第二種是建立 Facebook App的應用程式,用該應用程式的帳號,密碼當作權限
兩者的差別在於第一種會有時效限制,必須每隔一段時間去更新Access Token,才能使用
Access Token
本文是採用第二種方法
要先取得應用程式的帳號,密碼 app_id, app_secret
In [1]:
# 載入python 套件
import requests
import datetime
import time
import pandas as pd
第一步 - 要先取得應用程式的帳號,密碼 (app_id, app_secret)
第二步 - 輸入要分析的粉絲團的 id (page_id)
[教學]如何申請建立 Facebook APP ID 應用程式ID
In [2]:
# 分析的粉絲頁的id
page_id = "appledaily.tw"
app_id = ""
app_secret = ""
access_token = app_id + "|" + app_secret
爬取的基本概念是送request透過Facebook Graph API來取得資料
而request就是一個url,這個url會根據你的設定(你要拿的欄位)而回傳你需要的資料
但是在爬取大型粉絲頁時,很可能會因為你送的request太多了,就發生錯誤
這邊的解決方法很簡單用一個while迴圈,發生錯誤就休息5秒,5秒鐘後,再重新送request
基本上由5個function來完成:
request_until_succeed
來確保完成爬取
getFacebookPageFeedData
來產生post的各種資料(message,link,created_time,type,name,id...)
getReactionsForStatus
來獲得該post的各reaction數目(like, angry, sad ...)
processFacebookPageFeedStatus
是處理getFacebookPageFeedData得到的各種資料,把它們結構化
scrapeFacebookPageFeedStatus
為主程式
In [3]:
# 判斷response有無正常 正常 200,若無隔五秒鐘之後再試
def request_until_succeed(url):
success = False
while success is False:
try:
req = requests.get(url)
if req.status_code == 200:
success = True
except Exception as e:
print(e)
time.sleep(5)
print("Error for URL %s: %s" % (url, datetime.datetime.now()))
print("Retrying.")
return req
url = base + node + fields + parameters
base : 可以設定Facebook Graph API的版本,這邊設定v2.6
node : 分析哪個粉絲頁的post 由page_id去設定
fields : 你要取得資料的種類
parameters : 權限設定和每次取多少筆(num_statuses)
In [4]:
# 取得Facebook data
def getFacebookPageFeedData(page_id, access_token, num_statuses):
# Construct the URL string; see http://stackoverflow.com/a/37239851 for
# Reactions parameters
base = "https://graph.facebook.com/v2.6"
node = "/%s/posts" % page_id
fields = "/?fields=message,link,created_time,type,name,id," + \
"comments.limit(0).summary(true),shares,reactions" + \
".limit(0).summary(true)"
parameters = "&limit=%s&access_token=%s" % (num_statuses, access_token)
url = base + node + fields + parameters
# 取得data
data = request_until_succeed(url).json()
return data
In [5]:
# 取得該篇文章的 reactions like,love,wow,haha,sad,angry數目
def getReactionsForStatus(status_id, access_token):
# See http://stackoverflow.com/a/37239851 for Reactions parameters
# Reactions are only accessable at a single-post endpoint
base = "https://graph.facebook.com/v2.6"
node = "/%s" % status_id
reactions = "/?fields=" \
"reactions.type(LIKE).limit(0).summary(total_count).as(like)" \
",reactions.type(LOVE).limit(0).summary(total_count).as(love)" \
",reactions.type(WOW).limit(0).summary(total_count).as(wow)" \
",reactions.type(HAHA).limit(0).summary(total_count).as(haha)" \
",reactions.type(SAD).limit(0).summary(total_count).as(sad)" \
",reactions.type(ANGRY).limit(0).summary(total_count).as(angry)"
parameters = "&access_token=%s" % access_token
url = base + node + reactions + parameters
# 取得data
data = request_until_succeed(url).json()
return data
生成status_link ,此連結可以回到該臉書上的post
status_published = status_published + datetime.timedelta(hours=8) 根據所在時區 TW +8
In [6]:
def processFacebookPageFeedStatus(status, access_token):
# 要去確認抓到的資料是否為空
status_id = status['id']
status_type = status['type']
if 'message' not in status.keys():
status_message = ''
else:
status_message = status['message']
if 'name' not in status.keys():
link_name = ''
else:
link_name = status['name']
link = status_id.split('_')
# 此連結可以回到該臉書上的post
status_link = 'https://www.facebook.com/'+link[0]+'/posts/'+link[1]
status_published = datetime.datetime.strptime(status['created_time'],'%Y-%m-%dT%H:%M:%S+0000')
# 根據所在時區 TW +8
status_published = status_published + datetime.timedelta(hours=8)
status_published = status_published.strftime('%Y-%m-%d %H:%M:%S')
# 要去確認抓到的資料是否為空
if 'reactions' not in status:
num_reactions = 0
else:
num_reactions = status['reactions']['summary']['total_count']
if 'comments' not in status:
num_comments = 0
else:
num_comments = status['comments']['summary']['total_count']
if 'shares' not in status:
num_shares = 0
else:
num_shares = status['shares']['count']
def get_num_total_reactions(reaction_type, reactions):
if reaction_type not in reactions:
return 0
else:
return reactions[reaction_type]['summary']['total_count']
# 取得該篇文章的 reactions like,love,wow,haha,sad,angry數目
reactions = getReactionsForStatus(status_id, access_token)
num_loves = get_num_total_reactions('love', reactions)
num_wows = get_num_total_reactions('wow', reactions)
num_hahas = get_num_total_reactions('haha', reactions)
num_sads = get_num_total_reactions('sad', reactions)
num_angrys = get_num_total_reactions('angry', reactions)
num_likes = get_num_total_reactions('like', reactions)
# 回傳tuple形式的資料
return (status_id, status_message, link_name, status_type, status_link,
status_published, num_reactions, num_comments, num_shares,
num_likes, num_loves, num_wows, num_hahas, num_sads, num_angrys)
假設一個粉絲頁,有250個posts
第一次用 getFacebookPageFeedData
得到 url 送入 request_until_succeed
得到第一個dictionary
dictionary中有兩個key,一個是data(100筆資料都在其中)
而另一個是next(下一個100筆的url在裡面,把它送出去會在得到另一個dictionary,裡面又含兩個key,一樣是data和next)
第一次送的 request data: 第100筆資料 next: 下100筆資料的url
第二次送的 request data: 第101-200筆資料 next: 下100筆資料的url
第三次送的 request data: 第201- 250筆資料 next: 無 (因為沒有下一百筆了)
總共送3次request
由於Facebook限制每次最多抓100篇posts,因此當粉絲頁超過100篇時,
就會有 next 的 url,必須送出此url在獲得下100篇,由 has_next_page 來決定
是否下100篇
num_processed是用來計算處理多少posts,每處理100筆就輸出時間
最後會把結果輸出成csv,供後續章節繼續分析和預測
In [7]:
def scrapeFacebookPageFeedStatus(page_id, access_token):
# all_statuses 用來儲存的list,先放入欄位名稱
all_statuses = [('status_id', 'status_message', 'link_name', 'status_type', 'status_link',
'status_published', 'num_reactions', 'num_comments', 'num_shares',
'num_likes', 'num_loves', 'num_wows', 'num_hahas', 'num_sads', 'num_angrys')]
has_next_page = True
num_processed = 0 # 計算處理多少post
scrape_starttime = datetime.datetime.now()
print("Scraping %s Facebook Page: %s\n" % (page_id, scrape_starttime))
statuses = getFacebookPageFeedData(page_id, access_token, 100)
while has_next_page:
for status in statuses['data']:
# 確定有 reaction 再把結構化後的資料存入 all_statuses
if 'reactions' in status:
all_statuses.append(processFacebookPageFeedStatus(status,access_token))
# 觀察爬取進度,每處理100篇post,就輸出時間,
num_processed += 1
if num_processed % 100 == 0:
print("%s Statuses Processed: %s" % (num_processed, datetime.datetime.now()))
# 每超過100個post就會有next,可以從next中取得下100篇, 直到沒有next
if 'paging' in statuses.keys():
statuses = request_until_succeed(statuses['paging']['next']).json()
else:
has_next_page = False
print("\nDone!\n%s Statuses Processed in %s" % \
(num_processed, datetime.datetime.now() - scrape_starttime))
return all_statuses
In [8]:
all_statuses = scrapeFacebookPageFeedStatus(page_id, access_token)
5234篇post共花了20分鐘,把結果存成csv交給下一章去分析
all_statuses[0] 為 column name
all_statuses[1:] 為處理後結構化的資料
In [9]:
df = pd.DataFrame(all_statuses[1:], columns=all_statuses[0])
In [10]:
df.head()
Out[10]:
In [11]:
path = 'post/'+page_id+'_post.csv'
df.to_csv(path,index=False,encoding='utf8')
In [ ]: