Pelican

Python製の成熟したSSG。12k超のGitHubスターを持ち、Pythonエコシステムでのブログ構築に最適。

言語:Python
フレームワーク:None
ビルド速度:Medium
GitHub Stars:12k
初回リリース:2010
人気ランキング:第13位

トレンド・動向

Pythonコミュニティでのブログ構築標準として安定した地位。データサイエンス系ブログで人気。

# SSG Pelican ## 概要 Pelicanは「Python製の成熟した静的サイトジェネレータ」として開発された、Pythonエコシステムで最も信頼性の高いSSGの一つです。MarkdownとreStructuredTextの両方をサポートし、Jinja2テンプレートエンジンによる柔軟なカスタマイズが可能。12k超のGitHubスターを誇り、Pythonコミュニティでのブログ構築標準として安定した地位を確立。豊富なプラグインエコシステムと成熟したドキュメント、長期サポートにより、データサイエンティストや研究者の技術ブログから企業のドキュメントサイトまで幅広く採用されています。 ## 詳細 Pelican 2025年版はPython静的サイト生成の決定版として10年以上の開発実績を誇ります。シンプルな設定ファイル(pelicanconf.py)によるPythonicな設定管理と、強力なJinja2テンプレートシステムによる高度なカスタマイズが特徴。MarkdownとreStructuredTextの両フォーマット対応により、技術文書作成で好まれる記法を選択可能。タグ・カテゴリによる分類機能、RSSフィード生成、サイトマップ自動生成、多言語サイト対応など、ブログ運営に必要な機能を標準搭載。プラグインシステムにより検索機能、コメントシステム、画像最適化など拡張機能も豊富です。 ### 主な特徴 - **Pythonベース**: Pythonの豊富なエコシステムと統合可能 - **柔軟なテンプレート**: Jinja2による高度なカスタマイズ - **マルチフォーマット対応**: Markdown、reStructuredText、HTML対応 - **豊富なメタデータ**: 記事の詳細な分類・管理機能 - **テーマシステム**: 豊富なテーマとカスタマイズ性 - **プラグイン拡張**: 活発なプラグインエコシステム ## メリット・デメリット ### メリット - Pythonエコシステムとの完全統合による高い拡張性 - 成熟したドキュメントと豊富なチュートリアル・学習リソース - reStructuredTextサポートによる高度な技術文書作成 - Jinja2テンプレートによる柔軟なサイト設計とカスタマイズ - 豊富なプラグインによるSEO最適化と機能拡張 - Python開発者にとって学習コストが低く直感的な操作 ### デメリット - Python環境の構築が必要で初心者には環境設定が複雑 - 大規模サイトでのビルド時間が他のSSGより長い傾向 - 他の言語エコシステムとの統合に制約がある - フロントエンドの最新トレンドへの対応が限定的 - モダンなJavaScriptフレームワークとの連携に制約 - Node.js系SSGと比較してビルド速度で劣る場合がある ## 参考ページ - [Pelican 公式サイト](https://getpelican.com/) - [Pelican ドキュメント](https://docs.getpelican.com/) - [Pelican GitHub リポジトリ](https://github.com/getpelican/pelican) ## 書き方の例 ### インストールと基本設定 ```bash # Pelicanのインストール pip install pelican[markdown] # Markdownサポート付きインストール(推奨) pip install pelican[markdown] typogrify # プロジェクトの初期化 mkdir my-blog cd my-blog pelican-quickstart # 基本的なディレクトリ構造の確認 ls -la # content/ # 記事ファイル # output/ # 生成されたサイト # pelicanconf.py # 設定ファイル # publishconf.py # 本番用設定 ``` ### プロジェクト構造とページ作成 ```bash # 記事ディレクトリの作成 mkdir content/articles mkdir content/pages mkdir content/images # 最初の記事作成 cat > content/articles/first-post.md << 'EOF' Title: 私の最初の記事 Date: 2025-01-01 10:00 Category: Tech Tags: pelican, python, blog Slug: first-post Author: Your Name Summary: Pelicanで作成する最初の記事です。 # はじめに Pelicanを使って静的サイトを作成しました。 ## 特徴 - Python製のSSG - Markdownサポート - 豊富なテーマ [続きを読む...](/articles/second-post.html) EOF # 固定ページの作成 cat > content/pages/about.md << 'EOF' Title: About Date: 2025-01-01 Slug: about このサイトについての説明ページです。 ## プロフィール 技術ブログを運営しています。 ## 連絡先 - Email: [email protected] - Twitter: @example EOF # サイトの生成とプレビュー pelican content pelican --listen # http://localhost:8000でプレビュー ``` ### 設定カスタマイズ(pelicanconf.py) ```python #!/usr/bin/env python # -*- coding: utf-8 -*- # AUTHOR = 'Your Name' SITENAME = 'My Tech Blog' SITEURL = '' PATH = 'content' TIMEZONE = 'Asia/Tokyo' DEFAULT_LANG = 'ja' # フィード設定 FEED_DOMAIN = SITEURL FEED_ALL_ATOM = 'feeds/all.atom.xml' CATEGORY_FEED_ATOM = 'feeds/category_%s.atom.xml' TRANSLATION_FEED_ATOM = None AUTHOR_FEED_ATOM = None AUTHOR_FEED_RSS = None # ページネーション設定 DEFAULT_PAGINATION = 10 PAGINATION_PATTERNS = ( (1, '{base_name}/', '{base_name}/index.html'), (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), ) # テーマとスタイル設定 THEME = 'themes/elegant' # カスタムテーマ THEME_STATIC_DIR = 'theme' CSS_FILE = 'main.css' # URLとパス設定 ARTICLE_URL = 'articles/{slug}.html' ARTICLE_SAVE_AS = 'articles/{slug}.html' PAGE_URL = '{slug}.html' PAGE_SAVE_AS = '{slug}.html' CATEGORY_URL = 'category/{slug}.html' CATEGORY_SAVE_AS = 'category/{slug}.html' TAG_URL = 'tag/{slug}.html' TAG_SAVE_AS = 'tag/{slug}.html' # 静的ファイル設定 STATIC_PATHS = ['images', 'extra/CNAME', 'extra/robots.txt'] EXTRA_PATH_METADATA = { 'extra/CNAME': {'path': 'CNAME'}, 'extra/robots.txt': {'path': 'robots.txt'}, } # ソーシャルリンク SOCIAL = (('GitHub', 'https://github.com/username'), ('Twitter', 'https://twitter.com/username'), ('LinkedIn', 'https://linkedin.com/in/username')) # メニュー設定 MENUITEMS = ( ('Archives', '/archives.html'), ('Categories', '/categories.html'), ('Tags', '/tags.html'), ) # SEO設定 SITEMAP = { 'format': 'xml', 'priorities': { 'articles': 0.5, 'indexes': 0.5, 'pages': 0.5 }, 'changefreqs': { 'articles': 'monthly', 'indexes': 'daily', 'pages': 'monthly' } } # 記事メタデータのデフォルト値 DEFAULT_METADATA = { 'status': 'draft', } # Markdown設定 MARKDOWN = { 'extension_configs': { 'markdown.extensions.codehilite': {'css_class': 'highlight'}, 'markdown.extensions.extra': {}, 'markdown.extensions.meta': {}, 'markdown.extensions.toc': {}, }, 'output_format': 'html5', } ``` ### プラグインシステムの活用 ```python # pelicanconf.py - プラグイン設定 PLUGIN_PATHS = ['plugins'] PLUGINS = [ 'sitemap', # サイトマップ生成 'tag_cloud', # タグクラウド 'related_posts', # 関連記事 'tipue_search', # 検索機能 'neighbors', # 前後記事ナビ 'representative_image', # 代表画像 'liquid_tags.img', # 画像タグ 'render_math', # 数式レンダリング ] # 検索機能の設定 DIRECT_TEMPLATES = ['index', 'tags', 'categories', 'archives', 'search'] # タグクラウド設定 TAG_CLOUD_STEPS = 4 TAG_CLOUD_MAX_ITEMS = 100 TAG_CLOUD_SORTING = 'random' # 関連記事設定 RELATED_POSTS_MAX = 5 RELATED_POSTS_SKIP_SAME_CATEGORY = True # 数式設定 MATH_JAX = { 'align': 'left', 'indent': '2em', 'show_menu': 'true', 'process_escapes': 'true', 'latex_preview': 'tex2jax_preview', 'color': 'black', 'linebreaks': 'false', } # 画像処理設定 IMAGE_PROCESS = { 'article-image': ["scale_in 300 300 True"], 'thumb': ["crop 0 0 50% 50%", "scale_out 150 150 True", "crop 0 0 150 150"], } # カスタムプラグイン関数 def my_custom_filter(text): """カスタムJinja2フィルター""" return text.replace('old', 'new') JINJA_FILTERS = { 'my_filter': my_custom_filter } ``` ### テーマとテンプレートカスタマイズ ```html <!-- themes/custom/templates/base.html --> <!DOCTYPE html> <html lang="{{ DEFAULT_LANG }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %}{{ SITENAME }}{% endblock title %}</title> <!-- SEOメタタグ --> <meta name="description" content="{% block description %}{{ SITEDESCRIPTION }}{% endblock %}"> <meta name="keywords" content="{% block keywords %}{{ SITEKEYWORDS }}{% endblock %}"> <meta name="author" content="{{ AUTHOR }}"> <!-- OGP設定 --> <meta property="og:title" content="{% block og_title %}{{ SITENAME }}{% endblock %}"> <meta property="og:description" content="{% block og_description %}{{ SITEDESCRIPTION }}{% endblock %}"> <meta property="og:type" content="{% block og_type %}website{% endblock %}"> <meta property="og:url" content="{{ SITEURL }}{% block og_url %}{% endblock %}"> <meta property="og:image" content="{% block og_image %}{{ SITEURL }}/images/default-og.png{% endblock %}"> <!-- スタイルシート --> <link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}"> <link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/pygments.css"> <!-- ファビコン --> <link rel="icon" type="image/x-icon" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/favicon.ico"> <!-- フィード --> {% if FEED_ALL_ATOM %} <link href="{{ SITEURL }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" /> {% endif %} </head> <body> <header class="site-header"> <div class="container"> <nav class="site-nav"> <a href="{{ SITEURL }}/" class="site-title">{{ SITENAME }}</a> <ul class="nav-menu"> {% for title, link in MENUITEMS %} <li><a href="{{ link }}">{{ title }}</a></li> {% endfor %} </ul> </nav> </div> </header> <main class="site-main"> <div class="container"> {% block content %}{% endblock %} </div> </main> <footer class="site-footer"> <div class="container"> <p>&copy; {{ date.strftime('%Y') }} {{ AUTHOR }}. All rights reserved.</p> {% if SOCIAL %} <div class="social-links"> {% for name, link in SOCIAL %} <a href="{{ link }}" target="_blank">{{ name }}</a> {% endfor %} </div> {% endif %} </div> </footer> <!-- JavaScript --> <script src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/js/main.js"></script> {% block scripts %}{% endblock %} </body> </html> <!-- themes/custom/templates/article.html --> {% extends "base.html" %} {% block title %}{{ article.title }} - {{ super() }}{% endblock %} {% block description %}{{ article.summary|striptags|truncate(160) }}{% endblock %} {% block og_title %}{{ article.title }}{% endblock %} {% block og_description %}{{ article.summary|striptags|truncate(160) }}{% endblock %} {% block og_type %}article{% endblock %} {% block og_url %}{{ SITEURL }}/{{ article.url }}{% endblock %} {% block content %} <article class="post"> <header class="post-header"> <h1 class="post-title">{{ article.title }}</h1> <div class="post-meta"> <time datetime="{{ article.date.isoformat() }}">{{ article.locale_date }}</time> {% if article.category %} <span class="post-category"> <a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a> </span> {% endif %} {% if article.tags %} <div class="post-tags"> {% for tag in article.tags %} <a href="{{ SITEURL }}/{{ tag.url }}" class="tag">{{ tag }}</a> {% endfor %} </div> {% endif %} </div> </header> <div class="post-content"> {{ article.content }} </div> <footer class="post-footer"> {% if article.modified %} <p class="post-modified"> Last updated: <time datetime="{{ article.modified.isoformat() }}">{{ article.modified.strftime('%Y-%m-%d') }}</time> </p> {% endif %} <!-- 関連記事 --> {% if article.related_posts %} <section class="related-posts"> <h3>関連記事</h3> <ul> {% for related in article.related_posts %} <li><a href="{{ SITEURL }}/{{ related.url }}">{{ related.title }}</a></li> {% endfor %} </ul> </section> {% endif %} </footer> </article> <!-- 前後記事ナビ --> {% if article.prev_article or article.next_article %} <nav class="post-nav"> {% if article.prev_article %} <a href="{{ SITEURL }}/{{ article.prev_article.url }}" class="post-nav-prev"> ← {{ article.prev_article.title }} </a> {% endif %} {% if article.next_article %} <a href="{{ SITEURL }}/{{ article.next_article.url }}" class="post-nav-next"> {{ article.next_article.title }} → </a> {% endif %} </nav> {% endif %} {% endblock %} ``` ### 本番環境でのデプロイ設定 ```python # publishconf.py - 本番用設定 import os import sys sys.path.append(os.curdir) from pelicanconf import * # 本番用URL設定 SITEURL = 'https://yourdomain.com' RELATIVE_URLS = False # フィード設定 FEED_DOMAIN = SITEURL FEED_ALL_ATOM = 'feeds/all.atom.xml' CATEGORY_FEED_ATOM = 'feeds/category_%s.atom.xml' # 削除する開発用ファイル DELETE_OUTPUT_DIRECTORY = True # Google Analytics GOOGLE_ANALYTICS = 'UA-XXXXXXXX-1' # Disqus コメント DISQUS_SITENAME = 'your-disqus-sitename' # 本番用プラグイン追加 PLUGINS.extend([ 'optimize_images', 'gzip_cache', 'webassets', ]) # アセット最適化 WEBASSETS = True WEBASSETS_CONFIG = [ ('SASS_LOAD_PATHS', ['themes/custom/sass']), ('BABEL_PRESETS', 'es2015'), ] # 画像最適化 OPTIMIZE_IMAGES = True # SSL設定 FORCE_SSL = True ``` ### 自動化とGitHub Actions連携 ```yaml # .github/workflows/deploy.yml name: Deploy Pelican Site on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pelican[markdown] typogrify pip install -r requirements.txt - name: Build site run: | pelican content -s publishconf.py - name: Deploy to GitHub Pages if: github.ref == 'refs/heads/main' uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./output cname: yourdomain.com # Makefile - 開発用タスク PY?=python3 PELICAN?=pelican PELICANOPTS= BASEDIR=$(CURDIR) INPUTDIR=$(BASEDIR)/content OUTPUTDIR=$(BASEDIR)/output CONFFILE=$(BASEDIR)/pelicanconf.py PUBLISHCONF=$(BASEDIR)/publishconf.py DEBUG ?= 0 ifeq ($(DEBUG), 1) PELICANOPTS += -D endif RELATIVE ?= 0 ifeq ($(RELATIVE), 1) PELICANOPTS += --relative-urls endif help: @echo 'Makefile for Pelican Website ' @echo ' ' @echo 'Usage: ' @echo ' make html (re)generate the web site ' @echo ' make clean remove the generated files ' @echo ' make regenerate regenerate files upon modification' @echo ' make publish generate using production settings' @echo ' make serve [PORT=8000] serve site at http://localhost:8000' @echo ' make devserver [PORT=8000] start/restart develop_server.sh ' html: $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) clean: [ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR) regenerate: $(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) serve: ifdef PORT $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT) else $(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) endif publish: $(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS) .PHONY: html help clean regenerate serve publish ```