๐Ÿ“ผ 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๋ฅผ ์ง€์›ํ•˜๋‹ˆ ์ด๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๊ฐœ๋žต์ ์ธ ์•„์ด๋””์–ด

  1. ๋ธ”๋กœ๊ทธ RSS ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ RSS ํ”ผ๋“œ ์ „์ฒด๋ฅผ ๋ณต์‚ฌํ•ด์˜จ๋‹ค.
  2. RSS๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ๊ธ€์˜ HTML์„ ์ถ”์ถœํ•œ๋‹ค.
  3. ๊ฐ ๊ธ€๋งˆ๋‹ค ํด๋”๋ฅผ ํ•˜๋‚˜์”ฉ ์ƒ์„ฑํ•˜์—ฌ ๊ธ€์˜ HTML์„ ์ €์žฅํ•œ๋‹ค.
  4. ๊ธ€์˜ HTML์— ํฌํ•จ๋œ img ํƒœ๊ทธ์˜ src ์ฃผ์†Œ์— ์ ‘์†ํ•˜์—ฌ ์‚ฌ์ง„์„ ๋‚ด๋ ค ๋ฐ›๋Š”๋‹ค.
  5. ์‚ฌ์ง„์ด ์กด์žฌํ•˜๋Š” ๊ธ€์€ ๊ธ€ ํด๋”๋งˆ๋‹ค 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 ํ•ญ๋ชฉ์„ ๋„˜๊ธฐ๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

์ด ์ฝ”๋“œ๊ฐ€ ํ•˜๋Š” ์—ญํ• ์€,

  1. RSSReader Object๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ self.origin์— RSS ์ฃผ์†Œ๋ฅผ ์ €์žฅํ•˜๊ณ  RSS ์ฃผ์†Œ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ self.feed์— ์ €์žฅํ•œ๋‹ค.
  2. 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๋Š” ๋‚˜์ค‘์— ์‚ฌ์šฉ๋œ๋‹ค.

์ด ์ฝ”๋“œ๊ฐ€ ํ•˜๋Š” ์—ญํ• ์€,

  1. MDCreator Object๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ๋ธ”๋กœ๊ทธ ์ฃผ์†Œ๋ฅผ self.blogDomain์—, RSS ํ”ผ๋“œ ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ self.rawData ์— ์ €์žฅํ•œ๋‹ค. ์ด RSS ํ”ผ๋“œ ์›๋ณธ ๋ฐ์ดํ„ฐ๋Š” RSSReader์˜ parse()์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” self.feed.entries์ด๋‹ค.
  2. 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"
    )

์ด ์ฝ”๋“œ๊ฐ€ ํ•˜๋Š” ์—ญํ• ์€,

  1. RSS ์ฝ”๋“œ์—์„œ ๊ธ€์˜ ์ œ๋ชฉ, ํƒœ๊ทธ, ๋งํฌ, ID, ์ž‘๊ฐ€ ์ด๋ฆ„๋“ค, ๊ทธ๋ฆฌ๊ณ  ์ถœํŒ ๋‚ ์งœ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ Front Matter์— ๊ทธ ๊ฐ’์„ ์ž…๋ ฅํ•œ๋‹ค.
  2. ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ ~ 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 _/
---

Jekyll Style Front Matter on GitHub

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

์ด ์ฝ”๋“œ๊ฐ€ ํ•˜๋Š” ์—ญํ• ์€,

  1. ๋ฌธ์ž์—ด renderedData๋ฅผ HTML๋กœ ์ฝ์–ด์™€ img ํƒœ๊ทธ๋ฅผ ๋ชจ๋‘ ์ฐพ๋Š”๋‹ค.
  2. src๋‚˜ data-src attribute๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. data-src๋Š” Wordpress์— ๋Œ€์‘ํ•˜๋Š” attribute์ด๋‹ค.
  3. ๊ฐ ๊ธ€ ํด๋” ๋‚ด์— images ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— ์ด๋ฏธ์ง€๋“ค์„ ์ €์žฅํ•œ๋‹ค. ์ด ๋•Œ ์ด๋ฏธ์ง€์˜ ์ด๋ฆ„์€ img src์˜ ๊ฐ€์žฅ ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด img src๊ฐ€ https://blog.someone.com/images/example.png๋ผ๋ฉด images/example.png๋กœ ์ €์žฅ๋œ๋‹ค.
  4. ๊ธฐ์กด์˜ img src๋ฅผ images ํด๋”์˜ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
  5. srcset attribute๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์ด๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค (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๋งŒ ์‹คํ–‰ํ•˜๋ฉด ์ญ‰ ๋ฐฑ์—…์ด ์ง„ํ–‰๋œ๋‹ค.

๋ฐฑ์—…๋œ ๊ธ€๋“ค์ด๋‹ค. ํด๋” ์ด๋ฆ„์€ ๊ธ€์˜ ์ œ๋ชฉ์œผ๋กœ ์„ค์ •๋œ๋‹ค.

GitHub ์ƒ์— ๋ฐฑ์—…๋œ ๊ธ€์˜ ๋ชจ์Šต์ด๋‹ค. ์‚ฌ์ง„ ๋˜ํ•œ ๋ธ”๋กœ๊ทธ ์„œ๋ฒ„ ๋Œ€์‹  ํด๋”์— ์ง์ ‘ ์ €์žฅํ•˜์—ฌ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

ํด๋”์—๋Š” ๊ธ€์— ์‚ฌ์šฉ๋œ ์‚ฌ์ง„๋“ค์ด ์ €์žฅ๋œ๋‹ค.

ํ…Œ์ŠคํŠธํ•ด๋ณธ ๋ฐ”๋กœ ๋‹ค์Œ ์„œ๋น„์Šค๋“ค์ด ์ง€์›๋œ๋‹ค. ๊ธ€์˜ ์–‘์‹์ด๋‚˜ ๋ฐฐ์—ด์ด ์กฐ๊ธˆ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์ง€๋งŒ ๋ฐฑ์—…์˜ ๋ชฉ์ ์€ ์ถฉ๋ถ„ํžˆ ๋‹ฌ์„ฑํ•œ๋‹ค.

  • 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 ๋“ฑ์˜ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ทธ ๊ธ€์ž๋ฅผ ์ฐพ์•„ ๊ณ ์น˜๋ฉด ์ •์ƒ ๋™์ž‘ํ•œ๋‹ค.