앞에서는 블로그의 내용만 RSS 로 만들었는데 이번에는 사진 이미지까지 백업하는 파이썬 소스코드를 공개합니다.
티스토리에서 워드프레스로 완전히 이전을 원하시는 경우에 사용하시면 됩니다.
이미지는 본문 내용에 포함할 수도 있고 다운로드한 사진은 img src 의 url 을 치환할 수도 있습니다.
본문에 포함되는 방식이 아닌 1의 경우는 아래 코드에서 저장된 이미지 파일을 해당 경로에 수동으로 업로드 하셔야 합니다.
# coding=utf-8
# pip install beautifulsoup4
# pip install python-magic
# pip install pytz
# pip install requests
# pip install urllib3
import base64
import json
import magic
import math
import os
import requests
import urllib2
import urlparse
import xml.etree.ElementTree as xml
from bs4 import BeautifulSoup
from datetime import datetime
from pytz import timezone
URL_0 = 'https://www.tistory.com/auth/login' ### 티스토리 로그인 URL
URL_1 = 'https://www.tistory.com/oauth/authorize' ### 인증 요청 및 Authentication code 발급 URL
URL_2 = 'https://www.tistory.com/apis/blog/info' ### 블로그 정보 URL
URL_3 = 'https://www.tistory.com/apis/post/list' ### 블로그 리스트 URL
URL_4 = 'https://www.tistory.com/apis/post/read' ### 블로그 상세보기 URL
blogName = 'ivps' ### 블로그명
page = 0 ### 1 페이지 부터 시작
count = 30 ### 최대값 30
post_id = 0 ### 아래에서 데이터를 추출
access_token = '' ### 아래에서 데이터를 추출
loginParams = { ### 로그인 Parameters ( 블로그주소, 이메일계정, 비밀번호 )
'redirectUrl':'http://ivps.tistory.com',
'loginId':'이메일계정',
'password':'비밀번호'
}
tokenParams = { ### 토큰값을 받아오기 위한 Parameters ( App ID, CallBack, 'token' )
'client_id':'Open API App ID',
'redirect_uri':'Open API CallBack',
'response_type':'token'
}
def params_2(access_token): ### 블로그 정보 Parameters
return {'access_token':access_token, 'output':'json'}
def params_3(access_token, blogName, page, count): ### 블로그 리스트 Parameters
return {'access_token':access_token, 'output':'xml', 'targetUrl':blogName, 'page':page, 'count':count}
def params_4(access_token, blogName, post_id): ### 블로그 상세보기 Parameters
return {'access_token':access_token, 'output':'xml', 'targetUrl':blogName, 'postId':post_id}
IS_IMG_TO_SAVE = 1 ### 이미지 저장
IMG_SRC_TYPE = 1 ### 1:이미지 URL 변경, 2:이미지를 본문 내용에 포함
def image_save_from_html(html):
html_obj = BeautifulSoup(html, 'html.parser')
img_data = html_obj.find_all('img')
print('img count : ' + str(len(img_data)))
for image in img_data:
if '//cfile' in image['src']:
print('img src : ' + image['src'])
try:
imgUrl = image['src']
filename = image['src'].split('/')[-1]
imgData = urllib2.urlopen(image['src']).read()
f = open(filename, 'wb')
f.write(imgData)
f.close()
if(IMG_SRC_TYPE == 1):
imgSrc = image_url_change(filename)
html = html.replace(imgUrl, imgSrc)
elif(IMG_SRC_TYPE == 2):
imgSrc = image_to_rawdata(filename)
html = html.replace(imgUrl, imgSrc)
else:
print('@@@ imgUrl : ' + imgUrl)
except:
print('@@@ HTMLparse Error : ' + str(image))
else:
print('@@@ Pass img src : ' + image['src'])
return html
def image_to_rawdata(filename):
mime_type = magic.from_file(filename, mime=True)
print('mime_type : ' + mime_type)
f = open(filename, 'rb')
image = f.read()
f.close()
rawData = base64.b64encode(image).decode('utf-8')
imgSrc = 'data:'+ mime_type +';base64,' + rawData
return imgSrc
def image_url_change(filename):
mime_type = magic.from_file(filename, mime=True)
print('mime_type : ' + mime_type)
if '.' not in filename: ### 이미지 파일 확장자가 없으면 추가
ext = mime_type.split('/')[-1]
os.rename(filename, filename+'.'+ext)
filename = filename+'.'+ext
imgSrc = '/wp-content/uploads/tistory/' + filename
return imgSrc
rs = requests.session()
try:
r0 = rs.post(URL_0, data=loginParams)
try:
r1 = rs.get(URL_1, params=tokenParams)
access_token = str( urlparse.parse_qs( r1.url.split('#')[1] )['access_token'][0] )
print('### access_token : ' + access_token)
try:
r2 = rs.get(URL_2, params=params_2(access_token))
print('### Open API, Blog Info Url : ' + str(r2.url))
#print(r2.text)
item = json.loads(r2.text)
item_size = len(item['tistory']['item']['blogs'])
### RSS XML Create
rss = xml.Element('rss')
rss.set('version', '2.0')
x1_ch = xml.SubElement(rss, 'channel')
for i in range(item_size): ### 0 ~ 5, 없거나 최대 5개의 블로그
blog_name = item['tistory']['item']['blogs'][i]['name']
if(blog_name == blogName): # 일치하는 블로그만
print('### Find blog : ' + str(blog_name))
### ==> 필요는 없지만 티스토리 rss 에 나온는 형식에 맞춰줌
x1_ch_ti = xml.SubElement(x1_ch, 'title').text = item['tistory']['item']['blogs'][i]['title']
x1_ch_li = xml.SubElement(x1_ch, 'link').text = item['tistory']['item']['blogs'][i]['url']
x1_ch_de = xml.SubElement(x1_ch, 'description').text = item['tistory']['item']['blogs'][i]['description']
x1_ch_la = xml.SubElement(x1_ch, 'language').text = 'ko'
x1_ch_pu = xml.SubElement(x1_ch, 'pubDate').text = datetime.now(timezone('Asia/Seoul')).strftime('%a, %d %b %Y %H:%M:%S %z')
x1_ch_ge = xml.SubElement(x1_ch, 'generator').text = 'ivps.kr'
x1_ch_ma = xml.SubElement(x1_ch, 'managingEditor').text = item['tistory']['item']['blogs'][i]['nickname']
x1_ch_im = xml.SubElement(x1_ch, 'image')
x1_ch_im_ti = xml.SubElement(x1_ch_im, 'title').text = item['tistory']['item']['blogs'][i]['title']
x1_ch_im_ur = xml.SubElement(x1_ch_im, 'url').text = item['tistory']['item']['blogs'][i]['profileImageUrl']
x1_ch_im_li = xml.SubElement(x1_ch_im, 'link').text = item['tistory']['item']['blogs'][i]['url']
x1_ch_im_de = xml.SubElement(x1_ch_im, 'description').text = item['tistory']['item']['blogs'][i]['description']
### <==
nickname = item['tistory']['item']['blogs'][i]['nickname']
totalCnt = item['tistory']['item']['blogs'][i]['statistics']['post']
print('### post : ' + totalCnt) ### 포스팅 갯수
pages = int ( math.ceil ( float(totalCnt) / float(count) ) )
for j in range(pages): ### 총 페이지 만큼 반복
page = j+1
print('### Page : ' + str(page) + ' of ' + str(pages) + ' ###')
try:
r3 = rs.get(URL_3, params=params_3(access_token, blogName, page, count))
print('### Open API, Blog List Url : ' + str(r3.url))
xmlList = xml.fromstring(r3.text.encode(r3.encoding))
#xml.dump(xmlList)
for parent in xmlList.getiterator('post'): ### 목록에서 postId 추출
post_id = int( parent.find('id').text )
visibility = int( parent.find('visibility').text )
if(visibility in (2,3)): ### 2:??, 3:발행 게시글
try:
r4 = rs.get(URL_4, params=params_4(access_token, blogName, post_id))
print('### Open API, Blog Desc Url, postId(' + str(post_id) + ') : ' + str(r4.url))
xmlDesc = xml.fromstring(r4.text.encode(r4.encoding))
#print(xml.dump(xmlDesc))
for desc in xmlDesc.getiterator('item'): ### 상세내용 추출
if(IS_IMG_TO_SAVE == 1):
html = image_save_from_html(desc.find('content').text)
else:
html = desc.find('content').text
x2_it = xml.SubElement(x1_ch, 'item')
x3_ti = xml.SubElement(x2_it, 'title').text = parent.find('title').text
x3_li = xml.SubElement(x2_it, 'link').text = parent.find('postUrl').text
x3_de = xml.SubElement(x2_it, 'description').text = html
for tag in desc.find('tags').findall('tag'): ### 카테고리 갯수 만큼 반복
x3_ca = xml.SubElement(x2_it, 'category').text = tag.text
x3_au = xml.SubElement(x2_it, 'author').text = nickname
x3_gu = xml.SubElement(x2_it, 'guid').text = parent.find('postUrl').text
x3_pu = xml.SubElement(x2_it, 'pubDate').text = parent.find('date').text
except:
print('@@@ Error : ' + str(r4.url))
else: ### 0:비공개, 1:보호
print('### Pass PostId(' + str(post_id) + ') visibility : ' + str(visibility))
except:
print('@@@ Error : ' + str(r3.url))
else:
print('### Pass blog : ' + str(blog_name))
except:
print('@@@ Error : ' + str(r2.url))
except:
print('@@@ Error : ' + str(r1.url))
except:
print('@@@ Error : ' + str(r0.url))
#xml.dump(rss)
xml.ElementTree(rss).write('/var/www/html/rss.xml') # 적당한 위치에 저장
색깔이 다른 부분만 수정해서 사용하시면 됩니다.
이미지를 본문 내용에 포함하는 경우에 이미지가 많이 들어간 경우는 걸러주는 작업이 필요해 보입니다.