💡

ヤマト運輸「送り状発行システムB2クラウド」のAPIを開発してパッケージを公開した

2022/12/23に公開
42

python 3.10系では動作しません。(パッケージのSSLの仕様変更によるもの)
python 3.9.16で動作するのを確認しています。

結論

ヤマト運輸さんの「送り状発行サービスB2クラウド」のAPIパッケージをPythonで開発して公開しました。

動機:APIがなくて不便だった

  • データのやり取りは基本CSV
  • シールに印刷してDMに貼る
    シールを貼りに時間がかかる。直接印刷する方式は提供されていない。
  • 宛先データに不備があるとリジェクトされる
    自動補正の機能はない。

なのでAPIを開発しました

  1. データの送受信はAPI経由で
  2. 伝票毎にイメージが分割されるので、直接DMに印刷可能になった
  3. 宛先を郵便番号、都道府県、市区、町村・番地に分割する

ちょっと宣伝

住所を都道県、市区、町、番地、ビル名に仕分ける機能は、自社開発した住所を正規化するサービスAddressianを使っています。

https://addressian.netlify.app/

APIの機能一覧

b2cloud

function 機能
login ヤマトビジネスメンバーズにログインして、sessionを返します。以降のほぼ全てのfunctionの引数として必要です。
get_history 発行済み伝票の履歴を取得します。paramsに検索クエリを指定できます。
get_history_all 発行済み伝票の履歴を取得します。伝票はB2クラウド上に最大90日間保持されるようです。
get_history_deleted 削除済みに移された履歴を取得します。
put_tracking 配送状況を更新・取得します。get_historyでは、配送状況は更新されません。
post_new_checkonly 伝票情報に不備がないかチェックして、エラーまたはOKのfeedを返します。
post_new 新規に伝票を登録します。伝票チェックを行うpost_new_checkonlyでOKとなった戻り値です。
check_shipment post_new_checkonlyの結果から必要情報だけを取得します。単体用
check_shipment post_new_checkonlyの結果から必要情報だけを取得します。リスト用
get_new 発行されていない保存済みの伝票情報を取得します。paramsに検索クエリを指定できます。
delete_new 発行されていない保存済みの伝票情報を削除します。削除された伝票情報は元に戻せません。
print_issue 伝票を印刷してPDFで取得します。新規と再発行とも共通です。新規は印刷されると履歴に所属が移ります。
put_history_delete 伝票を履歴から削除します。display_flg=0にする
put_history_display 削除された伝票を履歴に戻します。display_flg=1にする
get_dm_number_print DMの送り状番号一覧を印刷します。
search_history 発行済み伝票の履歴の検索クエリのうち、よく使うパラメータを引数にした関数です。

b2cloud.utilities

function 機能
get_postal B2クラウドの郵便番号情報を取得します。
create_dm_shipment DM用の送り状情報を生成します。
create_empty_shipment 空の送り状情報を生成します。
split_pdf_dm DMの送り状PDF(1ページあたり8伝票)を1伝票毎に分割します。
split_pdf_nekopos ネコポスの送り状PDF(1ページあたり6伝票)を1伝票毎に分割します。
choice_postal 郵便情報のうち、住所に一番ちかい郵便情報を選択します。
get_address_info 住所を郵便番号、都道府県、市区、町村+番地、ビル・マンション等に分割します。住所正規化サービスAddressianのAPIキーが必要です。

インストール

pip install b2cloud

コード例

履歴の取得

import b2cloud
import b2cloud.utilities

session = b2cloud.login('your customer_code', 'your customer_password')
dm = b2cloud.search_history(session, service_type='3')

for entry in dm['feed']['entry']:
    print(entry['shipment']['tracking_number'], entry['shipment']['consignee_name'])

新規に伝票を作成し、データに不備がないかチェックする

# 伝票情報を生成する
shipment = b2cloud.utilities.create_dm_shipment(
    shipment_date='2022/12/24',
    consignee_telephone_display='00-0000-0000',
    consignee_name='テスト',
    consignee_zip_code='8900053',
    consignee_address1='鹿児島県',
    consignee_address2='鹿児島市',
    consignee_address3='中央町10',
    consignee_address4='キャンセビル6階',
    consignee_department1='インターマン株式会社'
)

# データに不備がないかチェックする
res = b2cloud.check_shipment(session, shipment)
print(res)

e.g.
{'success': True, 'errors': []}

伝票の新規保存

# shipmentsをpost_new_checkonlyを通す
checked_feed = b2cloud.post_new_checkonly(session, [shipment])
# 伝票情報をB2クラウドに保存する
res = b2cloud.post_new(session, checked_feed)

保存した伝票をDM形式で印刷し各伝票毎にPDFファイルに保存する

# 保存済みのDM伝票を取得
dm_feed = b2cloud.get_new(session, params={'service_type':'3'})
# DM伝票形式(1シート8枚)で印刷
dm_pdf = b2cloud.print_issue(session,'3', dm_feed)
# 1伝票毎に分割する
pdfs = b2cloud.utilities.split_pdf_dm(dm_pdf)
for i in range(len(pdfs)):
    with open(f'dm_{i}.pdf', 'wb') as f:
        f.write(pdfs[i])

住所を伝票情報に変換する

実行する前に住所正規化サービスAddressian(https://addressian.netlify.app/)のAPI Keyを取得してください。
Googleアカウントがあれば数秒で取得できます。

consignee_address = b2cloud.utilities.get_address_info(
                                                session=session,
                                                addressian_api_key='______apikey_______',
                                                address='鹿児島市中央町10キャンセビル6F'
                                            )
print(consignee_address)

e.g.
{
    "consignee_zip_code": "8900053",
    "consignee_address1": "鹿児島県",
    "consignee_address2": "鹿児島市",
    "consignee_address3": "中央町10",
    "consignee_address4": "キャンセビル6F"
}

今回開発したパッケージは公開しています

github レポジトリ(https://github.com/interman-corp/b2cloud)
pypi b2cloud (https://pypi.org/project/b2cloud/)

Discussion

ichihiroxichihirox

素晴らしいコンテンツです。
発払いの伝票をA5やA4で、pdfで取得することもできますか?

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

ありがとうございます。
print_issueのprint_typeパラメータを指定してください。
 'm':A4マルチ、 'm5':A5マルチ

ichihiroxichihirox

print_typeに3以外を指定すると以下のようなエラーが出ます。
service_typeを0にしてみたのですが状況は変わりません。
ちなみに、pythonのバージョンは3.9.7です。

dm_pdf = b2cloud.print_issue(session,'m5', dm_feed)

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/b2cloud/init.py", line 273, in print_issue
if 'tracking_number' in entry_feed['feed']['entry'][0]['shipment']:
KeyError: 'shipment'

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

DM以外の伝票は、shipmentの必須項目が多くなります。
■ b2cloud.post_new_checkonly(session, [shipment])の返す内容を確認してください。
  不備があるとエラー箇所が含まれて返ってきます。

下記が初払いで最低限必要な情報の設定内容です。

# 空の伝票情報を生成します
shipment = b2cloud.utilities.create_empty_shipment()

# パラメータを設定します
shipment['shipment']['service_type'] = '0' # 発払い
shipment['shipment']['shipment_date'] = 日付
shipment['shipment']['invoice_code'] = 支払用登録電話番号 (IDと同じだとおもいます)
shipment['shipment']['invoice_freight_no'] = 支払用番号 例 '01'
shipment['shipment']['consignee_telephone_display'] = 電話番号
shipment['shipment']['consignee_name'] = "テスト太郎"
shipment['shipment']['consignee_zip_code'] = "届け先の情報"
shipment['shipment']['consignee_address1'] = "都道府県"
shipment['shipment']['consignee_address2'] = "市区町村"
shipment['shipment']['consignee_address3'] = "町名番地"
shipment['shipment']['consignee_address3'] = "ビル名"
shipment['shipment']['shipper_address1'] = '発送先住所 都道府県'
shipment['shipment']['shipper_address2'] = ''
shipment['shipment']['shipper_address3'] = ''
shipment['shipment']['shipper_address4'] = ''
shipment['shipment']['shipper_name'] = '発送者名'
shipment['shipment']['shipper_telephone'] = '発送者伝番号'
shipment['shipment']['shipper_telephone_display'] = '発送者伝番号'
shipment['shipment']['shipper_zip_code'] = '郵便番号'
shipment['shipment']['item_name1'] = '物品名:例:本'

助けになると幸いです。

ichihiroxichihirox

シングルクォートにハマりましたが、おかげさまで無事pdfファイルを取得できました。
ちなみにサービスタイプ2が代引きでした。
わからないことがありましたら、またよろしくおねがいします。

ichihiroxichihirox

お届け予定eメールの設定は以下の部分だと思うのですが、使用するか否かと機種のパラメータをおしえていだだけませんか。0か1では反映されませんでした。
便利に使えるとだんだん欲が出てきてしまって。よろしくお願いします。
"is_using_shipment_post_email": "",
"shipment_post_email_address": "",
"shipment_post_input_device_type": "",
"shipment_post_message": "",

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

お届け予定メールのパラメータはこちらのようです。
お試しください。

"is_using_shipment_email": "1",
"shipment_email_address": "hogehoge@gmail.com",
"input_device_type": "1", # PC
"shipment_message": "メッセージ",

ichihiroxichihirox

ありがとうございます。うまく行きました。
UIはFilemakerでapple script経由でshell scriptで動かしています。
ボタン1発でしばらく待っていると送り状のpdfが開いて印刷画面が表示されるのは最高です。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

よかったです!
弊社もFilemakerを使い倒しております。
主にDMですが伝票のシール貼りがないだけでも効率が違いますね。喜ばれております。

(宣伝)Addressianをつかうと住所を分割するのがとても簡単になります。
無料アカウントでも100件/月使えるので感想を聞かせてもらえるとうれしいです。

kazkaz

お聞きしたいのですが、ヤマト運輸のAPIを利用しようとおもっていて、PHPで開発しているのですが、認証の部分ではじかれてしまい、困っています。もし、サンプルコードおもちでしたら、ご教授いただけないでしょうか?

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

githubで公開しておりますので確認いただけますでしょうか。
9月からB2Cloudに変更がありましたが、現在は対応しております。

松村稔@プロ牛乳石鹸松村稔@プロ牛乳石鹸

はじめまして。大変興味深いAPIで、試してみたのですが、b2cloud.loginで、"ログインに失敗しました。"のエラーになります。こちらのAPIは今も有効に使えるでしょうか?今も有効であれば、恐らく私の使用法が間違っていると思うので、もう少しトライしてみようと思います。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

B2クラウド側でURLが変更になったようです。バージョンを更新してますので再度トライしてみてください。

ichihiroxichihirox

これまで通り伝票を発行しようとしたらデータチェックのところでエラーになります。
10月1日からヤマトの仕様が変わったのかも知れません。ご確認ください。
以下はターミナルでの対話型で入力した結果です。
res = b2cloud.check_shipment(session, shipment)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/b2cloud/init.py", line 164, in check_shipment
errors = res['feed']['entry'][0].get('error',[])
KeyError: 'entry'

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

B2クラウドのURLが変わったようです。バージョンを更新していますのでご確認ください。

ichihiroxichihirox

更新してみたのですが同じエラーが出ます。ファイルのURLは変更されていたので更新は正常と思われます。このページの”新規に伝票を作成し、データに不備がないかチェックする”のサンプルでも試してみましたが同様です。何か他に原因は考えられますか?

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

確認したところ、さらに仕様が大きく変わったようです。
弊社はB2Cloudをやめてしまったので、対応致しかねます。
申し訳ございません。ソースコードは公開したままにしておきます。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

ヤマトのサイトを確認したところURLを含めて仕様が大きく変わったようです。
弊社では、別の会社に移行してしまったので、対応を含めてサポートを打ち切ろうと思います。
悪しからずご了承ください。

ichihiroxichihirox

同じ環境で実行できる3.9.13でやってみましたが同じエラーでした。
opensslのバージョンも影響しているのかもしれません。
せっかく便利になったと思ったのに残念です。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

こちらで動作を確認している環境をお伝えします。
・python 3.9.16 , MAC
こちらは特に何も必要ないです。
まずはpyenv等で新しい環境をつくってお試しください。

・python 3.10.11, MAC
urllib3を1.26.16にダウングレードする必要がありました。
pythonの標準のSSLの仕様変更によるものです。

ichihiroxichihirox

pyenvで3.9.16を入れてやってみたのですが同じエラーです。
urllb3が2.0.7だったので1.26.16や1.23.13にダウングレードしてみましたが変わらず。
以下はpipdeptreeでの結果ですが、どうでしょう?
pipdeptree -p b2cloud
b2cloud==0.2.0
├── lxml [required: >=4.9.2,<5.0.0, installed: 4.9.3]
├── PyMuPDF [required: >=1.21.1,<2.0.0, installed: 1.23.5]
│ └── PyMuPDFb [required: ==1.23.5, installed: 1.23.5]
└── requests [required: >=2.28.1,<3.0.0, installed: 2.31.0]
├── certifi [required: >=2017.4.17, installed: 2023.7.22]
├── charset-normalizer [required: >=2,<4, installed: 3.3.1]
├── idna [required: >=2.5,<4, installed: 3.4]
└── urllib3 [required: >=1.21.1,<3, installed: 2.0.7]

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

当方でもpipdeptreeをしてみましたが、差はないようですね。

b2cloud==0.2.0
├── lxml [required: >=4.9.2,<5.0.0, installed: 4.9.3]
├── PyMuPDF [required: >=1.21.1,<2.0.0, installed: 1.23.5]
│ └── PyMuPDFb [required: ==1.23.5, installed: 1.23.5]
└── requests [required: >=2.28.1,<3.0.0, installed: 2.31.0]
├── certifi [required: >=2017.4.17, installed: 2023.7.22]
├── charset-normalizer [required: >=2,<4, installed: 2.0.12]
├── idna [required: >=2.5,<4, installed: 3.4]
└── urllib3 [required: >=1.21.1,<3, installed: 2.0.7]

########
pip freezeもしてみました。
当方のOSはMac OS Venture 13.3です。

b2cloud==0.2.0
certifi==2023.7.22
charset-normalizer==3.3.2
idna==3.4
lxml==4.9.3
pipdeptree==2.13.0
PyMuPDF==1.23.5
PyMuPDFb==1.23.5
requests==2.31.0
urllib3==2.0.7

ichihiroxichihirox

完全にクリーンなVenturaのシステムを作ってbrewでpyenvを入れて、pyenvで3.9.16を入れて、pipでb2cloudを入れてやってみたのですが、やはり同様のエラーです。
~/.pyenv/versions/3.9.16/lib/python3.9/site-packages/b2cloud/init.py", line 164, in check_shipment
このチェックまでのヤマトのURLのnewb2web-s2をnewb2webに書き換えると通過するのですが、その後の保存あたりでエラーになります。
何か単純な問題のような気もするのですが。
なお、Mavericks、Montereyでも変わらずです。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

バージョン0.2.0でURLは変更済みですので、書き換える必要はないと思います。
post_new_checkonlyの結果にエラー(errorフィールド)がないが確認してください。
テストコードを置いておきます

from b2cloud import login, utilities, post_new_checkonly, post_new, print_issue
from datetime import datetime
import json
session = login('--id--', '--pw--', '', '')

shipment = utilities.create_empty_shipment()
shipment['shipment']['service_type'] = '0'
shipment['shipment']['shipment_date'] =  datetime.now().strftime('%Y/%m/%d')
shipment['shipment']['invoice_code'] = '00000000000' # 必須です
shipment['shipment']['invoice_freight_no'] = '01'
shipment['shipment']['consignee_telephone_display'] = '09000000000' # 必須です
shipment['shipment']['consignee_name'] = "テスト太郎"
shipment['shipment']['consignee_zip_code'] = "8950052"
shipment['shipment']['consignee_address1'] = "鹿児島県"
shipment['shipment']['consignee_address2'] = "薩摩川内市"
shipment['shipment']['consignee_address3'] = "神田町3−22"
shipment['shipment']['shipper_address1'] = '鹿児島県'
shipment['shipment']['shipper_address2'] = '鹿児島市'
shipment['shipment']['shipper_address3'] = '中央町10'
shipment['shipment']['shipper_address4'] = 'キャンセビル6階'
shipment['shipment']['shipper_name'] = 'インターマン株式会社'
shipment['shipment']['shipper_telephone'] = '0992066878'
shipment['shipment']['shipper_telephone_display'] = '0992066878'
shipment['shipment']['shipper_zip_code'] = '8900053'
shipment['shipment']['item_name1'] = '書籍'
checked_feed = post_new_checkonly(session, [shipment])
print(json.dumps(checked_feed, ensure_ascii=False, indent=2))
saved_feed = post_new(session, checked_feed)
pdf_data = print_issue(session, 'm5', saved_feed)
print(pdf_data[:100])
assert pdf_data[:4].decode() == "%PDF"

with open('test.pdf', 'wb') as f:
    f.write(pdf_data)

ichihiroxichihirox

ここでエラーが出ます。

checked_feed = post_new_checkonly(session, [shipment])
print(json.dumps(checked_feed, ensure_ascii=False, indent=2))
{
"feed": {
"title": "Authentication error."
}
}

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

ログインできてないですね。IDとPWをお確かめください。
Webサイト上ではログインできていますか?
--id-- --pw--の部分を書き換える必要があります。(老婆心ながら)

--追記--
ログインのチェックはsession=login()で通っているはずですね。
b2cloudのバージョンが古い気もします。はて・・・

ichihiroxichihirox

1行づつ入力してチェックしてますが、ログインには問題ありません。
b2cloudも0.20で、ファイルの中身も確認してみましたがヤマトのurlも新しくなっています。
試しにshipmentの必須項目だけでやってみても同じエラーです。
なんでしょう。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

認証周りでうちで使ってる認証情報だけでは把握できてない箇所がありそうですね。
確認ですが、ヤマト運輸さんのサイトでは伝票は作れますでしょうか。
こちらではWindows環境でも伝票が作れましたので、別の要因ぽいですね。

ichihiroxichihirox

ヤマトのサイトでCSVをインポートしてやる方法では伝票は作れます。
請求先コードは電話番号の末尾に−01が付加されたタイプです。
winでもいけるということはos環境の問題ではなさそうですね。
VenturaのOpenSSLは3.1.4ですが、python3xだと1.1.1だったと思うのですが関係ないでしょうか?

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

SSLの問題であればJSONの応答すらとれませんので、違うかと思います。

エラー内容的には、ログイン後のsessionが切れているようなんですよね。
ライブラリを使って、履歴の一覧はとれますでしょうか?
res = b2cloud.get_history_all(session)

内部的にもGETメソッドなので、ログインができていれば問題ないはずです。

ichihiroxichihirox

ログイン直後に切られているような感じです。

res = b2cloud.get_history_all(session)
print(res)
{'feed': {'title': 'Authentication error.'}}

ichihiroxichihirox

ネットワークやPCを複数変えてますが変わりません。これまでテストしたOSはmacだけですがwin8があるのでやってみます。
見当違いかと思いますが、ログイン直後のsessionを見ると以下のコードが返ってきます。

print (session)
<requests.sessions.Session object at 0x111b187f0>

ichihiroxichihirox

win8でも同じエラーでした。
原因はアカウントのタイプかもしれません。
返してきているエラーを取得できないんでしょうか?

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

アカウントの違いがあるとすれば、loginの後リダイレクト先のURLがちがうのかもしれませんね。

GitHubに公開しているソース追っていただくのがよいかとおもいます。
特にlogin関数の部分を1行ずつ追ってみてください。

その際に、戻り値やパラメータがブラウザを使ったログインと相違がないか確認してみてください。
・ Chromeの開発モードでネットワーク、対象のcURLを取得
・ 必要であればcURLコンバーターでpythonに変換
パラメータやヘッダ、クッキーで照合という流れです。

ichihiroxichihirox

web関連のノウハウが全くないので大変ですが頑張ってみます。
curlで取ってきてawkやsedで整形して使う程度なので。

ichihiroxichihirox

やはり、リダイレクト先はnewb2web-2ではなくnewb2webでした。
init.pyを全てnewb2webに書き換えると伝票を保存して、それを取得するところまでは動きました。
次のpdfを印刷するところで以下のエラーが出ます。
~/.pyenv/versions/3.9.16/lib/python3.9/site-packages/b2cloud/init.py", line 319, in print_issue
issue_no = json.loads(response.text)['feed']['title']
KeyError: 'title'
なお、実際に伝票が保存されていることは確認しました。

Naofumi.HigashikawauchiNaofumi.Higashikawauchi

アカウントによってちがうということですね・・・これは開発が困難ですね。
全部のURLを変えて試すしてみるのがよいですかね。

ichihiroxichihirox

原因がわかりました。
トラッキング番号の頭の3文字が変更されていました。
ここのVMNがUMNに変わっていました。

    if entry_feed['feed']['entry'][0]['shipment']['tracking_number'].startswith('VMN'):

番号が一杯になると変わるのでしょう。
ひとまず安心です。