๐ผ RSS๋ก ํ ๋ฒ์ ๋ฐฑ์ ํ๊ธฐ
๋ด Ghost ๋ธ๋ก๊ทธ๋ ์๋ฒ๋ฆฌ์คํ์ง ์๋ค. ์ง์์ ์ธ ๊ด๋ฆฌ๊ฐ ํ์ํจ์๋ ์๋ฒ๋ฅผ ์ ์งํ๋ ์ด์ ๋ ์๋ฒ๋ฅผ ํตํ ๋ธ๋ก๊ทธ ์ด์์ ์๋ง์ ์ฅ์ ๋ค์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ง๋ง ์๋ฒ๋ฅผ ํตํด ๋ธ๋ก๊ทธ๋ฅผ ๊ด๋ฆฌํ๊ฒ ๋๋ฉด ์์ฃผ ํฐ ๋จ์ ์ด ํ ๊ฐ์ง ์๊ธด๋ค. ์๋ฒ๊ฐ ํฐ์ง ๊ฒฝ์ฐ ์์ ๋ด๊ธด ๊ธ๋ค์ ๋ณต์ํ๊ธฐ ๋งค์ฐ ์ด๋ ค์์ง๋ค๋ ๊ฒ์ด๋ค. ์์ผ๋ก ๊ธ๊ณผ ์ฌ์ง์ด ํจ์ฌ ๋ง์์งํ ๋ฐ ๋งค๋ฒ ์ผ์ผ์ด ๋ณต์ฌํ์ฌ ๋ฐฑ์ ํ๊ธฐ๋ ๋๋ฌด ๊ท์ฐฎ์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ์ด๋ฅผ ๊ฐ์ ํด๋ณผ ๋์ฑ ์ ์ธ์๋ณด๊ณ ์ถ์๋ค.
Ghost ๋ด์ฅ ๋ฐฑ์ ์ ๋ฌธ์ ์
Ghost๋ .json ํํ์ ๋ธ๋ก๊ทธ ๋ฐฑ์
ํ์ผ์ ๋ด๋ ค ๋ฐ์ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. ์ ๋ง ๋ธ๋ก๊ทธ์ ์ํผ ๊ทธ๋๋ก ๋ณต์ฌ๋๋ค. ์๊ฐ ์ด๋ฆ, ์ฌ์ฉํ ํ๊ทธ๋ค, ๊ธ์ ๋ด์ฉ ๋ฐ ์์, ๊ธ์ ์
๋ก๋ํ ์๊ฐ๊ณผ HTML ๋ฉํํ๊ทธ ๋ด summary๊น์ง, Ghost ๋ด์์ ์ค์ ํ ์ ์๋ ๋ชจ๋ ๊ฒ๋ค์ ๊ทธ๋๋ก ๋ฐฑ์
๋๋ค.
ํ์ง๋ง ๋ ๊ฐ์ง ๋ฌธ์ ์ ์ด ์๋ค.
- Ghost ๋ด์ฅ ๋ฐฑ์ ํ์ผ์ ์ฌ๋์ด ์ฝ๊ธฐ ์ด๋ ต๋ค. Minified JSON์ผ ๋ฟ๋ง ์๋๋ผ, ์๋ง์ ์ ๋ณด๋ค์ ๋ด๊ณ ์๊ธฐ ๋๋ฌธ์ ํ์ผ ๊ตฌ์กฐ๋ ๋ณต์กํ๋ฉฐ ๊ธ๋ ์์ถ๋์ด ์๋ค.
- ๋ Ghost ๋ด์ฅ ๋ฐฑ์ ์ ์ฌ์ง์ ๋ฐฑ์ ํ์ง ์๋๋ค. ๋๋ฌธ์ ๋ธ๋ก๊ทธ๋ฅผ ๋ณต์ํ๋ฉด ์ฌ์ง ํ์ผ๋ค์ ๋ชจ๋ "์ฐพ์ ์ ์์"(์ผ๋ช ์๋ฐ)์ด ๋จ๊ฒ ๋๋ค. ๋ธ๋ก๊ทธ ์๋ฒ๊ฐ ์ด์์๊ฑฐ๋ ๋ณต์ฌํด๋ ์ฌ์ง๋ค์ด ์์ผ๋ฉด ๋คํ์ด์ง๋ง ์ฌ์ง์ ๋ณต์ํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๋ ์๊ธธ ์ ์๋ค๋ ๋ป์ด๋ค.
๋ชฉํ
์ฃผ ๋ชฉํ
- ๊ธ๊ณผ ์ฌ์ง์ ๋ชจ๋ ๋ฐฑ์ ํด์ผ ํ๋ค.
๋ณด๋์ค ๋ชฉํ
- ์ฌ๋์ด ์ฝ์ ์ ์๋ ํํ์ฌ์ผ ํ๋ค. (Human-Readable Medium)
- ๋ธ๋ก๊ทธ๋ฅผ ๋ณต์ํ ๋๋ฅผ ๋๋นํ์ฌ ์ด๋ ๊ธ์ ์ด๋ ์์น์ ์ด๋ ์ฌ์ง์ด ๋ค์ด๊ฐ๋์ง ๋ช ํํ๊ฒ ์ ์ ์์ด์ผ ํ๋ค.
- ๋ฐฑ์ ์ด ํธ๋ฆฌํด์ผ ํ๋ค.
- ๋ธ๋ก๊ทธ ์ธ๋ถ์์ ๋ณต์ ๋ณธ์ ๋ง๋ค ์ ์์ด์ผ ํ๋ค.
๊ตฌ์
๋ฐ๋ก RSS์ด๋ค. RSS๋ 2000๋ ๋ ์ด๋ฐ ๋ธ๋ก๊ทธ ๋ถ์ด ์ผ์์ ๋ ๋ฑ์ฅํ ๊ธฐ์ ๋ก ๋ง์น "๊ตฌ๋ "์ ์ญํ ์ ํ๋ค. RSS๋ฅผ ์ง์ํ๋ ์ฌ์ดํธ๋ ๋ธ๋ก๊ทธ๋ค์ RSS ํผ๋ ์ฃผ์๋ฅผ ์ ๊ณตํ๋ค. RSS ํผ๋ ์ฃผ์์๋ ๊ทธ ์ฌ์ดํธ์์ ์ ๋ฐ์ดํธ ๋๋ ๋ด์ฉ๋ค์ด ๊ธฐ๊ณ๊ฐ ์ฝ์ ์ ์๋ ํํ๋ก ์ ๋ฆฌ๋์ด ์ฌ๋ผ๊ฐ๋ค. ์ฌ์ฉ์๋ค์ด RSS ๋ฆฌ๋์ RSS ํผ๋ ์ฃผ์๋ฅผ ์ ๋ ฅํ๋ฉด ๋ฆฌ๋๊ฐ RSS ํผ๋ ์ฃผ์์์ ์๋ก์ด ์ปจํ ์ธ ๋ฅผ ๋งค๋ฒ ๊ธ์ด์ค๋ ๊ฒ์ด๋ค.
ํ๋์๋ SNS๊ฐ ํ์ฑํ๋๋ฉฐ RSS ๊ธฐ์ ์ด ์ฌ์ฅ๋์์ง๋ง ๋์ ๋ชฉํ๋ฅผ ๋ฌ์ฑํ๊ธฐ์๋ ์ถฉ๋ถํ ๊ธฐ์ ์ด๋ค. RSS ํผ๋๋ ๊ธ์ ๋ฐ์์ค๋ API ์ญํ ์ ํ๋ ๊ฒ์ด๋ค. Ghost๋ ๊ธฐ๋ณธ์ ์ผ๋ก RSS๋ฅผ ์ง์ํ๋ ์ด๋ฅผ ์ด์ฉํ๊ธฐ๋ก ํ๋ค.
๊ฐ๋ต์ ์ธ ์์ด๋์ด
- ๋ธ๋ก๊ทธ RSS ์ฃผ์๋ฅผ ์ ๋ ฅํ์ฌ RSS ํผ๋ ์ ์ฒด๋ฅผ ๋ณต์ฌํด์จ๋ค.
- RSS๋ฅผ ํ์ฑํ์ฌ ๊ธ์ HTML์ ์ถ์ถํ๋ค.
- ๊ฐ ๊ธ๋ง๋ค ํด๋๋ฅผ ํ๋์ฉ ์์ฑํ์ฌ ๊ธ์ HTML์ ์ ์ฅํ๋ค.
- ๊ธ์ HTML์ ํฌํจ๋
imgํ๊ทธ์src์ฃผ์์ ์ ์ํ์ฌ ์ฌ์ง์ ๋ด๋ ค ๋ฐ๋๋ค. - ์ฌ์ง์ด ์กด์ฌํ๋ ๊ธ์ ๊ธ ํด๋๋ง๋ค
imagesํด๋๋ฅผ ๋ง๋ค์ด ์ฌ์ง์ ์ ์ฅํ๊ณ , HTML ๋ดimgํ๊ทธ์src๋ฅผ ์ ์ฅ๋ ์ด๋ฏธ์ง์ ์๋ ๊ฒฝ๋ก๋ก ๋ณ๊ฒฝํ๋ค.
Development
์ฐธ๊ณ
์๋์ ๋ชจ๋ ์์๋ [anaclumos/backup-with-rss](https://github.com/anaclumos/backup-with-rss) ์ v1์ ๊ธฐ์ค์ผ๋ก ์์ฑ๋์๋ค. ์ด ๊ธ์ ์ฝ์ ์์ ์๋ ๋ฌด์ธ๊ฐ ์๋ก์ด ๊ธฐ๋ฅ์ด๋ ๋ฒ๊ทธ ์์ ์ด ์ถ๊ฐ๋์์์ง ๋ชจ๋ฅธ๋ค.
๋ํ ์ด ๊ธ์ ์ฒจ๋ถ๋ ์ฝ๋๋ ๊ฐ๋ต์ ์ธ ์ ๊ฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํ ๊ฒ์ด์ง ์ ์ฒด ์ฝ๋๊ฐ ์๋๋ค. ์ด ๊ธ๋๋ก ๋ณต์ฌํด์ ์คํํ๋ ค ํ๋ฉด ์๋ง ์คํ๋์ง ์์ ๊ฒ์ด๋ค! ์ ์ฒด ์ฝ๋๋ GitHub ์ ์ฅ์์ ๊ณต๊ฐ๋์ด ์๋ค.
1. Feedparser๋ฅผ ์ด์ฉํด RSS ํผ๋ ๋ณต์ฌ
Python์ Feedparser๋ผ๋ ๋ชจ๋์ ํตํด RSS ํผ๋๋ฅผ ๋ณต์ฌํ๋ค.
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
class RSSReader:
origin = ""
feed = ""
def __init__(self, URL):
self.origin = URL
self.feed = feedparser.parse(URL)
def parse(self):
return self.feed.entries
RSSReader๋ RSS ํผ๋๋ฅผ ๋ถ๋ฌ์ entries ํญ๋ชฉ์ ๋๊ธฐ๋๋ฐ ์ฌ์ฉ๋๋ค.
์ด ์ฝ๋๊ฐ ํ๋ ์ญํ ์,
- RSSReader Object๊ฐ ์์ฑ๋ ๋
self.origin์ RSS ์ฃผ์๋ฅผ ์ ์ฅํ๊ณ RSS ์ฃผ์๋ฅผ ํ์ฑํ์ฌself.feed์ ์ ์ฅํ๋ค. - parse ํจ์๊ฐ ์คํ๋ ์
self.feed์ ์ ์ฅ๋ ๊ฐ ์คentries๋ฅผ ๋ฐํํ๋ค.
์ด ์ค entries ์๋ RSS ํผ๋์ ๊ธ๋ค์ด list์ ํ์์ผ๋ก ๋ค์ด์๋ค. ๋ค์ ์์๋ ์ด ๊ธ์ RSS์ด๋ค.
parse()์ self.feed.entries์ ๊ตฌ์กฐ
// ์ผ๋ถ ์๋ต
{
"bozo": 0,
"encoding": "utf-8",
"entries": [],
"feed": {
"generator": "Ghost 3.13",
"generator_detail": {
"name": "Ghost 3.13"
},
"image": {
"href": "https://blog.chosunghyun.com/favicon.png",
"link": "https://blog.chosunghyun.com/",
"links": [
{
"href": "https://blog.chosunghyun.com/",
"rel": "alternate",
"type": "text/html"
}
],
"title": "Sunghyun Cho",
"title_detail": {
"base": "https://blog.chosunghyun.com/rss/",
"language": "None",
"type": "text/plain",
"value": "Sunghyun Cho"
}
},
"link": "https://blog.chosunghyun.com/",
"links": [
{
"href": "https://blog.chosunghyun.com/",
"rel": "alternate",
"type": "text/html"
},
{
"href": "https://blog.chosunghyun.com/rss/",
"rel": "self",
"type": "application/rss+xml"
}
],
"subtitle": "Sunghyun Cho's Blog",
"subtitle_detail": {
"base": "https://blog.chosunghyun.com/rss/",
"language": "None",
"type": "text/html",
"value": "Sunghyun Cho;s Blog"
},
"title": "Sunghyun Cho",
"title_detail": {
"base": "https://blog.chosunghyun.com/rss/",
"language": "None",
"type": "text/plain",
"value": "Sunghyun Cho"
},
"ttl": "60",
"href": "https://blog.chosunghyun.com/rss/",
"namespaces": {
"": "http://www.w3.org/2005/Atom",
"content": "http://purl.org/rss/1.0/modules/content/",
"dc": "http://purl.org/dc/elements/1.1/",
"media": "http://search.yahoo.com/mrss/",
"status": 200,
"version": "rss20"
}
}
}
2. RSS ๋ฐ์ดํฐ๋ก Markdown ํ์ผ ๋ง๋ค๊ธฐ
RSSReader๊ฐ ๋ฐํํ๋ self.feed.entries ์ค์์ ํ์ํ ๊ฐ๋ค๋ง ๋ฝ์๋ด๋ฉด ๋ ๊ฒ ๊ฐ์๋ค. RSSReader๊ฐ ์ ๊ณตํ ์ ๋ณด๋ฅผ ๊ฐ๊ณตํ MDCreator ํด๋์ค๋ฅผ ๋ง๋ค์๋ค.
class MDCreator:
def __init__(self, rawData, blogDomain):
self.rawData = rawData
self.blogDomain = blogDomain
def createFile(self, directory):
try:
os.makedirs(directory + "/" + self.rawData.title)
print('Folder "' + self.rawData.title + '" Created ')
except FileExistsError:
print(
'Folder "' + self.rawData.title + '" already exists'
)
self.directory = directory + "/" + self.rawData.title
MDFile = codecs.open(
self.directory + "/README.md", "w", "utf-8"
)
MDFile.write(self.render())
MDFile.close()
blogDomain parameter๋ ๋์ค์ ์ฌ์ฉ๋๋ค.
์ด ์ฝ๋๊ฐ ํ๋ ์ญํ ์,
- MDCreator Object๊ฐ ์์ฑ๋ ๋ ๋ธ๋ก๊ทธ ์ฃผ์๋ฅผ
self.blogDomain์, RSS ํผ๋ ์๋ณธ ๋ฐ์ดํฐ๋ฅผself.rawData์ ์ ์ฅํ๋ค. ์ด RSS ํผ๋ ์๋ณธ ๋ฐ์ดํฐ๋ RSSReader์parse()์์ ๋ฐํ๋๋self.feed.entries์ด๋ค. createFile()ํจ์๊ฐ ์คํ๋๋ฉด ๋ฐฑ์ ํด๋์ ๊ฐ ๊ธ๋ง๋ค ํ๋์ ํด๋๋ฅผ ๋ง๋ ๋ค. ์ด ๋ ํด๋ ์ ๋ชฉ์ ๊ธ์ ์ ๋ชฉ์ด๋ค. ๊ฐ ํด๋๋ง๋คREADME.md๋ฅผ ์์ฑํ๊ณ , ๊ทธ ์์ ๊ธ์ ๋ด์ฉ์ ๋ฃ๋๋ค.
codecs ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด์ ํ์ผ์ ์์ฑํ๋ ์ด์ ๋ Windows์์ CP949 ์ฝ๋ฑ ๋์ Unicode๋ฅผ ์ฌ์ฉํ๊ฒ๋ ํ๊ธฐ ์ํจ์ด๋ค. ๊ทธ๋์ผ RSS ์์ ํฌํจ๋ ์ด๋ชจ์ง๊ฐ ์ ์์ ์ผ๋ก ๋ํ๋๋ค ๐๐ฅ
3. ์์ฑํ Markdown ํ์ผ์ ๊ธ ์ ๋ณด ์ถ๊ฐํ๊ธฐ
๊ธ์ ์ ๋ณด๋ฅผ ํ์ํ ๋ Jekyll ํ์์ Front Matter๋ฅผ ์ฌ์ฉํ๊ณ ์ถ์๋ค. ๊ธ์ ์ ๋ชฉ, ํ๊ทธ, ๋งํฌ, ์๊ฐ ๋ฑ์ ๊ฐ์ฅ ์ฝ๊ฒ ํ์ธํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ผ๋ ์๊ฐ์ด ๋ค์๊ธฐ ๋๋ฌธ์ด๋ค.
def render(self):
try:
postTitle = str(self.rawData.title)
except AttributeError:
postTitle = "Post Title Unknown"
print("Post Title does not exist")
try:
postTags = str(
self.getValueListOfDictList(self.rawData.tags, "term")
)
except AttributeError:
postTags = "Post Tags Unknown"
print("Post Tags does not exist")
try:
postLink = "Post Link Unknown"
postLink = str(self.rawData.link)
except AttributeError:
print("Post Link does not exist")
try:
postID = str(self.rawData.id)
except AttributeError:
postID = "Post ID unknown"
print("Post ID does not exist")
try:
postAuthors = str(self.rawData.authors)
except AttributeError:
postAuthors = "Authors Unknown"
print("Authors does not exist")
try:
postPublished = str(self.rawData.published)
except AttributeError:
postPublished = "Published Date unknown"
print("Published Date does not exist")
self.renderedData = (
"---\nlayout: post\ntitle: "
+ postTitle
+ "\ntags: "
+ postTags
+ "\nurl: "
+ postLink
+ "\nauthors: "
+ postAuthors
+ "\npublished: "
+ postPublished
+ "\nid: "
+ postID
+ "\n---\n"
)
์ด ์ฝ๋๊ฐ ํ๋ ์ญํ ์,
- RSS ์ฝ๋์์ ๊ธ์ ์ ๋ชฉ, ํ๊ทธ, ๋งํฌ, ID, ์๊ฐ ์ด๋ฆ๋ค, ๊ทธ๋ฆฌ๊ณ ์ถํ ๋ ์ง๊ฐ ์๋์ง ํ์ธํ๊ณ , ๊ฐ์ด ์์ ๊ฒฝ์ฐ Front Matter์ ๊ทธ ๊ฐ์ ์ ๋ ฅํ๋ค.
- ๊ฐ์ด ์์ ๊ฒฝ์ฐ
~ Unknown์ ์ ๋ ฅํ๋ค.
Tags๋ฅผ self.getValueListOfDictList(self.rawData.tags, "term") ๊ฐ์ ์ฝ๋๋ฅผ ํตํด์ ๋ฃ๋ ์ด์ ๋ Ghost์์ ๋ค์๊ณผ ๊ฐ์ ํ์์ผ๋ก ํ๊ทธ๊ฐ ์ง์ ๋์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ด๋ Gatsby๋ Wordpress ๋ฑ๋ ๋ง์ฐฌ๊ฐ์ง์ด๋ค.
'tags': [{'label': None, 'scheme': None, 'term': 'English'},
{'label': None, 'scheme': None, 'term': 'Code'},
{'label': None, 'scheme': None, 'term': 'Apple'}],
def getValueListOfDictList(self, dicList, targetkey):
arr = []
for dic in dicList:
for key, value in dic.items():
if key == targetkey:
arr.append(value)
return arr
์ด์ ๊ฐ์ ๋ฐฉ์์ผ๋ก tags์์ term ํญ๋ชฉ๋ง ๊บผ๋ด Front Matter์ ์ถ๊ฐํ๋ค. ๊ทธ๋ ๊ฒ ๋๋ฉด ์คํํ์ ๋ ๋ค์๊ณผ ๊ฐ์ Jekyll Style Front Matter๊ฐ ์์ฑ๋๋ค.
---
layout: post
title: Apple's Easter Egg
tags: ['English', 'Code', 'Apple']
url: https://blog.chosunghyun.com/apples-easter-egg/
authors: [{ 'name': 'S Cho' }]
published: Sun, 19 Jan 2020 17:00:00 GMT
id: /_ Some Post ID _/
---

Front Matter๋ GitHub์์ ์ด๋ ๊ฒ ๋ ๋๋ง๋์ด ๋ณด์ธ๋ค.
4. ์์ฑํ Markdown ํ์ผ์ ๊ธ ์์ฝ ๋ฐ ๋ณธ๋ฌธ ์ถ๊ฐํ๊ธฐ
RSS ๋ฐ์ดํฐ์ Summary ํญ๋ชฉ๊ณผ Content ํญ๋ชฉ์ renderedData์ ์ถ๊ฐํ๋ค.
self.renderedData += "\n\n# " + postTitle + "\n\n## Summary\n\n"
try:
self.renderedData += self.rawData.summary
except AttributeError:
self.renderedData += "RSS summary does not exist."
self.renderedData += "\n\n## Content\n\n"
try:
for el in self.getValueListOfDictList(self.rawData.content, "value"):
self.renderedData += "\n" + str(el)
except AttributeError:
self.renderedData += "RSS content does not exist."
ํ ๊ฐ์ง ์ ๊ธฐํ๋ ์ ์ Ghost์ Wordpress ๊ธฐ๋ฐ ๋ธ๋ก๊ทธ๋ค์ RSS์ Summary์ Content๋ฅผ ๋ชจ๋ ์ง์ํ๋ ๋ฐ๋ฉด Jekyll-based GitHub Pages๋ Tistory๋ RSS Summary์ ๋ชจ๋ ๊ธ์ ๋ด์ฉ์ ์ง์ด๋ฃ๋๋ค๋ ์ ์ด๋ค. (...) Ghost๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธ์ Excerpt๋ฅผ ์ค์ ํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ, ์ด Excerpt ๊ฐ์ด RSS Summary๋ก ์ฌ์ฉ๋๋ค.
5. ์์ฑํ Markdown ํ์ผ์ ์ด๋ฏธ์ง ์ถ๊ฐํ๊ธฐ
๋ฐฑ์
์ ์ํด์๋ ์ด๋ฏธ์ง๊น์ง ์จ์ ํ๊ฒ ๋ณด์กด๋์ด์ผ ํ๋ค. HTML์ base64๋ก ์๋ฒ ๋๋๋์ด์๋ ์ด๋ฏธ์ง๊ฐ ์๋๋ผ๋ฉด ์ง๊ธ์ผ๋ก๋ ๋ชจ๋ img ํ๊ทธ์ src๋ง ์ง์ ๋์ด ์๋ ํํ์ด๋ค. ์๋ฒ๊ฐ ์ฃฝ์ผ๋ฉด img src์์ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ ํ
๋ ๋ฐฑ์
์ ํ ์์ ์ ์ด๋ฏธ์ง๋ฅผ ๋ชจ๋ ๋ค์ด๋ก๋ํด์ผํ๋ค.
PythonCode์ How to Download All Images from a Web Page in Python์ ์ฐธ๊ณ ํ์๋ค.
soup = bs(self.renderedData, features="html.parser")
for img in soup.findAll("img"):
for imgsrc in ["src", "data-src"]:
try:
remoteFile = img[imgsrc]
break
except KeyError:
continue
if self.isDomain(remoteFile) != True:
print("remoteFile", remoteFile, "is not a domain.")
remoteFile = self.blogDomain + "/" + remoteFile
print("Fixing it to", remoteFile)
print(
'Trying to download "'
+ remoteFile
+ '" and save it at "'
+ self.directory
+ '/images"'
)
self.download(remoteFile, self.directory + "/images")
img["src"] = "images/" + remoteFile.split("/")[-1]
img["srcset"] = ""
print(img["src"])
self.renderedData = str(soup)
return self.renderedData
์ด ์ฝ๋๊ฐ ํ๋ ์ญํ ์,
- ๋ฌธ์์ด
renderedData๋ฅผ HTML๋ก ์ฝ์ด์imgํ๊ทธ๋ฅผ ๋ชจ๋ ์ฐพ๋๋ค. src๋data-srcattribute๊ฐ ์๋์ง ํ์ธํ๋ค.data-src๋ Wordpress์ ๋์ํ๋ attribute์ด๋ค.- ๊ฐ ๊ธ ํด๋ ๋ด์ images ํด๋๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์์ ์ด๋ฏธ์ง๋ค์ ์ ์ฅํ๋ค. ์ด ๋ ์ด๋ฏธ์ง์ ์ด๋ฆ์ img src์ ๊ฐ์ฅ ํ์ ๋๋ ํ ๋ฆฌ์ด๋ค. ์๋ฅผ ๋ค์ด
img src๊ฐhttps://blog.someone.com/images/example.png๋ผ๋ฉดimages/example.png๋ก ์ ์ฅ๋๋ค. - ๊ธฐ์กด์
img src๋ฅผimagesํด๋์ ์๋ ๊ฒฝ๋ก๋ก ๋ณ๊ฒฝํ๋ค. srcsetattribute๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ฉด ์ด๋ฅผ ์ ๊ฑฐํ๋ค (Gatsby ๋์)
def download(self, url, pathname):
if not os.path.isdir(pathname):
os.makedirs(pathname)
response = requests.get(url, stream=True)
file_size = int(response.headers.get("Content-Length", 0))
filename = os.path.join(pathname, url.split("/")[-1])
if filename.find("?") > 0:
filename = filename.split("?")[0]
progress = tqdm(
response.iter_content(256),
f"Downloading {filename}",
total=file_size,
unit="B",
unit_scale=True,
unit_divisor=1024,
)
with open(filename, "wb") as f:
for data in progress:
f.write(data)
progress.update(len(data))
ํ ๊ฐ์ง ๋ฌธ์ ์ ์ ์ด๋ฏธ์ง์ ์ฃผ์๋ค์ด ์ผ๊ด์ ์ด์ง ์๋ค๋ ๊ฒ์ด๋ค. ์ด๋ ์ฌ์ดํธ๋ <img src = "https://example.png/images/example.png">์ ๊ฐ์ด ์ ์ฒด ๋๋ฉ์ธ์ ์ ๋ ๋ฐ๋ฉด ์ด๋ ์ฌ์ดํธ๋ <img src = "/images/example.png"> ๊ฐ์ด ์๋ธ๋๋ ํ ๋ฆฌ๋ถํฐ ์ ๋๋ค. ์ด๋ ๊ณณ์ <img src = "example.png">์ธ ๊ณณ๋ ์์๋ค. ์ต๋ํ ๋ง์ ๊ฒฝ์ฐ์ ๋์ํ๊ธฐ ์ํด ๋๋ฉ์ธ์ ๊ฐ์งํ๋ ํจ์ isDomain()์ ๋ง๋ค์๋ค. ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ .png์ ๊ฐ์ ํ์ผ ํ์ฅ์๋ฅผ .com๊ณผ ๊ฐ์ Top Level Domain์ผ๋ก ์ธ์ํ๊ธฐ์ ๋ช ๊ฐ์ง ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ค.
def isDomain(self, string):
if string.startswith("https://") or string.startswith("http://"):
return True
elif string.startswith("/"):
return False
else:
return validators.domain(string.split("/")[0])
๋ง์ฝ <img src = "/images/example.png">์ ๊ฐ์ด ์ง์ ์ ๊ทผ ๊ฐ๋ฅํ ๋๋ฉ์ธ์ด ์๋ ๊ฒฝ์ฐ ์์ ๋๋ฉ์ธ ์ด๋ฆ์ ๋ถ์ด๋๋ก ์ง์ ํ๋ค. ์ด ๋ ์๊น ์ง์ ํด๋ self.blogDomain์ด ์ฌ์ฉ๋๋ค.
๊ฒฐ๊ณผ
์ด ๋ธ๋ก๊ทธ๋ฅผ ๋ฐฑ์
ํด๋ณด์๋ค. ์ด ๋ธ๋ก๊ทธ๋ Self-hosted Ghost ๋ธ๋ก๊ทธ์ด๋ค. main.py๋ง ์คํํ๋ฉด ์ญ ๋ฐฑ์
์ด ์งํ๋๋ค.



ํ ์คํธํด๋ณธ ๋ฐ๋ก ๋ค์ ์๋น์ค๋ค์ด ์ง์๋๋ค. ๊ธ์ ์์์ด๋ ๋ฐฐ์ด์ด ์กฐ๊ธ ๋ค๋ฅผ ์ ์์ง๋ง ๋ฐฑ์ ์ ๋ชฉ์ ์ ์ถฉ๋ถํ ๋ฌ์ฑํ๋ค.
- Ghost
- Wordpress
- Jekyll-based GitHub Pages
- Gatsby-based GitHub Pages
- Medium
- Tistory
๋ชฉํ ๋ฌ์ฑ ํ๊ฐ
์ฃผ ๋ชฉํ
- ๊ธ๊ณผ ์ฌ์ง์ ๋ชจ๋ ๋ฐฑ์ ํด์ผ ํ๋ค. โ โ โ
๋ชฉํ๋ฅผ ์์ ํ ๋ฌ์ฑํ๋ค. ๋์์์ ๋ฐฑ์ ๋์ง ์๋๋ฐ, ์ด์ฐจํผ ๋์์์ YouTube๋ฅผ ํตํด์ embedded๋๋ฏ๋ก ์ ๋ณด๊ฐ ์ ์ค๋ ํ๋ฅ ์ด ํจ์ฌ ์ ๋ค. ๋๋ฌธ์ ์ฒ์๋ถํฐ ๋ชฉํ์์ ์ ์ธํ๋ค.
๋ณด๋์ค ๋ชฉํ
- ์ฌ๋์ด ์ฝ์ ์ ์๋ ํํ์ฌ์ผ ํ๋ค. (Human-Readable Medium) โ โ โ
Ghost ๋ด์ฅ ๋ฐฑ์ ๊ณผ ๋น๊ตํ์ฌ Front Matter์์ ์ค์ํ ์ ๋ณด๋ค์ ํ ๋์ ๋ณผ ์ ์๊ณ , ๋ธ๋ก๊ทธ์ ๊ฑฐ์ ๋์ผํ ํํ๋ก ๊ธ์ด ๋ ๋๋ง๋๋ค. ํด๋๋ณ๋ก ๊ธ๊ณผ ์ฌ์ง์ด ์ ๋ฆฌ๋์ด ์ํ๋ ์๋ฃ๋ฅผ ์ฐพ๊ธฐ๋ ํธ๋ฆฌํ๋ค. ํ์ง๋ง Markdown์ ํ์ฉํด๋ ๊ธ ๋ณธ๋ฌธ์ HTML์ด๊ธฐ์ ๊ธ์ ์์ ํ๊ธฐ๋ ๋ถํธํ๋ค. ๋ฑ Lots of copies keep stuff safe ์ ๋์ ๋ชฉ์ ์ ๋ฌ์ฑํ๋ ๋ฐฑ์ ์ด๋ค.
- ์ด๋ ๊ธ์ ์ด๋ ์์น์ ์ด๋ ์ฌ์ง์ด ๋ค์ด๊ฐ๋์ง ๋ช ํํ๊ฒ ์ ์ ์์ด์ผ ํ๋ค. (๋ธ๋ก๊ทธ ๋ณต์ํ ๋๋ฅผ ๋๋น) โ โ โ
์ด๋ ๊ธ์ ์ด๋ ์์น์ ์ด๋ ์ฌ์ง์ด ๋ค์ด๊ฐ๋์ง ๋ช ํํ๊ฒ ์ ์ ์๋ค.
- ๋ฐฑ์ ์ด ํธ๋ฆฌํด์ผ ํ๋ค. โ โ โ
์๋์ผ๋ก main.py๋ฅผ ์คํํด์ค์ผ ํ๋ค. ์ธ์ ๊ฐ crontab์ผ๋ก ์๋ํํ๋ ค ์๊ฐ ์ค์ด๋ค.
๋ํ RSS์ ์ฌ์ฉํ๋ ํน์ฑ ์ RSS ํผ๋์ ํฌํจ๋ ๊ฒ์๊ธ๋ง ๋ฐฑ์ ์ด ๋๋ค. RSS ํผ๋๋ bandwidth ์ฌ์ฉ๋์ ์ค์ด๊ธฐ ์ํด ์ต์ ๊ฒ์๋ฌผ๋ง ํฌํจํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ, ๊ฐ ๋ธ๋ก๊ทธ๋ง๋ค ์ด๋ฅผ ์กฐ์ ํ ์ ์๋ ์ต์ ์ด ์๋ค. Ghost ๋ธ๋ก๊ทธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก 15๊ฐ์ ์ต์ ๊ฒ์๋ฌผ์ RSS ํผ๋์ ํฌํจํ๋ค. Ghost ๋ธ๋ก๊ทธ์ RSS ํผ๋ ๊ฒ์๊ธ ์ซ์๋ Ghost CMS ์์์๋ ์กฐ์์ด ๋ถ๊ฐ๋ฅํ๋ฉฐ Ghost Core์ ์ฝ๋๋ฅผ ๊ฑด๋๋ ค์ผ ๊ฐ๋ฅํ๋ค.
- ๋ธ๋ก๊ทธ ์ธ๋ถ์์ ๋ณต์ ๋ณธ์ ๋ง๋ค ์ ์์ด์ผ ํ๋ค. โ โ โ
Wordpress ๋ธ๋ก๊ทธ์์ ๋ฐ๋ณต์ ์ผ๋ก ์๋ง์ ์ฌ์ง๋ค์ ๋ค์ด๋ก๋ํ๋ฉด ์ผ์์ ์ผ๋ก ์ ๊ทผ์ด ์ฐจ๋จ๋๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
ํฅํ ๊ณํ
์์ฑํ๊ณ ๊ณฐ๊ณฐ์ด ์๊ฐ์ ํด๋ณด๋ ๋ธ๋ก๊ทธ ์ด์ ์ ๊ณํ ์ค์ธ๋ฐ ์์๋ ์๋ฃ๊ฐ ๋๋ฌด ๋ง์์ ๊ณ ๋ฏผ์ธ ์ฌ๋๋ค์๊ฒ ์ข์ ๋๊ตฌ๊ฐ ๋ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ๋ธ๋ก๊ทธ ์ด์ ์ ๋์์ด ๋ ์ ์๋ ๋๊ตฌ๊ฐ ๋๋๋ก ๋์ฑ์ด ๊ฐ์ ์ ํด๋ณผ ๊ณํ์ด๋ค.
์ฐธ๊ณ
- How to Download All Images from a Web Page in Python by PythonCode
- Ghost Custom RSS Feed
- ๋ง์ฝ RSS ํผ๋์ ๋ฏธ์ง์ ์ ๋์ฝ๋ ๋ฌธ์๊ฐ ํฌํจ๋๋ฉด ์คํฌ๋ฆฝํธ๊ฐ ์ ์์ ์ผ๋ก ์คํ๋์ง ์๋๋ค. ์ด๋ ๋๋ถ๋ถ ์๋ํฐ ๋๋ ๋ธ๋ก๊ทธ์ ์ค๋ฅ๋ก ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ Feed Validator ๋ฑ์ ์๋น์ค๋ฅผ ์ด์ฉํ์ฌ ๊ทธ ๊ธ์๋ฅผ ์ฐพ์ ๊ณ ์น๋ฉด ์ ์ ๋์ํ๋ค.