見出し画像

【MTG】公式APIでカードデータ収集 ~《焚書》への備え~

先般、Magic:the Gatheringにおいて全く慮外の禁止カード発表が行われた。
(※この記事は全文公開です)

https://mtg-jp.com/reading/publicity/0034052/

これについて言いたいことは山ほどあるが、言いたいことも言えないこんな世の中じゃ、ポイズン(´・ω・`)

そういう訳で、この問題自体に今は触れないが、これにあたり今後も同様の、あるいは類似の事由によって禁止されるカードが出るのではないかと危惧している。
いや、単にゲームでの使用が禁止されるだけでも大きな懸念だが、それ以上に存在自体が《なかったことに》されるとなれば最早安穏としていられない。
実際に今回の禁止によってすでに公式から消えてしまったカードがあるうえ、一部のアーティストについての契約終了まで発表されてしまっている。

MTGはゲームとしての楽しみはもちろん、カードを集めることや背景ストーリーやキャラクターを楽しむこと、そしてカードに描かれたイラストを楽しむこともできる。
もし、この先、二度と目にすることができないカードが増えうる虞があるのであれば、少なくとも最低限である現在のうちに対策したいと考えた。

つまり、少なくともイラストを目にすることが出来るようにと考えた。

今回は公式のAPIを利用して公式のカードデータを収集する方法を紹介しようと思う。なお、公式のAPIについては次のリンクで確認ができる。自力で準備できる人は得意な言語を選んでやるといいだろう。

https://docs.magicthegathering.io/


Python

■インストーラーのDL

今回は手軽に利用できるという点でPythonを利用する。
バージョンは最新ではないが、筆者の環境に合わせて3.7.5をインストールする。

https://www.python.org/downloads/release/python-375/

ページ下部のリンクから「Windows x86 executable installer」を選択し、『python-3.7.5-amd64.exe』をDLする。

画像1


■Pythonをインストールする

DLした『python-3.7.5-amd64.exe』を実行し、次の番号順に操作する。
①環境パスの設定にチェックを入れる
②インストールを実行

画像2

インストール完了後は [Close] を押してウィンドウを閉じる。


公式ライブラリ

■公式ライブラリをインストールする

コマンドプロンプトを実行する。
何かわからない人は、次の通りにキーを入力する。
・Windowsキー+R
・[cmd] と入力し、Enter
コマンドプロンプトが立ち上がったら、

pip install mtgsdk

を入力し、Enter。


■カード画像を収集する

任意のテキストエディタ(メモ帳でも可)に次のコードを記述する。
記述後、ファイルをUTF-8形式で拡張子を「.py」として任意の名前で保存する。
※この記事では「fetch_cardImages.py」とする

import mtgsdk
import pickle

import urllib

from mtgsdk import Card
from mtgsdk import Set

import codecs
import string

import os
import re

from tkinter import messagebox

print( "Hello, Python" ) 

# エキスパンション・セット情報
sets = Set.all()

# イメージデータが一つも取れないセット(スキップ対象)
skipSet = {
	"AMH1":	"NoImage",	"ANA":	"NoImage",	"ARN":	"NoImage",	"ATH":	"NoImage",	"FMB1":	"NoImage",
	"C20":	"NoImage",	"CED":	"NoImage",	"CEI":	"NoImage",	"CP1":	"NoImage",	"CP2":	"NoImage",
	"CP3":	"NoImage",	"CST":	"NoImage",	"DKM":	"NoImage",	"DPA":	"NoImage",	"FNM":	"NoImage",
	"F01":	"NoImage",	"F02":	"NoImage",	"F03":	"NoImage",	"F04":	"NoImage",	"F05":	"NoImage",
	"F06":	"NoImage",	"F07":	"NoImage",	"F08":	"NoImage",	"F09":	"NoImage",	"F10":	"NoImage",
	"F11":	"NoImage",	"F12":	"NoImage",	"F13":	"NoImage",	"F14":	"NoImage",	"F15":	"NoImage",
	"F16":	"NoImage",	"F17":	"NoImage",	"F18":	"NoImage",	"FBB":	"NoImage",
	"G00":	"NoImage",	"G01":	"NoImage",	"G02":	"NoImage",	"G03":	"NoImage",	"G04":	"NoImage",
	"G05":	"NoImage",	"G06":	"NoImage",	"G07":	"NoImage",	"G08":	"NoImage",	"G09":	"NoImage",
	"G10":	"NoImage",	"G11":	"NoImage",	"G17":	"NoImage",	"H17":	"NoImage",	"HA1":	"NoImage",
	"HA2":	"NoImage",	"HHO":	"NoImage",	"HTR":	"NoImage",	"HTR1":	"NoImage",	"HTR1":	"NoImage",
	"IKO":	"NoImage",	"ITP":	"NoImage",
	"J12":	"NoImage",	"J13":	"NoImage",	"J14":	"NoImage",	"J15":	"NoImage",	"J16":	"NoImage",
	"J17":	"NoImage",	"J18":	"NoImage",	"J19":	"NoImage",	"J20":	"NoImage",	"JGP":	"NoImage",
	"L12":	"NoImage",	"L13":	"NoImage",	"L14":	"NoImage",	"L15":	"NoImage",	"L16":	"NoImage",
	"L17":	"NoImage",	"MB1":	"NoImage",	"MGB":	"NoImage",	"MPR":	"NoImage",
	"OC13":	"NoImage",	"OC14":	"NoImage",	"OC15":	"NoImage",	"OC16":	"NoImage",	"OC17":	"NoImage",
	"OC18":	"NoImage",	"OC19":	"NoImage",	"OCM1":	"NoImage",	"OCMD":	"NoImage",
	"OLGC":	"NoImage",	"OVNT":	"NoImage",
	"P02":	"NoImage",	"P03":	"NoImage",	"P04":	"NoImage",	"P05":	"NoImage",	"P06":	"NoImage",
	"P07":	"NoImage",	"P08":	"NoImage",	"P09":	"NoImage",	"P10":	"NoImage",	"P10E":	"NoImage",
	"P11":	"NoImage",	"P15A":	"NoImage",	"P2HG":	"NoImage",	"PAER":	"NoImage",	"PAKH":	"NoImage",
	"PAL00":"NoImage",	"PAL01":"NoImage",	"PAL02":"NoImage",	"PAL03":"NoImage",	"PAL04":"NoImage",
	"PAL05":"NoImage",	"PAL06":"NoImage",	"PAL99":"NoImage",	"PALP":	"NoImage",	"PANA":	"NoImage",
	"PARC":	"NoImage",	"PARL":	"NoImage",	"PAVR":	"NoImage",	"PBBD":	"NoImage",	"PBFZ":	"NoImage",
	"PBNG":	"NoImage",	"PBOK":	"NoImage",	"PCEL":	"NoImage",	"PCMD":	"NoImage",	"PCMP":	"NoImage",
	"PDGM":	"NoImage",	"PDKA":	"NoImage",	"PDOM":	"NoImage",
	"PDP10":"NoImage",	"PDP11":"NoImage",	"PDP12":"NoImage",	"PDP13":"NoImage",	"PDP14":"NoImage",
	"PDRC":	"NoImage",	"PDTK":	"NoImage",	"PDTP":	"NoImage",	"PELD":	"NoImage",	"PELP":	"NoImage",
	"PEMN":	"NoImage",	"PF19":	"NoImage",	"PF20":	"NoImage",	"PFRF":	"NoImage",
	"PG07":	"NoImage",	"PG08":	"NoImage",	"PGPX":	"NoImage",
	"PGRN":	"NoImage",	"PGRU":	"NoImage",	"PGTC":	"NoImage",	"PGTW":	"NoImage",	"PHEL":	"NoImage",
	"PHJ":	"NoImage",	"PHOU":	"NoImage",	"PHUK":	"NoImage",	"PI13":	"NoImage",	"PI14":	"NoImage",
	"PIDW":	"NoImage",	"PISD":	"NoImage",	"PJAS":	"NoImage",	"PJJT":	"NoImage",	"PJOU":	"NoImage",
	"PJSE":	"NoImage",	"PKLD":	"NoImage",	"PKTK":	"NoImage",	"PLGM":	"NoImage",	"PLNY":	"NoImage",
	"PLPA":	"NoImage",
	"PM10":	"NoImage",	"PM11":	"NoImage",	"PM12":	"NoImage",	"PM13":	"NoImage",	"PM14":	"NoImage",
	"PM15":	"NoImage",	"PM19":	"NoImage",	"PM20":	"NoImage",	"PMBS":	"NoImage",	"PMEI":	"NoImage",
	"PMH1":	"NoImage",	"PMPS":	"NoImage",
	"PMPS06":"NoImage",	"PMPS07":"NoImage",	"PMPS08":"NoImage",	"PMPS09":"NoImage",	"PMPS10":"NoImage",	"PMPS11":"NoImage",
	"PNAT":	"NoImage",	"PNPH":	"NoImage",	"POGW":	"NoImage",	"PORI":	"NoImage",
	"PPC1":	"NoImage",	"PPOD":	"NoImage",	"PPP1":	"NoImage",	"PPRO":	"NoImage",	"PR2":	"NoImage",
	"PRED":	"NoImage",	"PREL":	"NoImage",	"PRES":	"NoImage",	"PRIX":	"NoImage",	"PRM":	"NoImage",
	"PRNA":	"NoImage",	"PROE":	"NoImage",	"PRTR":	"NoImage",	"PRW2":	"NoImage",	"PRWK":	"NoImage",
	"PS11":	"NoImage",	"PS14":	"NoImage",	"PS15":	"NoImage",	"PS16":	"NoImage",	"PS17":	"NoImage",
	"PS18":	"NoImage",	"PS19":	"NoImage",
	"PSAL":	"NoImage",	"PSDC":	"NoImage",	"PSLD":	"NoImage",	"PSOI":	"NoImage",	"PSOM":	"NoImage",
	"PSS1":	"NoImage",	"PSS2":	"NoImage",	"PSS3":	"NoImage",	"PSUM":	"NoImage",
	"PSUS":	"NoImage",	"PTC":	"NoImage",	"PTG":	"NoImage",	"PTHB":	"NoImage",	"PTHS":	"NoImage",
	"PTKDF":	"NoImage",	"PUMA":	"NoImage",	"PURL":	"NoImage",	"PUST":	"NoImage",	"PWAR":	"NoImage",
	"PWCQ":	"NoImage",	"PWOR":	"NoImage",	"PWOS":	"NoImage",
	"PWP09":	"NoImage",	"PWP10":	"NoImage",	"PWP11":	"NoImage",	"PWP12":	"NoImage",	"PWPN":	"NoImage",
	"PWWK":	"NoImage",	"PXLN":	"NoImage",	"PXTC":	"NoImage",	"PZ1":	"NoImage",	"PZ2":	"NoImage",
	"PZEN":	"NoImage",	"REN":	"NoImage",	"RQS":	"NoImage",	"SLU":	"NoImage",	"SS3":	"NoImage",
	"SUM":	"NoImage",	"TBTH":	"NoImage",	"TD0":	"NoImage",	"TD2":	"NoImage",	"TDAG":	"NoImage",
	"TFTH":	"NoImage",	"THP1":	"NoImage",	"THP2":	"NoImage",	"THP3":	"NoImage",	"WC00":	"NoImage",
	"WC01":	"NoImage",	"WC02":	"NoImage",	"WC03":	"NoImage",	"WC04":	"NoImage",	"WC97":	"NoImage",
	"WC98":	"NoImage",	"WC99":	"NoImage",
}

# エラーログ用
logfile = codecs.open( '_log.log', 'a', 'utf-8' )

for s in sets:
	setCode = s.code
	try:
		a = skipSet[setCode]
		continue
		
	except:
		if setCode == "CON":
			setCode = "CONF"	# [CON]はWindowsの予約語でフォルダ名にできないため代用
			
		try:
			os.mkdir( setCode )
			
		except:
			print( '既存フォルダ:' + setCode )
			continue
		
	setCards = Card.where( set = s.code ).all()
	print("{} : {}".format( setCode, len( setCards ) ) )
	
	logfile.write( s.code + "\n" )
	
	for index, card in enumerate( setCards ):
		cName = card.name
		cName = cName.replace( '\"', '@', 2 )		# 《伏竜 孔明/Kongming, "Sleeping Dragon"》など対策(ダブルクォーテーション
		cName = cName.replace( ':', '__', 2 )		# 《~防御円/Circle of Protection: ~》など対策(コロン
		try:
			print( "{}_{} trystart".format( cName, str( index ) ) )
			imageUrlRequest = urllib.request.urlopen( card.image_url )
			imageData = imageUrlRequest.read()
			outFilename = setCode + "\\" +  s.code + "_" + card.number + "_" + cName + ".jpg"
			# print("{}".format( outFilename ) )
			outputFile = open( outFilename, "wb" )
			outputFile.write( imageData )
			outputFile.close()
			
		except:
			print( "Err {}\\{}_{}.jpg".format( setCode, card.number, cName ) )
			logfile.write( card.number + "_" + cName + ".jpg\n" )
			continue
			
	logfile.write( "\n" )


logfile.close()

print( "Good-bye, Python" )

・スクリプトの場所を確認する
処理が始まるとスクリプトと同じ階層にカードセットごとのフォルダを生成するので、デスクトップやドライブ直下以外の場所にスクリプトを移動させることをおすすめする。

・スクリプトを実行する
作成したスクリプトファイルをダブルクリックで実行すると処理が開始する。筆者の環境で試したところ、処理の完了までにおよそ20時間ほどかかった。
それでは遅すぎたので、イメージデータの無いセットをスキップするように改良したので、恐らくそれよりは早く終わると思うが検証していない。
外出前や就寝前などにPCの電源を入れたままに実行するのがよいと思う。

・スクリプトが完了する
処理中はカードセット名と取得中のカード名が表示される。
最後にGood-by Pythonが表示されれば正常に完了である。

・スクリプトがエラーした場合
可能な限りエラーはケアしたが100%完全ではないと思う。
Timeout系のエラーでスクリプトが終了してしまった場合、大抵は最後に生成したフォルダを削除し再度スクリプトを実行すると続きから処理が進むようになる。
もし解決しない場合は再現、解消ができるかは不明だが調査してみるので連絡をいただきたい。


■カードを確認する

全てが完了していれば、スクリプトと同じ場所にカードセットごとのフォルダとその中に画像データが入っている。
・注意点
1.カード名に「”」や「:」が使われているものはそれぞれ「@」と「__」に置き換えている
2.コンフラックスの略名は「CON」だがWindowsではCONは予約語のため使用できないので、CONFに置き換えている
3.イコリアのカードデータはまだない
4.プロモ系データはほとんど存在しない
5.イラスト違いカードでもイラスト違いになっていないものがある


■今後の展望

現在はカード画像をひたすら収集するだけで、しかも英語版に限られているが、日本語や他言語の画像やカードテキスト情報なども任意に選択し取得できるようにしたいと考えている。
アプリケーションとして展開できるかは未知数だが個人的な楽しみと実用性を求めてやってみようと思う。
現状のソースはもう少しデバッグと整理をしてからGitにでもあげようと思う。


■あとがき

私は悲しい。
ゲームはゲームなのだ。《Force of Will》や《ムーン・スプライト》など多くの人気カードのイラストを手掛けた Terese Nielsen女史の新しいイラストをこのゲームで見かけることはできなくなってしまった。
冒頭にも書いたが、この先どんな理由でこの魅力的で夢中に続けてきたゲームからそれが削ぎ落とされるかわからない。切に、これ以上そうならないことを切に願う。

それでは楽しいMTGライフを٩( 'ω' )و

………Pythonの勉強やモチベーション維持のため今回は有料設定にしてみようと思う。よろしくお願いいたします。

ここから先は

0字

¥ 100

期間限定 PayPay支払いすると抽選でお得に!

この記事が気に入ったらサポートをしてみませんか?