├── .gitignore ├── LICENSE ├── README.md ├── images └── install_driver_mac.png ├── pchome_autobuy.py ├── requirements.txt └── settings.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .DS_Store 3 | .vscode 4 | # 開發用程式碼 5 | *_dev.py 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | pip-wheel-metadata/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Jia Ping Chu 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 使用工具 2 | 3 | * Python 4 | * Selenium 5 | * requests 6 | * Chrome browser 7 | 8 | ## 使用方法 9 | 10 | 1. 將 repo 複製到自己的資料夾,並安裝需要的套件 11 | ```bash 12 | $ git clone https://github.com/jumpingchu/PChome-AutoBuy.git 13 | $ cd PChome-AutoBuy 14 | $ pip install -r requirements.txt 15 | ``` 16 | 2. 準備 chromedriver 17 | 18 | * **MacOS**: 19 | 1. 安裝 chromedriver 20 | ```bash 21 | $ brew install chromedriver 22 | ``` 23 | ![install_driver_mac](images/install_driver_mac.png) 24 | 25 | 2. `settings.py` 的 `DRIVER_PATH` 填入上面顯示的路徑(如:/usr/local/bin/chromedriver) 26 | 27 | * **Windows**: 28 | 1. 在 Chrome 網址列輸入 `chrome://settings/help` 確認自己的 Chrome 版本(本人是使用 v91.0) 29 | 2. 下載對應 Chrome 版本的 `chromedriver.exe` 並放在同個資料夾內 ([前往下載](https://sites.google.com/chromium.org/driver/)) 30 | 31 | 3. 在 `settings.py` 填入資料(請保管好個資) 32 | 33 | 4. 執行程式 34 | ```bash 35 | $ python pchome_autobuy.py 36 | ``` 37 | 38 | ## 注意事項 39 | 1. 可以先拿其他的商品連結做測試,以防搶購時的突發狀況或錯誤(但請記得馬上取消訂單!) 40 | 41 | 2. `settings.py` 內的 `CHROME_PATH` 可讓 chrome 記住登入資訊,可提升搶購速度,建議使用 42 | 43 | 3. 部分程式碼依照每個人不同狀況,需要做一些調整,細節請參閱 [下一小節](#程式執行流程) 或是程式碼註解 44 | 45 | 4. 本專案 **尚未適用** 於數量多於1或必須選擇顏色或樣式的商品 46 | 47 | 5. 禁止用於大量收購並哄抬價格的黃牛行為 48 | 49 | ## 程式執行流程 50 | 51 | 1. 進入商品頁連結、取得商品 ID,判斷商品是否開賣(若未開賣則會 1 秒後重試,達 5 次即停止) 52 | 53 | 2. 將商品加入購物車 54 | 55 | 3. 前往購物車 56 | 57 | 4. 登入帳戶 58 | * 若有使用 CHROME_PATH 記住登入資訊,第二次執行程式前可選擇註解掉 59 | 60 | 5. 點選一次付清 (或 LINE Pay) 61 | 62 | 6. 提示訊息點擊「確定」 63 | * 此為近期物流較忙碌的特殊狀況,但仍有某些商品不會出現 64 | 65 | 7. 填入身分證字號、生日、信用卡安全碼 66 | 67 | 8. 勾選同意 68 | * 若帳號有設定記住付款資訊,要記得註解掉 69 | 70 | 9. 點擊送出訂單 71 | 72 | ## FAQ 73 | 74 | ### Q1:程式沒有繼續進行下一個步驟? 75 | 76 | * 通常是因為前一個步驟無法執行,請檢查程式碼是否有按照正確順序進行 77 | 78 | * 例如:發現進入購物車頁面卻沒有點選「一次付清」,有可能是前面的登入步驟已被省略但程式沒有註解掉,導致找不到登入的位置 79 | 80 | -- 81 | ### Q2:使用了 CHROME_PATH 卻還是要重新登入? 82 | 83 | * 可能是 CHROME_PATH 設定有誤,導致登入 session 沒有成功被瀏覽器紀錄 84 | 85 | * 可能的解決方法可參考 [Issue #8](https://github.com/jumpingchu/PChome-AutoBuy/issues/8) 86 | 87 | -- 88 | ### Q3:有「結帳禮」擋住購買流程? 89 | 90 | * 經 [Issue #10](https://github.com/jumpingchu/PChome-AutoBuy/issues/10) 回報,可能是使用了「聯名卡付款」時會有的狀況,可手動點選、或是改用其他信用卡或付款方式 91 | 92 | -- 93 | ### Q4:一直無法加入購物車? 94 | 95 | * 目前的解決方案是在加入購物車之後等待 0.5 秒,秒數可依據搶購狀況自行斟酌,詳情請參考 [Issue #12](https://github.com/jumpingchu/PChome-AutoBuy/issues/12) 96 | 97 | ## 貢獻 98 | 99 | * 如果你有想要新增的功能,或是你有發現 bug,歡迎隨時發 Issue 或是發 PR 喔! 100 | 101 | * 感謝 [sheway](https://github.com/sheway) 提供新的功能與想法 102 | -------------------------------------------------------------------------------- /images/install_driver_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jumpingchu/PChome-AutoBuy/5c5b95d6111d4e3614fb87ef0a75826c5cd19f66/images/install_driver_mac.png -------------------------------------------------------------------------------- /pchome_autobuy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | import os 5 | import re 6 | import json 7 | import time 8 | import requests 9 | 10 | from selenium import webdriver 11 | from selenium.webdriver.support.ui import WebDriverWait 12 | from selenium.webdriver.support import expected_conditions 13 | from selenium.webdriver.common.by import By 14 | 15 | """ 16 | 匯入欲搶購的連結、登入帳號、登入密碼及其他個資 17 | """ 18 | from settings import ( 19 | URL, DRIVER_PATH, CHROME_PATH, ACC, PWD, 20 | BuyerSSN, BirthYear, BirthMonth, BirthDay, multi_CVV2Num 21 | ) 22 | 23 | def login(): 24 | WebDriverWait(driver, 2).until( 25 | expected_conditions.presence_of_element_located((By.ID, 'loginAcc')) 26 | ) 27 | elem = driver.find_element_by_id('loginAcc') 28 | elem.clear() 29 | elem.send_keys(ACC) 30 | elem = driver.find_element_by_id('loginPwd') 31 | elem.clear() 32 | elem.send_keys(PWD) 33 | WebDriverWait(driver, 20).until( 34 | expected_conditions.element_to_be_clickable((By.ID, "btnLogin")) 35 | ) 36 | driver.find_element_by_id('btnLogin').click() 37 | print('成功登入') 38 | 39 | def input_info(xpath, info): # info = 個資 40 | WebDriverWait(driver, 1).until( 41 | expected_conditions.element_to_be_clickable( 42 | (By.XPATH, xpath)) 43 | ) 44 | elem = driver.find_element_by_xpath(xpath) 45 | elem.clear() 46 | elem.send_keys(info) 47 | 48 | def click_button(xpath): 49 | WebDriverWait(driver, 20).until( 50 | expected_conditions.element_to_be_clickable( 51 | (By.XPATH, xpath)) 52 | ) 53 | driver.find_element_by_xpath(xpath).click() 54 | 55 | def input_flow(): 56 | """ 57 | 填入個資,若無法填入則直接填入信用卡背面安全碼 3 碼 (multi_CVV2Num) 58 | """ 59 | try: 60 | input_info(xpaths['BuyerSSN'], BuyerSSN) 61 | input_info(xpaths['BirthYear'], BirthYear) 62 | input_info(xpaths['BirthMonth'], BirthMonth) 63 | input_info(xpaths['BirthDay'], BirthDay) 64 | except: 65 | print("Birth's info already filled in!") 66 | finally: 67 | input_info(xpaths['multi_CVV2Num'], multi_CVV2Num) 68 | 69 | def get_product_id(url): 70 | pattern = '(?<=prod/)(\w+-\w+)' 71 | try: 72 | product_id = re.findall(pattern, url)[0] 73 | print(product_id) 74 | return product_id 75 | except Exception as e: 76 | print(e.__class__.__name__, ': 取得商品 ID 錯誤!') 77 | 78 | def get_product_status(product_id): 79 | api_url = f'https://ecapi.pchome.com.tw/ecshop/prodapi/v2/prod/button&id={product_id}' 80 | resp = requests.get(api_url) 81 | status = json.loads(resp.text)[0]['ButtonType'] 82 | return status 83 | 84 | """ 85 | 集中管理需要的 xpath 86 | """ 87 | xpaths = { 88 | 'add_to_cart': r"//li[@id='ButtonContainer']/button", 89 | 'check_agree': r"//input[@name='chk_agree']", 90 | 'BuyerSSN': r"//input[@id='BuyerSSN']", 91 | 'BirthYear': r"//input[@name='BirthYear']", 92 | 'BirthMonth': r"//input[@name='BirthMonth']", 93 | 'BirthDay': r"//input[@name='BirthDay']", 94 | 'multi_CVV2Num': r"//input[@name='multi_CVV2Num']" 95 | # 'pay_once': "//li[@class=CC]/a[@class='ui-btn']", 96 | # 'pay_line': "//li[@class=LIP]/a[@class='ui-btn line_pay']", 97 | # 'submit': "//a[@id='btnSubmit']", 98 | # 'warning_msg': "//a[@id='warning-timelimit_btn_confirm']", # 之後可能會有變動 99 | } 100 | 101 | def main(): 102 | driver.get(URL) 103 | 104 | """ 105 | 放入購物車 106 | """ 107 | click_button(xpaths['add_to_cart']) 108 | 109 | """ 110 | 前往購物車 111 | """ 112 | driver.get("https://ecssl.pchome.com.tw/sys/cflow/fsindex/BigCar/BIGCAR/ItemList") 113 | 114 | """ 115 | 登入帳戶(若有使用 CHROME_PATH 記住登入資訊,第二次執行時可註解掉) 116 | """ 117 | try: 118 | login() 119 | except: 120 | print('Already Logged in!') 121 | 122 | """ 123 | 前往結帳 (一次付清) (要使用 JS 的方式 execute_script 點擊) 124 | """ 125 | WebDriverWait(driver, 20).until( 126 | expected_conditions.element_to_be_clickable( 127 | (By.XPATH, "//li[@class='CC']/a[@class='ui-btn']")) 128 | ) 129 | button = driver.find_element_by_xpath( 130 | "//li[@class='CC']/a[@class='ui-btn']") 131 | driver.execute_script("arguments[0].click();", button) 132 | 133 | """ 134 | LINE Pay 付款 135 | """ 136 | # WebDriverWait(driver, 20).until( 137 | # expected_conditions.element_to_be_clickable( 138 | # (By.XPATH, "//li[@class='LIP']/a[@class='ui-btn line_pay']")) 139 | # ) 140 | # button = driver.find_element_by_xpath( 141 | # "//li[@class='LIP']/a[@class='ui-btn line_pay']") 142 | # driver.execute_script("arguments[0].click();", button) 143 | 144 | """ 145 | 點擊提示訊息確定 (有些商品可能不需要) 146 | """ 147 | try: 148 | WebDriverWait(driver, 1).until( 149 | expected_conditions.element_to_be_clickable( 150 | (By.XPATH, "//a[@id='warning-timelimit_btn_confirm']")) 151 | ) 152 | button = driver.find_element_by_xpath("//a[@id='warning-timelimit_btn_confirm']") 153 | driver.execute_script("arguments[0].click();", button) 154 | except: 155 | print('Warning message passed!') 156 | 157 | """ 158 | 填入個資 159 | """ 160 | input_flow() 161 | 162 | """ 163 | 勾選同意(注意!若帳號有儲存付款資訊的話,不需要再次勾選,請註解掉!) 164 | """ 165 | click_button(xpaths['check_agree']) 166 | 167 | """ 168 | 送出訂單 (要使用 JS 的方式 execute_script 點擊) 169 | """ 170 | WebDriverWait(driver, 20).until( 171 | expected_conditions.element_to_be_clickable( 172 | (By.XPATH, "//a[@id='btnSubmit']")) 173 | ) 174 | button = driver.find_element_by_xpath("//a[@id='btnSubmit']") 175 | driver.execute_script("arguments[0].click();", button) 176 | 177 | 178 | """ 179 | 設定 option 可讓 chrome 記住已登入帳戶,成功後可以省去後續"登入帳戶"的程式碼 180 | """ 181 | options = webdriver.ChromeOptions() 182 | options.add_argument(CHROME_PATH) 183 | 184 | driver = webdriver.Chrome( 185 | executable_path=DRIVER_PATH, chrome_options=options) 186 | driver.set_page_load_timeout(120) 187 | 188 | """ 189 | 抓取商品開賣資訊,並嘗試搶購 190 | """ 191 | curr_retry = 0 192 | max_retry = 5 # 重試達 5 次就結束程式,可自行調整 193 | wait_sec = 1 # 1 秒後重試,可自行調整秒數 194 | 195 | if __name__ == "__main__": 196 | product_id = get_product_id(URL) 197 | while curr_retry <= max_retry: 198 | status = get_product_status(product_id) 199 | if status != 'ForSale': 200 | print('商品尚未開賣!') 201 | curr_retry += 1 202 | time.sleep(wait_sec) 203 | else: 204 | print('商品已開賣!') 205 | main() 206 | break 207 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2024.7.4 2 | chardet==4.0.0 3 | idna==3.7 4 | requests==2.32.0 5 | selenium==3.141.0 6 | urllib3==1.26.19 7 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | URL = "商品連結" 2 | DRIVER_PATH = "chromedriver.exe" 3 | CHROME_PATH = r"--user-data-dir=C:\\" # 可透過網址列輸入 chrome://version/ 找到 4 | 5 | ### Only for Mac ### 6 | # DRIVER_PATH = "/usr/local/bin/chromedriver" 7 | ### Only for Mac (End) ### 8 | 9 | # 請注意!以下皆為機密個資,請小心謹慎,勿上傳至公開平台 10 | ACC = "PChome 登入帳號" 11 | PWD = "PChome 登入密碼" 12 | BuyerSSN = "身分證字號" 13 | BirthYear, BirthMonth, BirthDay = "民國出生年", "月", "日" 14 | multi_CVV2Num = "信用卡檢查碼(背面三碼)" 15 | --------------------------------------------------------------------------------