スイッチ転売屋をぶっとばせ!/Python+Seleniumで在庫監視・自動注文Botを作ってみる

更新 4/28 10:50 スイッチ買えました(Botは使っていないです)

この記事でわかること

  • 現在,転売屋がBotを大量に使用しており,ECサイトで任天堂スイッチを購入するのは困難.
  • そのBotの仕組みは必ずしも複雑ではなくて,在庫監視→自動注文というシンプルな実装でも十分.
  • Python + Seleniumで簡単なBotをすぐに作り,手元で動かすことができる.※スクレイピングは紳士的に.
  • 誰もが簡単にBotを使えるようになれば,買い占めによる不当な価格の釣り上げを利用する転売屋のシステムにとって致命傷かも?

今何が起きているのか

スイッチの品薄が発生

4月7日の報道より,新型コロナウイルスの影響による生産の遅れ+外出自粛による巣篭もり需要の高まりから,国内出荷を停止.
任天堂、スイッチ国内出荷を一時停止 巣ごもり需要急増 - 日本経済新聞

実店舗への転売屋の殺到

4月12日の報道より,スイッチが入荷のたびに転売屋がヨドバシカメラ店頭に殺到.
ヨドバシに「Nintendo Switch」転売ヤー殺到でパニック寸前 従業員が恐怖の告白「私たちはもう……」 - 文春オンライン

4月10日の報道より,ヨドバシカメラ 横浜店での新型コロナウイルス感染者の確認.
ヨドバシカメラ 横浜店を休業 派遣スタッフ1人の感染確認で - NHK

-> 販売が完全にオンライン化へ

今スイッチを購入するにはどうすれば良いか?

大きく3つの入手経路がある.

1. ECサイト

本記事で注目するのはこの経路.しかし,現在ECサイトで任天堂Switchを購入するのは非常に困難である.BOTによる在庫監視・自動購入が行われているからだろう.




これらのツイートからわかるように,現在ECサイトにスイッチが入荷しても,数十秒?〜数分以内には売り切れてしまう.海外ではその原因が転売屋の使うボットであると指摘されている.下記する自動注文ツールが出回っていることを鑑みて,日本でも同じ状況だと考えるべきだろう.
Nintendo Switchの買い占めに転売屋はボットを使っている - GIGAZINE
Resellers Using Checkout Bots Are Driving the Nintendo Switch Shortage - VICE
2. オークション・フリマ

この経路でスイッチを購入することは適切ではない.その理由は大きく2つある.

  • 定価以上の価格で販売されているから.

    多くの場合,マスクや消毒液よりも遥かに不要不急な娯楽品を定価を超える価格で購入することは,長期的にはあなたの家計にとって不適切な選択になるだろう.
    スクリーンショット 2020-04-27 15.33.14.png
    aucfanにおいて「ニンテンドースイッチ 本体」の検索結果(2020.4.27 15:34)

  • 現在の転売屋の市場での行動は不適切であり,転売によって利益を得る行為に加担すべきではない.
    利鞘によって利益を得る行為は古今東西の経済に見られる現象だが,現在のスイッチ転売はそれとは一線を画した不適切な行為と見るべきだろう.上記のECサイトのように,買い占めによる不当な価格の釣り上げが起きている可能性が高いからである.

3. 抽選販売

各家電量販店のECサイトなどで抽選販売を行っている.しかし,倍率は数十倍を優に超えることもあり当選は容易でない.またこの中にも転売屋が潜んでいることを肝に命じておくべきだろう.

スクリーンショット 2020-04-27 15.24.06.png
スクリーンショット 2020-04-27 15.24.21.png

ヨドバシ・ドット・コム Nintendo Switch抽選販売サイト
より引用(2020.4.27 15:27)

更にアクセス集中によるECサイトのサーバー障害という二次被害も多発しており,現状は前途多難である.

ヤマダウェブコムの「スイッチ本体」抽選販売がアクセス集中で早期終了に―その応募数は20万件以上 - Inside games
ゲオアプリのスイッチ抽選販売受付、アクセス集中でつながりにくい状況が続く。受付期間が4月20日17時59分まで延長へ - ファミ通.com

Botや自動注文ツールはありふれている

転売屋・Botがインターネットのアクセスの大部分を占めていることを知っている人は,実は少ないのではないだろうか.

チケット購入のアクセス「9割がbot」にびっくり “知恵比べ”の舞台裏 - ITmedia NEWS

Botの果たす役割は大きく「在庫監視」と「自動注文」の2つがある.ユーザー認証が求められる点で自動注文の方がわずかに高度だがどちらも同じフレームワークで扱える.

例えば,先ほどに見たツイートも自動化されている在庫監視Botの一種だろう.

一方,「せどり」などのキーワードで検索してみると,自動注文用の汎用ソフトが数多く見つかる.古典的なソフトもあれば,今まさにスイッチ騒動に乗じて利益を上げようと開発に勤しんでいるソフト(やソフト利用者の閉鎖的なコミュニティ)もあるようだ.

ShopDingDong - ネットショップ自動注文・監視ツール
ラクブラ - RAKUBURA BOT

利鞘で一儲けしようなどとは思わない一般の消費者にとって,有料の自動注文ソフトを購入することは合理的な選択ではないだろう.しかし,これらのBotはインターネット上に大量に存在しており,買い占めによる不当な価格の釣り上げという現象を引き起こしていることは看過すべきでない.

一方,これらのBotの仕組みは決して複雑ではない.この記事を読み通せば簡単なBotを数十分程度で動かすことができるようになるだろう.今起きている現象を理解する一つの取っ掛かりとして,以下の説明を行う.

Seleniumとは

Webアプリケーションのテストに使用されるフレームワークである..そして,一般的なプログラミング言語にバインドされている.
つまり,Webブラウザの操作を自動化することができる. 詳細は下記を参考.
10分で理解する Selenium - Qiita

導入方法

非常に簡単.

pip install selenium
pip install chromedriver-binary #使用しているchromeと同じバージョンを選ぶ

コーディング

0. スクレイピングに当たって知るべきこと

「スクレイピング 注意点」等で調べれば数多くのサイトが見つかる.

Webスクレイピングの注意事項一覧 - Qiita
スクレイピング・クローリングの注意点 - slideship

特に代表的な点は以下の点だろう.
- 情報を公開する場合,著作権法に引っかからないか注意
- サーバー側の負荷に注意(岡崎市立中央図書館がよく議題に取り上げられるように)

1. 事前準備

ログインの自動化は可能だが,二段階認証等の自動化はかなり面倒.ここでは,ログイン情報を保持するために,Selenium用のユーザープロファイルを予め手動で作成する.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import chromedriver_binary

userdata_dir = 'UserData'
options = Options()
options.add_argument('--user-data-dir=' + userdata_dir)
driver = webdriver.Chrome(options=options)

'''
selenium上でログイン・認証処理を行う.
'''

この後にdriver.quit()を行えば,カレントディレクトリのUserDataフォルダにユーザープロファイルが保存され,以後ログイン情報が保持される.

2. 在庫監視

在庫監視を行うには,「かごに入れる」ボタンの存在判定を行うのが簡単である.試しにうまい棒の監視を行ってみよう.

まず,driver.get(url)でサイトにアクセスした後,print(driver.page_source)でソースを見てみると,「かごに入れる」のボタンがclass="uniCartNum"内に記載されている.そして,在庫切れの商品のページソースを見るとclass="uniCartNum"が存在しない.そこで,class="uniCartNum"に注目した動作を行えば良いことがわかる.

<div class="uniCartNum" data-js-sscroll="cart" data-js-flotingactbtn="cart">
  (中略)
  <div class="enterBtn">
    <button type="submit" class=" order_input modCartBtn stEnterOrenge" onclick="DENA_EC = {};DENA_EC.sc_data={code:'item_addcart1',products:[{product_code: '265584570',product_category:'39/3908/390810'}]};_satellite.track('scItemAddClick'); Rtoaster.init(rtID);Rtoaster.track('/rt_cart_in');">
      <p class="modButton">
        <i class="wowma-icon-01_cart buttonIcon"></i>
        <span>かごに入れる</span>
      </p>
    </button>
  </div>
</div>

次のコードのように,res = driver.find_elements_by_class_name("uniCartNum")class="uniCartNum"の要素を検索できる.紳士的なinterval(監視間隔)とlim(監視回数上限)を定めた上で,これが存在するか否かの判定を繰り返すことが在庫監視に相当する.

スクレイピングの本質は,入手したい情報をホームページの構造から適当に抽出することであり,driver.find_element_...の使い方が鍵である.
4. 要素を見つける - Selenium Python Bindings

import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import chromedriver_binary
from logging import StreamHandler, Formatter, INFO, getLogger

url = "https://wowma.jp/item/265584570"
interval = 60 * 5
lim = 100


count = 0        
while True:
    self.driver.get(url)
    res = driver.find_elements_by_class_name("uniCartNum")
    if len(res) == 0:
        getLogger().info("no switch found 404")
    else:
        getLogger().info("switch has been found")
        ``` 自動注文へ ```
        break
    if count > lim:
        break

    time.sleep(interval)
    count += 1

3. 自動注文

重要な点はすでに述べた.ページソースを分析し,driver.find_element_...メソッドを使い,ホームページの構造から入手したい情報を適当に抽出することがスクレイピングの本質である.それを遷移先のページごとに行っていけば良い.面倒そうだが,意外と時間はかからない.
付け加えるとすれば,以下の3点である.

#選択したボタンをクリックする
driver.find_element_by_name(name).click()
#選択したフォームに文字を入力する
driver.find_element_by_name(name).send_keys(passwd)
#遷移が完了するまで適当な時間待機
time.sleep(wait_time)

それらのを踏まえるとと,例えば以下のように自動注文(最終画面まで)を行う関数が作れる.

def order(self):
    time.sleep(wait_time)
    self.driver.find_element_by_name("cartx_buy").click()
    res = self.driver.find_elements_by_name("auonePwd")
    if any(res):
        res[0].send_keys(passwd)
        self.driver.find_elements_by_name("btn_login")[1].click()

    self.driver.find_elements_by_tag_name("label")[-2].click()
    time.sleep(wait_time)
    self.driver.find_elements_by_tag_name("input")[-1].click()

4. 完成

2と3をクラス化したものをとりあえずの完成品とする.

import time
import os

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import chromedriver_binary
import concurrent.futures
from concurrent.futures import ThreadPoolExecutor
from logging import StreamHandler, Formatter, INFO, getLogger

class auto_order:
    def __init__(self, userdata_dir ,passwd, wait = 2):

        self.wait_time = 2 #遷移待機時間
        self.passwd = passwd
        self.userdata_dir = userdata_dir #ユーザープロファイル PATH
        options = Options()
        options.add_argument('--user-data-dir=' + self.userdata_dir)
        self.driver = webdriver.Chrome(options=options)

    def watch_start(self,url, interval = 300, lim = 1000, is_order = False):
        count = 0        
        while True:
            self.driver.get(url)
            res = self.driver.find_elements_by_class_name("uniCartNum")
            if len(res) == 0:
                getLogger().info("no switch found 404")
            else:
                getLogger().info("switch has been found")
                if is_order:
                    res[0].click()
                    self.order()
                break
            if count > lim:
                break

            time.sleep(interval)
            count += 1

    def order(self):
        time.sleep(self.wait_time)
        self.driver.find_element_by_name("cartx_buy").click()
        res = self.driver.find_elements_by_name("auonePwd")
        if any(res):
            res[0].send_keys(self.passwd)
            self.driver.find_elements_by_name("btn_login")[1].click()

        self.driver.find_elements_by_tag_name("label")[-2].click()
        time.sleep(self.wait_time)
        self.driver.find_elements_by_tag_name("input")[-1].click()

    def quit(self):
        self.driver.quit()

実際に任天堂スイッチの在庫監視・自動注文するならこれを以下のように実行すれば良い.
Nintendo Switch Joy-Con(L)/(R) グレー  HAD-S-KAAAA (新モデル)の通販はau Wowma!(ワウマ) - ヤマダ電機auWowma!店|商品ロットナンバー:389209708

model = auto_order(userdata_dir = "UserData",
                   passwd = "password",
                   wait = 3)
model.watch_start("https://wowma.jp/item/265584570", 
                   interval = 600,
                   lim = 50,
                   is_order = True)
model.quit()

注意点

  • 画面遷移が網羅されていないので安定動作が保証されていない.
  • find_elements_by_tag_name(name)[i]のような指定よりは,より構造から対象を一意的に特定できる指定方法の方がベター.

今何が起きているのか(その2)

品薄が発生→実店舗への殺到→オンライン販売へ移行
という部分を前半で取り上げた.
その後,後半で述べた内容の当然の帰結として今起き始めているのは,
オンライン販売へ移行→ECサイトへの殺到→サーバー障害
という現象だろう.ECサイトへの殺到には大きく2通りの経路が思いつく.

  1. 在庫監視+自動注文Botによる継続的な高頻度アクセス+在庫入荷時の大量アクセス
  2. SNS連携した在庫監視Botを閲覧した大量のSNSユーザーの手動アクセス

2として,例えばブログ型サイトのTwitterアカウント(レアチェック)が挙げられる.

こういったSNS上のBotの存在目的は,ブログやECサイトのアフィリエイトである可能性が高いが,ユーザー殺到の一つの大きな流入口であるのは確実だろう.

まとめ

技術

  • 本記事では,Python + Seleniumで簡単にBotが作れることを示した.
  • 営利を目的としたBotに用いられている技術は,本記事の技術の延長線上にあると考えられる.

今起きていること

  • 転売屋はBotを使用し,ECサイトの在庫監視・自動購入を行っている
  • その結果,買い占めによる不当な価格の釣り上げが発生している可能性がある
  • SNS連携した在庫監視BotからのSNSユーザー流入(事実上の人間Bot)がECサイトへの殺到をもたらしている.
  • 現状のままではECサイトの一時的なサーバー障害が頻発する可能性がある.

転売屋をぶっとばすために

一般消費者

  • 転売屋から商品を買わない(買い占めによる不当な価格の釣り上げを助長する事になる)
  • Botを作って転売屋Botとやり合うのは,ECサイトのアクセス殺到問題が起きている現状において適切ではない.
  • しかし,誰もがBotを簡単に使えるようになれば,オンライン上での既存の転売やアフィリエイト(在庫監視Bot)のシステムが揺らぐことは確か.
  • 待つ.(不要不急な商品については)

Olivine-Ryo
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした