AMP Start という AMP のテーマがリリースされたのを知り、
Blog の AMP テーマを作成してみました。
なかなか長い道のりだったので、実施したことを備忘録として記載します。
前提
以下の環境で実行しています。
-
OS
CentOS release 6.9 (Final) -
Python Version
Python 2.7.8 -
Package (必要そうなものだけ抜粋)
Django (1.10.6)
Mezzanine (4.2.3)
参考サイト
-
AMP Start - Accelarated Mobile Pages Templates
AMP Start テーマの元ネタとして、Blog Post を使用しました。 -
Device Handling — Mezzanine 4.2.3 documentation
Mezzanine の PC モバイル判定の頁 -
AMP の対応方法まとめ
AMP タグを説明してくれているサイト
AMP テーマを作成しようと思う背景
-
Google の検索順位が上がることを期待している。
現状、直接的に検索順位には影響しないそうですが、関節的にはいいかとがあるのではという気持ちがあります。 -
流行っている感がある。
流行っている感があり、仕事で使う可能性はないことはなく、
この辺で一通り覚えておこうかという気持ちはあります。
テーマ作成の方針
-
AMP Start の Blog Post をなるべくそのままで作成する。
そのままでいい感じに思えましたので、問題が発生しない限りはそのまま使います。 -
AMP HTML は、別 URL に置くではなく、同一 URL として、Mobile アクセスの場合、AMP HTML を返すようにする。
これは、Mezzanine に Mobile 判定機能が内蔵されている。
別 URL にした場合、Mezzanine の include ファイルが紐づけられている template タグ類が使えなくなりそうだったためです。
同一 URL で切り替えを行なっても、Google Search Console で認識されましたので、
同一 URL で、デバイスにより HTML を切り替えるでもうまくいくようです。
テーマ作成時に実施したこと
以下、思い出した順ですが、実施したことを記載します。
Mezzanine の テンプレート というか Django のテンプレートの観点での記載となります。
Mezzanine への 設定追加
Theme を INSTALL_APPS に追加、
Tempalte を読み込むための記載、
Mobile Device の判定の追加をしました。
-
Theme を INSTALL_APPS に追加
settings.py
の INSTALLED_APPS に amp_start_blog_post を追加しました。
################ # APPLICATIONS # ################ INSTALLED_APPS = ( .... "mezzanine.twitter", "mezzanine_pubsubhubbub_pub", "mezzanine_extentions", "amp_start_blog_post", ... )
-
Tempalte を読み込むための記載
settings.py
の DIRS に テーマの Template パスos.path.join(BASE_DIR, 'amp_start_blog_post/templates')
を追加しました。
TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': (os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'clean_blog/templates'), os.path.join(BASE_DIR, 'amp_start_blog_post/templates'), os.path.join(BASE_DIR, 'mezzanine_extentions/templates')), 'OPTIONS': {'builtins': ['mezzanine.template.loader_tags'], 'context_processors': (
-
Mobile Device の判定の追加
settings.py
の DEVICE_USER_AGENTS に テーマディレクトリと、
ユーザーエージェントを追加しました。
これで、Mobile ユーザーエージェントの場合は、amp_start_blog_post ディレクトリ配下の、
template が使用されるようになります。
############################# # Device Settings ############################# DEVICE_USER_AGENTS = ( ("amp_start_blog_post", ("Android", "BlackBerry", "iPhone" )), )
-
補足1. Mobile デバイスの場合の、include HTML を保持する template タグの動作について
Mezzanine の menu タグなどは、include HTML を保持しています。
include HTML の読み込みには、@register.inclusion_tag
というデコレータが使われています。
以下、editable_loader
の実装抜粋です。
この@register.inclusion_tag("includes/editable_loader.html", takes_context=True) def editable_loader(context): """ Set up the required JS/CSS for the in-line editing toolbar and controls. """ user = context["request"].user template_vars = { "has_site_permission": has_site_permission(user), "request": context["request"], } if (settings.INLINE_EDITING_ENABLED and template_vars["has_site_permission"]): t = get_template("includes/editable_toolbar.html") template_vars["REDIRECT_FIELD_NAME"] = REDIRECT_FIELD_NAME template_vars["editable_obj"] = context.get("editable_obj", context.get("page", None)) template_vars["accounts_logout_url"] = context.get( "accounts_logout_url", None) template_vars["toolbar"] = t.render(template_vars) template_vars["richtext_media"] = RichTextField().formfield( ).widget.media return template_vars
@register.inclusion_tag
ですが、mezzanine/init.py at master · stephenmcd/mezzanine · GitHub で定義されています。
このタグですが、内部で、def inclusion_tag(self, name, context_class=Context, takes_context=False): """ Replacement for Django's ``inclusion_tag`` which looks up device specific templates at render time. """ def tag_decorator(tag_func): @wraps(tag_func) def tag_wrapper(parser, token): class InclusionTagNode(template.Node): def render(self, context): if not getattr(self, "nodelist", False): try: request = context["request"] except KeyError: t = get_template(name) else: ts = templates_for_device(request, name) t = select_template(ts) self.template = t parts = [template.Variable(part).resolve(context) for part in token.split_contents()[1:]] if takes_context: parts.insert(0, context) result = tag_func(*parts) if context.autoescape: result = conditional_escape(result)fsup return self.template.render(context.flatten()) return InclusionTagNode() return self.tag(tag_wrapper) return tag_decorator
templates_for_device
でMoblie か PC かで、template を切り替えています。
逆に言うと、mezzanine の標準のタグは、Device Handling
機能を使わないと、レイアウトの切り替えができません。1
1. モジュールの関数を書き換えれば上手くいきそうには思います。Pythonによる黒魔術入門 -
補足2. Device Handring 機能が削除される
Mezzanine の 新しいVersionでは、Device Handring 機能が削除されるようです。
mezzanine/device-handling.rst at master · stephenmcd/mezzanine
このため、Device Handring を使用しない実装に書き換えました。
修正した結果はDjango で AMP ページ と 通常ページを振り分ける | Monotalk に記載しましたので、そちらもご確認ください。
Google Tag Manger タグの AMP 対応
amp-analytics
タグで、Google Tag Manger の js を読み込むようにしました。
記載は規定テンプレート base.html
に追加しています。
- base.html
{% if settings.AMP_GOOGLE_TAG_MANGER_ID %} <amp-analytics config="https://www.googletagmanager.com/amp.json?id={{settings.AMP_GOOGLE_TAG_MANGER_ID}}>m.url=SOURCE_URL"data-credentials="include"></amp-analytics> {% endif %}
Google analytics タグの AMP 対応
Google Tag Manager 経由で読み込んでいるため、
Template 上に特に記載は現れません。
設定方法は以下の記事が参考になりました。
Google adsense タグの AMP 対応
画面上部、下部で2つのinclude ファイルを作成しました。
上部は、fixed-height
、下部は、responsive
レイアウトとしました。
-
画面上部 (google_ads_top.html)
{% if settings.AMP_GOOGLE_ADS_CLIENT_ID_TOP and settings.AMP_GOOGLE_ADS_SLOT_ID_TOP %} <section class="{{ section_class }}"> <amp-ad layout="fixed-height" height=100 type="adsense" data-ad-client="{{settings.AMP_GOOGLE_ADS_CLIENT_ID_TOP}}" data-ad-slot="{{settings.AMP_GOOGLE_ADS_SLOT_ID_TOP}}"> </amp-ad> </section> {% endif %}
-
画面下部 (google_ads_bottom.html)
{% if settings.AMP_GOOGLE_ADS_CLIENT_ID_BOTTOM and settings.AMP_GOOGLE_ADS_SLOT_ID_BOTTOM %} <section class="{{ section_class }}"> <amp-ad layout="responsive" width=300 height=250 type="adsense" data-ad-client="{{settings.AMP_GOOGLE_ADS_CLIENT_ID_BOTTOM}}" data-ad-slot="{{settings.AMP_GOOGLE_ADS_SLOT_ID_BOTTOM}}"> </amp-ad> </section> {% endif %}
Disqus の AMP 対応
以下に記載しました。
別ドメインにiframe を設置して、そのiframe経由で読み込みます。
* AMP の HTML に disqus を組み込む | Monotalk
JSON-LD の 構造化マークアップの追加
過去に作成したテンプレートタグを、移植してtemplate 内に埋め込みました。
Mezzanine に JSON-LD 形式の構造化データ を埋め込む | Monotalk
本文HTMLの AMP 対応
本文HTMLの、AMPへの変換を実施しました。
現在、mezzanine-pagedown 1.0 : Python Package Index という、
plugin を使っていて、本文はMarkDown 形式で設定されており、MarkDown > HTML 変換が行われて、画面表示されています。
filter を新規で作成して、MarkDown > HTML 変換 > AMP HTML 変換 するようにしました。
以下、参考にした記事になります。
作成した filter は以下の通りです。
* amp_start_blog_post_tags.py
rom __future__ import unicode_literals
from mezzanine import template
from bs4 import BeautifulSoup
from PIL import Image
from StringIO import StringIO
import requests
import json
import logging
logger = logging.getLogger(__name__)
register = template.Library()
@register.filter
def to_amp_html(html):
"""
Markdown HTML to AMP HTML
"""
soup = BeautifulSoup(html, "html5lib")
# ------------------------------------------------
# amp id replace to "accelerated-mobile-pages"
# ------------------------------------------------
for elem in soup.find_all(True, id=lambda x: x and 'amp' in x):
elem["id"] = elem.get("id").replace("amp", "accelerated-mobile-pages")
# h2
for h2 in soup.find_all('h2'):
h2['class'] = h2.get('class', []) + ['bold', 'mt2', 'mb2']
# h3
for h3 in soup.find_all('h3'):
h3['class'] = h3.get('class', []) + ['bold', 'mt1', 'mb1']
# h4
for h4 in soup.find_all('h4'):
h4['class'] = h4.get('class', []) + ['bold', 'mt1', 'mb1']
# ------------------------------------------------
# img replace to amp-img
# -----------------------------------------------
for img in soup.find_all('img'):
try:
amp_img = soup.new_tag("amp-img")
for attr in img.attrs:
if "style" != attr:
amp_img[attr] = img[attr]
src = str(img.get("src"))
if src.startswith("//"):
src = src.replace("//", "https://")
req = requests.get(src)
picture_IO = StringIO(req.content)
picture_IO.seek(0)
im = Image.open(picture_IO)
amp_img["width"] = im.size[0]
amp_img["height"] = im.size[1]
amp_img["layout"] = "responsive"
img.replace_with(amp_img)
except IOError:
logger.warning("something raised an exception: ", exc_info=True)
amp_img["width"] = "4"
amp_img["height"] = "3"
amp_img["layout"] = "responsive"
img.replace_with(amp_img)
soup.body.hidden = True
return str(soup.body)
-
説明
-
BeautifulSoup(html, "html5lib")
は、BeautifulSoup(html)
でも問題なく動作しますが、WARNINGが出力されます。 -
id に
amp
が指定されている箇所があり、それで 妥当性検証エラーが発生していたので、amp
をaccelerated-mobile-pages
に置換する処理を追加しました。 -
h2,h3,h4 タグにcss クラス属性を付与する必要があり、このタイミングで実施するようにしました。
-
Image.open(picture_IO)
で、画像を取得しwidth、height を取得していますが何故かJPEGの場合エラーとなり、エラーの原因を解決できなかったため、乱暴ですが例外発生時は、縦横比率 3対4 固定で設定しています。
-
PC 向けテンプレート に AMP HTMLへのリンクを追加
PC 向けのテンプレートに、AMP HTML へのリンクを追加しました。
PC も Mobile の URL も変わらないので、以下の記載となりました。
<link rel="amphtml" href="{{ request.get_full_path }}">
AMP対応するまでに失敗したこと - なりせなるてず
にもそのようなことが書いてありますが、PC / Mobile で URL同一 テンプレートは変える場合、
もしかしたら記述は不要に思います。
書いてても、ampのページがあることは認識してくれたので、問題はなさそうです。
Vary User-Agent の設定
記事作成後の後日対応しました。
動的な配信 | Google Developers
今回のように、1URL で 複数のHTML を返す場合は、この動的な配信にあたるため、
Vary HTTP ヘッダー に Vary: User-Agent
を設定する必要があります。
当ブログだと、既に以下のようなVary HTTP ヘッダーが付与されていました。
Vary:Cookie,Accept-Language,Accept-Encoding
Order of MIDDLEWARE
を読む限り、
-
SessionMiddleware は、
Vary:Cookie
を追加する -
GZipMiddleware は、
Vary:Accept-Encoding
を追加する -
LocaleMiddleware は、
Vary:Accept-Language
を追加する
という動作をするようです。
settings.py で、GZipMiddleware は使用する設定にしていませんでしたが、
Apache の mod_deflate を使用していたため、
そちらの設定で、Vary:Accept-Encoding
が付与されていたようです。
そのため、Vary: User-Agent
の設定は、mod_deflate に以下の設定を追加して実施しました。
ドキュメントmod_deflate - Apache HTTP サーバ にも
やり方は記載されていたので、よくやる?パターンなのかと思いました。
- mod_deflate.conf
# Make sure proxies don't deliver the wrong content Header append Vary User-Agent env=!dont-vary
作成したテーマ一式
Github に作成したテーマ一式はUPしました。
kemsakurai/mezzanine-theme-amp-start-blog-post: mezzanine theme based by amp start
以上です。
過去に、通常のテーマについても書いてますので、よろしければそちらもご確認ください。
mezzanineのテーマを作成してみました。 | Monotalk
コメント