🍼

amazonと楽天市場、どちらでオムツを買うべきか、pythonでスクレイピングして価格を比較したい人生でした。

2021/11/24に公開

amazon・楽天市場、双方のヘビーユーザでして・・・
どちらでオムツを買うべきか、pythonでスクレイピングして価格を比較したい衝動に駆られました。

やりたいこと

  • amazon で「オムツ ビッグサイズ L」を検索して、その結果を一覧で欲しい(商品名・価格・レビュー平均点・レビュー数)
  • 楽天市場でも同じ商品名を検索、価格を取得してどちらが安いかざっくり見てみたい

紳士協定

  • 随所にsleepを入れて、負荷をかけないように配慮
  • ログインせず、ゲストとして閲覧する

事前確認

amazon を chrome の デベロッパーツール で探索

  • ドロップダウンのリスト内に商品カテゴリが格納されているようです、今回は「ドラッグストア」が良いので「search-alias=hpc」を選択させるのが良さそうです。

  • 検索文字列のインプットフィールド、検索ボタンの要素を控えておきます。

  • 商品情報が入っている divタグ、要素も控えておきます。

  • レビューの評価順 も抑えておきます。

  • あとは、ページネーションを抑えておきます。

全体を通しでみたところ、商品情報の以下カラムに値が無い(空)ものが何個が出てきたので、Noneで埋めるのが吉かなぁという具合でした。

■価格
■レビュー数

また、商品名に「Amazon.co.jp限定」と入っているものは、楽天市場と比較する際に邪魔となりそうです。

楽天市場 を chrome の デベロッパーツール で探索

  • 商品名は「div.content.title > h2 > a」で取れそうです

  • 価格は「div.content.description.price > span」で取れそうです

楽天市場を見ていると、安い商品を探すなら「送料無料」を選択して
並べ替えで「価格の安い順」を選択するのが良さそう。でしたので、抑えておきます。


  • 「送料無料・価格の安い順」選択時のURLの変化を見ていると、クエリパラメータが付与されていたので、こちらを使用するのが良さそう。

■送料無料 → filter=fs
■価格が安い順 → s=2

また商品名もURLで指定しちゃうのが良さそうです。


愚直にコードを書いていく

超ざっくりな流れ

  • <amazon> 商品カテゴリ「ドラッグストア」で「オムツ ビッグサイズ L」を検索。
  • <amazon> レビューの評価順に並べる。
  • <amazon> 商品情報をまとめる。(商品名・価格・レビュー平均点・レビュー数)
  • <楽天> 商品名を検索。「送料無料+価格の安い順」を指定。
  • <楽天> 検索結果に現れた一つ目の商品の「商品名・価格」を取得。
  • 価格を比較。
  • 商品情報に楽天での検索結果(商品名・価格・価格の比較結果)を追記。
  • 商品情報をcsv出力。

使用モジュール

事前確認の結果を踏まえ、以下を使用することにしました。
pythonは3.6を使用しています。

import pandas as pd
import requests
from selenium import webdriver
from time import sleep
from selenium.webdriver.support.select import Select
from bs4 import BeautifulSoup
module 用途
pandas CSV出力用
requests 楽天市場の情報取得
selenium amazonの情報取得
time 紳士協定として随所でsleepさせます
selenium Select ドロップダウンリストの操作
BeautifulSoup 味噌汁生成(HTML解析)

こんな感じになりました

import pandas as pd
import requests
from selenium import webdriver
from time import sleep
from selenium.webdriver.support.select import Select
from bs4 import BeautifulSoup


def amazon_get():
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    driver = webdriver.Chrome(
        executable_path='/Users/ireba-pc/webdriver/chromedriver',
        options=options)

    driver.get('https://www.amazon.co.jp/')
    driver.implicitly_wait(5)
    sleep(1)

    driver.find_element_by_id('nav-search-dropdown-card').click()
    select_category = Select(driver.find_element_by_id('searchDropdownBox'))
    ### 商品カテゴリ「ドラッグストア(search-alias=hpc)」 を選択
    select_category.select_by_value('search-alias=hpc')

    ### オムツ ビッグサイズ L を検索
    driver.find_element_by_css_selector('div.nav-search-field > input').send_keys('オムツ ビッグサイズ L')
    driver.find_element_by_css_selector('div.nav-right > div > span > input').click()

    ### レビューの評価順(review-rank)に並べ替える
    sort_select = Select(driver.find_element_by_id('s-result-sort-select'))
    sort_select.select_by_value('review-rank')
    sleep(1)

    ### ページを取得
    a_tags = driver.find_elements_by_css_selector('ul.a-pagination > li.a-selected > a')
    a_tags += driver.find_elements_by_css_selector('ul.a-pagination > li.a-normal > a')
    ### aタグをa_tagsに格納後、page_linksに入れ直します
    page_links = [a_tag.get_attribute('href') for a_tag in a_tags]

    ### 商品情報をリスト(amz_prd_info)にぶち込んでいく
    amz_prd_info = []
    for page_link in page_links:
        driver.get(page_link)
        sleep(1)
        source = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(source, 'lxml')
        product_soup = soup.select('div.a-section.a-spacing-medium')

        ### 一番下にヘルプのブロックが入ってくるようだったので、-1 として除外しました
        for i in range(len(product_soup) - 1):
	    ### product_soupで味噌汁を作ります
            miso_soup = product_soup[i]
	    ### 書籍名を取得
            amz_prd_name = miso_soup.find('span', class_='a-size-base-plus a-color-base a-text-normal').text
	    ### 価格を取得、値が無い場合は「None」とします
            tmp_price = miso_soup.find('span', class_='a-price-whole')
            amz_price = tmp_price.text.replace('¥', '').replace(',', '') if tmp_price else None
	    ### レビューの平均☆を取得、値が無い場合は「None」とします
            tmp_review_avg = miso_soup.find('span', class_='a-icon-alt')
            review_avg = tmp_review_avg.text if tmp_review_avg else None
	    ### レビュー数を取得、値が無い場合は「None」とします
            tmp_review_num = miso_soup.select_one('div.a-row.a-size-small > span:nth-of-type(2) > a > span')
            review_num = tmp_review_num.text if tmp_review_num else None

            ### 味噌汁から取得した情報を、読みやすく整え、親切丁寧にぶち込んでいきます
            amz_prd_info.append({
                'amz_prd_name': amz_prd_name,
                'amz_price': amz_price,
                'amz_review_avg': review_avg,
                'amz_review_num': review_num
            })

    driver.quit()

    return amz_prd_info

def add_rakuten_comp(amz_prd_info):
    ### amz_prd_infoを一つずつまわしていく
    for amz_prd in amz_prd_info:
        ### 商品名を取得、楽天市場での検索時に「Amazon.co.jp限定」の文字列は邪魔なので滅する
        amz_prd_name = amz_prd['amz_prd_name'].replace('Amazon.co.jp ', 'Amazon.co.jp').replace('【Amazon.co.jp限定】', '')
        ### 「送料無料・価格の安い順(?filter=fs&s=2)」を付与して商品名を検索
        url = 'https://search.rakuten.co.jp/search/mall/' + amz_prd_name + '?filter=fs&s=2'
        r = requests.get(url)
        soup = BeautifulSoup(r.content, 'lxml')
        tmp_price = soup.select_one('div.content.description.price > span')
        rak_price = tmp_price.text.replace('円', '').replace(',', '') if tmp_price else None
        tmp_name = soup.select_one('div.content.title > h2 > a')
        rak_name = tmp_name.text if tmp_name else None

        ### 価格を比較
        if rak_price is not None and amz_prd['amz_price'] is not None:
            amz_price = int(amz_prd['amz_price'])
            rak_price = int(rak_price)
            ### amazonの方が安ければ、cheaperという変数に「Amazon」と入れてあげる
            if amz_price < rak_price:
                cheaper = 'Amazon'
            ### 楽天市場の方が安ければ、cheaperという変数に「Rakuten」と入れてあげる
            elif rak_price < amz_price:
                cheaper = 'Rakuten'
            ### 同じ価格であれば、cheaperという変数に「Same price」と入れてあげる
            else:
                cheaper = 'Same price'
        ### 価格がNoneのものは、cheaperもNoneにしておく
        else:
            cheaper = None

        ### 楽天での検索結果「商品名・価格・価格の安い方(cheaper)」を追記
        amz_prd.update({
            'rakuten_prd_name': rak_name,
            'rakuten_price': rak_price,
            'cheaper': cheaper
        })

    return amz_prd_info


if __name__ == '__main__':
    amz_prd_info = amazon_get()
    result = add_rakuten_comp(amz_prd_info=amz_prd_info)

    ### pandasを使って結果をcsv出力
    df = pd.DataFrame(result)
    df.index = df.index + 1
    df.to_csv('amazon-omutsu.csv', encoding='utf-8-sig')

結果のcsvを見てみます

jupyter notebookを起動

$  jupyter notebook

amazon-omutsu.csvを読み込む

 import pandas as pd
 df = pd.read_csv('amazon-omutsu.csv', index_col=0)



ある程度は同じ商品が比較できていましたが・・・
同じ商品だけど、セット販売(2セットや3セット)になっていて価格に差が出ているものもあり、
商品名の検索だけではちゃんとした比較は難しいなぁ・・・と思いつつ
オムツはウェル活することにします。

Discussion