Twitter/Facebookへのページシェアでコンテンツを埋め込む(OGP)

概要

Sphinxで作成したページのURLをTwitterやFacebookといったSNSに投稿することがあります。このとき、そのページの内容数行と画像が投稿内容に自動的に表示されれば、その投稿を見た人が内容により興味を持ってくれるかもしれません。

このような、SNS投稿にコンテンツ内容を表示するための仕組みとして、Open Graph protocol (OGP)という仕組みがあります。OGPは、HTMLのメタタグを適切に持たせることで、投稿先SNS等が表示するべきコンテンツ内容を把握し、その情報を表示してくれる仕組みです。

../../_images/sphinx-ogp.png

OGP対応ページの投稿

この拡張が出力するmetaタグ

この拡張は、以下のHTMLタグを出力します。

<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@sphinxjp" />
<meta property="og:site_name" content="Python製ドキュメンテーションビルダー、Sphinxの日本ユーザ会">
<meta property="og:title" content="Twitter/Facebookへのページシェアでコンテンツを埋め込む(OGP)">
<meta property="og:description" content="Sphinxで作成したページのURLをTwitterやFacebookといったSNSに投稿することがあります。このとき、そのページの内容数行と画像が投稿内容に自動的に表示されれば、その投稿を見た人が内容により興味を持ってくれるかもしれません。このような、SNS投稿にコンテンツ内容を表示するための仕組みとして、Open Graph protocol (OGP)という仕組みがあります。OGPは、HTMLのメタタグを適切に持たせることで、投稿先SNS等が表示するべきコンテンツ内容を把握し、その情報を表示してくれる仕組み...">
<meta property='og:url' content="http://sphinx-users.jp/cookbook/ogp/index.html">
<meta property="og:image" content="http://sphinx-users.jp/_images/sphinx-ogp-no-image.png">

注釈

このタグが出力されるためには、Sphinxテーマで使っているテンプレートがmetatagsを出力している必要があります。Sphinx組み込みのテーマ、alabaster, sphinx_rtd_theme はこの出力に対応しています。

コード

以下の内容をそれぞれのファイル名でSphinxプロジェクトに組み込んでください。

conf.py
import sys
import os
sys.path.append(os.path.abspath('_ext'))

extensions = [
    'ogtag',
]

og_site_url = 'http://sphinx-users.jp/'
og_twitter_site = '@sphinxjp'

conf.pyog_site_urlには、HTML公開先のドキュメントルートのURLを記載します。

_ext/ogtag.py
from docutils import nodes
from sphinx import addnodes
from urllib.parse import urljoin


class Visitor:

    def __init__(self, document):
        self.document = document
        self.text_list = []
        self.images = []
        self.n_sections = 0

    def dispatch_visit(self, node):
        # toctreeは飛ばす
        if isinstance(node, addnodes.compact_paragraph) and node.get('toctree'):
            raise nodes.SkipChildren

        # 画像を収集
        if isinstance(node, nodes.image):
            self.images.append(node)

        # 3つ目のセクションまではテキスト収集する
        if self.n_sections < 3:

            # テキストを収集
            if isinstance(node, nodes.paragraph):
                self.text_list.append(node.astext())

            # セクションに来たら深さを追加
            if isinstance(node, nodes.section):
                self.n_sections += 1

    def dispatch_departure(self, node):
        pass

    def get_og_description(self):
        # TODO: 何文字までが良いのか?
        text = ' '.join(self.text_list)
        if len(text) > 200:
            text = text[:197] + '...'
        return text

    def get_og_image_url(self, page_url):
        # TODO: 必ず最初の画像で良いのか
        if self.images:
            return urljoin(page_url, self.images[0]['uri'])
        else:
            return None


def get_og_tags(context, doctree, config):
    # page_url
    site_url = config['og_site_url']
    page_url = urljoin(site_url, context['pagename'] + context['file_suffix'])

    # collection
    visitor = Visitor(doctree)
    doctree.walkabout(visitor)

    # og:description
    og_desc = visitor.get_og_description()

    # og:image
    og_image = visitor.get_og_image_url(page_url)

    ## OGP
    tags = '''
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:site" content="{cfg[og_twitter_site]}" />
    <meta property="og:site_name" content="{ctx[shorttitle]}">
    <meta property="og:title" content="{ctx[title]}">
    <meta property="og:description" content="{desc}">
    <meta property='og:url' content="{page_url}">
    '''.format(ctx=context, desc=og_desc, page_url=page_url, cfg=config)
    if og_image:
        tags += '<meta property="og:image" content="{url}">'.format(url=og_image)
    return tags


def html_page_context(app, pagename, templatename, context, doctree):
    if not doctree:
        return

    context['metatags'] += get_og_tags(context, doctree, app.config)


def setup(app):
    app.add_config_value('og_site_url', None, 'html')
    app.add_config_value('og_twitter_site', None, 'html')
    app.connect('html-page-context', html_page_context)
    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }

Twitterでの追加手順

Twitterの場合、ogタグのあるページを検証サイトに入力して、承認してもらう必要があります。詳しくは以下のページにある「検証ツールでURLを実行して申請」を参照してください。