seleniumでAmazonの購入履歴を取得したい②

こんにちは、ツバサです。

前回、購入歴画面を開くところまで実装し、今回は日付、価格、商品名を取得します。

購入履歴で取得する情報

処理の流れ

プログラムの流れは下記のイメージです!

  1. 注文の期間を選択する(今回は2022年)
  2. 開いているページの日付、価格、商品名を取得する
  3. 次のページに進む
  4. 2~3を最後のページまで続ける

完成したコード

まずは完成したコード全てを記載しておきます。

※前回の続きなので、前回の書いたコードも含まれています。

# coding:utf-8
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys                     # キー入力
from webdriver_manager.chrome import ChromeDriverManager            # webdriver自動更新用
from getpass import getpass                                         # パスワード入力
import csv                                                          # csv出力

def main():
    user = "****@gmail.com"             # ユーザID
    print('user name is ' + user)
    pw = getpass('input password:')     # パスワード

    # ログインページのURLとXpath
    loginPage = {
        'url':'https://www.amazon.co.jp/ap/signin?_encoding=UTF8&accountStatusPolicy=P1&openid.assoc_handle=jpflex&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.pape.max_auth_age=0&openid.return_to=https%3A%2F%2Fwww.amazon.co.jp%2Fgp%2Fcss%2Forder-history%3Fie%3DUTF8%26ref_%3Dnav_AccountFlyout_orders&pageId=webcs-yourorder&showRmrMe=1',
        'userForm':'/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div[1]/form/div/div/div/div[1]/input[1]',
        'pwForm':'/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div/form/div/div[1]/input',
        'nextButton':'/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div[1]/form/div/div/div/div[2]/span/span/input',
        'loginButton':'/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div/form/div/div[2]/span/span/input'
        }
    controller = WebController(user,pw,loginPage)
    controller.login()          # Amazonへログイン
    controller.getAllHistory()  # Amazon購入履歴を取得

    fName = 'amazon_history.csv'            # 出力ファイル名
    field_name = ['date','amount','name']   # 出力するcsvのヘッダ
    # csv出力処理
    with open(fName, 'w') as file:
        writer = csv.DictWriter(file, fieldnames = field_name,lineterminator='\n')
        writer.writerows(controller.orderInfo)

    #待機(確認用)
    time.sleep(5)

# Web操作関連のクラス
class WebController:
    # 初期設定
    def __init__(self,user,pw,loginPage):
        self.userID = user  # ユーザID
        self.pw = pw        # パスワード
        self.orderInfo = [] # 購入履歴のリスト
        # ログインページの情報
        self.loginPage = {
            'url':loginPage['url'],                 # ページのURL
            'userForm':loginPage['userForm'],       # ユーザIDのテキストボックス
            'pwForm':loginPage['pwForm'],           # PWのテキストボックス
            'nextButton':loginPage['nextButton'],   # PWのテキストボックスを表示するためのボタン
            'loginButton':loginPage['loginButton']  # ログインボタン
            }
        self.driver  = webdriver.Chrome(ChromeDriverManager().install())    # 1.webdriverを更新してChromeを起動
        self.driver.implicitly_wait(10)                                     # 要素が見つかるまで10秒待機
    
    # ログイン部分
    def login(self):
        #2~3.Amazonのログインページを開く
        self.driver.get(self.loginPage['url'])

        # ユーザID入力フォームを取得
        userSelector = self.driver.find_element_by_xpath(self.loginPage['userForm'])
        # ユーザID入力
        print('Input user name.')
        userSelector.send_keys(self.userID)
        
        # 次へボタンを取得(ID入力後、PW入力フォームへ進むためのボタン)
        # ボタンがないサイトもあると思うのでない場合はスキップする
        nextButtonSelector = self.driver.find_element_by_xpath(self.loginPage['nextButton'])
        #次へボタン
        if(len(self.loginPage['nextButton']) > 0):
            print('Push next button.')
            nextButtonSelector.click()
        
        #pw入力フォームを取得
        pwSelector = self.driver.find_element_by_xpath(self.loginPage['pwForm'])
        #pw入力
        print('Input password.')
        pwSelector.send_keys(self.pw)
        
        #ログインボタンを取得
        loginButtonSelector = self.driver.find_element_by_xpath(self.loginPage['loginButton'])
        #ログインボタン押下
        print('Push login button.')
        loginButtonSelector.click()
    
    # クラス外で動かしたい時用(あれば)
    def getDriver(self):
        return self.driver

    ###################################
    # 1ページの購入履歴を取得する
    ###################################
    def pageHistory(self,page):
        # li:ページングのボタンを取り直す
        ul = self.driver.find_elements_by_xpath('//ul[@class="a-pagination"]')
        li = ul[0].find_elements_by_tag_name("li")
        
        # card:注文情報のリスト
        card = self.driver.find_elements_by_xpath('//div[@class="a-box-group a-spacing-base order js-order-card"]')

        # 1注文ごとに処理を行う
        for order in card:
            # 日付と金額を取得
            span = order.find_elements_by_tag_name("span")      # 日付と金額を持つ要素(spanタグ)を取得
            for i in range(len(span)):
                if(i > 3):
                    continue        # 4個目までのデータ(spanタグ)しか使わないので以降は飛ばす
                text = span[i].get_attribute("textContent").strip()     # spanタグ内のテキストを取得する
                if(i==1):
                    date = text     # 2個目のspan:日付を取得
                elif(i==3):
                    amount = text   # 4個目のspan:金額を取得
            
            # 商品名を取得
            # left_grid:商品名を持つHTML要素
            left_grid = order.find_elements_by_class_name('a-fixed-left-grid-col.yohtmlc-item.a-col-right')
            name = ''   # ここに1つの注文にある全商品名を連結する
            
            # left_gridから商品名を抜き出す
            for j in range(len(left_grid)):
                a_row = left_grid[j].find_element_by_class_name('a-row')    # 商品名を持つタグのクラスを取得
                if(j > 0):
                    name = name + ',' + a_row.get_attribute("textContent").strip()
                else :
                    name = a_row.get_attribute("textContent").strip()
            
            # order['date']:日付,order['amount']:金額,order['name']:商品名 のディクショナリ
            order = {'date':date,'amount':amount,'name':name}
            self.orderInfo.append(order)   # ディクショナリをリストへ格納
        
        li[page].click()    # 次のページへ移動
        time.sleep(3)

    ###################################
    # 購入履歴を取得する
    ###################################
    def getAllHistory(self):
        # セレクトボックス取得
        select = self.driver.find_elements_by_xpath('//select[@name="orderFilter"]')
        option = select[0].find_elements_by_tag_name('option')
        option[2].click() #セレクトボックスの3つ目(2022年)をクリックして履歴の件数変更
        
        # ページングのボタンを取得する
        ul = self.driver.find_elements_by_xpath('//ul[@class="a-pagination"]') # ボタン群
        li = ul[0].find_elements_by_tag_name("li")
        nextButtonNum = len(li)

        # 前へボタンと次へボタンを省いた数分ループ
        # 1ページずつ履歴を取得していく
        for i in range(2,nextButtonNum,1):
            self.pageHistory(i)

main()

1. 表示範囲を2022年にする

まずはセレクトボックスの値を変更して表示件数を変更します。

セレクトボックス

セレクトボックスの2022年をクリックするために、ここのXPathを取得します。

セレクトボックスのソースコード(HTML)を抜粋すると下記のようになっていました。

<select name="orderFilter" id="orderFilter" tabindex="0" class="a-native-dropdown a-declarative">
    <option value="last30" id="orderFilterEntry-last30">
        過去30日間
    </option>
    <option value="months-3" id="orderFilterEntry-months-3">
        過去3か月
    </option>
    <option value="year-2022" id="orderFilterEntry-year-2022" selected="">
        2022年
    </option>
    <option value="" id="" selected="">
        以下続く
    </option>
</select>

親のselectタグを取得すると、配下のoptionタグ達はlistの要素としてまとまるので、listのn番目を指定すれば任意の期間をクリックできます。今回は2022年なので三番目(添字は2)です。こいつをクリックします。

完成したコードの94~95行目と、143~144行目です。

# セレクトボックス取得
select = self.driver.find_elements_by_xpath('//select[@name="orderFilter"]')
option = select[0].find_elements_by_tag_name('option')
option[2].click() #セレクトボックスの3つ目(2022年)をクリックして履歴の件数変更

2. 開いているページの日付、価格、商品名を取得する

一旦下記画像の枠内で大きく取って、そこから日付、金額、商品名に分けました。

赤枠のHTMLはざっくりこんなかんじになっているので、

<div class="a-box-group a-spacing-base order js-order-card">
   <div class="***">
      ここに日付や金額などの中身がある
   </div>
</div>

クラス名からXPathを使ってこんな感じで取得できます。

完成したコードの98行目です。

card = driver.find_elements_by_xpath('//div[@class="a-box-group a-spacing-base order js-order-card"]')

このcard変数から、日付、金額、商品名を抜き出します。

日付と金額はそれぞれspanタグ内に書かれています。cardからspanタグの値を抜き出し、そのリストの2個目と4個目が日付と金額のデータです。

商品名だけはspanタグにないので別の方法(今回はクラス)を使って取得します。1回の注文で複数の商品を買っているとリストの数が複数になるのでこちらもループを使ってあるだけやります。

HTMLはイメージでこんなかんじでした。

<div class="a-box-group a-spacing-base order js-order-card">
      <!-- 1部タグ省略 -->
      <!-- 日付はこんな感じ -->
      <div>
            <span class="a-color-secondary value">
                  2022年10月8日
            </span>
      </div>
      <!-- 日付はこんな感じ -->
      <div>
            <span class="a-color-secondary value">
                  2022年10月8日
            </span>
      </div>

      <!-- 商品名はこんな感じ -->
      <div class="">
            <div class="a-row">
                  <a href="a-fixed-left-grid-col yohtmlc-item a-col-right">商品名</a>
            <div>
      </div>
</div>

日付、金額、商品名取得部分です。完成したコードの97~128行目が該当します。

# card:注文情報のリスト
card = self.driver.find_elements_by_xpath('//div[@class="a-box-group a-spacing-base order js-order-card"]')

# 1注文ごとに処理を行う
for order in card:
    # 日付と金額を取得
    span = order.find_elements_by_tag_name("span")      # 日付と金額を持つ要素(spanタグ)を取得
    for i in range(len(span)):
        if(i > 3):
            continue        # 4個目までのデータ(spanタグ)しか使わないので以降は飛ばす
        text = span[i].get_attribute("textContent").strip()     # spanタグ内のテキストを取得する
        if(i==1):
            date = text     # 2個目のspan:日付を取得
        elif(i==3):
            amount = text   # 4個目のspan:金額を取得
    
    # 商品名を取得
    # left_grid:商品名を持つHTML要素
    left_grid = order.find_elements_by_class_name('a-fixed-left-grid-col.yohtmlc-item.a-col-right')
    name = ''   # ここに1つの注文にある全商品名を連結する
    
    # left_gridから商品名を抜き出す
    for j in range(len(left_grid)):
        a_row = left_grid[j].find_element_by_class_name('a-row')    # 商品名を持つタグのクラスを取得
        if(j > 0):
            name = name + ',' + a_row.get_attribute("textContent").strip()
        else :
            name = a_row.get_attribute("textContent").strip()
    
    # order['date']:日付,order['amount']:金額,order['name']:商品名 のディクショナリ
    order = {'date':date,'amount':amount,'name':name}
    self.orderInfo.append(order)   # ディクショナリをリストへ格納

3. 次のページに進む

上記までで1ページに表示されている履歴は取得できたので、2ページ目以降の履歴も取得します。

購入履歴画面下にあるページングのボタンをあるだけクリックし、ページ遷移するたびに2でやったデータ取得を行うだけです。

注意点としては一度ページ遷移すると2までで取得したhtml elementの変数の値が消えてしまうようです。そのため、ページ遷移するために値を格納し直す必要があります。そうしないと下記のようなエラーが出ます。

selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document

4. csvに出力

各ページで取得した購入履歴はディクショナリ変数に格納していました。このディクショナリ変数から、csvに出力して終了です。

出力にはcsvライブラリを使用します。完成コードから該当部分を抜き出したのが下記です。

完成したコードの26~31行目です。

import csv
fName = 'amazon_history.csv'
field_name = ['date','amount','name']
with open(fName, 'w') as file:
    writer = csv.DictWriter(file, fieldnames = field_name,lineterminator='\n')
    writer.writerows(controller.orderInfo)

おわりに

これで1年間の購入履歴の取得ができました。各データの取得方法については、正直もっとシンプルな方法があると思っています。こちらについてはスクレイピングにもっと慣れてスキル向上していきたいですね。

最後までご覧いただき、ありがとうございました。

コメント

タイトルとURLをコピーしました