Pelican

Mature Python-based SSG. Over 12k GitHub stars, ideal for blog construction in Python ecosystem.

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

トレンド・動向

Stable position as blog construction standard in Python community. Popular for data science blogs.

# SSG Pelican ## Overview Pelican is one of the most reliable SSGs in the Python ecosystem, developed as "a mature static site generator written in Python". Supporting both Markdown and reStructuredText, it enables flexible customization through the Jinja2 template engine. With over 12k GitHub stars, it has established a stable position as the standard for blog construction in the Python community. With its rich plugin ecosystem, mature documentation, and long-term support, it has been widely adopted from technical blogs by data scientists and researchers to corporate documentation sites. ## Details Pelican 2025 edition boasts over 10 years of development history as the definitive Python static site generator. It features Pythonic configuration management through simple configuration files (pelicanconf.py) and advanced customization via the powerful Jinja2 template system. Supporting both Markdown and reStructuredText formats, it allows choosing the notation preferred for technical documentation. Standard features include tag and category classification, RSS feed generation, automatic sitemap generation, and multilingual site support - everything needed for blog operation. The plugin system provides abundant extension features such as search functionality, comment systems, and image optimization. ### Key Features - **Python-based**: Integrable with Python's rich ecosystem - **Flexible Templates**: Advanced customization with Jinja2 - **Multi-format Support**: Markdown, reStructuredText, and HTML support - **Rich Metadata**: Detailed article classification and management features - **Theme System**: Abundant themes and customizability - **Plugin Extensions**: Active plugin ecosystem ## Pros and Cons ### Pros - High extensibility through complete integration with Python ecosystem - Mature documentation and abundant tutorials and learning resources - Advanced technical documentation creation with reStructuredText support - Flexible site design and customization through Jinja2 templates - SEO optimization and feature extension through abundant plugins - Low learning cost and intuitive operation for Python developers ### Cons - Python environment setup required, complex configuration for beginners - Build times tend to be longer than other SSGs for large sites - Constraints in integration with other language ecosystems - Limited support for latest frontend trends - Constraints in collaboration with modern JavaScript frameworks - May lag in build speed compared to Node.js-based SSGs ## Reference Pages - [Pelican Official Site](https://getpelican.com/) - [Pelican Documentation](https://docs.getpelican.com/) - [Pelican GitHub Repository](https://github.com/getpelican/pelican) ## Code Examples ### Installation and Basic Setup ```bash # Install Pelican pip install pelican[markdown] # Install with Markdown support (recommended) pip install pelican[markdown] typogrify # Initialize project mkdir my-blog cd my-blog pelican-quickstart # Check basic directory structure ls -la # content/ # Article files # output/ # Generated site # pelicanconf.py # Configuration file # publishconf.py # Production configuration ``` ### Project Structure and Page Creation ```bash # Create article directories mkdir content/articles mkdir content/pages mkdir content/images # Create first article cat > content/articles/first-post.md << 'EOF' Title: My First Article Date: 2025-01-01 10:00 Category: Tech Tags: pelican, python, blog Slug: first-post Author: Your Name Summary: My first article created with Pelican. # Introduction I created a static site using Pelican. ## Features - Python-based SSG - Markdown support - Rich themes [Read more...](/articles/second-post.html) EOF # Create static page cat > content/pages/about.md << 'EOF' Title: About Date: 2025-01-01 Slug: about This is the about page for this site. ## Profile Running a technical blog. ## Contact - Email: [email protected] - Twitter: @example EOF # Generate site and preview pelican content pelican --listen # Preview at http://localhost:8000 ``` ### Configuration Customization (pelicanconf.py) ```python #!/usr/bin/env python # -*- coding: utf-8 -*- # AUTHOR = 'Your Name' SITENAME = 'My Tech Blog' SITEURL = '' PATH = 'content' TIMEZONE = 'America/New_York' DEFAULT_LANG = 'en' # Feed generation 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 # Pagination settings DEFAULT_PAGINATION = 10 PAGINATION_PATTERNS = ( (1, '{base_name}/', '{base_name}/index.html'), (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), ) # Theme and style settings THEME = 'themes/elegant' # Custom theme THEME_STATIC_DIR = 'theme' CSS_FILE = 'main.css' # URL and path settings 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 path settings STATIC_PATHS = ['images', 'extra/CNAME', 'extra/robots.txt'] EXTRA_PATH_METADATA = { 'extra/CNAME': {'path': 'CNAME'}, 'extra/robots.txt': {'path': 'robots.txt'}, } # Social links SOCIAL = (('GitHub', 'https://github.com/username'), ('Twitter', 'https://twitter.com/username'), ('LinkedIn', 'https://linkedin.com/in/username')) # Menu settings MENUITEMS = ( ('Archives', '/archives.html'), ('Categories', '/categories.html'), ('Tags', '/tags.html'), ) # SEO settings SITEMAP = { 'format': 'xml', 'priorities': { 'articles': 0.5, 'indexes': 0.5, 'pages': 0.5 }, 'changefreqs': { 'articles': 'monthly', 'indexes': 'daily', 'pages': 'monthly' } } # Default metadata values DEFAULT_METADATA = { 'status': 'draft', } # Markdown settings MARKDOWN = { 'extension_configs': { 'markdown.extensions.codehilite': {'css_class': 'highlight'}, 'markdown.extensions.extra': {}, 'markdown.extensions.meta': {}, 'markdown.extensions.toc': {}, }, 'output_format': 'html5', } ``` ### Plugin System Usage ```python # pelicanconf.py - Plugin configuration PLUGIN_PATHS = ['plugins'] PLUGINS = [ 'sitemap', # Sitemap generation 'tag_cloud', # Tag cloud 'related_posts', # Related posts 'tipue_search', # Search functionality 'neighbors', # Previous/next article navigation 'representative_image', # Representative image 'liquid_tags.img', # Image tags 'render_math', # Math rendering ] # Search functionality settings DIRECT_TEMPLATES = ['index', 'tags', 'categories', 'archives', 'search'] # Tag cloud settings TAG_CLOUD_STEPS = 4 TAG_CLOUD_MAX_ITEMS = 100 TAG_CLOUD_SORTING = 'random' # Related posts settings RELATED_POSTS_MAX = 5 RELATED_POSTS_SKIP_SAME_CATEGORY = True # Math settings MATH_JAX = { 'align': 'left', 'indent': '2em', 'show_menu': 'true', 'process_escapes': 'true', 'latex_preview': 'tex2jax_preview', 'color': 'black', 'linebreaks': 'false', } # Image processing settings 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"], } # Custom plugin function def my_custom_filter(text): """Custom Jinja2 filter""" return text.replace('old', 'new') JINJA_FILTERS = { 'my_filter': my_custom_filter } ``` ### Theme and Template Customization ```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 tags --> <meta name="description" content="{% block description %}{{ SITEDESCRIPTION }}{% endblock %}"> <meta name="keywords" content="{% block keywords %}{{ SITEKEYWORDS }}{% endblock %}"> <meta name="author" content="{{ AUTHOR }}"> <!-- OGP settings --> <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 %}"> <!-- Stylesheets --> <link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}"> <link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/pygments.css"> <!-- Favicon --> <link rel="icon" type="image/x-icon" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/favicon.ico"> <!-- Feeds --> {% 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 %} <!-- Related posts --> {% if article.related_posts %} <section class="related-posts"> <h3>Related Posts</h3> <ul> {% for related in article.related_posts %} <li><a href="{{ SITEURL }}/{{ related.url }}">{{ related.title }}</a></li> {% endfor %} </ul> </section> {% endif %} </footer> </article> <!-- Previous/Next article navigation --> {% 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 %} ``` ### Production Deployment Configuration ```python # publishconf.py - Production configuration import os import sys sys.path.append(os.curdir) from pelicanconf import * # Production URL settings SITEURL = 'https://yourdomain.com' RELATIVE_URLS = False # Feed settings FEED_DOMAIN = SITEURL FEED_ALL_ATOM = 'feeds/all.atom.xml' CATEGORY_FEED_ATOM = 'feeds/category_%s.atom.xml' # Delete development files DELETE_OUTPUT_DIRECTORY = True # Google Analytics GOOGLE_ANALYTICS = 'UA-XXXXXXXX-1' # Disqus comments DISQUS_SITENAME = 'your-disqus-sitename' # Add production plugins PLUGINS.extend([ 'optimize_images', 'gzip_cache', 'webassets', ]) # Asset optimization WEBASSETS = True WEBASSETS_CONFIG = [ ('SASS_LOAD_PATHS', ['themes/custom/sass']), ('BABEL_PRESETS', 'es2015'), ] # Image optimization OPTIMIZE_IMAGES = True # SSL settings FORCE_SSL = True ``` ### Automation and GitHub Actions Integration ```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 - Development tasks 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 ```