スクレイピングした画像にタイトルやコメントなんかを設定しておく piexif

# この画像どこのだっけ

Webサイトから画像を一括で取得するのに便利な[スクレイピング](https://www.sbbit.jp/article/cont1/71102)ですが、後々その画像をどのURLから取得したかを知りたくなるときがあります。

**いちいちメモ帳などに保存しておくのもめんどくさい…**

ということで画像のここら辺に保存しておこうというのが今日やろうと思うことです。

![スクリーンショット 2024-07-19 220509.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/827cea7b-717b-5732-13b1-d04e0eed3df9.png)

<br>

**※今回紹介するコードはJPEG形式の画像しか対応しておりません**

# 画像に情報を付加するExif
上記の画像はWindowsで画像のプロパティを開いたときに出てくる表示です。これらの情報は[**Exif**](https://wa3.i-3-i.info/word120.html) と言われています。Exifは富士フイルムが規格したもので、撮影した際の日時やシャッター速度、カメラのモデル名などの情報を画像に付加するために作られたものです。最近…でもないですがスマホからはGPSによる位置情報も設定できます（[ジオタグ](https://kotobank.jp/word/%E3%82%B8%E3%82%AA%E3%82%BF%E3%82%B0-188622)と呼ばれます）。

便利な機能ですが、**「画像の内容に関わらず撮影場所が特定できてしまう」** など恐ろしい面もあります。（もちろん設定でオフにできますし、大抵のSNSではアップ時に消されます）

# Exifの操作
ということでExifをいじることになるのですが、**Exif編集＝ファイルのバイナリ編集**であり、仕組みとしてはシンプルですので多くのツールが用意されています。（Pythonに限らず）

今回は簡単で使いやすいPythonのパッケージ[**piexif**](https://pypi.org/project/piexif/)を利用させて頂きます。

最初に画像のExifを出力してみます。

```:piexifインストール
!pip install piexif
```

```python:
import piexif

file_name = 'test.jpg'

# EXIFデータ読み込み
exif_dict = piexif.load(file_name)

print(exif_dict)
```

```:出力結果
{'0th': {34665: 330, 40091: (75, 106, 44, 103, 176, 116, 72, 89, 110, 48, 59, 117, 207, 80, 0, 0), 40092: (50, 0, 48, 0, 50, 0, 48, 0, 116, 94, 0, 0)}, 'Exif': {}, 'GPS': {}, 'Interop': {}, '1st': {}, 'thumbnail': None}
```
<br>

このtest.jpgのExifは次の通りでタイトルとコメントを設定しただけです。

![スクリーンショット 2024-07-26 205818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/5ff4d897-02eb-8eba-9472-8b3ce9672751.png)

<br>

なのですが出力結果を見ても何がどこに設定されているかよくわかりません。
Exifの世界はディープなので簡単なとこだけ説明させて頂きます。
（以下のサイトが細かく解説されています。）

http://www2.airnet.ne.jp/~kenshi/exif.html

<br>
とりあえず<b>"0th"</b>に今回編集したいタイトルなど基本的な情報が入っています。
0thの中身を出力してみます。※piexifはdictで返しますが、中身も含めこれらは利用するパッケージにより異なります

```python:"0th"出力
print(exif_dict["0th"])
```
```:出力結果
{34665: 330, 40091: (75, 106, 44, 103, 176, 116, 72, 89, 110, 48, 59, 117, 207, 80, 0, 0), 40092: (50, 0, 48, 0, 50, 0, 48, 0, 116, 94, 0, 0)}
```
<br>

34665、40091…など謎の数字が並びます。

**40091がタイトルとなり、40092はコメントとなります。（共にWindowsの場合）**
なんで？と思いますが、これはExifの規格でそうなっているので、そういうものと思うしかありません。

https://exiv2.org/tags.html

<br>

ちなみにpiexifから一覧も出せます。

```python:TAGS出力
piexif.TAGS["0th"]
```
```:出力結果
{11: {'name': 'ProcessingSoftware', 'type': 2},
 254: {'name': 'NewSubfileType', 'type': 4},
 255: {'name': 'SubfileType', 'type': 3},
 256: {'name': 'ImageWidth', 'type': 4},
 257: {'name': 'ImageLength', 'type': 4},
 258: {'name': 'BitsPerSample', 'type': 3},
 259: {'name': 'Compression', 'type': 3},
 262: {'name': 'PhotometricInterpretation', 'type': 3},
 263: {'name': 'Threshholding', 'type': 3},
 264: {'name': 'CellWidth', 'type': 3},
 265: {'name': 'CellLength', 'type': 3},
 266: {'name': 'FillOrder', 'type': 3},…

 ～省略～
```
※34665はよくわからないので無視させて下さい。


で各キーの意味がわかったとこで肝心のその中身 **(75, 106, 44, 103…)** が今度はわからない。

ざっくり言うと**2つの数字で１文字**を現わしています。簡単な40092の方を見ると
<br>**(50, 0, 48, 0, 50, 0, 48, 0, 116, 94, 0, 0)**<br>
となっていますが **(50, 0) (48, 0) (50, 0) (48, 0) (116, 94)** で「2020年」を現わしています。2つの値をバイト列にして**UTF-16LE**でデコードすると **(50, 0)→"2"** など文字に変換されます。

他の項目はUTF-8なのですが40000番台はUTF-16です。
なんで？か詳しくわかりませんがMicrosoftがそう決めたようです。

<br>

ごちゃごちゃ書きましたが便利なコードを見つけましたので、こちらを利用させてもらえば簡単にEXIFのコメントの取得・設定が行えます。（[こちらのアンサーから引用させて頂きました。](https://teratail.com/questions/278978)）

```python:変換用関数
def decode(t):
    b = bytes(t)
    return b[:-2].decode('utf-16-le')

def encode(s):
    b = s.encode('utf-16-le') + b'\x00\x00'
    return tuple([int(i) for i in b])
```

<br>

こちらを利用したサンプルです。
まずは先ほどの画像のタイトル・コメントを表示してみます。

```python:タイトル・コメント表示
import piexif

file_name = 'test.jpg'

exif_dict = piexif.load(file_name)

# UTF-16LEでデコード（piexif.ImageIFD.XPTitleは40091）
title = exif_dict['0th'][piexif.ImageIFD.XPTitle]
print(decode(title))

# UTF-16LEでデコード（piexif.ImageIFD.XPCommentは40092）
comment = exif_dict['0th'][piexif.ImageIFD.XPComment]
print(decode(comment))
```
```:出力結果
橋本環奈の画像
2020年
```

<br>

次に修正してみます。

```python:タイトル・コメント修正
from PIL import Image

new_title = "森香澄の画像"
new_comment = "2022年"

# エンコードしてセット
exif_dict["0th"][piexif.ImageIFD.XPTitle]= encode(new_title)
exif_dict["0th"][piexif.ImageIFD.XPComment]= encode(new_comment)

# バイトにdump
exif_bytes = piexif.dump(exif_dict)

# PILを利用し保存
with Image.open(file_name) as im:
    im.save(file_name, "jpeg", exif=exif_bytes)
```
※piexif.insertでもできるはずなのですが、タイトル修正が上手く行かなかったのでPILを利用しました。新規の設定の場合はPILの利用は不要です。


# 実際にスクレイピング＆設定をしてみる

では実際にサイトから商品の画像を取得し、タイトルに「商品名」コメントに「リンク先のURL」を設定してみます。
※decode、encode関数は先ほどと同じです。requests、BeautifulSoupはインストールして下さい。

```python:タイトル・コメント修正
import requests
from bs4 import BeautifulSoup
import piexif

url = "https://shopping.yahoo.co.jp/categoryranking/49331/list?sc_i=shopping-pc-web-category-catrecom-cark1-more"
save_fol = "/content/sample_data"

response = requests.get(url)
soup = BeautifulSoup(response.text,'html.parser')

elems = soup.find_all("div", class_="column-middle-left-wrapper")

for el in elems:

    href = el.find("a").get("href")
    src = el.find("img").get("src")
    alt = el.find("img").get("alt")

    save_path = save_fol + "/" + src.split('/')[-1] + ".jpg"

    # まず画像を保存
    with open(save_path, 'wb') as f:
        f.write(requests.get(src).content)

    # 空のEXIF用dictを用意
    exif_dict = {"0th":{}}

    # タイトルとコメント（取得先URL）を設定
    exif_dict["0th"][piexif.ImageIFD.XPTitle]= encode(alt)
    exif_dict["0th"][piexif.ImageIFD.XPComment]= encode(href)

    # バイトにして画像に設定
    exif_bytes = piexif.dump(exif_dict)
    piexif.insert(exif_bytes, save_path)

    break   # テスト用に1回で終了
```
**ダウンロード画像のプロパティ**
![スクリーンショット 2024-07-30 222409.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/c4d5038c-86f7-21bb-9251-64eb07e39d36.png)



<br>

こちらは空のEXIF用dictを用意して、piexif.insertで設定しています。恐らくスクレイピングで取得した場合は既存のEXIFは不要なのでこちらで構わないかと思います。

# おまけ（Windows以外）

これまではWindows用の設定項目で、Macで表示されるか不明ですが（Macを持っていないのでわかりません…）OSが関係ない **"DocumentName(269)、ImageDescription(270)"** という項目への設定方法を紹介します。

```python:DocumentNameなどの利用
import piexif

file_name = 'test.jpg'

exif_dict = {"0th":{}}

name = "DocumentName"
description = "ImageDescription"

exif_dict["0th"][piexif.ImageIFD.DocumentName]= name.encode('utf-8')
exif_dict["0th"][piexif.ImageIFD.ImageDescription]= description.encode('utf-8')

exif_bytes = piexif.dump(exif_dict)
piexif.insert(exif_bytes, file_name)
```

<br>

先ほどのとの違いはこちらはUTF-8でエンコードして設定しています。
注意点はこちらの[EXIF確認サイト](http://exif-check.org/)で見てみると

**DocumentName：DocumentName
画像タイトル：ImageDescription**

となり画像タイトルがImageDescriptionとなっています。
ここら辺は誰が悪いとかいうわけではなく、項目の扱い方によるのでこのような違いが産まれてしまいます。

**※ちなみに本編でこちらを利用しなかったのはWindowsから表示しにくいからです**

# おわりに

今さらですがショッピングサイトはスクレイピング禁止が多いので注意してください。
今回の内容、まだまだわかってないことも多いのでこれから勉強したいと思います！