Re:ゼロから始めるBeatufulSoupによるXMLドキュメント作成【Python】



WebAPIなんかを使っていると、リクエストをするためにXML文字列をリクエストボディに含める要求方法なんかに出くわします。

Yahooショッピングの注文API関係なんかがそのタイプです。

普段は解析専門のBeautifulSoupですが、ゼロからXMLドキュメントの作成もできるんだぞって事を紹介します。

BeautifulSoupによるXML作成

PythonによるXML作成はメジャーどころで言うと、ElementTreeなんかが紹介されていることも多いですが、実はそれBeautifulSoupでも出来ちゃいます。

パース専門というイメージの強いBeautifulSoupですが、割と万能戦士なんですね。

今回はYahooショッピングの注文APIを例にリクエスト用のXMLを作ってみます。

目的のXMLドキュメント構成

ヤフーショッピングの注文検索APIを今回の題材とします。

要求されるリクエストのXML構造は以下の要件。

パラメータ説明
/Reqリクエストパラメータ
/Req/Search検索条件指定情報
/Req/Search/Result
(デフォルト : 10)
integer最大取得件数(上限値:2000)
/Req/Search/Start
(デフォルト : 1)
integer取得開始件数
/Req/Search/Sortstring・注文時間で昇順ソート:+order_time(デフォルト)
・注文時間で降順ソート:-order_time
/Req/Search/Condition検索条件
/Req/Search/Condition/
(検索条件キー)
検索条件
/Req/Search/Field
(必須)
string取得情報
/Req/SellerId
(必須)
stringストアアカウント

提示されているサンプルリクエストXML

<Req>
 <Search>
 <Result>50</Result>
 <Start>1</Start>
 <Sort>+order_time</Sort>
 <Condition>
  <OrderId>testseller-10000001</OrderId>
  <DeviceType>1</DeviceType>
  <IsRoyalty>true</IsRoyalty>
  <IsAffiliate>false</IsAffiliate>
  <OrderStatus>2</OrderStatus>
  <StoreStatus>入荷未定</StoreStatus>
  <IsActive>true</IsActive>
  <Seen>true</Seen>
  <IsSplit>false</IsSplit>
  <Suspect>0</Suspect>
  <IsRoyaltyFix>false</IsRoyaltyFix>
  <PayStatus>1</PayStatus>
  <SettleStatus>1011</SettleStatus>
  <PayType>0</PayType>
  <PayMethod>payment_a1</PayMethod>
  <NeedBillSlip>1</NeedBillSlip>
  <NeedDetailedSlip>0</NeedDetailedSlip>
  <NeedReceipt>1</NeedReceipt>
  <BillPrefecture>千葉県</BillPrefecture>
  <BillZipCode>123-4567</BillZipCode>
  <BillPhoneNumber>123-4567-8900</BillPhoneNumber>
  <BillMailAddress>[email protected]</BillMailAddress>
  <ShipStatus>4</ShipStatus>
  <ShipMethod>佐川急便</ShipMethod>
  <ShipRequestDateNo>true</ShipRequestDateNo>
  <ShipCompanyCode>1002</ShipCompanyCode>
  <ShipInvoiceNumber>333-666666-4444</ShipInvoiceNumber>
  <BuyerCommentsFlag>1</BuyerCommentsFlag>
  <ShipPrefecture>千葉県</ShipPrefecture>
  <ShipZipCode>223-4444</ShipZipCode>
  <ShipPhoneNumber>111-2222-3333</ShipPhoneNumber>
  <ItemId>item-a</ItemId>
  <ReleaseDateFrom>21501231</ReleaseDateFrom>
  <ReleaseDateTo>21501231</ReleaseDateTo>
  <GetPointFixDateFrom>20110309</GetPointFixDateFrom>
  <GetPointFixDateTo>20110309</GetPointFixDateTo>
  <UsePointFixDateFrom>20110309</UsePointFixDateFrom>
  <UsePointFixDateTo>20110309</UsePointFixDateTo>
  <IsLogin>true</IsLogin>
 </Condition>
 <Field>OrderId,Version</Field>
 </Search>
 <SellerId>testseller</SellerId>
</Req>

XML階層構造を見極める

XMLって単純にテキスト量がJSONに比べて多くなるので、「うっ・・・」ってなりがちです。

XML全体で見ると、めちゃくちゃ複雑そうに見えますが、上の方で提示されているツリー構造だけみれば、割と単純なもんです。

「Req」というrootの中に「SellerId」「Search」があり、「Search」の中に「Result」「Start」「Sort」「Condition」「Field」の五つの兄弟要素が存在ます。

あまりに長かったので、上で要件を提示してはいませんが、更にCondition直下に条件指定のタグとテキストが兄弟として並ぶ構造です。

BeautifulSoupでXMLを作ってみる

XMLの作成とかはぶっちゃけた話、どのライブラリ作っても大してやることは変わりません(笑)

なら普段からスクレイピングで使い慣れているBeautifulSoupでやってみようと思い、今回の記事に至っています。

使うメソッドは、

  • append()
  • new_tag()

主にこの二つだけです。

XMLの外殻を作成

実際に作成していきます。

from bs4 import BeatifulSoup

# rootタグのみでbs4オブジェクトを生成
# パーサは'lxml-xml'を使います
soup = BeatifulSoup('<Req>', 'lxml-xml')

# 毎回書くのメンドクサイのでメソッド短縮
new = bs.new_tag 
app = bs.Req.append

# ここからどんどんタグを作っていきます
# Req内に「Search」「SellerId」を配置
app(new('Search')), app(new('SellerId'))

# app 作り直し
# append対象がReqだったのでSearchに切り替えます
app = bs.Search.append

# Search内に更に小要素を作る
app(new('Result')), app(new('Start'))
app(new('Sort')), app(new('Field')), app(new('Condition'))

ここまでで要求されているXMLの外殻が完成です。
完成したXMLは以下の状態。

>>>print(bs.prettify())
<?xml version="1.0" encoding="utf-8"?>
<Req>
 <Search>
  <Result/>
  <Start/>
  <Sort/>
  <Field/>
  <Condition/>
 </Search>
 <SellerId/>
</Req>

何かそれっぽくなりましたね。
後は中身をブチ込んでいきます。

タグ内の要素を作成

今回はディフォルトと必須項目を入れていってみます。

Field内にタグは不要なのでリスト型、コンディションはタグが必要なので辞書型で適当にリクエスト作成してXMLにしてみます。

要件の確認はリファレンスから。

Field と Conditionのダミーデータ作成

# Field ダミーデータ作成
# pandasでリファレンスページにアクセス
import pandas as pd
url = 'https://developer.yahoo.co.jp/webapi/shopping/orderList.html'
# Fieldの概要テーブルから項目名をリスト型で根こそぎ取得
Field = pd.read_html(url)[2]['キー名'].tolist()

>>>print(Field)
['OrderId',
 'Version',
 'OriginalOrderId',
 'ParentOrderId',
 'DeviceType',
 'IsActive',
 'IsSeen',
 'IsSplit',
 'IsRoyalty',
 'IsSeller',
 'IsAffiliate',
 'IsRatingB2s',
 'OrderTime',
 'ExistMultiReleaseDate',
 'ReleaseDate',
 'LastUpdateTime',
 'Suspect',
 'OrderStatus',
 'StoreStatus',
 'RoyaltyFixTime',
 'PrintSlipFlag',
 'PrintDeliveryFlag',
 'PrintBillFlag',
 'BuyerCommentsFlag',
 'PayStatus',
 'SettleStatus',
 'PayType',
 'PayMethod',
 'PayMethodName',
 'PayDate',
 'SettleId',
 'UseWallet',
 'NeedBillSlip',
 'NeedDetailedSlip',
 'NeedReceipt',
 'BillFirstName',
 'BillFirstNameKana',
 'BillLastName',
 'BillLastNameKana',
 'BillPrefecture',
 'ShipStatus',
 'ShipMethod',
 'ShipMethodName',
 'ShipRequestDate',
 'ShipRequestTime',
 'ShipNotes',
 'ShipCompanyCode',
 'ReceiveShopCode',
 'ShipInvoiceNumber1',
 'ShipInvoiceNumber2',
 'ShipInvoiceNumberEmptyReason',
 'ShipUrl',
 'ArriveType',
 'ShipDate',
 'NeedGiftWrap',
 'NeedGiftWrapMessage',
 'NeedGiftWrapPaper',
 'ShipFirstName',
 'ShipFirstNameKana',
 'ShipLastName',
 'ShipLastNameKana',
 'ShipPrefecture',
 'PayCharge',
 'ShipCharge',
 'GiftWrapCharge',
 'Discount',
 'UsePoint',
 'TotalPrice',
 'RefundTotalPrice',
 'UsePointFixDate',
 'IsUsePointFix',
 'IsGetPointFixAll',
 'SellerId',
 'IsLogin',
 'PayNo',
 'PayNoIssueDate',
 'SellerType',
 'IsPayManagement',
 'ArrivalDate',
 'TotalMallCouponDiscount',
 'IsReadOnly',
 'IsApplePay',
 'IsFirstClassDrugIncludes',
 'IsFirstClassDrugAgreement']


# ConditionはOrderTime[From][To]の二項目のみ設定
from datetime import datetime
from datetime import timedelta

# 今の時間
now = datetime.now()
# 「今」をフォーマットに従って文字列化
to_default = now.strftime('%Y%m%d%H%M%S')
# 「今」から30日前を計算して、文字列化
from_default = (now - timedelta(30)).strftime('%Y%m%d%H%M%S')

Condition = {
    'OrderTimeTo': to_default,
    'OrderTimeFrom': from_default
}

XMLのタグ内に代入していく

# 外殻作った続きから
bs.SellerId.string = 'test-seller'
bs.Result.string = str(10) # int型だと代入できないので注意
bs.Start.string = str(1) # int型だと代入できないので注意
bs.Sort.string = '+order_time'
bs.Field.string = ','.join(Field)

#コンディションだけはタグ構造なので追加先をappに代入
app = bs.Condition.append
for key in Condition:
    app(new(key))
    bs.find(key).string = Condition[key]

XML完成系

print(bs.prettify())
<?xml version="1.0" encoding="utf-8"?>
<Req>
 <Search>
  <Result>
   10
  </Result>
  <Start>
   1
  </Start>
  <Sort>
   +order_time
  </Sort>
  <Field>
OrderId,Version,OriginalOrderId,ParentOrderId,DeviceType,IsActive,IsSeen,IsSplit,IsRoyalty,IsSeller,IsAffiliate,IsRatingB2s,OrderTime,ExistMultiReleaseDate,ReleaseDate,LastUpdateTime,Suspect,OrderStatus,StoreStatus,RoyaltyFixTime,PrintSlipFlag,PrintDeliveryFlag,PrintBillFlag,BuyerCommentsFlag,PayStatus,SettleStatus,PayType,PayMethod,PayMethodName,PayDate,SettleId,UseWallet,NeedBillSlip,NeedDetailedSlip,NeedReceipt,BillFirstName,BillFirstNameKana,BillLastName,BillLastNameKana,BillPrefecture,ShipStatus,ShipMethod,ShipMethodName,ShipRequestDate,ShipRequestTime,ShipNotes,ShipCompanyCode,ReceiveShopCode,ShipInvoiceNumber1,ShipInvoiceNumber2,ShipInvoiceNumberEmptyReason,ShipUrl,ArriveType,ShipDate,NeedGiftWrap,NeedGiftWrapMessage,NeedGiftWrapPaper,ShipFirstName,ShipFirstNameKana,ShipLastName,ShipLastNameKana,ShipPrefecture,PayCharge,ShipCharge,GiftWrapCharge,Discount,UsePoint,TotalPrice,RefundTotalPrice,UsePointFixDate,IsUsePointFix,IsGetPointFixAll,SellerId,IsLogin,PayNo,PayNoIssueDate,SellerType,IsPayManagement,ArrivalDate,TotalMallCouponDiscount,IsReadOnly,IsApplePay,IsFirstClassDrugIncludes,IsFirstClassDrugAgreement
  </Field>
  <Condition>
   <OrderTimeTo>
    20190828155037
   </OrderTimeTo>
   <OrderTimeFrom>
    20190729155037
   </OrderTimeFrom>
  </Condition>
 </Search>
 <SellerId>
  test-seller
 </SellerId>
</Req>

BeautifulSoupによるXML作成 - 総括

元々パーサのイメージが強いBeautifulSoupですが、十分にXML作成の機能があることは伝わったかと思います。

結構万能なので使い始めると手放せない便利なヤツです。

元々フォーマットが決まっている場合は、わざわざ0から作らなくても、空枠をXMLファイルとして保管しておいて、BeautifulSoupで「テンプレ読み込み」→「代入」→「再出力」なんかでもいいかもしれませんね。

当たり前ですが、XMLの作成だけでなくパース(解析)もできます。以下の記事で紹介してますので、気になる方は覗いてみてください。

created by Rinker
¥755 (2022/07/01 16:28:44時点 Amazon調べ-詳細)