Jupyter로 Jekyll 포스팅하기
이번 포스트는 jupyter notebook으로 jekyll 포스트를 작성하는 방법에 대해 다룹니다. 이 포스트는 주피터 노트북으로 작성되었습니다.
지킬의 장점은 마크다운 파일에 YAML Front Matter만 추가하면 사전 설정된 테마에 따라 멋있는 블로그 포스트를 만들어주기 때문에 별도의 프론트엔드 지식 없이도 손쉽게 블로그 포스트를 만들 수 있다는 것입니다. 하지만, ipynb 파일을 별도의 설정 없이 마크다운으로 변환해 포스트 폴더에 넣는 경우 대참사가 일어나는 것을 볼 수 있는데 이는 기본 변환기가 input과 output을 구분하는 class를 포함하고 있지 않아 전부 코드 블럭으로 표현되고, output의 이미지 경로가 쉽게 꼬인다는 문제입니다. (지킬은 현재 _posts
폴더에 png 파일이 포함되는 경우 정상적으로 이미지를 참조하지 못하는 에러가 있습니다. 관련 이슈)
ipynb 파일을 markdown이나 html로 변환할 때 주피터는 nbconvert를 사용합니다. nbconvert는 내부적으로 파이썬의 템플릿 엔진인 jinja 템플릿을 사용하는데, nbconvert 사용시에 기본으로 적용되는 템플릿을 수정하고 반영하여 ipynb 파일이 지금 이 글과 같이 블로그 스타일로 변환될 수 있도록 하려고 합니다. 목적에 따라 마크다운 파일이 아닌 html 파일로 변환을 할 수도 있으나, 주로 참조한 Chris Holdgraf의 포스트가 마크다운으로 변환하는 방법을 알려주어 이 포스트도 마크다운으로 변환하는 방식을 포스팅했습니다. 목적에 따라 html로 변환해서 포스팅을 하는 것도 물론 가능합니다.
nbconvert 설치
nbconvert는 pip install nbconvert
, 콘다 환경인 경우 conda install nbconvert
를 통해 간단하게 설치할 수 있습니다. 만약 주피터 파일을 html이나 md가 아닌 pdf, tex 등으로 변환하는 등 nbconvert의 모든 기능을 이용하기 위해서는 pandoc 과 XeLaTeX를 추가로 설치해야 합니다. 해당 내용은 nbconvert 공식문서에서 확인하실 수 있습니다.
nbconvert의 markdown 템플릿 변환하기
nbconvert가 노트북 파일을 마크다운으로 변환할 때 사용하는 템플릿을 커스터마이징하고 싶은 경우 Jinja 템플릿에 대한 이해가 조금은 필요합니다. Jinja 공식문서를 읽어볼 수 있으면 좋겠지만, 목적에 비해 그 내용이 방대하므로 다음의 글을 참조하시면 간략하게 작동원리를 이해하실 수 있습니다.
nbconvert는 html로 변환하는 경우에는 basic.tpl
, 마크다운으로 변환하는 경우에는 markdown.tpl
을 기본값으로 사용하여 렌더링을 합니다. 마크다운으로 변환해 포스팅을 하는 것이 목적이므로, nbconvert 패키지 내에 markdown.tpl
을 상속받는 jekyll.tpl
을 다음과 같이 만들었습니다.
위의 템플릿은 다음의 글 의 블로그 글을 참고하여 작성하였으며, 반영한 수정사항은 다음과 같습니다.
- YAML Front Matter 양식 헤더에 추가
- input 블록은 기존 템플릿의 변환 방식을 그대로 사용
- output의 텍스트는
inline code
양식 사용 - output에 table, image 등 html 코드가 포함된 경우 마크다운으로 렌더링하지 않고 그대로 사용
- input prompt(
In: [0]
)은 살리고, output prompt는 숨기기
그런데 위 템플릿을 이용해 막상 변환을 진행하니 블로그의 테이블 양식이 jupyter의 pandas dataframe을 표현하기에는 조금 부적합하다고 느껴져서 별도의 stylesheet를 작성해주었고, 이를 layout이 jupyter인 경우에만 적용되도록 수정하였습니다.
<style type="text/css">
.input_area div.highlighter-rouge {
background-color: #263238 !important;
}
div.highlighter-rouge, figure.highlight {
font-size: 0.675em !important;
}
.output_stream, .output_data_text, .output_traceback_line {
margin-left: 3% !important;
border: none !important;
border-radius: 4px !important;
background-color: #fafafa !important;
box-shadow: none !important;
color: #000000 !important;
font-size: 0.6em !important;
}
.output_stream:before, .output_data_text:before, .output_traceback_line:before{
content: none !important;
}
table.dataframe {
background-color: #fafafa;
width: 100%;
max-height: 240px;
display: block;
overflow: auto;
font-family: Arial, sans-serif;
font-size: 13px;
line-height: 20px;
text-align: center;
}
table.dataframe th {
font-weight: bold;
padding: 4px;
}
table.dataframe td {
padding: 4px;
}
table.dataframe tr:hover {
background: #b8d1f3;
}
</style>
쉘 함수로 경로 설정 자동화하기
위에서 언급한 _post
폴더에 png파일이 포함되는 경우 에러가 발생하는 이슈 때문에, 파일에 이미지가 포함되는 경우 그 경로를 모두 _post
폴더 바깥으로 바꾼 뒤, 이미지를 모두 옮긴 후, 위에서 작성한 템플릿을 적용해 nbconvert를 실행해야 하는데 그 번거로움이 이만저만이 아닙니다. 그래서, images
폴더를 별도로 생성해준 다음에, 이를 자동으로 수행해주는 쉘 함수를 다음의 글 을 참조하여 만들어 .zshrc
에 추가해 주었습니다. bash를 사용하시는 분들은 .bash_profile
에 추가해주시면 됩니다.
function new_post {
#change these 2 lines to match your specific setup
POST_PATH="/Users/jungsooyun/Desktop/dev/frontend/jungsooyun.github.io/_posts"
IMG_PATH="/Users/jungsooyun/Desktop/dev/frontend/jungsooyun.github.io/images"
FILE_NAME="$1"
CURR_DIR=`pwd`
FILE_BASE=`basename $FILE_NAME .ipynb`
POST_NAME="${FILE_BASE}.md"
IMG_NAME="${FILE_BASE}_files"
POST_DATE_NAME=`date "+%Y-%m-%d-"`${POST_NAME}
# convert the notebook
jupyter nbconvert --to markdown --template jekyll.tpl $FILE_NAME
# change image paths
sed -i .bak "s:\[png\](:[png](/images//images/:" $POST_NAME
# move everything to blog area
mv $POST_NAME "${POST_PATH}/${POST_DATE_NAME}"
mv $IMG_NAME "${IMG_PATH}/"
}
이제 다음의 코드를 실행하면 자동으로 _posts
폴더에 주피터 파일이 변환됩니다!
new_post post-with-jupyter.ipynb
➜ _ipynb git:(master) ✗ new_post post-with-jupyter.ipynb
[NbConvertApp] Converting notebook post-with-jupyter.ipynb to markdown
[NbConvertApp] Support files will be in post-with-jupyter_files/
[NbConvertApp] Making directory post-with-jupyter_files
[NbConvertApp] Writing 7704 bytes to post-with-jupyter.md
YAML Front Matter는 손으로 한땀한땀 수정해주어야 하지만, 블로그 글 완성에는 시간이 오래걸리니까 그 정도는 수동으로 하기로 했습니다.
다음은 주피터 코드 실행 예시입니다. 결과물이 어떻게 나오는지 확인해보세요!
import seaborn as sns
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
%matplotlib inline
meta = load_iris()
print(meta.keys())
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
data = pd.DataFrame(meta['data'], columns=meta['feature_names'])
data.head(10)
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 |
1 | 4.9 | 3.0 | 1.4 | 0.2 |
2 | 4.7 | 3.2 | 1.3 | 0.2 |
3 | 4.6 | 3.1 | 1.5 | 0.2 |
4 | 5.0 | 3.6 | 1.4 | 0.2 |
5 | 5.4 | 3.9 | 1.7 | 0.4 |
6 | 4.6 | 3.4 | 1.4 | 0.3 |
7 | 5.0 | 3.4 | 1.5 | 0.2 |
8 | 4.4 | 2.9 | 1.4 | 0.2 |
9 | 4.9 | 3.1 | 1.5 | 0.1 |
sns.set_style('whitegrid')
sns.scatterplot(data['sepal length (cm)'], data['petal length (cm)'])
<matplotlib.axes._subplots.AxesSubplot at 0x1a23e05b38>
Comments