This is an old revision of the document!
Facebook API 포스팅 가져오기 #2 python으로 정리
Why Python?
본 홈페이지의 플랫폼인 dokuwiki는 PHP 기반으로 만들어졌기 때문에, PHP를 통해서 포스팅을 긁어올 수 있겠지만,
- 저가 웹호스팅을 쓰기 때문에 트래픽 문제가 발생할 수 있음.
- 접속할 때마다 데이터를 불러오면 로딩 시간이 늘어남
- 오프라인에도 자료를 저장하고 싶음
- 데이터 정리나 라이브러리 사용이 용이함. 익숙하기 때문에 개발 시간이 단축
이라는 이유 때문에 데스크톱에서 주기적으로 실행시켜줘야 하지만, Python을 이용하기로 하고,
PHP는 최신 현황만 불러오는 간단한 스크립트를 작성하기로 했다.
- 데이터 호출 및 테이블로 정리
- wiki 문법으로 변환
- 포스팅에 연관된 이미지 다운로드
- FTP를 통한 이미지 업로드
- wiki 문서 수정하기
이번 편에서는 1-2까지를 기술해 보도록 하겠다.
함수 선언부를 제외하고는 코드 순서대로 설명.
데이터 받아오기
API 호출
1편(API 사용하기) 에서 받아온 cURL 과 액세스 토큰을 통해서 데이터를 받아온다.
requests 라이브러리의 get 함수를 사용해서 데이터를 받아서 json을 dict 형식으로 변환한다.
(Line 1의 utils 는 nested_json, get_images, UTCtoKST 등 아래에서 설명할 함수들이 포함된 파일)
- highlight:
from utils import * import pandas as pd import requests from urllib.parse import unquote import re output = pd.DataFrame() token = "[액세스토큰]" url = f"https://graph.facebook.com/v6.0/100300361542753/posts?fields=created_time%2Cfull_picture%2Cicon%2Cid%2Cmessage%2Cmessage_tags%2Cpicture%2Cattachments.limit(10)%7Burl%2Cmedia%2Cunshimmed_url%2Cmedia_type%2Ctitle%2Cdescription%2Cdescription_tags%2Csubattachments%2Ctype%2Ctarget%7D%2Cshares&limit=100&pretty=0&access_token={token}" resp = requests.get(url=url) json_out = resp.json() output = nested_json(json_out)
다음페이지 호출
json구조-다음section는 1-depth 에서 data와 paging 으로 구분되어 있고, paging > next 에 다음페이지 URL이 있기 때문에 존재하면 계속 받아오도록 한다.
- highlight:
while True: if 'next' in json_out['paging'].keys(): print('===============Next Page==================') url2 = json_out['paging']['next'] resp = requests.get(url=url2) json_out = resp.json() output2 = nested_json(json_out) output = output.append(output2) else: break output.to_excel('facebook.xlsx')
json을 DataFrame으로
우선 Archiving과 추후 페이스북 포스팅 효과 분석 등을 위해서 눈으로 볼 수 있는 DataFrame 형식으로 변환을 하고 싶었다.
하지만 받아오는 json이 아래와 같이 attachments 가 다시 dict 구조로 되어 있기 때문에 일반적인 방법으로는 DataFrame 형식으로 바꾸기 어려워 보였다.
- highlight: ; title: Facebook API JSON 구조
json_out = dict({ ㄴdata : [ 첫번째포스팅({ title, created_time, message 등 attachments : dict( { data: [ 첫번째attachment{ ㄴ url ㄴ media ㄴ image : height, width, src ㄴ source ㄴ ... }, 두번째attachment{} ] }) }),두번째포스팅,..] ㄴpaging : ... ㄴ next : 다음페이지 주소 )}
그래서 recursion을 이용해서 nested json 형식을 DataFrame으로 바꿀 수 있도록 11번 라인의 nested_json 함수를 다음과 같이 만들어 보았다.
def nested_json(json_out): df_out = pd.DataFrame() #리스트 값 추출 if json_out == list: data = json_out else: data = json_out[next(iter(json_out))] # 한줄씩 Series로 만들어서 DataFrame에 추가한다. for i,_ in enumerate(data): output = nested_json_row(data[i]) df_out = df_out.append(output.to_frame().T, ignore_index=True) return df_out
nested_json 에서 데이터의 리스트 element 하나(DataFrame에서 한 행이 될 부분)에 대해서 nested_json_row 를 실행해서 DataFrame을 만들게 된다.
nested_json_row 에서는 아래와 같이 dict나 list가 아닌 '값'이 나올 때까지 recursion을 이용해서 column명-값 을 가져오도록 했다.
dict일 때는 dict의 값을 컬럼명으로, list일 때는 index를 컬럼명에 추가해서 구분이 가능하도록 하였다.
- highlight:
def nested_json_row(dict_data): out = pd.Series() for k,v in dict_data.items(): if type(v) == dict: out_child = nested_json_row(v) out_child.index = k + '_' + out_child.index out = out.append(out_child) elif type(v) == list: for idx,it in enumerate(v): out_child = nested_json_row(it) out_child.index = f"{k}_{idx}_" + out_child.index out = out.append(out_child) else: out[k] = v return out
엑셀로 저장해서 보면, 컬럼명이 다음과 같이 잘 들어간 것을 볼 수 있다.
wiki문법으로 변환
데이터들을 사용가능하도록 정리하고, 업로드가 가능한 형태로 수정하는 부분.
본 사이트는 dokuwiki 를 사용하여 만들었기 때문에, 이 테이블을 wiki 문법으로 바꾸어줘야 했다.
아래 코드에서 wiki 문법을 HTML 이나 Markdown 문법 등으로 수정해서 사용하면 다른 곳에서도 사용할 수 있을 것이다.
기본정보 처리
from tqdm import tqdm output = output.fillna('!!None!!') output['img_base64']='' contents = '' #for loop 시작 for idx,row in tqdm(output.iterrows()): con = { 'title': row['attachments_data_0_title'], 'message': row['message'].replace('!!None!!', ''), 'desc': row['attachments_data_0_description'].replace('!!None!!',''), 'url': "[[https://www.facebook.com/data.triviaz/posts/"+row['id'].split('_')[1]+"|페이스북에서 보기]]", 'picture': row['attachments_data_0_media_image_src'], 'type': row['attachments_data_0_media_type'], } #제목 처리 : 없으면 내용 or No title if con['title'] == '!!None!!': if con['desc'] == '' and con['message'] == '': con['title'] = 'No title' else: con['title'] = con['message'][:20] + '...'
- 제목title : 주로 링크를 공유하는 포스팅을 올리기 때문에 attachment에 있는 title을 사용
- Line 15의 제목처리 부분에서 링크 공유가 아닌 경우 내용의 앞 20 글자를 가져오도록 처리
- 내용message : message 컬럼
- Description : 페이스북에서 링크 내용을 자동스크랩해서 보여주는 부분
- url : id는
페이지id_포스팅id
형태로 되어 있기 때문에 포스팅id 부분을 통해서 facebook 링크를 생성 - picture : 링크 내의 이미지 또는 제가 업로드한 이미지 URL
- type : 포스팅 종류 (link, picutre, video 등)
Image 처리
API를 통해서 정보는 original URL이 아닌, Facebook CDN 서버를 통하는 URL로 제공.
이 이미지 URL은 다음의 두 가지 경우가 존재한다.
- Facebook 내부 이미지(line3) :
https://scontent.xx.fbcdn.net
형식은 바로 접근이 가능하기 때문에 URL을 그대로 사용. - 외부 이미지(line6~) :
https://external.xx.fbcdn.net/safe_image.php
의&url=
부분이 외부 이미지 URL.
여기에 몇가지 예외 처리를 추가하여 다음과 같이 이미지 URL 을 추출하였다.
# image 처리 ################ img_url = '' if 'scontent' in con['picture']: #Facebook 내부 img_url = row['picture'] else: #외부 이미지 if '&url=' in con['picture']: #url 부터 cfs 까지 img_url = con['picture'][con['picture'].index('&url')+5:con['picture'].index('&cfs')] img_url = unquote(img_url) if img_url[-3:] in ['jpg','png'] or 'daum' in img_url: #daum은 확장자 없음 img_url = img_url else: img_url = img_url.split('?')[0] #가끔 ? 붙은게 있음 img_url = img_url.replace('%3A',':').replace('%2F',"/")
그리고 아래와 같이 get_images 함수
(다음편(이미지 및 FTP)에서 설명)를 호출하여
Archiving을 위한,
- 이미지를 다운로드
- 이미지를 HTML에서 바로 사용할 수 있도록 base64로 인코딩하여 DataFrame에 추가
하도록 했다.
if img_url != '': img_base64 = get_images(img_url, row['id']) output.loc[idx,'img_base64'] = img_base64 con['picture'] = "{{blogs_facebook_upload:" + row['id']+ ".png?100}}"
기타 처리
- 포스팅의 대표 링크는 페이스북에서 보이는 것처럼 도메인 이름만 추출하도록 하고,
- message 내에 있는 링크가 길이가 길 때는 말 줄임표로 줄이도록 하고.
- 또한 참고로 dokuwiki문법 적용시 개행을 의미하는 '\\'가 f-string에서는 적용되지 않기 때문에 따로 처리하였다.
con['link'] = "[[" + row['attachments_data_0_unshimmed_url'] + "|"+ row['attachments_data_0_unshimmed_url'].replace("https://","").replace("http://","").split("/")[0] + "]]" #내용 안에 있는 링크는 줄임표로 줄인다 urlfound = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\), ]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', con['message']) for urlf in urlfound: if len(urlf) > 30: con['message'] = con['message'].replace(urlf, f"[[{urlf}|{urlf[:30]}...]]") con['message'] = con['message'].replace('\n', ' \\\\ ') # SyntaxError: f-string expression part cannot include a backslash
날짜 처리 및 Template 완성
API를 통해서 제공하는 날짜 정보는 모두 UTC 기준으로 되어 있다.
한국은 UTC+9 시간이기 때문에 아래와 같이 timezone 라이브러리를 이용하여 변환해주는 작업이 필요하다.
from pytz import timezone import datetime def UTCtoKST(timestr): KST = timezone('Asia/Seoul') #https://blog.kimkevin.net/python-utc-to-kst/ return datetime.datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%S%z').astimezone(KST).strftime( '%Y-%m-%d %H:%M:%S')
지금까지 작업을 바탕으로 아래처럼 각 포스팅별로 wiki문법을 적용한 Template을 만들었다.
content = f""" === {con['title']} === | {con['type'].upper()} | {UTCtoKST(row['created_time'])} | {con['url']} | \\\\ \\\\ {con['message']} > <wrap group> <wrap column> {con['picture'].replace('!!None!!', '(No image)')} </wrap> <wrap column> {con['desc']} \\\\ {con['link']} </wrap> </wrap> ---- """ contents += content # END of for ##################################
지금까지 for loop 를 통해서 각 포스팅 들을 만들었고, 아래처럼 Header와 Footer 를 추가하고, txt 파일로 저장하기까지 완성
- highlight:
iframe = "{{url>fb_newest.php?date="+max(output['created_time']).replace('+','%2B')+"&format=m/d%20H&front=[최신:%20&mid=%EC%8B%9C%EA%B9%8C%EC%A7%80%20&end=%20%ED%8F%AC%EC%8A%A4%ED%8C%85%EC%9D%B4%20%EB%8D%94%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4]&style=font-size:11pt;font-color:%23333333;font-family:Helvetica,Arial,sans-serif; 100%,30 noscroll noborder left|no iframe error}}" contents = """====== Facebook Posting Archive ====== {{tag> blog Facebook 페이스북 페이지}} """ + f""" > {UTCtoKST(max(output['created_time']))} 까지 총 {len(output)} 개 포스팅 Archived > {iframe} > 최신 포스팅과 더 많은 소식은 [[https://facebook.com/data.triviaz|Data.triviaz]] 좋아요, 팔로잉 해주세요 ---- [[weblog:facebook_api_포스팅_가져오기_1_api사용|API사용,Python데이터정리,PHP최신현황 방법]] ---- """ + contents + """ ~~~DISCUSSION~~~ """ # end of HEADER ################################### f = open('facebook_posting.txt','w+', encoding="utf-8") f.write(contents) f.close()
추후 작업 / 다음 편
- Image 처리부분에서 호출한 get_images 함수 부분 : Image 다운로드, base64 로 인코딩
- FTP 를 통한 Image 업로드
- txt파일로 저장한 글로 웹 상의 문서 생성/수정
을 설명하도록 하겠다.
또한, 4편(PHP최신현황)에서는, 바로 위 코드의 Line 1에서 ifram으로 가져오는 PHP 페이지에 대한 설명을 해보도록 하겠다.
Discussion