Blogger: モバイルで画像の解像度が低いときの対処法

2021年12月28日火曜日

BeautifulSoup Blogger Python レスポンシブ 画像

t f B! P L

このブログはGoogleのブログサービス、Bloggerを使っています。無料で広告が出ず、アドセンスOKという神対応のようなブログサービスですが、レスポンスが遅かったり、機能が少ない、情報も少ないなどデメリットもあります。

Bloggerで画像を挿入した際、スマホなどモバイル端末で画像の表示が粗くなってしまうことに気づいたので原因と対応方法を記事にしてみます。

前提条件と利用するもの

前提条件として、Googleのブログサービス、Bloggerを利用しているものとします。

Python3のライブラリ、BeautifulSoup4を使い画像のURLを変更します。

画像挿入時に作成されるHTML

例えば、Bloggerで画像を「中サイズ」挿入した際、HTMLは下記のようになります。

<img alt="" border="0" width="320"
 data-original-height="427"
 data-original-width="640"
 src="https://blogger.googleusercontent.com/
       img/a/AVvXsEgP_Oc9HB9maKwSYsZKg60Gt6YZGC
       tnYwjoHXbnfLtN-2n7Sp3unjVVNKXt1n_1
       ZZB1s3uX69wlgNk6IVvU2tIm5VgpiGG_f
       Q0aASgGp2dqBqun35fBtya5kBDiSyeUwn
       8R-uO9atA7DH-H7ei2fNzZD8F3ZPh3C
       m-pRm6SBA1mWD1MI1l4Bh8VTt3g
       =s320"/>

(srcの中身が長いので改行しています)ここで注目するのは、width="320"srcタグの長い文字列の最後、s=320、そしてdata-original-widthです。縦長の画像の場合は、widthのところがheightになっていると思います。

widthはもちろん画像の表示ピクセル幅です。

srcタグのs=320は、画像自体のピクセル幅です。Bloggerは、アップロードした画像そのままの大きさではなく、ここで指定したサイズにリサイズします。sの意味は、長辺のサイズが指定した大きさになり、wだと幅、hは高さが指定され、cがつくとトリミングされたりします。ここではsで長辺を指定したものとして話を進めます。

少し古い記事だと、srcタグの中の末尾のs=320の代わりに、途中に/s320/という形になっています。

モバイルで画像の解像度が低い理由

さて、s=320で320ピクセルにリサイズされた画像を、width=320を指定し320ピクセルで表示したので何も問題無いように思えます。実際、PCで表示した場合はきれいに表示されます。

問題はモバイルの場合です。最近のモバイル端末は画面が小さいわりに解像度が高いため、320ピクセルの画像を320ピクセルで表示すると端末上で非常に小さな画像になってしまいます。そこで、HTMLやCSSでのピクセル値と、実際の画面のピクセル値が異なり、端末上の640ピクセルを使って320ピクセルの画像を表示したりします。この場合、「ピクセル密度は2」と言います。

ということで、ピクセル密度2のモバイル端末を使っている場合、HTMLのimgタグでwidth=320とした場合、実際には640ピクセル分の幅が確保されます。そこにsrc属性でs=320、すなわち320ピクセルの画像を用意すると2倍に引き伸ばされるため解像感の足りない画像になってしまします。

下が320ピクセルの画像をwidth=320で表示した例です。

次に640ピクセルの画像をwidth=320で表示した例です。PCでは違いがわからないと思います。モバイル端末では上の画像より芝や木などが細かく表示されているのがわかるでしょうか。

imgタグで複数の解像度を指定する

では、解像度2倍の画像を用意するには、単純にwidth=320srcに640ピクセルの画像を用意すればよいですが、これだとPC、すなわちピクセル密度1の端末で見る場合には無駄に大きな画像を読み込むことになります。

imgタグで複数の解像度の画像を用意し、切り替えることができます。書式は下記のようにsrcset属性を使います。(他にもpictureタグを使うなどやり方があります)

<img src="ピクセル密度1の画像"
     srcset="ピクセル密度1の画像 1x,
             ピクセル密度2の画像 2x">

もうおわかりですね、ピクセル密度1の画像には、Bloggerで普通に挿入した画像のURLを入れます(中サイズだと、320ピクセルで、URLの最後が=s320で終わります。)

ピクセル密度2の画像は、ピクセル密度1の画像のURLの最後=s320=s640に書き換えればOKです。

PythonのBeautifulSoupでタグを書き換える

いきなりソースコードです。下記を例えばimg-srcset.pyなどの名前で保存します。Bloggerの記事を画像を入れて書き終えたら、メモ帳などにコピペし同じフォルダにindex.htmlの名前で保存します。img-srcset.pyを実行するとsrcsetにピクセル密度2の画像URLが追加されたout.htmlができるのでこれをまたBloggerにコピペします。

自分で実際に使っているコードですが、うまく行かなかった場合はindex.htmlに変更前の記事が残っているので戻してください。

from bs4 import BeautifulSoup
import sys

soup = BeautifulSoup(open("index.html","r",encoding='utf-8'),'lxml')
imgs = soup.find_all('img')

for imgtag in imgs:
    if(imgtag.has_attr('data-original-width')):
        srcset = "{0} 2x, {1} 1x".format(imgtag['src'].replace('=s320','=s640').replace('=s400','=s800').replace('/s320/','/s640/').replace('/s400/', '/s800/') ,imgtag['src'])
        imgtag['srcset'] = srcset
        aspect_ratio = int(imgtag['data-original-height']) / int(imgtag['data-original-width'])
        if(imgtag.has_attr('width')):
            imgtag['height'] = round(int(imgtag['width']) * aspect_ratio)
        elif(imgtag.has_attr('height')):
            imgtag['width'] = round(int(imgtag['height']) / aspect_ratio)
    imgtag['loading'] = "lazy"

soup.html.unwrap()
soup.body.unwrap()
buff = soup.prettify()
f = open('out.html', 'w', encoding='utf-8')
f.writelines(buff)
f.flush()
print(buff)

BeautifulSoupでDOMツリーを作成し、imgタグ全てを抽出してdata-original-width属性のあるimgタグに対してsrcsetを追加しています。画像サイズ「中」は、320px→640pxに、「大」は、400px→800pxにしています。新しい画像URL形式と古い形式両方に対応しています。

ついでにwidth, height属性やLazy Loadingも追加しています。

Windowsの場合は、pyperclipを使ってクリップボードへコピーして変更後もクリップボードへ渡してもいいですね。Linuxではpyperclipが動かなかったので標準入力から読み、標準出力へ出すようにし、Emacsのshell-command-on-regionで呼び出しています。

まとめ

Bloggerの画像は、モバイル端末で見た場合に解像感に欠ける画像になってしまいますが、imgタグにsrcset属性を追加することで解像感の高い画像にすることができます。画像がたくさんあると手作業では大変なのでPythonを使いある程度自動でできるようにしました。

関連記事

QooQ