๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Notion2Tistory (Notion To Tistory)

category Project 2022. 5. 10. 22:22
๐Ÿ’›
StatusFinished
Goal
Learning Stacks
CategoryProject
TagNotion2Tistory
Tagsbs4notionnotion-pyshutiltistory
๐Ÿ’ก
details ํƒœ๊ทธ ๊ด€๋ จํ•˜์—ฌ ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด, safari ์—์„œ ์ ‘์†ํ•ด์•ผ ๋” ๊น”๋”ํ•œ ๊ธ€์„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (notion ์—์„œ๋Š” ์ƒ๊ด€์—†์Œ)

๐Ÿ’ก
๊ธฐ๋ก / ๊ณต๋ถ€์šฉ์œผ๋กœ notion ์„ ์“ฐ๊ณ  ์žˆ์ง€๋งŒ notion ์ด ๊ธฐ์กด์˜ git ์ด๋‚˜ ๋ธ”๋กœ๊ทธ์˜ ์—ญํ• ๊นŒ์ง€ ํ•ด์ค„ ์ˆ˜๋Š” ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ, notion ์—์„œ ์ž‘์„ฑํ•œ ๊ธ€๋“ค์„ html ํ˜•์‹์œผ๋กœ ๋‚ด๋ณด๋‚ด์–ด, tistory API ๋ฅผ ํ†ตํ•ด tistory ์— ์—…๋กœ๋“œ ํ•˜๋Š” ๊ณผ์ •์„ ๊ฐ„ํŽธํžˆ ํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. (๊ตฐ ๋ณต๋ฌด ์‹œ์ ˆ ์—ฐ๋“ฑํ•˜๋ฉด์„œ ๋งŒ๋“ค์–ด ๋ณธ๊ฑฐ๋ผ, ๋งŽ์€ ๋ถ€๋ถ„์ด ํ•˜๋“œ์ฝ”๋”ฉ์ด๊ณ , ์ €์—๊ฒŒ ๋งž์ถฐ๋‚˜๊ฐ€๋Š๋ผ.. ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์ฐธ๊ณ ๋งŒ ํ•ด์ฃผ์„ธ์š”. ) ๊ตฐ ๋ณต๋ฌด ์‹œ์ ˆ colab ํ™œ์šฉํ•˜์—ฌ ์ผ์—ˆ๋˜ ์ถ”์–ต๋“ค https://github.com/nomad2569/Notion-To-Tistory
https://github.com/nomad2569/Notion-To-Tistory
  • ๋„์›€์„ ์ค€ ์‚ฌ์ดํŠธ๋“ค
    • notion-py๋กœ ๋…ธ์…˜ CMS ๊ตฌ์ถ•ํ•˜๊ธฐ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ™œ์šฉ

      https://younho9.dev/notion-cms-using-database

      ์ด์ „ ๊ธ€์—์„œ notion-py๋ฅผ ์‚ฌ์šฉํ•ด ๋…ธ์…˜์— ํŽ˜์ด์ง€์— ์žˆ๋Š” ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋“ค์„ ๋งˆํฌ๋‹ค์šด ๋ฌธ์„œ๋กœ ์ถ”์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€ ์‚ฌ์šฉํ•ด๋ดค๋‹ค. ํ•˜์ง€๋งŒ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋…ธ์…˜์„ CMS๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋…ธ์…˜์˜ ๊ฐ•๋ ฅํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

      ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ™œ์šฉํ•˜๊ธฐ

      ์ด์ „ ๋ฐฉ์‹์ฒ˜๋Ÿผ ํ•œ ํŽ˜์ด์ง€ ์•ˆ์— ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋“ค์„ ๋‹จ์ˆœํžˆ ๋ชจ์•„๋†“๋Š” ๊ฒƒ์€ ์—ฌ๋Ÿฌ๋ชจ๋กœ ๋ถˆํŽธํ•˜๋‹ค. ํฌ์ŠคํŠธ์˜ ์ œ๋ชฉ๋งŒ ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์—, ์–ธ์ œ ์ž‘์„ฑํ–ˆ๋Š”์ง€, ๋ฌด์Šจ ์ฃผ์ œ์ธ์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

      ๋…ธ์…˜์€ ๋‹ค์–‘ํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š”๋ฐ, ์ด๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋„ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋‹ค์–‘ํ•œ ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์ž‘์„ฑ ๋‚ ์งœ, ์ƒํƒœ, ํƒœ๊ทธ, ์ž‘์„ฑ์ž ๋“ฑ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ์— ํ•„์š”ํ•œ ํ—ค๋” ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๊ทธ๋ฆฌ๊ณ  ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ด๋Ÿฌํ•œ ํ”„๋กœํผํ‹ฐ๋“ค์„ ํ™œ์šฉํ•ด์„œ ํ•„ํ„ฐ๋งํ•˜๊ณ  ์ •๋ ฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๋ฉฐ, ํ…Œ์ด๋ธ”, ๋ณด๋“œ, ์บ˜๋ฆฐ๋” ๋“ฑ ๋‹ค์–‘ํ•œ ๋ทฐ๋ฅผ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ˜ํ…์ธ ๋“ค์„ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (Linked database)

      ๋…ธ์…˜์—๋Š” ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ผ๋Š” ๊ฐœ๋…์ด ์žˆ๋‹ค. ๋…ธ์…˜์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์ธ๋ผ์ธ ๋˜๋Š” ํ’€ํŽ˜์ด์ง€๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ โ€œ๋งํฌโ€ํ•˜์—ฌ ๋‹ค๋ฅธ ํŽ˜์ด์ง€ ์•ˆ์— ๋„ฃ๊ฑฐ๋‚˜, ์ž์ฒด ํŽ˜์ด์ง€๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

      ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ, ๋ฐ์ดํ„ฐ๊ฐ€ ์™„์ „ํžˆ ๋™๊ธฐํ™”๋œ๋‹ค.

      ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํŽ˜์ด์ง€์— ์‚ฝ์ž…ํ•œ ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค. ์™ผ์ชฝ์˜ โ†—ํ™”์‚ดํ‘œ๊ฐ€ ๋ณด์ธ๋‹ค.

      ๋ณธ๊ฒฉ์ ์ธ CMS ๊ตฌ์ถ•

      ์ด๋ ‡๊ฒŒ ๊ฐ•๋ ฅํ•œ ๋…ธ์…˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋…ธ์…˜ CMS๋ฅผ ๊ตฌ์ถ•ํ•ด๋ณด์ž.

      ๋จผ์ € ๋…ธ์…˜์— ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค. ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‹ค์–‘ํ•œ ์†์„ฑ๊ณผ ๋ทฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ฐ์ž์˜ ๋ฐฉ์‹์œผ๋กœ ๋ธ”๋กœ๊ทธ CMS๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

      ๊ทธ๋ฆฌ๊ณ  ๋นˆ ํŽ˜์ด์ง€๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด, ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ธ๋ผ์ธ์œผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.

      ์™ผ์ชฝ ์‚ฌ์ด๋“œ๋ฐ”์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์šฐํด๋ฆญํ•˜์—ฌ ๋งํฌ๋ฅผ ๋ณต์‚ฌํ•˜๊ณ  ํŽ˜์ด์ง€์— ๋ถ™์—ฌ๋„ฃ๊ธฐํ•˜๋ฉด ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์‚ฝ์ž…๋œ๋‹ค.

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค id ๊ตฌํ•˜๊ธฐ

      ์œ„์—์„œ ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํŽ˜์ด์ง€์— ์‚ฝ์ž…ํ•œ ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ id ๋ฅผ ๊ตฌํ•˜๊ธฐ ์œ„ํ•ด์„œ์˜€๋‹ค.

      ์ด์ „ ๊ธ€์˜ ์ฝ”๋“œ์—์„œ๋Š” ํŽ˜์ด์ง€์˜ url ๋กœ ์ ‘๊ทผํ•ด ํŽ˜์ด์ง€ ๋‚ด์˜ ํฌ์ŠคํŠธ์— ์ ‘๊ทผํ•œ ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์žˆ๋Š” ํฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ํŽ˜์ด์ง€์˜ url ์ด ์•„๋‹ˆ๋ผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ id ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

      ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋™์ผํ•œ id ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ ์ด์ „์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค id ๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ id ๋Š” ์•„๋ž˜์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค. token_v2 ๋Š” ์ž์‹ ์˜ token_v2 ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ณ , ๋งํฌ๋“œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ธ”๋Ÿญ์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€์˜ url ์„ ๋„ฃ์–ด, ํŽ˜์ด์ง€ ์•ˆ์— ์žˆ๋Š” ๋ธ”๋Ÿญ๋“ค์„ ์ถœ๋ ฅํ•ด๋ณด๋ฉด, ๋ธ”๋Ÿญ์˜ id , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ œ๋ชฉ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค( collection )์˜ id ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

      3from notion.client import NotionClient5
      client = NotionClient(token_v2="NOTION_TOKEN")
      6page = client.get_block("NOTION_PAGE_URL")8
      for block in page.children
      :9    print(block)

      ์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด, block ์˜ id ์™€ collection ์ฆ‰, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํƒ€์ดํ‹€๊ณผ id ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. ์ด collection ์˜ id ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํฌ์ŠคํŠธ์— ์ ‘๊ทผํ•˜๊ธฐ

      ์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•ด ๊ธ€์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์ž. notion-py์—์„œ๋Š” get_collection(collection_id) ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋Š” ์ •๋ณด๋“ค์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ณ , collection.get_rows() ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ ํ–‰์— ์žˆ๋Š” ํฌ์ŠคํŠธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

      3from notion.client import NotionClient5client = NotionClient("NOTION_TOKEN")6contents_collection = client.get_collection("COLLECTION_ID")7posts = contents_collection.get_rows()9for post in posts:10    print(post)

      ์ด์ œ ์ด๋ ‡๊ฒŒ ์ ‘๊ทผํ•œ ํฌ์ŠคํŠธ์˜ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

      6from notion.client import NotionClient8client = NotionClient("NOTION_TOKEN")9contents_collection = client.get_collection("COLLECTION_ID")10posts = contents_collection.get_rows()12for post in posts:14    text = """---18---""" % (post.title, datetime.datetime.now())20    text = text + '\n\n' + '# ' + post.title + '\n\n'21    for block in post.children:23        if (block.type == 'header'):24            text = text + '# ' + block.title + '\n\n'26        if (block.type == 'sub_header'):27            text = text + '## ' + block.title + '\n\n'29        if (block.type == 'sub_sub_header'):30            text = text + '### ' + block.title + '\n\n'32        if (block.type == 'code'):33            text = text + '```\n' + block.title + '\n```\n'35        if (block.type == 'image'):36            text = text + '![' + block.id + '](' + block.source + ')\n\n'38        if (block.type == 'bulleted_list'):39            text = text + '* ' + block.title + '\n\n'41        if (block.type == 'divider'):42            text = text + '---' + '\n\n'44        if (block.type == 'text'):45            text = text + block.title + '\n\n'46    title = post.title.replace(' ', '-')47    title = title.replace(',', '')48    title = title.replace(':', '')49    title = title.replace(';', '')50    title = title.lower()52        os.mkdir('../content/blog/' + title)55    file = open('../content/blog/' + title + '/index.md', 'w')57    print(text)58    file.write(text)

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ property ๊ฐ€์ ธ์˜ค๊ธฐ

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ง€์ •ํ•œ ์†์„ฑ๋“ค ์—ญ์‹œ ๊ฐ€์ ธ์™€ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ž‘์„ฑ ์™„๋ฃŒ๋œ ํฌ์ŠคํŠธ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค๊ฑฐ๋‚˜, ํฌ์ŠคํŠธ์˜ ์ž‘์„ฑ ๋‚ ์งœ๋‚˜ ํƒœ๊ทธ๋“ค์„ ๊ฐ€์ ธ์™€ ํ—ค๋”๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋“ฑ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

      6from notion.client import NotionClient8client = NotionClient("NOTION_TOKEN")9contents_collection = client.get_collection("COLLECTION_ID")10properties = contents_collection.get_schema_properties()12for property in properties:13    print(property)

      ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋Š” ์†์„ฑ๋“ค์˜ ๋ชฉ๋ก์€ collection ์˜ get_schema_properties() ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

      ์ด property ๋“ค์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์•ˆ์— ์žˆ๋Š” ๊ฐ๊ฐ์˜ ํฌ์ŠคํŠธ๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋‹ค. post.status , post.tags ๋“ฑ ์•„๋ž˜์—์„œ slug ์˜ ๊ฐ’์œผ๋กœ ํฌ์ŠคํŠธ์˜ property ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๐Ÿค” ์‹ ๊ธฐํ•˜๊ฒŒ๋„, ํ•œ๊ธ€ ์†์„ฑ์€ ๋ฐœ์Œ๋Œ€๋กœ ์˜์–ด๋กœ ๋ฐ”๋€Œ์–ด ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

      ์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ํฌ์ŠคํŠธ๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋Š” ์†์„ฑ๋„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

      6from notion.client import NotionClient8client = NotionClient("NOTION_TOKEN")9contents_collection = client.get_collection("COLLECTION_ID")10posts = contents_collection.get_rows()12for post in posts:13    print(post.tags)

      ์ถ”๊ฐ€ ์„ค์ •

      ๋จธ๋ฆฌ๋ง ๊ตฌ์„ฑ

      ์ด๋ ‡๊ฒŒ ๊ฐ€์ ธ์˜จ ์†์„ฑ๋“ค์„ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ์˜ ๋จธ๋ฆฌ๋ง ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•ด๋ณด์ž.

      hero : ์ธ๋„ค์ผ ์ด๋ฏธ์ง€ excerpt : ์š”์•ฝ ๋ฌธ๊ตฌ

      ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” Gatsby ๋ธ”๋กœ๊ทธ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋จธ๋ฆฌ๋ง ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํ—ค๋”๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋“ค์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

      ์ด์ œ ๊ฐ ์†์„ฑ์˜ slug ๊ฐ’์œผ๋กœ ์ ‘๊ทผํ•˜์—ฌ ๋จธ๋ฆฟ๋ง์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

      4from notion.client import NotionClient6client = NotionClient("NOTION_TOKEN")7contents_collection = client.get_collection("COLLECTION_ID")8posts = contents_collection.get_rows()10for post in posts:12    text = """---17""" % (post.title, post.author, post.created_time.strftime("%Y-%m-%d"), post.excerpt)18    if not post.hero:19        text = text + '---\n\n'20    elif 'png' in post.hero[0]:21        text = text + 'hero: ' + './images/hero.png\n---\n\n'22        image_format = 'png'23    elif 'jpg' in post.hero[0]:24        text = text + 'hero: ' + './images/hero.jpg\n---\n\n'25        image_format = 'jpg'27    print(text)

      ์—ฌ๊ธฐ์„œ hero ์ฆ‰, ์ปค๋ฒ„ ์ด๋ฏธ์ง€ ํŒŒ์ผ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค. ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์†์„ฑ ์ค‘ Files & media ๋Š” ์ด๋ฏธ์ง€์™€ ํŒŒ์ผ url ์ด ๋ฌธ์ž์—ด๋กœ ๋‹ด๊ธด ๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, url ์— ์žˆ๋Š” ์ด๋ฏธ์ง€ ํŒŒ์ผ ํ˜•์‹์ž์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์—ˆ๋‹ค.

      ์ดํ›„์— ์ด ์ด๋ฏธ์ง€ ๋งํฌ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ./image/hero.xxx ๊ฒฝ๋กœ์— ๋‹ค์šด๋ฐ›์„ ๊ฒƒ์ด๋‹ค.

      ๋จธ๋ฆฟ๋ง์ด ์ž˜ ๊ตฌ์„ฑ๋˜์—ˆ๋‹ค.

      ๋” ๋งŽ์€ ๋…ธ์…˜ ๋ธ”๋ก ํƒ€์ž… ์ง€์›ํ•˜๊ธฐ

      ์ด์ „ ๊ธ€์˜ ์ฝ”๋“œ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๋…ธ์…˜ ๋ธ”๋ก์— ๋Œ€ํ•œ ์ง€์›์ด ๋ถ€์กฑํ–ˆ๋‹ค.

      ์—ฌ๋Ÿฌ ๋ธ”๋ก ํƒ€์ž…์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ block.type ์œผ๋กœ type ์„ ์•Œ์•„๋‚ด๊ณ  block.title ๋กœ ์ ‘๊ทผํ•ด ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

      1for post in posts:3    text = """---8""" % (post.title, post.author, post.created_time.strftime("%Y-%m-%d"), post.excerpt)9    if not post.hero:10        text = text + '---\n\n'11    elif 'png' in post.hero[0]:12        text = text + 'hero: ' + './images/hero.png\n---\n\n'13        image_format = 'png'14    elif 'jpg' in post.hero[0]:15        text = text + 'hero: ' + './images/hero.jpg\n---\n\n'16        image_format = 'jpg'19    text = text + '\n\n' + '# ' + post.title + '\n\n'20    for block in post.children:21        print(block.type)22        print(block)

      ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ block ์˜ ํƒ€์ž… ์ด๋ฆ„๊ณผ title ์— ์–ด๋–ค ๋‚ด์šฉ์ด ๋‹ด๊ธฐ๋Š”์ง€ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

      block ํƒ€์ž… ๋ณ„๋กœ ์–ด๋–ค ์ถ”๊ฐ€์ ์ธ ์†์„ฑ์„ ๊ฐ€์ง€๋Š”์ง€๋Š” ์œ„์˜ ์†Œ์Šค๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋‹ค.

      ์˜ˆ๋ฅผ ๋“ค์–ด callout ๋ธ”๋Ÿญ์€ icon ์†์„ฑ์„ ๊ฐ–๊ณ , code ๋ธ”๋Ÿญ์€ language ์†์„ฑ์„ ๊ฐ–๊ณ , bookmark ๋ธ”๋Ÿญ์€ link , bookmark_cover ๋“ฑ์˜ ์†์„ฑ์„ ๊ฐ–๋Š”๋‹ค.

      ์ด๋Ÿฐ ์†์„ฑ๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ๋…ธ์…˜ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

      ์‹ฌ์ง€์–ด ๋…ธ์…˜ ์ž์ฒด ๋งˆํฌ๋‹ค์šด export ๊ธฐ๋Šฅ์€ ์–ธ์–ด ์ง€์ •์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋ฐ language ์†์„ฑ์œผ๋กœ ์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
      1for post in posts:3    text = """---8""" % (post.title, post.author, post.created_time.strftime("%Y-%m-%d"), post.excerpt)9    if not post.hero:10        text = text + '---\n\n'11    elif 'png' in post.hero[0]:12        text = text + 'hero: ' + './images/hero.png\n---\n\n'13        image_format = 'png'14    elif 'jpg' in post.hero[0]:15        text = text + 'hero: ' + './images/hero.jpg\n---\n\n'16        image_format = 'jpg'19    text = text + '# ' + post.title + '\n\n'20    for block in post.children:22        if (block.type == 'header'):23            text = text + '# ' + block.title + '\n\n'25        if (block.type == 'sub_header'):26            text = text + '## ' + block.title + '\n\n'28        if (block.type == 'sub_sub_header'):29            text = text + '### ' + block.title + '\n\n'31        if (block.type == 'code'):32            text = text + '```' + block.language.lower() + '\n' + block.title + '\n```\n\n'34        if (block.type == 'callout'):35            text = text + '> ' + block.icon + ' ' + block.title + '\n\n'37        if (block.type == 'quote'):38            text = text + '> ' + block.title + '\n\n'40        if (block.type == "bookmark"):41            text = text + "๐Ÿ”— [" + block.title + "](" + block.link + ")\n\n"43        if (block.type == 'image'):44            text = text + '![' + block.id + '](' + block.source + ')\n\n'46        if (block.type == 'bulleted_list'):47            text = text + '* ' + block.title + '\n\n'49        if (block.type == 'divider'):50            text = text + '---' + '\n\n'52        if (block.type == 'text'):53            text = text + block.title + '\n\n'

      callout, code, bookmark, quote ์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋” ๋งŽ์€ ์ฝ”๋“œ๋Š” ๊ณ„์† ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ด๋‹ค.

      ์ปค๋ฒ„ ์ด๋ฏธ์ง€ ๋‹ค์šด๋ฐ›๊ธฐ

      ์•ž์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ hero ์†์„ฑ์— ์ถ”๊ฐ€ํ•œ cover ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋ฐ›์ž.

      2import urllib.request6for post in posts:11    title = post.title.replace(' ', '-')12    title = title.replace(',', '')13    title = title.replace(':', '')14    title = title.replace(';', '')15    title = title.lower()18        os.mkdir('../content/blog/' + title)23    if post.hero:25            os.mkdir('../content/blog/' + title + '/images')28        if 'png' in post.hero[0]:29            urllib.request.urlretrieve(post.hero[0], "../content/blog/" + title + "/images/hero.png")30        elif 'jpg' in post.hero[0]:31            urllib.request.urlretrieve(post.hero[0], "../content/blog/" + title + "/images/hero.jpg")33    file = open('../content/blog/' + title + '/index.md', 'w')35    print(text)36    file.write(text)

      ํฌ์ŠคํŠธ ์ œ๋ชฉ์— ๋”ฐ๋ผ ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ , ํฌ์ŠคํŠธ ํด๋” ์•ˆ์— images ํด๋”์— hero ์ด๋ฏธ์ง€๋ฅผ urllib.request ๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์šด๋ฐ›์•˜๋‹ค. ์ž์„ธํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

      ์›ํ•˜๋Š” ์œ„์น˜์— ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ–ˆ๋‹ค. ๋จธ๋ฆฌ๋ง์— hero ์˜ ๊ฒฝ๋กœ๊ฐ€ ์ง€์ •๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ Gatsby ๋ธ”๋กœ๊ทธ์—์„œ๋Š” ์ด ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์™€ ์ปค๋ฒ„๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

      ๋งˆ์น˜๋ฉฐ

      ์ด๋ ‡๊ฒŒ notion-py๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์žˆ๋Š” ๋งˆํฌ๋‹ค์šด ๋ฌธ์„œ๋“ค์„ ๋กœ์ปฌ๋กœ ๊ฐ€์ ธ์™”๋‹ค.

      ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋‹ค์–‘ํ•˜๊ฒŒ ๋ทฐ๋ฅผ ๋ฐ”๊ฟ”๊ฐ€๋ฉฐ ์ฝ˜ํ…์ธ ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , sort, filter ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ์†์„ฑ์„ ํ™œ์šฉํ•˜์—ฌ ๊ธ€์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋“ฑ CMS๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋ฌผ๋ก  ์œ„์˜ ์ฝ”๋“œ๋Š” ๋งˆํฌ๋‹ค์šด ๋ฌธ์„œ๋กœ ์ถ”์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋งˆํฌ๋‹ค์šด์„ ์ง€์›ํ•˜๋Š” ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ์ด ์•„๋‹ˆ๋ผ๋ฉด ์ˆ˜์ •์ด ํ•„์š”ํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ๋งŒ ์ˆ˜์ •ํ•ด๋„ ์–ด๋–ค ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

      ๋‹ค์Œ ๊ธ€์—์„œ๋Š”

      • os์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜์— token_v2 ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•
      • config.py ์— collection_id ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•
      • ๋…ธ์…˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ status ์— ๋”ฐ๋ผ ์ฝ˜ํ…์ธ ๋ฅผ ํ•„ํ„ฐ๋งํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•
      • ๋…ธ์…˜์—์„œ ๊ธ€ ์ž‘์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ Github์— ์ž๋™์œผ๋กœ ์—…๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•

      ๋“ค์„ ๋‹ค๋ค„๋ณผ ์˜ˆ์ •์ด๋‹ค.

      notion-py๋Š” ๋น„๊ณต์‹ ๋…ธ์…˜ API์ด๋‹ค

      ๋ฌผ๋ก  notion-py๊ฐ€ โ€œ๋น„๊ณต์‹โ€ ๋…ธ์…˜ API์ด๊ณ , ๋…ธ์…˜์—์„œ๋Š” ๊ณง ๊ณต์‹ API๋ฅผ ์ถœ์‹œํ•  ์˜ˆ์ •์ด๋ผ๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด API๋ฅผ ์–ธ์  ๊ฐ€ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

      ํ•˜์ง€๋งŒ notion-py๋ฅผ ๋‹ค๋ฃจ๋ฉฐ ๋…ธ์…˜ ๊ณต์‹ API๊ฐ€ ๊ฐ€์ ธ์˜ฌ ์–ด๋งˆ์–ด๋งˆํ•œ ๊ฐ€๋Šฅ์„ฑ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•ด ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋…ธ์…˜์ด ์ •๋ง โ€œAll-In-One Workspaceโ€ ๊ฐ€ ๋  ์ˆ˜ ์žˆ์„๊นŒ?

      ๋น ๋ฅธ ์‹œ์ผ ๋‚ด ๊ณต์‹ API๊ฐ€ ์ถœ์‹œ๋˜๊ณ  ๋งŽ์€ ์œ ์ €๋“ค์ด ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ˆ˜๋งŽ์€ ํ™œ์šฉ ์˜ˆ์ œ๋“ค์ด ํƒ„์ƒํ•˜๊ธธ ๊ธฐ๋Œ€ํ•œ๋‹ค.

  • ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณผ์ •
    1. main.py ์ฝ”๋“œ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜์—ฌ workspace ์— ๊ฐ€์ ธ๋‹ค์ค๋‹ˆ๋‹ค. ํ˜น์€ ์œ„์˜ github ์„ ๋ชจ๋‘ ๋‹ค์šด๋ฐ›์•„ ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.
      1. ํด๋” ๊ตฌ์กฐ
        • workspace
          • main.py
          • upload (folder)
            • ์—…๋กœ๋“œ ํ•  ํŒŒ์ผ๋“ค
    1. ๋…ธ์…˜ ๊ฒŒ์‹œ๋ฌผ ์ค€๋น„ํ•˜๊ธฐ (๋…ธ์…˜ ์ œ๋ชฉ์— ํ•œ๊ธ€์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ์ž˜ ์•ˆ๋ฉ๋‹ˆ๋‹ค.)
      1. ๊ธ€์— ์˜ฌ๋ฐ”๋ฅธ ์†์„ฑ ๋ถ€์—ฌํ•˜๊ธฐ
        • ํ•„์ˆ˜์ ์ธ ์†์„ฑ
          • Category : ํ‹ฐ์Šคํ† ๋ฆฌ์— ์กด์žฌํ•˜๋Š” ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งž๊ฒŒ ์ž‘์„ฑ
        • ๋น„ ํ•„์ˆ˜์ ์ธ ์†์„ฑ
          • Tag : ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธ€์— ์ถ”๊ฐ€ํ•  ํƒœ๊ทธ
          • Tags : ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธ€์— ์ถ”๊ฐ€ํ•  ํƒœ๊ทธ๋“ค
            • ๋‘ ๊ฐœ๋ฅผ ๋‚˜๋ˆˆ ์ด์œ ๋Š” ํ•„์ž๊ฐ€, ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฌธ์ œ ํ’€์ด ํ•œ ๊ฒƒ๋“ค์„ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ถ„๋ฅ˜ํ•  ๋•Œ, ์‚ฌ์ดํŠธ/์ค‘์‹ฌ tag ๋กœ ๋‚˜๋ˆ„์–ด ์ผ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‘ ๊ฐœ ๋‹ค ์—†์–ด๋„ ์ž‘๋™์€ ํ•ฉ๋‹ˆ๋‹ค.
    1. ๋…ธ์…˜ ๊ฒŒ์‹œ๋ฌผ ๋‹ค์šด๋กœ๋“œํ•˜๊ธฐ
      1. ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์— ๋“ค์–ด๊ฐ€์„œ, ์šฐ์ธก ์ƒ๋‹จ์˜ ์„ค์ •์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
      1. HTML ํ˜•์‹์œผ๋กœ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค.

      c. ์•„๊นŒ ๋งŒ๋“ค์–ด๋‘” upload ํด๋”์— ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ํด๋”๋ฅผ ๋ถ™์—ฌ๋„ฃ์Šต๋‹ˆ๋‹ค.

      ๐Ÿ’ก
      ์—ฌ๋Ÿฌ ๊ฐœ ๋™์‹œ์— ๋„ฃ์–ด๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
      ๐Ÿ’ก
      ๋…ธ์…˜์—์„œ html ์„ export ํ•  ๋•Œ mac ์—์„œ, ์–ด๋Š ๊ฒฝ์šฐ์—๋Š” zip ์œผ๋กœ, ์–ด๋Š ๊ฒฝ์šฐ์—๋Š” ํด๋”๋กœ ๋ฐ”๋กœ ๋‹ค์šด๋ฐ›์•„์กŒ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ํ†ต์ผํ•˜๊ธฐ ์œ„ํ•ด์„œ, zip ํŒŒ์ผ ์ž์ฒด๋ฅผ ๋„ฃ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ zip ์„ ์••์ถ• ํ’€์–ด ์ค€ ๋’ค์—, ํด๋” ํ˜•์‹์œผ๋กœ upload ํด๋”์— ๋„ฃ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

    1. main.py ์˜ config ์„ค์ •ํ•˜๊ธฐ
      • 5๊ฐœ์˜ ๋ณ€์ˆ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
        config = {
            "ACCESS_TOKEN" :"์ž์‹ ์˜ tistory access token",
            "BLOG_NAME" : "์ž์‹ ์˜ tistory ๋ธ”๋กœ๊ทธ ์ด๋ฆ„",
            "REMOVE_AFTER_UPLOAD" : ์—…๋กœ๋“œ ํ•œ ๋’ค์— ํŒŒ์ผ์„ ์‚ญ์ œํ•˜๋ ค๋ฉด True, ์•„๋‹ˆ๋ฉด False
            "upload_path" : "์ž์‹ ์˜ `upload` ํด๋”์˜ ๊ฒฝ๋กœ (์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ํ•˜๋Š” ๊ฒŒ ๋งˆ์Œ ํŽธํ•จ)",
            
            "CODE_AS_PYTHON" : ์ฝ”๋“œ ํ•˜์ด๋ผ์ดํŠธ๋ฅผ python ์œผ๋กœ ํ†ต์ผํ•˜๋ ค๋ฉด True,
        }
    1. ์ฝ”๋“œ ์‹คํ–‰ํ•˜๊ธฐ

      ์œ„์™€ ๊ฐ™์ด ๋œจ๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค.

  • ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ณผ์ •
    • ์ด๋ฏธ์ง€ ๊นจ์ง ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ
      ๐Ÿ’ก
      ๋กœ์ปฌ ํŒŒ์ผ์—์„œ ๋…ธ์…˜์— ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์˜ฌ๋ฆฌ๊ณ  ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ tistory ์— ์˜ฌ๋ ค๋ณด๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์—†์–ด์ง„๋‹ค. ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊นŒ?

      ์™œ ๊ทธ๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๊ฐ€

      • ๋…ธ์…˜์—์„œ HTML ๋กœ ๋‚ด๋ณด๋‚ด๋ฉด ํ•ด๋‹น ํŽ˜์ด์ง€์˜ img ๊ฐ€ ์•Œ์•„์„œ ๊ฐ™์ด ๋‹ค์šด๋กœ๋“œ ๋œ๋‹ค.
      • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์—, HTML ์ƒ์—์„œ์˜ img ์˜ src tag ์†์„ฑ์€ ๋กœ์ปฌ ํŒŒ์ผ ๊ฒฝ๋กœ๊ฐ€ ๋œ๋‹ค.
      • ์ด๊ฒƒ์„ ๋ฐ”๋กœ Tistory API ๋กœ ๋ณด๋‚ด๋ฒ„๋ฆฌ๋ฉด ๋‹น์—ฐํžˆ, ๋กœ์ปฌ ํŒŒ์ผ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์•ˆ๋œ๋‹ค.

      ํ•ด๊ฒฐ์ฑ…

      • ๋…ธ์…˜์—์„œ HTML ๊ณผ ๊ฐ™์ด ๋‹ค์šด๋ฐ›์€ img ํŒŒ์ผ์„ base64 ํ˜•ํƒœ๋กœ ๋ฐ”๊พธ์–ด ๊ทธ๋Œ€๋กœ img ์˜ src์— ๋„ฃ๋Š”๋‹ค.
      # img base64๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์—…๋กœ๋“œ
          all_img_columns = article.find_all('img')
          img_columns = []
          for i, img_column in enumerate(all_img_columns):
              if not ('http' in img_column['src'] or 'ico' in img_column['src']):
                  img_columns.append(img_column)
      
          if img_columns != 0:
              for img_column in img_columns:
                  modified_img_path = img_column['src'].replace("%5B", '[').replace("%5D", ']').replace("%20", ' ')
                  im_b64 = base64.b64encode(
                      open(f"{path_list[html_idx]}/{modified_img_path}","rb").read()
                  ).decode('utf-8')
                  img_column['src'] = f"data:image/png;base64,{im_b64}"

    • ์—…๋กœ๋“œํ•œ ํŒŒ์ผ ์‚ญ์ œํ•˜๊ธฐ
      • os.rmdir ์€ ๋น„์–ด์žˆ๋Š” ํด๋”๋งŒ ์‚ญ์ œํ•ด์ค˜์„œ, shutil.rmtree ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.
    • notion ์—์„œ export ํ•  ์‹œ์—, []|() ๊ฐ™์€ ํŠน์ˆ˜๋ฌธ์ž๋“ค์ด %20 %30 ๊ฐ™์€ ๊ฒƒ๋“ค๋กœ ๋ณ€ํ™˜๋˜์–ด์„œ, ๊ฒฝ๋กœ ์ฐพ๊ธฐ๊ฐ€ ํž˜๋“ค์—ˆ๋˜ ๋ฌธ์ œ
      • ์ •๋ง.. ํ•˜๋“œ์ฝ”๋”ฉ ๊ทธ์ž์ฒด ํ•ด๋ฒ„๋ ธ์Šต๋‹ˆ๋‹ค.. ํ•˜๋‚˜ํ•˜๋‚˜ ํ”„๋ฆฐํŠธ ํ•ด๊ฐ€๋ฉด์„œ ์–ด๋–ค ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™˜๋˜๋Š”์ง€ ์ฐพ์€ ๋’ค์—, ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ณ€ํ™˜ ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‚ด๊ฐ€ ๋ณ€ํ™˜ํ•ด๋‘์ง€ ์•Š์€ ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ์ œ๋ชฉ์— ์‚ฌ์šฉํ–ˆ๋˜์ง€ ํ•œ ๊ฒƒ์ผ ๊ฒ๋‹ˆ๋‹ค.
      modified_img_path = img_column['src'].replace("%5B", '[').replace("%5D", ']').replace("%20", ' ')
    • Mac ์˜ .DS_Store ํŒŒ์ผ ์ œ๊ฑฐํ•˜๊ธฐ
      • Mac ์˜ ํด๋”์—๋Š” DS_Store ๋ผ๋Š” ํŒŒ์ผ์ด ์•Œ๊ฒŒ๋ชจ๋ฅด๊ฒŒ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฑธ ์ œ๊ฑฐ ์•ˆํ•˜๊ณ  ํ•˜๋‹ค๊ฐ€ ์˜ค๋ฅ˜๊ฐ€ ๋‚ฌ์—ˆ์Šต๋‹ˆ๋‹ค.
    • details ํƒœ๊ทธ์˜ open attribute ์ œ๊ฑฐํ•˜๊ธฐ
      • details ํƒœ๊ทธ ๋“ค์ด ์˜ฎ๊ฒจ์ง„ ๋’ค์—, ๋‹ค ์—ด๋ ค์žˆ์–ด์„œ ๋ณด๊ธฐ ์‹ซ์—ˆ๋‹ค.
      • bs4 ๋กœ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ–ˆ๋‹ค.
    • โญ๏ธโญ๏ธโญ๏ธ๋…ธ์…˜์—์„œ ์ œ๋ชฉ ํ† ๊ธ€ 1,2,3 ๊ฐ€ ์ถ”๊ฐ€๋จ์— ๋”ฐ๋ผ, ๊ธฐ๋Šฅ ์ถ”๊ฐ€โญ๏ธโญ๏ธโญ๏ธ
      ๐Ÿ’ก
      ๋…ธ์…˜ ์ƒ์—์„œ๋Š” ๋‹น์—ฐํžˆ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ์œผ๋‚˜, export ํ•˜์—ฌ html ์ƒ์—์„œ ๋ณด์•˜์„ ๋•Œ๋Š” notion ์ž์ฒด์ ์ธ css ๋„ ์‚ฌ๋ผ์ง€๊ณ  ํ•ด์„œ, toggle ์˜ ๊ธฐ๋Šฅ๋„ ํ•˜์ง€ ๋ชปํ–ˆ์„ ๋ฟ๋”๋Ÿฌ, toggle ์˜ ์œ„์น˜๋„ ๋งž์ง€ ์•Š์•˜๋‹ค.
      • ์–ด๋–ป๊ฒŒ html ์ด ๊นจ์ ธ์„œ, toggle ์ด ์ž‘๋™์ด ์•ˆ๋˜๋Š”์ง€ ํŒŒ์•…ํ–ˆ๋‹ค.
        • ์ œ๋ชฉ h1
          • details
            • summary
        • div class=โ€indentedโ€ ์—ฌ๊ธฐ์— toggle ์•ˆ์— ์“ด ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐ
      • ๋ผ๊ณ  ๊นจ์กŒ๋‹ค. ์›๋ž˜๋Š”
        • ์ œ๋ชฉ h1
          • details
            • summary
            • div class=โ€indentedโ€
      • ์ด ๋˜์–ด์„œ, h1 ์•„๋ž˜์— ๋‚ด์šฉ์ด ์ž˜ ์ถœ๋ ฅ๋˜์–ด์•ผ ํ–ˆ๋‹ค.
      • ๋˜ ํ•˜๋‚˜์˜ ๋ฌธ์ œ๋Š”, ๋ชจ๋‘๊ฐ€ h1 ์•„๋ž˜์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋‘ ํฐ ๊ธ€์”จ๋กœ ์ถœ๋ ฅ๋œ๋‹ค๋Š” ์ ์ด๋‹ค. ๊ทธ๊ฒƒ์„ ์›ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.
      • ๊ทธ๋ž˜์„œ ๋‚˜๋Š” ์ด๋Ÿฐ ๊ตฌ์กฐ๋ฅผ ์ƒ๊ฐํ–ˆ๋‹ค.
        • details
          • summary
            • h1
              • ์ œ๋ชฉ
          • div class=โ€indentedโ€
      • ํ•ด๊ฒฐ๋ฒ•
        1. indented ๋ผ๋Š” class ๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  div ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
        1. h1 ์ด๋ฉด์„œ, ์ž์‹์ด summary ์ธ ๋ชจ๋“  h1 ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
          1. ์œ„์˜ h1 ๋“ค์„ ๋ชจ๋‘ ์ˆœํšŒํ•˜๋ฉด์„œ, ์ž์‹  ๋ฐ”๋กœ ๋‹ค์Œ์˜ ํ˜•์ œ๊ฐ€ <div class=โ€œindentedโ€ ๋ผ๋ฉด
            1. ๋ฐ”๋กœ ์•„๋ž˜์˜ details ํƒœ๊ทธ๋ฅผ ์ฐพ์•„๋‚ธ๋‹ค.
            1. ํ•ด๋‹น details ํƒœ๊ทธ์— ์•„๊นŒ ์ฐพ์•„๋‚ธ indented div ๋ฅผ ์ˆœ์„œ์— ๋งž๊ฒŒ ์ถ”๊ฐ€ํ•œ๋‹ค.
            1. details ์•ˆ์˜ summary ๋ฅผ h1 ์œผ๋กœ ๋ฌถ์–ด์ค€๋‹ค.
              1. ๋ฌถ์–ด์ฃผ๋ฉด์„œ, Notion_summary_h1 ์ด๋ผ๋Š” class ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ
              1. tistory ๋‚ด์—์„œ css ์กฐ์ž‘์ด ํŽธํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
        1. h1 h2 h3 ์— ๋Œ€ํ•ด์„œ ์‹คํ–‰ํ•ด์ค€๋‹ค.
        # indented block ๊ฐ€์ ธ์˜ค๊ธฐ
            indented_divs = article.find_all('div', 'indented')
            # h1 ์ด๋ฉด์„œ ์ž์‹์ด summary ๊ฐ€์ ธ์˜ค๊ธฐ
            all_h1 = article.find_all('h1')
            all_indent_h1 = []
            for idx, h1 in enumerate(all_h1):
                if '<details>' in str(all_h1[idx]):
                    all_indent_h1.append(all_h1[idx])
            for h1_idx, indent_h1 in enumerate(all_indent_h1):    
                for idx, sibling in enumerate(all_indent_h1[h1_idx].next_siblings):
                    # ๋ฐ”๋กœ ๋‹ค์Œ์˜ ํ˜•์ œ๊ฐ€ indented div ๋ผ๋ฉด
                    if idx == 0 and '<div class="indented"' in str(sibling):
                        details = all_indent_h1[h1_idx].find("details")
                        details.append(sibling)
                        summary = details.find("summary")
                        summary.name = "h1"
                        summary.wrap(soup.new_tag('summary', **{'class':'Notion_summary_h1'}))
                        all_indent_h1[h1_idx].unwrap()
            # h2 ์ด๋ฉด์„œ ์ž์‹์ด summary ๊ฐ€์ ธ์˜ค๊ธฐ
            all_h2 = article.find_all('h2')
            all_indent_h2 = []
            for idx, h2 in enumerate(all_h2):
                if '<details>' in str(all_h2[idx]):
                    all_indent_h2.append(all_h2[idx])
            for h2_idx, indent_h2 in enumerate(all_indent_h2):    
                for idx, sibling in enumerate(all_indent_h2[h2_idx].next_siblings):
                    # ๋ฐ”๋กœ ๋‹ค์Œ์˜ ํ˜•์ œ๊ฐ€ indented div ๋ผ๋ฉด
                    if idx == 0 and '<div class="indented"' in str(sibling):
                        details = all_indent_h2[h2_idx].find("details")
                        details.append(sibling)
                        summary = details.find("summary")
                        summary.name = "h2"
                        summary.wrap(soup.new_tag('summary', **{'class':'Notion_summary_h2'}))
                        all_indent_h2[h2_idx].unwrap()
            # h3 ์ด๋ฉด์„œ ์ž์‹์ด summary ๊ฐ€์ ธ์˜ค๊ธฐ
            all_h3 = article.find_all('h3')
            all_indent_h3 = []
            for idx, h3 in enumerate(all_h3):
                if '<details>' in str(all_h3[idx]):
                    all_indent_h3.append(all_h3[idx])
            for h3_idx, indent_h3 in enumerate(all_indent_h3):    
                for idx, sibling in enumerate(all_indent_h3[h3_idx].next_siblings):
                    # ๋ฐ”๋กœ ๋‹ค์Œ์˜ ํ˜•์ œ๊ฐ€ indented div ๋ผ๋ฉด
                    if idx == 0 and '<div class="indented"' in str(sibling):
                        details = all_indent_h3[h3_idx].find("details")
                        details.append(sibling)
                        summary = details.find("summary")
                        summary.name = "h3"
                        summary.wrap(soup.new_tag('summary', **{'class':'Notion_summary_h3'}))
                        all_indent_h3[h3_idx].unwrap()
  • ํ•œ๊ณ„์  ๋ฐ ๋ฐœ์ „ ์‚ฌํ•ญ
    • ์—ฌ์ „ํžˆ ๋งž์ง€ ์•Š๋Š” css ๋“ค
      • details ํƒœ๊ทธ ๋ผ๋“ ์ง€, ์ž์ž˜ํ•œ margin ๊ณผ padding ๋“ค ๊ผด๋ณด๊ธฐ ์‹ซ๋‹ค.
        • details ํƒœ๊ทธ๋Š” ์‚ฌํŒŒ๋ฆฌ์—์„œ๋Š” ์ •์ƒ ์ž‘๋™ํ•˜๋Š”๋ฐ, ํฌ๋กฌ์—์„œ๋Š” ํ™”์‚ดํ‘œ๊ฐ€ ์•ˆ๋ณด์ž„
    • zip ํŒŒ์ผ๊ณผ ํด๋”๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์—…๋กœ๋“œ ์‹œ์ผœ๋ณด๊ธฐ
    • ์ผ์ผ์ด ์†์œผ๋กœ export ํ•ด์•ผํ•˜๋Š” ๋ถˆํŽธํ•จ
      • selenium ์„ ํ™œ์šฉํ•ด ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
        • ํ•˜์ง€๋งŒ, notion-py ๊ฐ€ ๋ง์ฝ์ด์–ด์„œ ์‹œ๋„ํ•˜์ง€ ์•Š์Œ
    • Watchdog ์„ ์‚ฌ์šฉํ•ด์„œ ํด๋” ๊ฐ์‹œํ•˜์—ฌ ์ž๋™ ์—…๋กœ๋“œ ํ•ด๋ณด๊ธฐ
    • ์ˆ˜์ •ํ•˜๋ ค๋ฉด, ๋…ธ์…˜์—์„œ ์ˆ˜์ •ํ•ด์„œ ๋‹ค์‹œ ๋‚ด๋ณด๋‚ด๊ธฐ ํ•ด์„œ ๋‹ค์‹œ ์ฝ”๋“œ ๋™์ž‘์‹œ์ผœ์•ผํ•จ.
    • ์ œ๋ชฉ์— ํ•œ๊ธ€ ํฌํ•จ๋˜์–ด์žˆ์œผ๋ฉด ์ž˜ ์•ˆ๋จ.

๋ฐ˜์‘ํ˜•

'Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

React TS Masterclass  (0) 2022.05.10