1. はじめに
社内の蔵書管理をするに当たって、手作業で目録を作ろうとしている人がいたので、ISBNのバーコードを写真で撮って書籍情報APIから必要な情報を取ってくるプログラムを作ってみたのでメモとして記載します。
(色々と似た記事はあるので有用性は不明です。。。)
また社内の蔵書が思った以上にISBNがない古い書籍が多かったのもあって、プログラムがないよりは楽ですが実用的にちょっと微妙な感じはありました…
環境
Python: 3.9.13
OS: Windows 10 Pro 64bit
2. 想定作業手順
- 蔵書目録を作成したい書籍の裏表紙のバーコードを全部撮影
- 写真を1つのフォルダに保存
- プログラムで保存フォルダを指定して、処理を実行 -> 蔵書目録CSVが出力される
3. バーコードからISBNの取得
Pythonでのバーコードの認識はpyzbar
、画像読み込みにpillow
が必要なので、pip install
pip install pyzbar pillow
pyzbar
でとりあえず本の裏表紙にあるバーコードを撮影した画像を読み込んで出力すると以下のような形式で出力されます。
from PIL import Image
import pyzbar.pyzbar as pyzbar
img = Image.open('img/barcode.jpg')
barcode = pyzbar.decode(img)
print(barcode)
[Decoded(data=b'1923055032804', type='EAN13', rect=Rect(left=677, top=151, width=178, height=532), polygon=[Point(x=677, y=158), Point(x=678, y=683), Point(x=710, y=683), Point(x=852, y=677), Point(x=854, y=676), Point(x=855, y=151), Point(x=831, y=151), Point(x=715, y=156)], quality=183, orientation='LEFT'),
Decoded(data=b'9784297108434', type='EAN13', rect=Rect(left=325, top=166, width=176, height=531), polygon=[Point(x=325, y=174), Point(x=326, y=697), Point(x=362, y=696), Point(x=418, y=694), Point(x=494, y=691), Point(x=498, y=690), Point(x=501, y=166), Point(x=491, y=166), Point(x=463, y=167), Point(x=439, y=168), Point(x=339, y=173)], quality=162, orientation='LEFT')]
pyzbar
は画像内のどこにバーコードが存在するかなどの情報についても出力されます。
しかし今回の目的としてはDecoded
内のdata
を抽出すればOKなので、以下のような表記でISBNのみ抽出できます。
(書籍裏表紙のバーコードは2列ありますが、ISBNは978 or 979で始まる方の値になります)
from PIL import Image
import pyzbar.pyzbar as pyzbar
img = Image.open('img/barcode.jpg')
isbn = [x.data.decode() for x in pyzbar.decode(img) if x.data.decode().startswith('97')][0]
print(isbn)
'9784297108434'
4. APIから書籍情報を取得
4.1. どのAPIを使うか
書籍情報を取得できるAPIとしては色々ありますが、今回は以下の3つを検討しました。
いくつか読み込んでみた結果としては、基本的にはopenBDの書籍情報の内容が最も充実していました。
しかしたまにopenBDにはないけど、GoogleBooksAPIや国立国会図書館サーチAPIには書籍情報が登録されている場合も存在したため、openBDを最初に読み込んで書籍情報がなければ、GoogleBooksAPIや国立国会図書館サーチAPIを使うのが良いと思っています。
ただ今回の実装においてはxmlをjsonに変換して追加するのが面倒になって、結局openBDとGoogleBooksAPIの2つで実装する形式にしています。
4.2. APIからの書籍情報取得
取得するデータはjsonで扱ったほうが楽なので、openBDとGoogleBooksAPIはそのままjsonとして扱いますが、国立国会図書館サーチはxml形式なのでjsonに変換するのが良いと思います(要pip install xmltodict
)。
import json
import urllib
import pprint
from PIL import Image
import pyzbar.pyzbar as pyzbar
img = Image.open('img/barcode.jpg')
isbn = [x.data.decode() for x in pyzbar.decode(img) if x.data.decode().startswith('97')][0]
url = f'https://api.openbd.jp/v1/get?isbn={isbn}'
# url = f'https://www.googleapis.com/books/v1/volumes?q=isbn:{isbn}'
urlopen = urllib.request.urlopen(url)
bookdata = json.loads(urlopen.read().decode('utf-8'))
pprint.pprint(bookdata)
import json
import urllib
import pprint
import xmltodict
from PIL import Image
import pyzbar.pyzbar as pyzbar
img = Image.open('img/barcode.jpg')
isbn = [x.data.decode() for x in pyzbar.decode(img) if x.data.decode().startswith('97')][0]
url = f'http://iss.ndl.go.jp/api/opensearch?isbn={isbn}'
urlopen = urllib.request.urlopen(url)
bookdata = xmltodict.parse(urlopen.read().decode('utf-8'))
pprint.pprint(bookdata['rss']['channel']['item'])
出力例
openBD
[{'hanmoto': {'datecreated': '2019-09-02 16:15:31',
'datekoukai': '2019-09-02',
'datemodified': '2019-09-25 16:15:59'},
'onix': {'CollateralDetail': {'SupportingResource': [{'ContentAudience': '01',
'ResourceContentType': '01',
'ResourceMode': '03',
'ResourceVersion': [{'ResourceForm': '02',
'ResourceLink': 'https://cover.openbd.jp/9784297108434.jpg',
'ResourceVersionFeature': [{'FeatureValue': 'D502',
'ResourceVersionFeatureType': '01'},
{'FeatureValue': '9784297108434.jpg',
'ResourceVersionFeatureType': '04'}]}]}],
'TextContent': [{'ContentAudience': '00',
'Text': 'データサイエンスの認知の高まりとともに、データ分析に関するコンペティションが多数開催されるようになってきました。最も有名なコンペティションプラットフォームであるKaggleにおけるプレイヤー数は10万人を超え、多くのエンジニアが自分の腕を試すためにコンペティションに参加しています。分析コンペでは、実際のデータを扱うため、機械学習の解説書にはあまり載っていないような手法やテクニックが数多く活用されています。これらを理解し自身で使えるようにしておくことはコンペだけでなく、実務でのモデル構築において非常に役に立ちます。\n'
'そこでこれらのテクニックや事例を多くの人に知っていただくために、現時点で最新のものを整理して本書にまとめました。特徴量の作り方、バリデーション、パラメータチューニングなどについて、一般的な書籍ではあまり言及されない暗黙知やポイントについて記述しています。分析コンペにこれから参加してみたい方、あるいはもっと上を目指したい方だけでなく、実務で予測モデルの精度を上げたいという方にも参考になる情報が多いでしょう。',
'TextType': '03'},
{'ContentAudience': '00',
'Text': '第1章\u3000分析コンペとは?\n'
'1.1 分析コンペって何?\n'
' 1.1.1 何をするものか\n'
' 1.1.2 '
'予測結果の提出と順位表(Leaderboard)\n'
' 1.1.3 チームでの参加\n'
' 1.1.4 入賞賞金・特典\n'
'1.2 分析コンペのプラットフォーム\n'
' 1.2.1 Kaggle\n'
' 1.2.2 '
'Rankings(ランキング・称号制度)\n'
' 1.2.3 Kernel\n'
' 1.2.4 Discussion\n'
' 1.2.5 Datasets\n'
' 1.2.6 API\n'
' 1.2.7 Newsfeed\n'
' 1.2.8 '
'開催された分析コンペの種類と具体例\n'
' 1.2.9 '
'分析コンペのフォーマット\n'
'1.3 '
'分析コンペに参加してから終わるまで\n'
' 1.3.1 分析コンペに参加\n'
' 1.3.2 規約に同意\n'
' 1.3.3 データをダウンロード\n'
' 1.3.4 予測値の作成\n'
' 1.3.5 予測値の提出\n'
' 1.3.6 Public '
'Leaderboardをチェック\n'
' 1.3.7 最終予測値を選ぶ\n'
' 1.3.8 Private '
'Leaderboardをチェック\n'
'1.4 分析コンペに参加する意義\n'
' 1.4.1 賞金を得る\n'
' 1.4.2 称号やランキングを得る\n'
' 1.4.3 '
'実データを用いた分析の経験・技術を得る\n'
' 1.4.4 '
'データサイエンティストとのつながりを得る\n'
' 1.4.5 就業機会を得る\n'
'1.5 上位を目指すためのポイント\n'
' 1.5.1 タスクと評価指標\n'
' 1.5.2 特徴量の作成\n'
' 1.5.3 モデルの作成\n'
' 1.5.4 モデルの評価\n'
' 1.5.5 モデルのチューニング\n'
' 1.5.6 アンサンブル\n'
' 1.5.7 分析コンペの流れ\n'
' Column \u3000'
'計算リソース\n'
'第2章\u3000タスクと評価指標\n'
'2.1 分析コンペにおけるタスクの種類\n'
' 2.1.1 回帰タスク\n'
' 2.1.2 分類タスク\n'
' 2.1.3 レコメンデーション\n'
' 2.1.4 その他のタスク\n'
'2.2 分析コンペのデータセット\n'
' 2.2.1 テーブルデータ\n'
' 2.2.2 外部データ\n'
' 2.2.3 時系列データ\n'
' 2.2.4 '
'画像や自然言語などのデータ\n'
'2.3 評価指標\n'
' 2.3.1 '
'評価指標(evaluation '
'metrics)とは\n'
' 2.3.2 回帰における評価指標\n'
' 2.3.3 '
'二値分類における評価指標?正例か負例かを予測値とする場合\n'
' 2.3.4 '
'二値分類における評価指標?正例である確率を予測値とする場合\n'
' 2.3.5 '
'多クラス分類における評価指標\n'
' 2.3.6 '
'レコメンデーションにおける評価指標\n'
'2.4 評価指標と目的関数\n'
' 2.4.1 '
'評価指標と目的関数の違い\n'
' 2.4.2 '
'カスタム評価指標とカスタム目的関数\n'
'2.5 評価指標の最適化\n'
' 2.5.1 '
'評価指標の最適化のアプローチ\n'
' 2.5.2 閾値の最適化\n'
' 2.5.3 '
'閾値の最適化をout-of-foldで行うべきか?\n'
' Column '
'out-of-foldとは?\n'
' 2.5.4 予測確率とその調整\n'
'2.6 評価指標の最適化の例\n'
' 2.6.1 balanced '
'accuracyの最適化\n'
' 2.6.2 '
'mean-F1における閾値の最適化\n'
' 2.6.3 quadratic '
'weighted '
'kappaにおける閾値の最適化\n'
' 2.6.4 '
'カスタム目的関数での評価指標の近似によるMAEの最適化\n'
' 2.6.5 '
'MCCのPR-AUCによる近似とモデル選択\n'
'2.7 リーク(data '
'leakage) 107\n'
' 2.7.1 '
'予測に有用な情報が想定外に漏れている意味でのリーク\n'
' 2.7.2 '
'バリデーションの枠組みの誤りという意味でのリーク\n'
'第3章\u3000特徴量の作成\n'
'3.1 本章の構成\n'
'3.2 モデルと特徴量\n'
' 3.2.1 モデルと特徴量\n'
' 3.2.2 '
'ベースラインとなる特徴量\n'
' 3.2.3 '
'決定木の気持ちになって考える\n'
'3.3 欠損値の扱い\n'
' 3.3.1 欠損値のまま取り扱う\n'
' 3.3.2 欠損値を代表値で埋める\n'
' 3.3.3 '
'欠損値を他の変数から予測する\n'
' 3.3.4 '
'欠損値から新たな特徴量を作成する\n'
' 3.3.5 データ上の欠損の認識\n'
'3.4 数値変数の変換\n'
' 3.4.1 '
'標準化(standardization)\n'
'Column '
'データ全体の数値を利用して変換を行うときに、学習データのみを使うか、テストデータも使うか\n'
' 3.4.2 '
'Min-Maxスケーリング\n'
' 3.4.3 非線形変換\n'
' 3.4.4 clipping\n'
' 3.4.5 binning\n'
' 3.4.6 順位への変換\n'
' 3.4.7 RankGauss\n'
'3.5 カテゴリ変数の変換\n'
' 3.5.1 one-hot '
'encoding\n'
' 3.5.2 label '
'encoding\n'
' 3.5.3 feature '
'hashing\n'
' 3.5.4 frequency '
'encoding\n'
' 3.5.5 target '
'encoding\n'
' 3.5.6 embedding\n'
' 3.5.7 順序変数の扱い\n'
' 3.5.8 '
'カテゴリ変数の値の意味を抽出する\n'
'3.6 日付・時刻を表す変数の変換\n'
' 3.6.1 '
'日付・時刻を表す変数の変換のポイント\n'
' 3.6.2 '
'日付・時刻を表す変数の変換による特徴量\n'
'3.7 変数の組み合わせ\n'
'3.8 他のテーブルの結合\n'
'3.9 集約して統計量をとる\n'
' 3.9.1 単純な統計量をとる\n'
' 3.9.2 時間的な統計量をとる\n'
' 3.9.3 条件を絞る\n'
' 3.9.4 集計する単位を変える\n'
' 3.9.5 '
'ユーザ側でなく、アイテム側に注目する\n'
'3.10 時系列データの扱い\n'
' 3.10.1 時系列データとは?\n'
' 3.10.2 '
'予測する時点より過去の情報のみを使う\n'
' 3.10.3 '
'ワイドフォーマットとロングフォーマット\n'
' 3.10.4 ラグ特徴量\n'
' 3.10.5 '
'時点と紐付いた特徴量を作る\n'
' 3.10.6 '
'予測に使えるデータの期間\n'
'3.11 '
'次元削減・教師なし学習による特徴量\n'
' 3.11.1 主成分分析(PCA)\n'
' 3.11.2 '
'非負値行列因子分解(NMF)\n'
' 3.11.3 Latent '
'Dirichlet '
'Allocation(LDA)\n'
' 3.11.4 '
'線形判別分析(LDA)\n'
' 3.11.5 t-SNE、UMAP\n'
' 3.11.6 オートエンコーダ\n'
' 3.11.7 クラスタリング\n'
'3.12 その他のテクニック\n'
' 3.12.1 '
'背景にあるメカニズムから考える\n'
' 3.12.2 '
'レコード間の関係性に注目する\n'
' 3.12.3 相対値に注目する\n'
' 3.12.4 位置情報に注目する\n'
' 3.12.5 自然言語処理の手法\n'
' 3.12.6 '
'自然言語処理の手法の応用\n'
' 3.12.7 '
'トピックモデルの応用によるカテゴリ変数の変換\n'
' 3.12.8 画像特徴量を扱う手法\n'
' 3.12.9 decision '
'tree feature '
'transformation\n'
' 3.12.10 '
'匿名化されたデータの変換前の値を推測する\n'
' 3.12.11 '
'データの誤りを修正する\n'
'3.13 '
'分析コンペにおける特徴量の作成の例\n'
' 3.13.1 '
'Kaggleの「Recruit '
'Restaurant Visitor '
'Forecasting」\n'
' 3.13.2 '
'Kaggleの「Santander '
'Product '
'Recommendation」\n'
' 3.13.3 '
'Kaggleの「Instacart '
'Market Basket '
'Analysis」\n'
' 3.13.4 KDD Cup '
'2015\n'
' 3.13.5 '
'分析コンペにおけるその他のテクニックの例\n'
'第4章\u3000モデルの作成\n'
'4.1 モデルとは何か?\n'
' 4.1.1 モデルとは何か?\n'
' 4.1.2 モデル作成の流れ\n'
' 4.1.3 '
'モデルに関連する用語とポイント\n'
'4.2 分析コンペで使われるモデル\n'
'4.3 '
'GBDT(勾配ブースティング木)\n'
' 4.3.1 GBDTの概要\n'
' 4.3.2 GBDTの特徴\n'
' 4.3.3 '
'GBDTの主なライブラリ\n'
' 4.3.4 GBDTの実装\n'
' 4.3.5 '
'xgboostの使い方のポイント\n'
' 4.3.6 lightgbm\n'
' 4.3.7 catboost\n'
' Column '
'xgboostのアルゴリズムの解説\n'
'4.4 ニューラルネット\n'
' 4.4.1 ニューラルネットの概要\n'
' 4.4.2 ニューラルネットの特徴\n'
' 4.4.3 '
'ニューラルネットの主なライブラリ\n'
' 4.4.4 ニューラルネットの実装\n'
' 4.4.5 '
'kerasの使い方のポイント\n'
' 4.4.6 参考になるソリューション '
'- 多層パーセプトロン\n'
' 4.4.7 参考になるソリューション '
'- 最近のニューラルネットの発展\n'
'4.5 線形モデル\n'
' 4.5.1 線形モデルの概要\n'
' 4.5.2 線形モデルの特徴\n'
' 4.5.3 '
'線形モデルの主なライブラリ\n'
' 4.5.4 線形モデルの実装\n'
' 4.5.5 '
'線形モデルの使い方のポイント\n'
'4.6 その他のモデル\n'
' 4.6.1 '
'k近傍法(k-nearest '
'neighbor '
'algorithm、kNN)\n'
' 4.6.2 '
'ランダムフォレスト(Random '
'Forest、RF)\n'
' 4.6.3 Extremely '
'Randomized '
'Trees(ERT)\n'
' 4.6.4 Regularized '
'Greedy Forest(RGF)\n'
' 4.6.5 Field-aware '
'Factorization '
'Machines(FFM)\n'
'4.7 '
'モデルのその他のポイントとテクニック\n'
' 4.7.1 欠損値がある場合\n'
' 4.7.2 特徴量の数が多い場合\n'
' 4.7.3 '
'目的変数に1対1で対応するテーブルでない場合\n'
' 4.7.4 pseudo '
'labeling\n'
' Column '
'分析コンペ用のクラスやフォルダの構成\n'
'第5章\u3000モデルの評価\n'
'5.1 モデルの評価とは?\n'
'5.2 バリデーションの手法\n'
' 5.2.1 hold-out法\n'
' 5.2.2 クロスバリデーション\n'
' 5.2.3 stratified '
'k-fold\n'
' 5.2.4 group '
'k-fold\n'
' 5.2.5 '
'leave-one-out\n'
'5.3 '
'時系列データのバリデーション手法\n'
' 5.3.1 '
'時系列データのhold-out法\n'
' 5.3.2 '
'時系列データのクロスバリデーション(時系列に沿って行う方法)\n'
' 5.3.3 '
'時系列データのクロスバリデーション(単純に時間で分割する方法)\n'
' 5.3.4 '
'時系列データのバリデーションの注意点\n'
' 5.3.5 '
'Kaggleの「Recruit '
'Restaurant Visitor '
'Forecasting」\n'
' 5.3.6 '
'Kaggleの「Santander '
'Product '
'Recommendation」\n'
'5.4 '
'バリデーションのポイントとテクニック\n'
' 5.4.1 '
'バリデーションを行う目的\n'
' 5.4.2 '
'学習データとテストデータの分割をまねる\n'
' 5.4.3 '
'学習データとテストデータの分布が違う場合\n'
' 5.4.4 '
'Leaderboardの情報を利用する\n'
' 5.4.5 '
'バリデーションデータやPublic '
'Leaderboardへの過剰な適合\n'
' 5.4.6 '
'クロスバリデーションのfoldごとに特徴量を作り直す\n'
' 5.4.7 '
'使える学習データを増やす\n'
'第6章\u3000モデルのチューニング\n'
'6.1 パラメータチューニング\n'
' 6.1.1 '
'ハイパーパラメータの探索手法\n'
' 6.1.2 '
'パラメータチューニングで設定すること\n'
' 6.1.3 '
'パラメータチューニングのポイント\n'
' 6.1.4 '
'ベイズ最適化でのパラメータ探索\n'
' 6.1.5 '
'GBDTのパラメータおよびそのチューニング\n'
' Column '
'xgboostの具体的なパラメータチューニングの方法\n'
' 6.1.6 '
'ニューラルネットのパラメータおよびそのチューニング\n'
' Column '
'多層パーセプトロンの具体的なパラメータチューニングの方法\n'
' 6.1.7 '
'線形モデルのパラメータおよびそのチューニング\n'
'6.2 特徴選択および特徴量の重要度\n'
' 6.2.1 単変量統計を用いる方法\n'
' 6.2.2 '
'特徴量の重要度を用いる方法\n'
' 6.2.3 反復して探索する方法\n'
'6.3 クラスの分布が偏っている場合\n'
' Column '
'ベイズ最適化およびTPEのアルゴリズム\n'
'第7章\u3000アンサンブル\n'
'7.1 アンサンブルとは?\n'
'7.2 シンプルなアンサンブル手法\n'
' 7.2.1 平均、加重平均\n'
' 7.2.2 多数決、重みづけ多数決\n'
' 7.2.3 '
'注意点とその他のテクニック\n'
'7.3 スタッキング\n'
' 7.3.1 スタッキングの概要\n'
' 7.3.2 '
'特徴量作成の方法としてのスタッキング\n'
' 7.3.3 スタッキングの実装\n'
' 7.3.4 スタッキングのポイント\n'
' 7.3.5 '
'hold-outデータへの予測値を用いたアンサンブル\n'
'7.4 '
'どんなモデルをアンサンブルすると良いか?\n'
' 7.4.1 多様なモデルを使う\n'
' 7.4.2 '
'ハイパーパラメータを変える\n'
' 7.4.3 特徴量を変える\n'
' 7.4.4 問題のとらえ方を変える\n'
' 7.4.5 '
'スタッキングに含めるモデルの選択\n'
'7.5 '
'分析コンペにおけるアンサンブルの例\n'
' 7.5.1 Kaggleの「Otto '
'Group Product '
'Classification '
'Challenge」\n'
' 7.5.2 Kaggleの「Home '
'Depot Product Search '
'Relevance」\n'
' 7.5.3 Kaggleの「Home '
'Credit Default '
'Risk」\n'
'付\u3000録\n'
'A.1 分析コンペの参考資料\n'
'A.2 参考文献\n'
'A.3 本書で参照した分析コンペ\n'
'索引\n'
'著者プロフィール',
'TextType': '04'}]},
'DescriptiveDetail': {'Audience': [{'AudienceCodeType': '22',
'AudienceCodeValue': '00'}],
'Collection': {'CollectionSequence': {'CollectionSequenceNumber': '0',
'CollectionSequenceType': '01',
'CollectionSequenceTypeName': '完結フラグ'},
'CollectionSequenceArray': [{'CollectionSequenceNumber': '0',
'CollectionSequenceType': '01',
'CollectionSequenceTypeName': '完結フラグ'}],
'CollectionType': '10'},
'Contributor': [{'BiographicalNote': '京都大学総合人間学部卒業後、生命保険会社でアクチュアリーとして10年ほど商品開発・リスク管理などに従事した後、Kaggleに出会ったことをきっかけにキャリアを放り出してKaggleや競技プログラミングで学んだ技術でお仕事をするようになった。Kaggle '
'Competitions '
'Master(Walmart '
'Recruiting '
'II: '
'Sales '
'in '
'Stormy '
'Weather '
'優勝、Coupon '
'Purchase '
'Prediction '
'3位)、日本アクチュアリー会正会員Kaggle: '
'https://www.kaggle.com/threecourseTwitter: '
'https://twitter.com/threecourse本書の4章、6章、7章および1章、2章、3章、5章の一部を執筆。',
'ContributorRole': ['A01'],
'PersonName': {'collationkey': 'カドワキダイスケ',
'content': '門脇大輔'},
'SequenceNumber': '1'},
{'BiographicalNote': '2012年に京都大学大学院修了後、国内電機メーカーに入社。以来、データサイエンティストおよび研究員として従事。仕事柄、データサイエンス・機械学習に興味を持ち、2014年よりKaggleを始め、2019年にKaggle '
'Competitions '
'Grandmasterとなる。Kaggle: '
'https://www.kaggle.com/rsakataTwitter: '
'https://twitter.com/sakata_ryuji本書の3章、5章を執筆。',
'ContributorRole': ['A01'],
'PersonName': {'collationkey': 'サカタリュウジ',
'content': '阪田隆司'},
'SequenceNumber': '2'},
{'BiographicalNote': '東京大学大学院総合文化研究科広域科学専攻で天体シミュレーションの研究で修士号を取得後、データ分析のコンサルティング企業で、10年近く企業のデータ分析支援に携わった。その後大手Webサービス企業に入社し、データ活用の推進に携わったあと、現在はデータサイエンティストや機械学習エンジニアの育成、マネジメントに従事。プライベートでは子育てに専念中。Kaggle '
'Competitions '
'ExpertKaggle: '
'https://www.kaggle.com/hskkskTwitter: '
'https://twitter.com/free_skier本書の1章、および6章の一部を執筆。',
'ContributorRole': ['A01'],
'PersonName': {'collationkey': 'ホサカケイスケ',
'content': '保坂桂佑'},
'SequenceNumber': '3'},
{'BiographicalNote': '東京大学理学部物理学科卒業、同大学大学院理学研究科物理学専攻修了後、国内電機大手に就職した後、金融業界へと転身し、金融システム会社にてデリバティブクオンツ、国内大手損保グループにてリスクアクチュアリー業務に携わった。現在は、アクサ生命保険株式会社にてシニアデータサイエンティストとして社内のデータ分析の促進に従事。また、東京大学へ研究員としても出向中で、医療データの分析・研究を行っている。日本アクチュアリー会準会員。Kaggleを本格的に始めたのは2016年頃からであり、2018年にKaggle '
'CompetitionsMasterとなっている。くまのぬいぐるみが好きでたまらない。Kaggle: '
'https://www.kaggle.com/maxwell110Twitter: '
'https://twitter.com/Maxwell_110本書の2章、および7章の一部を執筆。',
'ContributorRole': ['A01'],
'PersonName': {'collationkey': 'ヒラマツユウジ',
'content': '平松雄司'},
'SequenceNumber': '4'}],
'Extent': [{'ExtentType': '11',
'ExtentUnit': '03',
'ExtentValue': '424'}],
'Language': [{'LanguageCode': 'jpn',
'LanguageRole': '01'}],
'ProductComposition': '00',
'ProductForm': 'BA',
'ProductFormDetail': 'B124',
'Subject': [{'MainSubject': '',
'SubjectCode': '3055',
'SubjectSchemeIdentifier': '78'},
{'SubjectCode': '20',
'SubjectSchemeIdentifier': '79'},
{'SubjectHeadingText': '予測モデル;バリデーション;パラメータチューニング;データサイエンス;データ分析;Kaggle;カグル;分析コンペ;機械学習',
'SubjectSchemeIdentifier': '20'}],
'TitleDetail': {'TitleElement': {'TitleElementLevel': '01',
'TitleText': {'collationkey': 'カグルデカツデータブンセキノギジュツ',
'content': 'Kaggleで勝つデータ分析の技術'}},
'TitleType': '01'}},
'NotificationType': '03',
'ProductIdentifier': {'IDValue': '9784297108434',
'ProductIDType': '15'},
'ProductSupply': {'MarketPublishingDetail': {'MarketPublishingStatus': '00'},
'SupplyDetail': {'Price': [{'CurrencyCode': 'JPY',
'PriceAmount': '3280',
'PriceType': '03'}],
'ProductAvailability': '99'}},
'PublishingDetail': {'Imprint': {'ImprintIdentifier': [{'IDValue': '297',
'ImprintIDType': '19'},
{'IDValue': '1477',
'ImprintIDType': '24'}],
'ImprintName': '技術評論社'},
'PublishingDate': [{'Date': '20191009',
'PublishingDateRole': '01'}]},
'RecordReference': '9784297108434'},
'summary': {'author': '門脇大輔/著 阪田隆司/著 保坂桂佑/著 平松雄司/著',
'cover': 'https://cover.openbd.jp/9784297108434.jpg',
'isbn': '9784297108434',
'pubdate': '20191009',
'publisher': '技術評論社',
'series': '',
'title': 'Kaggleで勝つデータ分析の技術',
'volume': ''}}]
GoogleBooksAPI
{'items': [{'accessInfo': {'accessViewStatus': 'NONE',
'country': 'JP',
'embeddable': False,
'epub': {'isAvailable': False},
'pdf': {'isAvailable': False},
'publicDomain': False,
'quoteSharingAllowed': False,
'textToSpeechPermission': 'ALLOWED',
'viewability': 'NO_PAGES',
'webReaderLink': 'http://play.google.com/books/reader?id=SY-KygEACAAJ&hl=&source=gbs_api'},
'etag': '1ktJpkKKubI',
'id': 'SY-KygEACAAJ',
'kind': 'books#volume',
'saleInfo': {'country': 'JP',
'isEbook': False,
'saleability': 'NOT_FOR_SALE'},
'selfLink': 'https://www.googleapis.com/books/v1/volumes/SY-KygEACAAJ',
'volumeInfo': {'allowAnonLogging': False,
'authors': ['門脇大輔', '阪田隆司', '保坂桂佑', '平松雄司'],
'canonicalVolumeLink': 'https://books.google.com/books/about/Kaggle%E3%81%A7%E5%8B%9D%E3%81%A4%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E3%81%AE%E6%8A%80%E8%A1%93.html?hl=&id=SY-KygEACAAJ',
'contentVersion': 'preview-1.0.0',
'imageLinks': {'smallThumbnail': 'http://books.google.com/books/content?id=SY-KygEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api',
'thumbnail': 'http://books.google.com/books/content?id=SY-KygEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api'},
'industryIdentifiers': [{'identifier': '4297108437',
'type': 'ISBN_10'},
{'identifier': '9784297108434',
'type': 'ISBN_13'}],
'infoLink': 'http://books.google.co.jp/books?id=SY-KygEACAAJ&dq=isbn:9784297108434&hl=&source=gbs_api',
'language': 'ja',
'maturityRating': 'NOT_MATURE',
'pageCount': 407,
'panelizationSummary': {'containsEpubBubbles': False,
'containsImageBubbles': False},
'previewLink': 'http://books.google.co.jp/books?id=SY-KygEACAAJ&dq=isbn:9784297108434&hl=&cd=1&source=gbs_api',
'printType': 'BOOK',
'publishedDate': '2019-10',
'readingModes': {'image': False, 'text': False},
'title': 'Kaggleで勝つデータ分析の技術'}}],
'kind': 'books#volumes',
'totalItems': 1}
5. jsonからのデータ読み取り
以下の記事で書籍情報APIから得られるjsonデータから情報を抽出する手法についてまとめています。
6. 実装例
撮影しておいたバーコード画像のフォルダを指定して、ISBNから書籍情報をまとめたCSVを出力する実装例になります。
自分以外も使うため、tkinterでGUI化したものをexe化しています。
exe化する際の注意点としては、pyzbarはspecファイルを変更しないと正しくexe化できないので、以下のサイトを参照しました。
https://zenn.dev/timoneko/articles/b978f846171074
import os
import sys
import csv
import glob
import json
import datetime
import urllib.parse
import urllib.request
import pyzbar.pyzbar as pyzbar
from PIL import Image
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from tkinter import filedialog
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master.geometry('500x120')
self.master.title('Application for Getting Books Information')
self.master.resizable(width=False, height=False)
self.master.grid_rowconfigure((0, 1, 2), weight=1)
self.master.grid_columnconfigure((0, 1, 2), weight=1)
self.dir_label = ttk.Label(self.master, text='フォルダ選択:')
self.dir_label.grid(row=0, column=0, padx=(10, 0), pady=10)
self.dir_entry_var = tk.StringVar()
self.dir_entry = ttk.Entry(self.master, textvariable=self.dir_entry_var, width=50)
self.dir_entry.grid(row=0, column=1, padx=10, pady=10)
self.dir_button = ttk.Button(self.master, text='参照', command=self.dir_dialog_open)
self.dir_button.grid(row=0, column=2, padx=(0, 10), pady=10)
self.start_button = ttk.Button(self.master, text='バーコードから書籍情報取得開始', command=self.compose_books_info_from_isbn, width=40)
self.start_button.grid(row=1, column=1, padx=10, pady=(0, 10))
self.progress_bar = ttk.Progressbar(self.master, length=480, mode='determinate', maximum=1)
self.progress_bar.grid(row=2, column=0, columnspan=3, padx=10, pady=(0, 10))
# フォルダ選択ダイアログボックス
def dir_dialog_open(self):
# カレントディレクトリを開く(__file__はexe化すると使えないのでsys.argv[0])
self.current_dir = os.path.dirname(sys.argv[0])
# フォルダ選択ダイアログボックスの生成 -> フォルダ選択を実施
self.current_path = filedialog.askdirectory(initialdir=self.current_dir)
# エントリーボックスに選択したフォルダパス(フルパス)を格納
self.entry.set(self.current_path)
# 実行ボタン押下時の実行関数
def compose_books_info_from_isbn(self):
# フォルダパス(フルパス)の取得
dir_path = self.dir_entry_var.get()
if not dir_path:
messagebox.showerror('Error', 'パスの指定がありません。')
self.master.destroy()
# フォルダ名のみを抽出
dir_name = os.path.basename(dir_path)
# まとめ用配列の準備(タイトル行のみ設定)
books = [['裏表紙画像ファイル', 'タイトル', 'サブタイトル', '著者', '出版社', '概要1', '概要2', '概要3',
'巻', 'シリーズ', '出版日', 'ISBN', 'サムネイル画像', '読み仮名']]
# 画像からISBNを抽出して、書籍情報APIからデータ読み込む
# フォルダ内画像の読込み
files = sorted(glob.glob(f'{dir_path}/*.jpg'))
# ファイル数分だけ繰り返し
for i, file in enumerate(files):
# 裏表紙画像の読込み
img = Image.open(file)
try:
# 画像の中のバーコードからISBN13(先頭978)のデータを配列格納(基本的に1つになる)
isbn = [x.data.decode() for x in pyzbar.decode(img) if x.data.decode().startswith('97')][0]
# jsonをAPIから取得する
json_data, api = self.fetch_json_of_book_info_from_isbn(isbn)
# jsonから必要な情報を抽出する
book_data = self.extract_value_from_json_of_book_info(file, isbn, json_data, api)
# openBDにデータが存在しない場合の処理
except IndexError:
book_data = [file, '画像からISBNを読み込めませんでした']
# 1冊の情報をまとめ用配列へ追加
books.append(book_data)
# プログレスバー表示
self.progress_bar.configure(value=(i + 1) / len(files))
self.progress_bar.update()
if len(books) > 1:
books = [[item.encode('cp932', 'replace').decode('cp932') if isinstance(item, str) else item for item in book] for book in books]
# csvファイルに書き出し
with open(f'{self.current_dir}/{dir_name}_{datetime.datetime.now().strftime("%y%m%d%H%M%S")}.csv', 'w') as file:
writer = csv.writer(file, lineterminator='\n')
writer.writerows(books)
# 終了メッセージ
messagebox.showinfo('確認', '書籍情報の取得\ncsv保存が完了しました。')
# フォルダ内に画像が存在しない場合のエラーメッセージ
else:
messagebox.showerror('エラー', 'JPEG画像が存在しません。')
# 画像からISBNを抽出して、APIからjsonデータを取ってくる関数
def fetch_json_of_book_info_from_isbn(self, isbn):
# まずopenBDでjson取得
api_call = urllib.request.urlopen(f'https://api.openbd.jp/v1/get?isbn={isbn}')
json_data = json.loads(api_call.read().decode('utf-8'))
# jsonが空だったら、GoogleBooksAPIで取得
if json_data == [None]:
api_call = urllib.request.urlopen(f'https://www.googleapis.com/books/v1/volumes?q=isbn:{isbn}')
json_data = json.loads(api_call.read().decode('utf-8'))
api = 'google'
else:
api = 'openbd'
# 読み出したデータとAPI種別を返す
return json_data, api
# jsonデータから必要なデータを抜き出す関数
def extract_value_from_json_of_book_info(self, file, isbn, json_data, api):
# 1'裏表紙画像ファイル', 2'タイトル', 3'サブタイトル', 4'著者', 5'出版社', 6'概要1', 7'概要2', 8'概要3',
# 9'巻', 10'シリーズ', 11'出版日', 12'ISBN', 13'サムネイル画像', 14'読み仮名'
# 格納リストの生成
book_data = [file]
# openBDのjsonファイルからの抽出
if api == 'openbd':
keys = [[0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'TitleText', 'content'], # 2'タイトル'
[0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'Subtitle', 'content'], # 3'サブタイトル'
[0, 'summary', 'author'], # 4'著者'
[0, 'summary', 'publisher'], # 5'出版社'
[0, 'onix', 'CollateralDetail', 'TextContent', 0, 'Text'], # 6'概要1'
[0, 'onix', 'CollateralDetail', 'TextContent', 1, 'Text'], # 7'概要2'
[0, 'onix', 'CollateralDetail', 'TextContent', 2, 'Text'], # 8'概要3'
[0, 'summary', 'volume'], # 9'巻'
[0, 'summary', 'series'], # 10'シリーズ'
[0, 'summary', 'pubdate'], # 11'出版日'
[0, 'summary', 'isbn'], # 12'ISBN'
[0, 'summary', 'cover'], # 13'サムネイル画像'
[0, 'onix', 'DescriptiveDetail', 'TitleDetail', 'TitleElement', 'TitleText', 'collationkey']] # 14'読み仮名'
# GoogleBooksAPIsのjsonファイルからの抽出
else:
if json_data['totalItems'] != 0:
keys = [['items', 0, 'volumeInfo', 'title'], # 2'タイトル'
['items', 0, 'volumeInfo', 'subtitle'], # 3'サブタイトル'
['items', 0, 'volumeInfo', 'authors'], # 4'著者'
['null'], # GoogleBooksAPIに存在しない項目 # 5'出版社'
['items', 0, 'volumeInfo', 'description'], # 6'概要1'
['null'], # GoogleBooksAPIに存在しない項目 # 7'概要2'
['null'], # GoogleBooksAPIに存在しない項目 # 8'概要3'
['null'], # GoogleBooksAPIに存在しない項目 # 9'巻'
['null'], # GoogleBooksAPIに存在しない項目 # 10'シリーズ'
['items', 0, 'volumeInfo', 'publishedDate'], # 11'出版日'
['null'], # ISBNはjsonから読むより元データを使ったほうが楽 # 12'ISBN'
['items', 0, 'volumeInfo', 'imageLinks', 'thumbnail'], # 13'サムネイル画像'
['null']] # GoogleBooksAPIに存在しない項目 # 14'読み仮名'
else:
keys = []
if len(keys) > 0:
for key in keys:
book_data.append(self.extract_item_from_dict_by_key_list(json_data, key))
if api == 'google':
book_data[3] = ', '.join(json_data['items'][0]['volumeInfo']['authors'])
book_data[11] = isbn
else:
book_data.append('指定のISBNは書籍情報APIに存在しませんでした')
# コンソールには出力
print(book_data)
# まとめたリストを返す
return book_data
# ネストされた辞書から、keyのリスト情報でvalueを取り出す関数
def extract_item_from_dict_by_key_list(self, parent_data, key_list):
# 初期値None(key_listが空だった場合forが回らないのでここで定義)
result = None
for key in key_list:
# 型が辞書の場合
if type(parent_data) is dict:
result = parent_data.get(key, None)
# 型がリストでkeyがインデックス数値の場合
elif type(parent_data) is list and type(key) is int:
result = parent_data[key] if key < len(parent_data) else None
# それ以外全て
else:
result = None
# resultがNoneの場合は処理終了(returnでNoneを返す)
if result is None:
break
# parent_dataに再代入
parent_data = result
return result
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root)
app.mainloop()
- 以下の部分での例外は、
isbn=
でpyzbar
がバーコードを認識できないとISBNを取得できずに空のリストになるのでIndexError
が発生するため、書籍情報APIへの問合せを飛ばす処理になります。
try:
isbn = [x.data.decode() for x in pyzbar.decode(img) if x.data.decode().startswith('97')][0]
json_data, api = self.get_book_info_from_isbn(isbn)
book_data = self.read_json_data(file, isbn, json_data, api)
except IndexError:
book_data = [file, '画像からISBNを読み込めませんでした']
- 以下の部分はGoogleBooksAPIにおいて、著者情報が
list
でまとめられているので、文字列に変換結合している + ISBN情報が取り出しにくいキー設定なので、元々呼び出すのに使うISBNをそのまま代入しています。
if api == 'google':
book_data[3] = ', '.join(json_data['items'][0]['volumeInfo']['authors'])
book_data[11] = isbn