2021年9月 クロネコヤマトの荷物問い合わせシステムのページレイアウトが変更になって、送り状番号から荷物の配送状況を取得していたバッチがエラーとなった。
APIを使用すればよいという話だが、ヤマトビジネスメンバーズは他部署で契約&契約した人は退職で詳細不明になっている。まあ、面倒なので、PHPで問い合わせページから荷物状況を取得するプログラムを書いた。
実際はベタ書きしているが、かっこ悪いので、公開に当たりクラス化。1回テスト確認はしたが、使っていないので、エラーがああるかも。PHP5.4だが、7でも8でも動くと思う。取得した状況には「▶」が付いているので不要であれば消して使ってください。
内部でサイト解析のためDOMDocument使っているので使える環境のこと。以下、使い方とクラス。
使い方
$truckNumbers = array(
'444486855064',
'4444-9099-9090',
'444490999101',
); // いくつでも可
$objYamato = new clsGetYamatoDeliverStatus();
$aryDelivStatus = $objYamato->getYamatoDeliverStatus($truckNumbers);
if ($aryDelivStatus === false){ /* エラー ヤマト追跡番号 状況取得失敗 */ }
var_dump($aryDelivStatus);
/*
結果は[
(‐無しの追跡番号),
(‐有りの追跡番号),
状況日,
配達状況,
問い合わせ時間]の配列
array(3) {
[0] =>
array(5) {
[0] =>
string(12) "444486855064"
[1] =>
string(14) "4444-8685-5064"
[2] =>
string(5) "10/20"
[3] =>
string(16) "▶ 配達完了"
[4] =>
string(19) "2021/10/12 09:59:10"
}
[1] =>
array(5) {
[0] =>
string(12) "444490999090"
[1] =>
string(14) "4444-9099-9090"
[2] =>
string(5) "10/20"
[3] =>
string(16) "▶ 配達完了"
[4] =>
string(19) "2021/10/12 09:59:10"
}
[2] =>
array(5) {
[0] =>
string(12) "444490999101"
[1] =>
string(14) "4444-9099-9101"
[2] =>
string(5) "10/20"
[3] =>
string(16) "▶ 配達完了"
[4] =>
string(19) "2021/10/12 09:59:10"
}
}
*/
荷物状況取得 クラス
<?php
class clsGetYamatoDeliverStatus{
// 配列$truckNumbersに指定された追跡番号の状況を全て取得する。$truckNumbersは‐有でも無しでも可。
// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
// 戻り値がfalseの場合、エラー(ヤマト追跡サイトにアクセス出来ない)
public function getYamatoDeliverStatus($truckNumbers){
// 10件ずつヤマトのサイトからトラッキング情報を取得する。
$numnerCnt = count($truckNumbers);
$getTruckNumbers = array();
$aryAllStatusInfo = array();
for ($i=0;$i < $numnerCnt;$i++){
$getTruckNumbers[] = $truckNumbers[$i];
if (count($getTruckNumbers) == 10){
$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
if ($aryTemp === false){ return false; }
$aryAllStatusInfo = $aryTemp;
$getTruckNumbers = array();
}
}
// 残っている場合
if (count($getTruckNumbers) > 0){
$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
if ($aryTemp === false){ return false; }
$aryAllStatusInfo = $aryTemp;
}
return $aryAllStatusInfo;
}
// ヤマトの追跡サイトにアクセスし、10件づつ結果を取得する。
// 引数の$truckNumbersは配列で10件まで。
// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
// 戻り値がfalseの場合エラー(サイトにアクセス出来ない場合のみ)
private function getYamatoDeliverStatusCore($truckNumbers){
$aryRet = array();
$url = 'https://toi.kuronekoyamato.co.jp/cgi-bin/tneko';
$tnumCnt = count($truckNumbers);
$aryPOST = array(
'mypagesession' => '',
'backaddress' => '',
'backrequest' => '',
'number00' => 2
);
$j=0;
for ($i=1;$i<=10;$i++){
$postName = 'number';
if ($i < 10){
$postName .= '0' . $i;
}else{
$postName .= $i;
}
if ($j < $tnumCnt){
$aryPOST[$postName] = $truckNumbers[$j];
}else{
$aryPOST[$postName] = '';
}
$j++;
}
$http_build = http_build_query($aryPOST,'','&');
$header = array(
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: ".strlen($http_build)
);
// オプション
$opts = array("http" => array(
"method" => "POST",
"header" => implode("\r\n", $header),
"content" => $http_build)
);
$scc = stream_context_create($opts);
// 読み取り
$get_html = file_get_contents($url, false, $scc);
if ($get_html === false){ return false; }
// staus取得時間
$getTime = Date('Y/m/d H:i:s');
$dom = new DOMDocument();
$ret = $dom->loadHTML($get_html);
$divResult = $this->getElementsByClassName($dom, 'tracking-box-area', 'div');
$divLen = count($divResult);
for($i=0;$i < $divLen;$i++){
// このdivの下のinputのvalueを取得
$domInputs = $this->getDomNodeFromTagName($divResult[$i], 'input');
if (count($domInputs) == 0){ continue; }
$trNum = $domInputs[0]->attributes->getNamedItem('value')->nodeValue;
if ($trNum == ''){ continue; }
// このdiv以下の日付を取得
$date = '';
$dateNodes = $this->getDomNodeFromClassName($divResult[$i], 'data date');
if (count($dateNodes) == 0){ continue; }
$date = $dateNodes[0]->nodeValue;
// このdiv以下の状況を取得
$state = '';
$dateStates = $this->getDomNodeFromClassName($divResult[$i], 'data state');
if (count($dateStates) == 0){ continue; }
$state = $dateStates[0]->nodeValue;
$aryRet[] = array(
str_replace('-','',$trNum), // ‐なし
$trNum, // ‐あり
$date, // 状況になった日付
$state, // 状況
$getTime // 取得時刻
);
}
return $aryRet;
}
private function getElementsByClassName($dom, $ClassName, $tagName=null) {
if($tagName){
$Elements = $dom->getElementsByTagName($tagName);
}else {
$Elements = $dom->getElementsByTagName("*");
}
$Matched = array();
for($i=0;$i<$Elements->length;$i++) {
if($Elements->item($i)->attributes->getNamedItem('class')){
if(strpos($Elements->item($i)->attributes->getNamedItem('class')->nodeValue, $ClassName) !== false) {
$Matched[]=$Elements->item($i);
}
}
}
return $Matched;
}
// 子ノード以下に指定のタグ名を持つdomNodeの一覧を返す。
private function getDomNodeFromTagName($domNode, $tagName){
$aryRet = array();
foreach ( $domNode->childNodes as $chiledNode ){
if ($chiledNode->nodeName == $tagName){
$aryRet[] = $chiledNode;
}
if ($chiledNode->hasChildNodes()){
$aryTemp = $this->getDomNodeFromTagName($chiledNode, $tagName);
foreach($aryTemp as $temp){
$aryRet[] = $temp;
}
}
}
return $aryRet;
}
// 子ノード以下に指定のクラス名を持つdomNodeの一覧を返す。
private function getDomNodeFromClassName($domNode, $className){
$aryRet = array();
foreach ( $domNode->childNodes as $chiledNode ){
if ($chiledNode->hasAttributes()){
$nodAttr = $chiledNode->attributes->getNamedItem('class');
if ($nodAttr != null){
$nodeClasses = $nodAttr->nodeValue;
if (strpos($nodeClasses, $className) !== false){
$aryRet[] = $chiledNode;
}
}
}
if ($chiledNode->hasChildNodes()){
$aryTemp = $this->getDomNodeFromClassName($chiledNode, $className);
foreach($aryTemp as $temp){
$aryRet[] = $temp;
}
}
}
return $aryRet;
}
}