スクレイピングで名札を作る

# Slackの情報から名札を作ってみた
目新しくはない話で恐縮ですが、記事を書かせて頂きます。


この記事はマナビDX Questで得たもの Advent Calendar 2023　16日目の記事です。今回がQiitaで初投稿となります。よろしくお願いします。
昨日はエイキさん（@eikidesu）の記事でした。本日シリーズ2の方ではmikanさん(@minan0_0jp)もご投稿予定ですので、そちらも絶対チェックしましょう！！！

# 背景
AIQuest2021, マナビDXQuest2022, マナビDXQuest2023の参加者さんと飲み会をしました。
1次会約30人、2次会約20人の規模で、話したい人多すぎるくらいでした。またやりたいな。

![IMG_1595 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/6d44539c-add1-0173-81f2-1a60fd9f4f2b.jpeg)




その時に作った名札がこちらです。とてもおしゃいですね。
飲み会帰りの電車で身に着けても、周りの景色と自然に調和する洗練されたデザインです。
![図1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/89b94d10-772a-eb8f-6725-2cb8b0865b84.jpeg)




# 参考にさせて頂いた記事
LightGBMの記事でも有名なu++さんの記事です。
関東kaggler会でconnpassの参加者リストから名札を作成するというものでした。
https://upura.hatenablog.com/entry/2020/02/19/180700

# 関東kaggler会との違い　こまったこと
*    u++さんの記事のように、画像とアカウント名の揃った表がない
*    Slackで飲み会参加者全員を1つのDM部屋には入れられない（Max9人まで）
*    マナビDXQuestのルール上、プライベートチャンネルの作成が禁止されている
<br>
これは困りました。なにかうまいこと考えないといけなさそうです。

さらに「〇〇さんはこのSlackにいるけど、◇◇さんはあっちのSlackにしかいない」ということも問題でした。
参加者全員を1つのDM部屋に～というのは諦めることで、以下のようなフローを作りました。

# 今回の名札作成のフロー
*    DM部屋を複数個作成。構成人数は飲み会企画者1名＋参加者n名になるようにする　　　　（当日の緊急連絡先にもなるため、ちょうどよかった）
*    SlackをWebブラウザで開き、そのWeb版Slackをスクレイピング
*    スクレイピングで画像とアカウント名を取得
*    名刺サイズの図形に画像とアカウントを貼り付ける
*    その画像をDriveに保存
*    A4サイズに設定したパワポに貼り付け
*    コンビニで印刷。ちょきちょき

# つくります！
※詳しくコードを見たい方はこちら
と思いましたが、全体後悔になるとのことなので消しちゃいました。DXQuestの人はSlackでDM頂ければ共有します。

### 要素の取得
スクレイピングのことを話すと書ききれないので、基本的な概念やコツだけ書きます。
Webの情報は大きく分けるとHTML（文字や画像のみ）とCSS（文字サイズや色付けなど脚色するもの）の2つから成り立ってます。主にHTMLの情報から取り出したい文字列などを抽出するのがスクレイピングです。

要素の取り方はWindowsだとF12キーを押し、この赤枠アイコンを押して目的の箇所にマウスを重ねると、Web上でどういう名前が付けられているかなどの情報が分かります。
<br>
![図9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/d4cc4a57-e80f-02c7-1f2c-62e7add7fa69.png)

<br>
この名前などを頼りに、欲しい文字列や画像を一意に（他にカブることなく）取得します。

![図10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/e36f3c2e-f3f2-4087-b8f8-0a0a47765de0.png)


### 画像の取得
このDM部屋で名札を作っていくことにします。<font color="Red">ヒナタさんとノタさんと僕の3人部屋です。</font>
まずは画像を取得します。「DM部屋やし、3人おったら3つしか画像出んやろ」と都合の良い解釈をしていました。

###     理想：この3つの画像だけが出て来るやろ
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/2b68caa3-f217-fd5d-3d44-6e3c55d14800.png)

<br>

###     現実：**<font color="Red">なんでいっぱい出て来るんや</font>**
![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/38f88ca3-679d-a654-af7c-c1eb66dac480.png)


**なんでおるねんって人がいっぱい出てきました。本当はもっといろんな人がいっぱいいましたがアドカレに載せる都合上見えなくしてます。3人部屋なはずなのにおかしいな。
まあMiyanoさんは色んな所にいらっしゃる方なので、ここにいても全然何も不思議ではないですね。このまま進めます。**
後々ヒナタ師匠から教えて頂いたのですが、アイコン画像全部を取得しちゃってるのでは？とのことでした。DMしてた人のアイコンが出てたので、たしかにそうっぽい。。

<br>
いっぱい出てきた人の中から目的の人、かつ大きい画像を取得します。
僕を含む3人部屋なんですが、大きい画像の僕がいません。僕は一旦置いといて、ヒナタ師匠やノタ様を優先して作ることにします。


### Slackアカウント名の取得
アカウント名はここから取ります。他にも取り方はあるかもですが、DM部屋全員分（何人か「その他」に含まれずに）取得できるところで最初に見つけたのがここでした。「@」を除去したりなど色々加工しながらも取得できました。
![図6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/5d922cd4-10e8-9402-da75-25c99b2a4c00.png)


### 画像とアカウント名の配置
あとは良い感じに配置するだけです。
長方形の画像の上に画像とアカウント名を載せます。配置の微調整が地味に難しかったです。。
![図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/d22502f4-231c-bb56-42de-ba44a9c50c3a.png)


<br>
まずはアイコン画像を左の方に配置します。
その後にアカウント名の記載されたテキストボックスを良い感じに配置します。配置は横座標が特に難しかったです。
このテキストボックスを


```math
(画像の右端～下地にした長方形の右端の中点座標) = (テキストボックスの中心座標)
```

になるように計算します。

つまり、数式に起こすとこの式で理論上は配置がバランスよくなるはずです。使っている各図形の右端の座標、サイズ、に着目して、text_xというテキストボックスの座標を定義します

    # テキストのX座標（画像とのバランスを考慮）
    text_x = (icon_x + icon_size + (nametag_width - (icon_x + icon_size) - text_width) // 2)


こんな感じになりました。

コードを実装して名札化してみても良い感じです。
![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/2ca17c17-d7ce-ce3f-1906-8ce50cacbebe.png)


### 細かな修正
上記では問題の起こるケースもあります。それは名前の長い人への対処です。
色々実験した結果、半角英数だと11文字以上、日本語のようなマルチバイト文字だと6文字以上で画像と重なったり、名札からはみ出してしまうことが分かりました。
![図8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/34be7b4e-dea8-5615-9b11-650555669d08.png)



対処としてはマルチバイト文字であるかどうかで条件分岐し、更にその後文字列長でフォントサイズを変えるかどうかの条件分岐させます。
具体的にどういうことかというと、こんな感じです。

    # 名前を名札の右側に追加

    # マルチバイト文字がある場合（日本語とか）
    if len(name) != len(name.encode('utf-8')):


        # 文字列長が5文字より多い場合の分岐
        if len(name) > 5:
            # テキストのフォントサイズを決定
            # font_size = 35
            font_size = 50
            font = ImageFont.truetype(font_path, font_size)

            # テキストの幅と高さを計算
            text_width, text_height = draw.textsize(name, font=font)

            # テキストのX座標（画像とのバランスを考慮）
            text_x = (icon_x + icon_size + (nametag_width - (icon_x + icon_size) - text_width) // 2)

            # テキストのY座標
            text_y = (nametag_height - text_height) // 2

        # 文字列長が5文字以下の分岐
        else:
            # テキストのフォントサイズを決定
            # font_size = 50
            font_size = 70
            font = ImageFont.truetype(font_path, font_size)

            # テキストの幅と高さを計算
            text_width, text_height = draw.textsize(name, font=font)

            # テキストのX座標（画像とのバランスを考慮）
            text_x = (icon_x + icon_size + (nametag_width - (icon_x + icon_size) - text_width) // 2)

            # テキストのY座標
            text_y = (nametag_height - text_height) // 2



    # マルチバイト文字がない場合（半角英数）
    else:

        # 文字列長が8文字より多い場合の分岐
        if len(name) > 8:

            # テキストのフォントサイズを決定
            # font_size = 35
            font_size = 50
            font = ImageFont.truetype(font_path, font_size)

            # テキストの幅と高さを計算
            text_width, text_height = draw.textsize(name, font=font)

            # テキストのX座標（画像とのバランスを考慮）
            text_x = (icon_x + icon_size + (nametag_width - (icon_x + icon_size) - text_width) // 2)

            # テキストのY座標
            text_y = (nametag_height - text_height) // 2

        # 文字列長が8文字以下の分岐
        else:
            # テキストのフォントサイズを決定
            # font_size = 50
            font_size = 70
            font = ImageFont.truetype(font_path, font_size)
            # テキストの幅と高さを計算

            text_width, text_height = draw.textsize(name, font=font)

            # テキストのX座標（画像とのバランスを考慮）
            text_x = (icon_x + icon_size + (nametag_width - (icon_x + icon_size) - text_width) // 2)

            # テキストのY座標
            text_y = (nametag_height - text_height) // 2

<br>
<br>

これを実装しました。画像はノタさんとヒナタさんのものをお借りしつつ、名前を仮のものにして検証してみます！

![図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/9f19928d-66cb-36c5-6f43-865e654be682.png)


### **やったわね:laughing:**
本当はもっといろいろ紆余曲折あったので、細かな経緯とか見てみたい方はご一報ください（そんな人おらんと思うけど）

# 作った画像をパワポに貼ってコピー。その後
ここで気付いたのですが、画像を良い感じに何cm x 何cmで調整していたとしても、パワポに画像挿入ときには何倍かに拡大されてしまいます。対処法などあったのかもしれませんが、調べきれずわかりませんでした。。
<br>
なので、縦横比だけ目的の画像と合わせておいて、パワポで画像挿入時に倍率変更することでちょうどいいサイズに変更しました。全選択して一括変更できるのでラクでした。

印刷して切り取ると、最初にお見せした通りこんな感じです。

![図1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884129/89b94d10-772a-eb8f-6725-2cb8b0865b84.jpeg)


### やったわね:laughing:


# やらなかったこと
*    1枚のA4の紙に複数人分の名札を貼り、この時の貼り方の最適解を求める
*    「これ手作業でやった方が早くない？」と思った方への対処

1枚の紙に貼り付けるところは手作業でした。pythonで色々やってるときに、A4サイズの長方形を用意し、作成した名刺画像を一番A4用紙を無駄にしない形で（余白面積が小さくなるように最適化して）画像を配置するとろまでは思いつきましたが、実装は面倒だと諦めました。昨年度のどこかで似た感じのプログラムを作っているのを見せて頂いたので、勉強したくなったらいつかやってみようと思います。
pulpでも良い感じのライブラリでも、何かしらの形で最適化問題は解ける気がします。


「これ手作業でやった方が早くない？」と思った方もいらっしゃると思います。ところが、突然数十人規模のオフ会が次にいつ来ても名札が作れる安心感を得ることができます。
これはまたやるしかないですね！関東以外にお住まいの方、みんなで日程を合わせて出張を入れて都内に集まりましょう！

# おわりに
以上です。今後もQuestの皆さんとも仲良くできたら嬉しいです。
以下宣伝のYouTubeのチャンネル登録もよろしくお願い致します！:heart_eyes:

# とても大事な宣伝
**宣伝させてください。**
統計＋Python講座というリレー講座を企画させて頂きました。Quest修了生の皆様が活躍されています。ご視聴＆チャンネル登録よろしくお願いします。統計+Python講座はSlackで限定公開となってますので、そちらからどうぞ。

**統計検定2~3級を目指す方が対象の内容となっています！統計検定だけ頑張りたい人はPythonパートは飛ばして、Pythonも勉強したい方は演習部分も見ると良いと思います。**
https://www.youtube.com/@mmurata4315/videos