質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.87%

tkinter 画像

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,378

ads3hcgff

score 14

 tkinterでwebから画像をスクレイピングする部分を関数化してキャンパスに描写できない

tkinterでボタンをクリックしたらwebから画像を習得したいのですが、毎回webからスクレイピングを
するのは負担がかかると思い↓のように関数化したのですがなぜか関数化すると画像がキャンパスに描写されませんでした。普通に書くと問題なく描写されるのですが、それだと毎回通信をして画像をwebからスクレイピングしているのかと思いボタンの中で関数化しました。
そもそもtkinterのループの仕組みがわかっていないからなのか、とりあえずリファレンスを読みましたが手抜きのあまりにもひどいリファレンスだったので全く理解できず・・・

##関数化の部分
def imageprint(url):

    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0",
        }

    request = urllib.request.Request(url=url, headers=headers)


    f = io.BytesIO(urllib.request.urlopen(request).read())

    img = Image.open(f)

    img.thumbnail((200, 200), Image.ANTIALIAS)
    img = ImageTk.PhotoImage(img)  # 表示するイメージを用意  

    return img 
##こちらが全ソース
import tkinter as tk
from PIL import Image, ImageTk
import urllib.request 
import io

from PIL import Image 
'''
web上にある画像を保存します
'''
img =""
url = "http://imgcc.naver.jp/kaze/mission/USER/20180519/35/3012705/157/500x718xedab7ba7e203cd7576d12004.jpg"
root = tk.Tk()
root.geometry('800x560')
root.title('IMG')
canvas = tk.Canvas(
    root, # 親要素をメインウィンドウに設定
    width=500,  # 幅を設定
    height=500, # 高さを設定
    #relief=tk.RIDGE  # 枠線を表示
    # 枠線の幅を設定
    bg = "black"
    )
canvas.place(x=0, y=0)  # メインウィンドウ上に配置

def btn_click_1_1():
    global url
    global img
    img = imageprint(url)
    print(img)
# ボタンを表示 command=処理
button= tk.Button(text="button",command=btn_click_1_1)
button.place(x=0, y=200)


def imageprint(url):

    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0",
        }

    request = urllib.request.Request(url=url, headers=headers)


    f = io.BytesIO(urllib.request.urlopen(request).read())

    img = Image.open(f)

    img.thumbnail((200, 200), Image.ANTIALIAS)
    img = ImageTk.PhotoImage(img)  # 表示するイメージを用意  

    return img 


canvas.create_image(  # キャンバス上にイメージを配置
            0,  # x座標
            0, # y座標
            image=img,  # 配置するイメージオブジェクトを指定
            tag="illust",  # タグで引数を追加する。
            anchor=tk.NW  # 配置の起点となる位置を左上隅に指定
    ) 

root.mainloop()
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • hayataka2049

    2018/09/17 03:17

    ?  imageprintを使っているので関数化したコードだと思ったのですが、違うのですか?

    キャンセル

  • ads3hcgff

    2018/09/17 03:49

    imageprintが、スクレイピングを関数化してそれをボタンが押されたらコールバック関数としたんです。

    キャンセル

  • ads3hcgff

    2018/09/17 17:13

    いまさらですが、関数化していないコードの意味が分かりました。すいませんもう関数化してしまったので元のコードはないです。ただ、もう一つtkinterで画像を表示させるときに注意しなければならないことが見つかったのでそれを回答に載せておきます。

    キャンセル

回答 2

checkベストアンサー

+1

btn_click_1_1関数にcanvas.create_imageの呼び出し文がないです。
そのため、変数:imgに値を格納しても画面の更新が行わなれないかと。

def btn_click_1_1():
    global url
    global img
    img = imageprint(url)
    # canvas.create_imageの行を追加root.mainloopのcanvas.create_imageは不要です。
    canvas.create_image(  # キャンバス上にイメージを配置
            0,  # x座標
            0, # y座標
            image=img,  # 配置するイメージオブジェクトを指定
            tag="illust",  # タグで引数を追加する。
            anchor=tk.NW  # 配置の起点となる位置を左上隅に指定
    ) 
    print(img)

毎回webからスクレイピングをするのは負担がかかる

pathlibを使って画像をローカルに保存するという手(※)もありますが。
hashlibを使って、urlからハッシュ関数の出力結果を求め、保存ファイル名として使用すればよいかと。

同じURLに対してプログラム起動中はキャッシュを行いたいだけならば。
functools.lru_cacheが使えます。

# -*- coding: utf-8 -*-
"""
web上にある画像を保存します
"""
import tkinter as tk
from PIL import Image, ImageTk
import urllib.request
from io import BytesIO
from functools import lru_cache
from PIL import Image


@lru_cache(maxsize=None)
def get_content(url: str):
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0",
        }
    request = urllib.request.Request(url=url, headers=headers)
    print("content")
    return BytesIO(urllib.request.urlopen(request).read())


def imageprint(url):
    img = Image.open(get_content(url))
    img.thumbnail((200, 200), Image.ANTIALIAS)
    img = ImageTk.PhotoImage(img)  # 表示するイメージを用意
    return img


img =""
url = "http://imgcc.naver.jp/kaze/mission/USER/20180519/35/3012705/157/500x718xedab7ba7e203cd7576d12004.jpg"
root = tk.Tk()
root.geometry('800x560')
root.title('IMG')
canvas = tk.Canvas(
    root, # 親要素をメインウィンドウに設定
    width=500,  # 幅を設定
    height=500, # 高さを設定
    #relief=tk.RIDGE  # 枠線を表示
    # 枠線の幅を設定
    bg = "black"
    )
canvas.place(x=0, y=0)  # メインウィンドウ上に配置

def btn_click_1_1():
    #global url # 再代入を伴わないので、globalはコメントアウト
    global img
    img = imageprint(url)
    print(img)
    canvas.create_image(  # キャンバス上にイメージを配置
        0,  # x座標
        0,  # y座標
        image=img,  # 配置するイメージオブジェクトを指定
        tag="illust",  # タグで引数を追加する。
        anchor=tk.NW  # 配置の起点となる位置を左上隅に指定
    )
# ボタンを表示 command=処理
button= tk.Button(text="button",command=btn_click_1_1)
button.place(x=0, y=200)

root.mainloop()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/17 03:53

    かぶった。

    キャンセル

  • 2018/09/17 03:56

    回答ありがとうございます。最初のコードおっしゃる通りです。
    Tk()でインスタンス化してからloopのなかのwigetを毎回読み込んでいると思っていましたが違うようですね。すいません。把握漏れです。2つ目のソースはあまり理解していません。取り合えずローカルには保存せず、メモリに保存して展開したかったのですが・・・url.openでメモリ上にロードしまくるとGCとかしてくれないんでしょうか。urllibの仕組みがよく分かっておらず・・・

    キャンセル

  • 2018/09/17 04:05

    @ads3hcgffさんへ
    >メモリに保存して展開
    urllibは毎回HTTPリクエストをしますよー。
    https://docs.python.jp/3/library/urllib.request.html#module-urllib.request
    というかHTTPリクエストを勝手にキャッシュしてレスポンスを返すものは他言語でも少ないような。

    キャンセル

  • 2018/09/17 04:09

    ありがとうございます。精進します。

    キャンセル

0

すいません。できました
create_imageを関数化したコードの中にいれればよかったみたいです。

tk()でインスタンス化してからloopのなかは実は一回しか読み込まれてない?
試しにcreate_imageを関数の外に出したら無理でした。

 追記

勝手ながらもう一つ重要な項目が見つかったので載せておきます。
こちらの記事

create_canvasの引数 imageにはローカル変数ではだめで、グローバル変数を入れなきゃだめらしい
ここに入れるのはImageTk.PhotoImage()で作成したオブジェクトなんですが
どうやらこれをローカル変数に入れたままcanvas_create関数に入れるとうまく動きません。
理由はImageTk.PhotoImage()をローカルに入れると一瞬でGCが起きるらしいです。
意味不明ですね、この仕様・・・・
これはtkinterが悪いのかpythonの仕様上そうなってしまうのかそれともPILが悪いのか謎・・・
根本的な理由はtkinterにあるらしいのですが・・・やれやれw

    canvas.create_image(  # キャンバス上にイメージを配置
                0,  # x座標
                0, # y座標
                image=img,  ここをローカル変数に入れたまま代入すると動きませんw無能すぎる・・
                anchor=tk.NW  # 配置の起点となる位置を左上隅に指定
                tag="illust", 
        ) 

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる