Amazon PA-API が v5になって使えなくなったから、スクレイピングして強引にデーターを持ってくるVBAを作った話

## 初めに

**amazonPA-API**と**楽天ブックス書籍検索API**に書籍やCD、DVDの情報を取りに行き、記事の文末にアフィリエイトのリンクを貼るためのHTMLを生成するツールをPHPで作っていました。

ツールの仕様は、本の見出しやISBN-10で検索し、amazonのAPIから必要な情報を取得した後に、ISBNを持って楽天APIに行って、楽天からも書籍データーを取得します。それを規定のHTMLの指定の場所に挿入して、１つのHTMLの塊として書き出し、それをコピペして使うというものです。

商品画像や本のタイトル、著者情報や価格はAmazonから引用したもので、それらをクリックするとAmazonの商品ページにジャンプし、楽天ショッピング用のボタンもあり、それを押すと、楽天の該当商品の画面にジャンプします。

## ツールが動かないとの連絡
２０２０年３月中旬に、このツールが動かなくなったと各部署から連絡があり、調べるとAmazon PA-APIがv5にバージョンアップしていて、 `PA-API v5への移行を2020年3月9日までに実施しなかった場合、現在のPA-APIはご利用をいただけなくなります`とありました。 
>▪️PA-API v5移行のご案内 (2020/3/9まで）
>https://affiliate.amazon.co.jp/help/node/topic/GZBFW3F79Y7FADBL

もともとアカウントを管理しているのは、営業部門で、我々IT部門はツールを作っただけでした。
連絡も管理者にはメールが届いていたようですが、見事に見逃していたようで、こちらには連絡が一切ありませんでした。

##ツールをPA-API 5.0に移行

amazonの移行ガイドには以下のように書かれています。
そのため、アカウントを移行してから新しい認証情報(AWSアクセスキーとAWSシークレットキー) を取得し、それをツールにセットしました。

>現在お持ちのAWS認証情報 (AWSアクセスキーとAWSシークレットキー) は PA-API 5.0では使えません。
>現在AWSの認証情報をお持ちの場合は、アカウントを移行してから新しい認証情報を作ることができます。

##「リクエスト回数が多すぎる」というエラーで値が取れない

```
Error! RequestThrottled
You are submitting requests too quickly. Please retry your requests at a slower rate.
```

というエラーが出て値が取れない。
`requests too quickly`とあるので時間をおいてアクセスしてもダメ！なんどやってもダメ！全然「too quickly」じゃ無いのに。

そもそも1日に多くても数回しかアクセスしていない（テストでも２桁前半）のに、「requests too quickly」なら、誰が使えんねん！と思いつつググる。

以下のポリシーの変更（特に売り上げ実績のところ）のせいなのかと思いアカウントを管理している部署に問い合わせるも、売り上げはあるとの回答（額は教えてくれない）
https://affiliate.amazon.co.jp/help/node/topic/GW65C7J2CSK7CA6C

>2019年01月23日以降、過去30日以内のPA-API経由の売上実績（発送済み商品売上）がない場合、PA-APIへのアクセスができなくなる可能性がございます。

結局、理由がわからない。

ここで、ツールがループしてなんどもamazon PA-APIにアクセスしているのかと疑う。

##スクラッチパッドでもデーターが取れない
v5.0用の認証情報 (AWSアクセスキーとAWSシークレットキー) でスクラッチパッドで書籍を検索してもデーターが取れない。

また、これでツールがループしてなんどもamazon PA-APIにアクセスしていないこが証明できた。

もうわけわからん。

##amazonの担当者にメールで問い合わせ
送った内容

```
今週から、「Associate Tag：●●●●●●」アカウントでAPIを叩くと
「Error! RequestThrottled」とのエラーが出るようになりました。
「 You are submitting requests too quickly. Please retry your requests at a
slower rate.」とありますが、多くてもアクセスは１時間に数回です。

また、ネットで調べると、規約改定により、30日間、売り上げが無いとAPIが使えなくなり、その時も上記エラーが出る。とありました。
上記のエラーはこれに該当するものでしょうか？

また、ほかに原因となりそうな点がございましたらご教示いただけると幸いです。
よろしくお願い申し上げます。
```

amazonカスタマーサービスから返ってきたのは

```
Amazon.co.jpアソシエイト・プログラムにお問い合わせいただき、ありがとうございます。

下記メッセージが表示される状態はAPIのサーバーに複数の同時アクセスがあり、先着順で接続処理が行われ、その中で接続出来なかった方に表示されるものとなります。

---------------------------------------------------------------------
You are submitting requests too quickly. Please retry your requests at a
slower rate.
---------------------------------------------------------------------
つまり、お客様以外にも多数のお客様がPA-APIのリクエストを試みている場合は上記メッセージは出やすくなるかと存じます。

1日のリクエスト可能数をご理解の上、上記メッセージが返却・表示された場合は、再度リクエストを送信いただきますようお願いいたします。

アソシエイト・プログラムをご利用いただき、ありがとうございます。
```

この回答に対し、再度の返信

```
返信いただいた下記メッセージでは「お客様以外にも多数のお客様がPA-APIのリクエストを試みている場合は上記メッセージは出やすくなるかと存じます。」とありますが、これは我々だけでなく、その他の（世界中の）ユーザーも
含めてのことでしょうか。

今もAPIにアクセスを行いましたが、到達できませんでした。今週一度もAPIにアクセスできていません。
原因は、複数の同時アクセスが問題というだけでしょうか。
ご回答いただけると幸いです。よろしくお願い申し上げます。
```

amazonカスタマーサービスからの返答

```
カスタマーサービスからのお知らせ

Amazon.co.jpアソシエイト・プログラムにお問い合わせいただき、ありがとうございます。

下記メッセージが表示される状態はAPIのサーバーに複数の同時アクセスがあり、先着順で接続処理が行われ、その中で接続出来なかった方に表示されるものとなります。

---------------------------------------------------------------------
You are submitting requests too quickly. Please retry your requests at a
slower rate.
---------------------------------------------------------------------

つまり、お客様以外にも多数のお客様がPA-APIのリクエストを試みている場合は上記メッセージは出やすくなるかと存じます。

1日のリクエスト可能数をご理解の上、上記メッセージが返却・表示された場合は、再度リクエストを送信いただきますようお願いいたします。
```
要するに、世界中から同時にAPIにアクセスする数が単純に多いから、ということ？？？

##諦めて、ツールを作り直す
もはや、APIから情報を取ることを諦め、該当の商品ページのHTMLからスクレイピングして必要なデーターを取ってこれるツールを作成することに決めました。

ツールを何で作ろうかと考えたのだが、**Excel VBA**にしました。

ググると、amazon PA-APIの問題は時間が解決してくれるとの情報がちらほらあったのと、
phpを使っているサーバーが古いということもあり、
会社のAWSにインスタンス立てて、そこでもよかったのですが、お金もかかるし申請が面倒なので、

どの環境でも動く「Excel VBA」で作りました。

##基本設計
```
Dim isbn As String
Dim ans As String
Dim doc As New XmlScraping
isbn = InputBox("ISBN-10を入力ください", "")
ans = "https://www.amazon.co.jp/dp/" & isbn
If ans <> "" Then
   doc.gotoPage ans
End If
```
ユーザーにinputboxから**ISBN-10**を入力してもらい、その値を、`https://www.amazon.co.jp/dp/`の文末に追加し、変数`ans`に格納。
`gotoPage`でそのページに移動し、データーを変数`doc`に格納する。

後は、ワークシートの「tool」をターゲットに値を貼り付けていく

```
Worksheets("tool").Select
numberTitles = doc.css(".content ul li").count
Cells(1, 1) = "タイトル"
Cells(1, 2) = doc.css("#productTitle").index(0).text
Cells(2, 1) = "画像URL"
Cells(2, 2) = doc.css("#img-canvas").index(0).html
Cells(3, 1) = "価格"
Cells(3, 2) = doc.css(".a-color-price").index(0).text
Cells(3, 2) = Replace(Cells(3, 2), "￥", "")
Cells(3, 2) = Replace(Cells(3, 2), "\", "")
For i = 1 To numberTitles - 1
    If InStr(doc.css(".content ul li").index(i).text, "出版社") > 0 Then
        Cells(5, 2) = doc.css(".content ul li").index(i).text
        Cells(5, 2) = Replace(Cells(5, 2), "出版社: ", "")
        Cells(5, 1) = "出版社"
    ElseIf InStr(doc.css(".content ul li").index(i).text, "ISBN-10") > 0 Then
        Cells(6, 2) = doc.css(".content ul li").index(i).text
        Cells(6, 2).NumberFormatLocal = "@"
        Cells(6, 2) = Replace(Cells(6, 2), "ISBN-10: ", "")
        Cells(6, 1) = "amazonコード"
    End If
Next i
```
後は、正規表現で必要な値を取り出していく。例えば以下。

```
    Dim x As String
    Dim regx As RegExp
    Dim m As Match
    Dim ms As MatchCollection
    Set regx = New RegExp
    regx.Pattern = "https:\/\/images-.{2}\.ssl-images-amazon\.com\/images\/.*\.jpg"
    regx.Global = True
    Set ms = regx.Execute(Cells(2, 2))
    For Each m In ms
        Cells(2, 2) = m
    Next
    Set regx = Nothing
    Cells(2, 2) = Replace(Cells(2, 2), "https://images-fe.ssl-images-amazon.com/images/I/", "")
```

同じようにISBNを使って楽天の該当商品まで行き、値を取って正規表現で綺麗にしてあげればOK。

```
ans2 = "https://books.rakuten.co.jp/search?sitem=" & Cells(6, 2)
If ans2 <> "" Then
    doc2.gotoPage ans2
End If
Cells(7, 2) = doc2.css(".rbcomp__item-list__item__image").index(0).html
Dim x2 As String
Dim regx2 As RegExp
Dim m2 As Match
Dim ms2 As MatchCollection
Set regx2 = New RegExp
regx2.Pattern = "https:\/\/books\.rakuten\.co\.jp\/rb\/.{8}\/"
regx2.Global = True
Set ms2 = regx2.Execute(Cells(7, 2))
For Each m2 In ms2
Cells(7, 2) = m2
Next
Cells(7, 2) = Replace(Cells(7, 2), "https://books.rakuten.co.jp/", "")
Set regx2 = Nothing
```

##ASINの場合は楽天の商品と紐付かない
商品コードがISBNの場合はそれをアマゾンにも楽天にも持っていいって使えるのだが、ASINの場合は紐付かない。
色々考えたんですが結局、ユーザーに`ASIN`と`JAN`を入力してもらう事にしました。

以上。