読者です 読者をやめる 読者になる 読者になる

試される大地から

furaiboが送る技術ブログ。プログラミングのTipsなど書いていきます。

Pythonで画像をダウンロードするGUIアプリケーションを書いた


先の日記で述べた画像ダウンロードスクリプトを発展させたGUIアプリケーションを書いてみました。GUIはwxPythonで作っています。Windowsでも使えるようにexeファイル化したいと思い、いろいろ試しましたが結局挫折しました・・・Windows版のツールは近々C#で作りなおそうと考えています。



そして、出来たアプリの外見がこちら。


f:id:incodethx3932:20131218151509p:plain




わりとよくまとまったと思います。ソースコードは以下のようになりました。



・main.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

# パッケージのインポート
import wx
import os

# 他ファイルのインポート
import download

# 変数
title            = "Imagecollector"			# wxPythonウィンドウのタイトル
default_img_path = os.getcwd() + "/downloads"		# デフォルトのダウンロード画像の保存パス
filter_width     = "100"				# ダウンロード画像の幅の下限
filter_height    = "100"				# ダウンロード画像の高さの下限
webpage_url      = ""					# WebページのURL



# ウィンドウを表示するクラス
class MakeWindow(wx.App):

	# 関数定義
	def OnInit(self):
		self.init_frame()
		return True

	# フレームの初期化
	def init_frame(self):

		# メインのフレーム
		self.frm_main = wx.Frame(None)
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		self.sizer.Add((-1, 12))

		# 文面の表示
		self.sizer0 = wx.BoxSizer(wx.HORIZONTAL)

		self.header_st1 = wx.StaticText(self.frm_main, label="ダウンロード設定", size=(160, 20))
		self.sizer0.Add(self.header_st1, 0, wx.LEFT, 15)
		self.header_st2 = wx.StaticText(self.frm_main, label="", size=(260, 20))
		self.sizer0.Add(self.header_st2, 0, wx.LEFT, 0)		

		self.sizer.Add(self.sizer0, 0)
		self.sizer.Add((-1, 5))


		# URL入力部分
		self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)

		self.st1 = wx.StaticText(self.frm_main, label="URL : ", size=(60, 30))
		self.sizer1.Add(self.st1, 0)
		self.txt_url = wx.TextCtrl(self.frm_main, -1, size=(330, 30))
		self.sizer1.Add(self.txt_url, 1)
		self.btn_submit1 = wx.Button(self.frm_main, -1, "クリア", size=(60, 30))
		self.btn_submit1.Bind(wx.EVT_BUTTON, self.on_submit1)
		self.sizer1.Add(self.btn_submit1, 0)
		self.sizer.Add(self.sizer1, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
		

		# ファイル選択部分
		self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)

		self.st2 = wx.StaticText(self.frm_main, label="保存先 : ", size=(60, 30))
		self.sizer2.Add(self.st2, 0)
		self.select_path = wx.TextCtrl(self.frm_main, -1, size=(330, 30))
		self.select_path.SetValue(default_img_path)
		self.sizer2.Add(self.select_path, 1)
		self.btn_submit2 = wx.Button(self.frm_main, -1, "変更", size=(60, 30))
		self.btn_submit2.Bind(wx.EVT_BUTTON, self.on_submit2)
		self.sizer2.Add(self.btn_submit2, 0)
		self.sizer.Add(self.sizer2, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
		self.sizer.Add((-1, 10))		


		# 画像サイズのしきい値を設定
		self.sizer3 = wx.BoxSizer(wx.HORIZONTAL)

		self.st3 = wx.StaticText(self.frm_main, label="画像サイズフィルタ : ", size=(140, 30))
		self.sizer3.Add(self.st3, 0)
		self.st3_1 = wx.StaticText(self.frm_main, label="縦", size=(20, 30))
		self.sizer3.Add(self.st3_1, 0)
		self.txt_height = wx.TextCtrl(self.frm_main, -1, size=(20, 30))
		self.txt_height.SetValue(filter_height)
		self.sizer3.Add(self.txt_height, 1)
		self.st3_2 = wx.StaticText(self.frm_main, label="px以上 ", size=(50, 30))
		self.sizer3.Add(self.st3_2, 0)
		self.st3_3 = wx.StaticText(self.frm_main, label=" 横", size=(20, 30))
		self.sizer3.Add(self.st3_3, 0)
		self.txt_width = wx.TextCtrl(self.frm_main, -1, size=(20, 30))
		self.txt_width.SetValue(filter_width)
		self.sizer3.Add(self.txt_width, 1)
		self.st3_4 = wx.StaticText(self.frm_main, label="px以上", size=(50, 30))
		self.sizer3.Add(self.st3_4, 0)
		self.sizer.Add(self.sizer3, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
		self.sizer.Add((-1, 20))


		# 画像取得ボタン
		self.sizer4 = wx.BoxSizer(wx.HORIZONTAL)
		self.btn_submit3 = wx.Button(self.frm_main, -1, "画像を取得", size=(470, 80))
		self.btn_submit3.Bind(wx.EVT_BUTTON, self.on_submit3)
		self.sizer4.Add(self.btn_submit3, 0)
		self.sizer.Add(self.sizer4, 0, wx.LEFT, 15)


		# 表示
		self.frm_main.SetSizer(self.sizer)
		self.frm_main.SetTitle(title)
		self.frm_main.SetSize((500, 280))
		self.frm_main.Show()

		

	# ボタンが押下された時の挙動を決定する関数
	# テキストボックスの内容ををクリアする
	def on_submit1(self, event):
		# クリア
		self.txt_url.SetValue("")

	# ファイル選択のボックス
	def on_submit2(self, event):
		# デフォルトの画像保存パスを作成
		if not os.path.exists(default_img_path):
			os.mkdir(default_img_path)
			os.chmod(img_path, 0777)

		# ディレクトリ選択のダイアログを表示
		dialog = wx.DirDialog(None, message="Choose a directory:", defaultPath=default_img_path,style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
		if dialog.ShowModal() == wx.ID_OK:
			self.select_path.SetValue(dialog.GetPath())
		dialog.Destroy()

	# 画像を取得する
	def on_submit3(self, event):
		# テキストボックス内のURLを示す文字列を取得		
		webpage_url = self.txt_url.GetValue()

		# ダウンロード画像の保存パスを取得
		img_path = self.select_path.GetValue().encode('utf-8')

		# フィルタリングの基準サイズを取得
		# 値が入力されているかどうかを確認
		if self.txt_height.GetValue() == "" or self.txt_width.GetValue() == "":
			result = "Error! : 画像フィルタのサイズ値が未入力です"
			print result
			self.header_st2.SetLabel(result)
			return

		# 例外処理
		try:
			filter_height = int(self.txt_height.GetValue().encode('utf-8'))
			filter_width  = int(self.txt_width.GetValue().encode('utf-8'))
		except:
			result = "Error! : 画像サイズには整数値を入力してください"
			print result
			self.header_st2.SetLabel(result)
			return


		# テキストボックスに入力されたURLのWebページない画像ファイルを取得
		result = download.image_collect(webpage_url, img_path, filter_height, filter_width)

		# 結果の表示
		print result
		self.header_st2.SetLabel(result)




# ウィンドウを表示する
if __name__ == "__main__":
	
	# ウィンドウを表示
	app = MakeWindow(False)
	app.MainLoop()



・download.py

#!/usr/bin/python
#-*- coding:utf-8 -*-

# パッケージのインポート
import os
import sys
import re
import urllib2
import codecs
import StringIO
import gzip
import Image
from datetime import datetime
from os.path import join, getsize


# 関数定義
# URLを元に画像をダウンロードする
def image_download(img_url, output):

	# HTTPリクエスト
	opener = urllib2.build_opener()
	req = urllib2.Request(img_url, headers={'User-Agent' : "Magic Browser"})

	# もし同名の画像ファイルが存在するならば、上書きは行わない
	if not os.path.exists(output):
		img_file = open(output, 'wb')
		img_file.write(opener.open(req).read())
		img_file.close()



# 画像サイズフィルタ
def image_size_filter(img_path, filter_height, filter_width):

	# 画像サイズを取得
	img = Image.open(img_path)
	img_width  = img.size[0]
	img_height = img.size[1]

	# 画像の削除
	if img_width < filter_width or img_height < filter_width:
		os.remove(img_path)



# HTMLページの取得・タグの抜き出しの処理後、画像収集を行う
def image_collect(html_url, img_path, filter_height, filter_width):
	
	# 変数
	title    = ""						# Webページのタイトル
	img_tag  = []						# imgタグのリスト
	img_url  = []						# 画像URLのリスト

	# 正規表現
	pat_title   = re.compile('<title>(.*?)</title>')		# ページタイトルを抜き出す
	pat_a1      = re.compile('<a[\s]*href[\s]*=.*?>')	# aタグを抜き出す
	pat_a2      = re.compile('href[\s]*="(.*?)"')		# URL先を抜き出す
	pat_img1    = re.compile('<img[\s]*src[\s]*=.*?>')	# imgタグを抜き出す
	pat_img2    = re.compile('src[\s]*="(.*?)"')		# 画像元URLを抜き出す
	pat_img3    = re.compile('.+/(.*)')			# 画像ファイル名をURLから決定
	pat_imgname = re.compile('^(.+)\.([a-zA-Z]*)$')		# 拡張子抜きの画像ファイル名を抜き出す
	img_format  = [".jpg", ".png", ".gif", ".bmp"]		# 画像ファイル形式


	# URLが空かどうかを判定する
	if html_url == "":
		return "Error! : URLが入力されていません"
	# URLが有効であるかどうかの確認
	elif re.match('^http://', html_url) is None and re.match('^https://', html_url) is None:
		return "Error! : 有効なURLではありません"
	

	# 例外処理
	# HTMLページを取得しリストに格納
	# 失敗時にはエラーメッセージのみ返す
	try:
		# HTTPリクエスト
		req = urllib2.Request(html_url, headers={'User-Agent' : "Magic Browser"})
		con = urllib2.urlopen(req)
		html = con.read()
		
		# body部分がgzip化されている場合の処理
		if con.info().get('Content-Encoding') == 'gzip':
			data = StringIO.StringIO( html )
			gzipper = gzip.GzipFile(fileobj=data)
			html = gzipper.read()

	except urllib2.URLError, e:
		return "Error! : URLエラーにより画像を取得できません"
	except urllib2.HTTPError, e:
		return "Error! : HTTPエラーにより画像を取得できません"


	# ページタイトルを取得
	m = pat_title.search(html)
	
	# タイトルをディレクトリ名とする
	# 取得オブジェクトがNoneでない場合に処理を行う
	if not m is None:
		title = m.group(1)
	else:
		d = datetime.now()
		title = d.strftime('%Y%m%d%H%M%S')

	# 画像をダウンロードするディレクトリ名
	dl_path = "%s/%s"%(img_path, title)

	# ページタイトルと同名のディレクトリを作成する
	if not os.path.exists(dl_path):
		os.makedirs(dl_path)

	# ディレクトリ権限の変更
	os.chmod(dl_path, 0777)

	# 正規表現を利用してaタグ, imgタグを抜き出してリストに格納
	a_tag   = pat_a1.findall(html)
	img_tag = pat_img1.findall(html)

	# aタグのリストから画像のURLを抜き出す
	for i in a_tag:
		# URLを抜き出す
		m = pat_a2.search(i)
	
		# 取得オブジェクトがNoneでない場合に処理を行う
		if not m is None:
			# URLの取得
			tmp = m.group(1)
			
			# 画像フォーマットが一致すればリストに追加
			for j in img_format:
				if tmp.find(j) > -1:
					img_url.append(tmp)
					break


	# imgタグのリストから画像のURLを抜き出す
	for i in img_tag:
		# URLを抜き出す
		m = pat_img2.search(i)

		# 取得オブジェクトがNoneでない場合に処理を行う
		if not m is None:
			tmp = m.group(1)
	
			# 画像フォーマットが一致すればリストに追加
			for j in img_format:
				if tmp.find(j) > -1:
					img_url.append(tmp)
					break


	# サムネイル画像のURLを除去
	for i in img_url:
		# 画像URLを抜き出す
		m = pat_imgname.search(i)

		# 取得オブジェクトがNoneでない場合に処理を行う
		if not m is None:
			tmp1 = m.group(1)
			tmp2 = m.group(2)
			thumb_name = "%ss.%s"%(tmp1, tmp2)
	
			# もし一致するURLがあれば除去する
			for j in img_url:
				if thumb_name == j:
					img_url.remove(j)
					break


	# 画像URLのリストから実際の画像をダウンロードする
	for i in img_url:
		# 画像ファイル名を決定
		m = pat_img3.search(i)
		name = m.group(1)
		output = "%s/%s"%(dl_path, name)
	
		# 画像のダウンロード
		# 例外処理
		try:
			image_download(i, output)
		except Exception:
			result = "Error! : urllib2でエラーが発生したため、実行を停止します"
			print result

		# 画像ファイルの権限を変更する
		#os.chmod(output, 0777)

		# 画像ファイルのサイズでフィルタリング
		# 例外処理
		try:
			image_size_filter(output, filter_height, filter_width)
		except Exception:
			result = "Error! : PILでIOエラーが発生したため、実行を停止します"
			print result
			

	# 成功のメッセージ
	return "Success! : 画像の取得が完了しました"




Githubのリポジトリも作成しました。

https://github.com/furaibo/imagecollector_linux




追記(2013/12/26)

ソースコードの一部を修正しました。これにより、一部エラーが修正され、大きい画像とサムネイル画像が重複してダウンロードされる問題が解決されています。