tkinterを使ってBUYMAをクローリングするGUIアプリケーションを作ってみた

2022年5月3日

tkinterを使ってBUYMAをクローリングするGUIアプリケーションを作ってみた

前回はnetkeibaからデータを抜き取ってくるプログラムを作りましたが、今回はECサイト「BUYMA」からデータを抜き取ってきたいと思い作成しました。(※本記事ではスクレイピングは実装していませんが...)今回は、対象データの選定があるのでGUIアプリケーションから実行できる方が良いと思いtkinterからseleniumを実行するような仕様にしました。

GUIにログを出力できる仕様したのですが、GUIとブラウザを並行処理する為に「マルチスレッド」にしないといけないのでそこが気を付けどころでした。参考のURLを貼っておきます。

GUIは下のようなシンプルな見た目です。クローリングだけならログインは必要ありませんが、自動出品などの機能を後から追加するのも考えてIDとパスワード入力欄も設置しました。

最終的にブラウザが自動で各商品のページにアクセスします。

参考:
  ・tkinterを別スレッドで起動したときは、そのスレッド自身で停止処理して変数を後始末してからスレッド終了すること
  ・Python スレッドの停止と再開の簡易サンプル

コード


# --スクレイピング-----------------------------------------
from selenium import webdriver
import datetime
import time
# --デスクトップアプリ-------------------------------------
import tkinter as tk 
from tkinter import scrolledtext
import threading
class BrowserController():
    def __init__(self, tkinterController, setting_params):
        self.tkinterController = tkinterController
        self.base_url        = 'https://www.buyma.com'
        self.login_id        = setting_params['input_login_id']
        self.login_password  = setting_params['input_login_password']
        self.target_page_url = setting_params['input_url']
    def open_buyma(self):
        options = webdriver.ChromeOptions()
        options.add_argument('--ignore-certificate-errors')
        options.add_argument('--ignore-ssl-errors')
        self.browser = webdriver.Chrome(executable_path = 'C://xxx//xxx//xxx//chromedriver.exe', chrome_options=options)
        self.browser.implicitly_wait(3)
        self.browser.maximize_window()
        self.browser.get(self.base_url)
        time.sleep(3)
        self.tkinterController.activate_logging('ページが表示されました。')
        return
    def login_buyma(self):
        # ポップアップを閉じる
        self.browser.find_element_by_class_name('fab-cp-modal__close').click()
        # ログインボタンを押す
        self.browser.find_element_by_id('js-pc-header-login-link').click()
        # ログインIDを入力する
        self.browser.find_element_by_id('txtLoginId').send_keys(self.login_id)
        # パスワードを入力する
        self.browser.find_element_by_id('txtLoginPass').send_keys(self.login_password)
        time.sleep(3)
        # ログインボタンを押す
        self.browser.find_element_by_id('login_do').click()
        time.sleep(2)
        self.tkinterController.activate_logging('ログインが完了しました。')
        return
    def get_item_urls(self):
        # 初期化
        page_index = 0
        first_page = True
        is_data    = False
        item_urls  = []
        # データが取得できなくなったら終了
        while first_page or is_data:
            # 初期ページフラグ無効化
            first_page = False
            # ページ用URLを生成
            page_index = page_index + 1
            suffix_url = f'_{str(page_index)}'
            # 一番後ろの「/」にページ番号を付与
            target_page_url = self.target_page_url.split('/')
            target_page_url.insert(-1, suffix_url)
            target_page_url = '/'.join(target_page_url)
            # URLを開く
            self.browser.get(target_page_url)
            time.sleep(2)
            # アイテム情報を配列で取得
            items = self.browser.find_elements_by_class_name('product')
            if items:
                # 継続フラグを有効化
                is_data = True
                # アイテム情報が存在する場合アイテム個別のURLを取得
                for item in items:
                    end_of_item_url = item.find_element_by_tag_name('div').get_attribute('item-url')
                    item_url = self.base_url + end_of_item_url
                    item_urls.append(item_url)
                self.tkinterController.activate_logging(target_page_url+'の情報を取得しました。')
            else:
                # 継続フラグを無効化
                is_data = False
                self.tkinterController.activate_logging('全てのページの情報を取得しました。')
        return item_urls
    def open_item_urls_for_each(self, item_urls):
        for item_url in item_urls:
            self.browser.get(item_url)
            """
            ここにアイテムの詳細情報を取得するプログラムを記述する
            """
            self.tkinterController.activate_logging(item_url+'の情報を取得しました。')
            time.sleep(3)
        return
class TkinterController():
    def __init__(self):
        self.input_login_id       = ''
        self.input_login_password = ''
        self.input_url            = ''
        self.logging_area         = ''
        self.win                  = ''
        # ウィンドウ設定
        self.win = tk.Tk()
        self.win.title("GetBuymaData")
        self.win.geometry("800x580")
        # フレーム設定
        setting_frame = tk.Frame(self.win, width=780, height=300, bd=2, relief='groove')
        setting_frame.place(x=10, y=10)
        logging_frame = tk.Frame(self.win, width=780, height=250, bd=2, relief='groove')
        logging_frame.place(x=10, y=320)
        # ラベル設定
        login_id_label = tk.Label(setting_frame, justify="center", text='login id')
        login_id_label.place(x=150, y=10, width=500, height=30)
        login_password_label = tk.Label(setting_frame, justify="center", text='login password')
        login_password_label.place(x=150, y=80, width=500, height=30)
        url_label = tk.Label(setting_frame, justify="center", text='取得したいデータのURLを張り付けてください')
        url_label.place(x=150, y=150, width=500, height=30)
        logging_label = tk.Label(logging_frame, justify="center", text='LOG')
        logging_label.place(x=150, y=10, width=500, height=30)
        # テキストボックス設定
        self.input_login_id = tk.Entry(justify="center")
        self.input_login_id.place(x=150, y=50, width=500, height=30)
        self.input_login_id.insert(tk.END, "")
        self.input_login_password = tk.Entry(justify="center")
        self.input_login_password.place(x=150, y=120, width=500, height=30)
        self.input_login_password.insert(tk.END, "")
        self.input_url = tk.Entry(justify="center")
        self.input_url.place(x=150, y=190, width=500, height=30)
        self.input_url.insert(tk.END, "")
        # ボタン設定
        # ボタン「データ取得を開始する」を押下するとブラウザに制御が移ってしまい、GUIにログを書き込みが出来なくなってしまう。
        # この為ブラウザを動作させる「activate_get_data_btn」を別スレッド「activate_get_data_btn_thread」として定義し実行「.start」とする。
        # このように対応する事でブラウザとGUIを同時に実行する事を実現する。
        activate_get_data_btn_thread = threading.Thread(target=self.activate_get_data_btn)
        get_data_btn = tk.Button(setting_frame, text='データの取得を開始する', width=70, height=1, command=activate_get_data_btn_thread.start)
        get_data_btn.place(x=140, y=220)
        stop_working_btn = tk.Button(setting_frame, text='終了', width=70, height=1, command=self.activate_stop_working_btn)
        stop_working_btn.place(x=140, y=260)
        # ログエリア設定
        self.logging_area = scrolledtext.ScrolledText(logging_frame,
            wrap = tk.WORD,
            width = 100,
            height = 14,
        ) 
        self.logging_area.place(x=30, y=35)
        self.logging_area.focus()
        # ループ開始
        self.win.mainloop()
    def activate_get_data_btn(self):
        # tkinterのパラメータをbrowserに渡す
        setting_params = {}
        setting_params['input_login_id']       = self.input_login_id.get()
        setting_params['input_login_password'] = self.input_login_password.get()
        setting_params['input_url']            = self.input_url.get()
        # browserでデータ取得実行
        try:
            browserController = BrowserController(self, setting_params)
            browserController.open_buyma()
            browserController.login_buyma()
            item_urls = browserController.get_item_urls()
            browserController.open_item_urls_for_each(item_urls)
            self.activate_logging('全ての処理が完了しました。')
        except:
            self.activate_logging('ブラウザーの操作でエラーが発生しました。')
            self.win.quit()
    def activate_stop_working_btn(self):
        self.win.quit()
    def activate_logging(self, log):
        # ログ書き込み
        dt_now = datetime.datetime.now()
        dt     = str(dt_now.strftime('%Y年%m月%d日%H:%M:%S -- '))
        log    = dt + log + '\n'
        print(log)
        self.logging_area.insert(tk.END, log)
def main():
    # tkinter実行
    tkinterController = TkinterController()
if __name__ == "__main__":
    main()

最後に

後日「スクレイピング」と「CSV出力」を実装したいと思います。

2022年5月3日