Selenium 使用 Chrome 瀏覽器 webdriver

這篇文章要分享如何實作你己的搶票機器人,如果你不會寫程式,針對電腦只會使用滑鼠的使用者,目前我們有提供點2下就可以動的執行檔,請參考教學影片:

拓元售票系統使用Max自動搶票機器人(2018/11/23)
https://www.youtube.com/watch?v=QX8u2iF1Gm8

上面影片裡的exe執行檔的下載點:Max的拓元搶票機器人
https://max-everyday.com/2018/03/tixcraft-bot/

Max搶票機器人的Facebook粉絲團:
https://www.facebook.com/maxbot.ticket/


2018-12-14 的版本可以指定日期和區域:https://www.youtube.com/watch?v=tSOBgcrMmuA


如果你是Firefox瀏覽器的愛好者請參考這一篇:Selenium 使用Firefox 瀏覽器 webdriver
https://stackoverflow.max-everyday.com/2018/11/selenium-firefox-webdriver/


接下來文章內容是要寫給想要「練功」(寫程式)或「有一些電腦基礎」而且有辦法在「命令提示字元」(Windows平台)或的終端機(Linux/Mac平台)執行指令的人。


Step 1:安裝 python

如果是 Linux 或 MacOS 平台,如果內建的版本是 python 2 的的,因為目前已經不支援,請下載並安裝 python 3 ,不限定版本,建議到 python 的官方網站下載並安裝最新 python 3.x 的版本。目前(2023年4月)比較穩定的版本似乎 python 3.9 或 python 3.10, 而 python 3.11 似乎太新了, 相容性還沒有很好。

除了python 如果你懂其他程式語言(例如:java 或 C#)也可以實作,不限於python程式語言,各個程式語言之間大同小異)

selenium 可以支援的程式語言,參考看看:
https://www.selenium.dev/documentation/

Step 2:安裝 pip

附註:這個太簡單,如果你的電腦裡沒有pip 指令,自行google 看看如何安裝。

Step 3:安裝selenium套件,請執行指令:

pip install selenium

如果在 Linux 或 MacOS 平台裡執行pip install 失敗,請先pip install virtualenv. 我自己本身是使用  macOS, 一開始是無法安裝,使用 virtualenv,是一定可以跑,後來不知道修改到什麼,變成不用進入 venv 環境裡也可以直接執行。

沒有使用 venv 也是可以的,但用過 venv 的人都很推薦使用,因為比較不會被其他專案使用的元件影響到,也不會去影響到其他專案。

如果是 Windows平台,請直接跳到 Step 4.

virtualenv 基礎教學:
http://docs.python-guide.org/en/latest/dev/virtualenvs/


Step 4:下載ChromeDriver

ChromeDriver 說明:

ChromeDriver 可以讓 Selenium Server 呼叫 Google Chrome 執行,ChromeDriver 網站:
https://sites.google.com/a/chromium.org/chromedriver/

ChromeDriver 下載頁面:

https://sites.google.com/a/chromium.org/chromedriver/downloads

附註1:ChromeDriver目前有支援 Linux 64bit / macOS 64bit / Windows 32bit (64bit 也可以執行 32bit程式)

附註2:除了有 ChromeDriver 還有 SafariDriver 可以讓 Selenium Server 呼叫 Safari 瀏覽器來執行,參考看看 Safari Extension,建議使用ChromeDriver即可。

太舊的 chrome 執行起來會有問題,請先更新chrome瀏覽器為最近的版本,更新方式為:「設定」->「關於Chrome」。

完成 step 3 的 selenium 的安裝,和 step 4 下載ChromeDriver 之後,先試看看這個sample code:

from selenium import webdriver   
chromedriver = "/Users/max/Documents/chromedriver"
driver = webdriver.Chrome(chromedriver)
driver.get("http://tw.yahoo.com/")
  • 說明1:這個ChromeDriver 路徑請換成您電腦實際下載的資料夾。
  • 說明2:這個範例會開一個新的 chrome 視窗並連到網址 http://tw.yahoo.com/
  • 附註:目前的範例是透過 python 去控制 selenium + chromedriver,如果你懂其他的程式語言,也是可以實作的出來。

範例 2 號:

from selenium import webdriver
 
chromedriver = "/Users/max/Documents/chromedriver"
driver = webdriver.Chrome(chromedriver)
driver.get('http://www.cwb.gov.tw/V7/')
driver.set_window_position(0,0) #瀏覽器位置
driver.set_window_size(700,700) #瀏覽器大小

driver.find_element_by_link_text('天氣預報').click() #點擊頁面上"天氣預報"的連結

webdriver有許多方法,

範例 2 號使用的是find_element_by_link_text(),還有許多方法如下:

find_element_by_name()
find_element_by_id()
find_element_by_tag_name()
find_element_by_partial_link_text()
find_element_by_css_selector()

另一種用法:

area = el.find_element(By.TAG_NAME, “a”)

  • By.TAG_NAME
  • By.CSS_SELECTOR

用法可以參考SeleniumHQ:

http://www.seleniumhq.org/docs/03_webdriver.jsp


關於 macOS 的 Safari’s WebDriver

如果 macOS 版本是High Sierra and later,請Run:

safaridriver --enable

once. (If you’re upgrading from a previous macOS release, you may need to use sudo.)

如果 macOS 版本是 Sierra and earlier:

  1. If you haven’t already done so, make the Develop menu available. Choose Safari > Preferences, and on the Advanced tab, select “Show Develop menu in menu bar.” For details, see Safari Help.
  2. Choose Develop > Allow Remote Automation.
  3. Authorize safaridriver to launch the XPC service that hosts the local web server. To permit this, manually run /usr/bin/safaridriver once and follow the authentication prompt.

常見問題(Q&A)

Q:網速到底有沒有差?
光世代有分300M/100M,100m/40m,請知道的是如果在同樣的操作環境,人也一樣,那300m的會不會比100m的更高的機率買到票? 到底什麼是比別人快買到票的關鍵因素?? 售票系統的運作原理是什麼? 為何我的手速也很快了,但為什麼一進去票就是都被買了?他們到底是什麼原因比我快進去,快送出購票請求??

A:高速的網路下載速度是有差異的,也許差幾個毫秒吧,以100Mbps和20Mbps來說下載一個拓元的網頁可能差異不大。

買票前建議先試著去買其他表演,事先下載好購票網頁會使用到的 javascript 和 css 檔案,可以透過離線檔案的快取(cache) 加速網頁的反應時間。

建議使用chrome 瀏覽器來搶票,反應時間會快一點。

「售票系統的運作原理」不難,google 一下就可以看到大量的實作和原理教學,在這裡就不詳述。以目前常見的網頁相關技術來說,在網路上大型的網站實作原理都大同小異,大致上會使用負載平衡(load balance)架構分散主機的網頁流量和要求,通常是(但不是絕對)在資料庫的伺服器那一段程式碼來決定那一個要求可以買到票的,大多數伺服器在處理排隊(queue)的要求是先進先出(first in first out),所以如果你的網路早一點下載完網頁,早一點執行完javascript,早一點送出搶票的要求,理論上搶到票的機率會高一點。

Q:您提到,拓元有分 detail 和 game, 連到game的網址,搶票才會快,假如一開始時間還沒到,「立即購票」的按鈕根本還沒出現,用這個方法,要怎麼使用呢??
A:detail 網址和 game 網址是一樣的,是獨立的,在購票流程裡你可以對可以購票的場次各使用 detail 和 game 網址去訂一次票,就可以知道其中差異,速度上 game 網址也可以買到票,由於傳回的網頁裡的資訊還有所執行的javascript較少,理論上也許會快幾個毫秒。

Q:用搶票機器人,跟手動的差別? 這個問題主要是問,機器人的速度跟用手動的速度,會差距明顯嗎?手的速度能不能贏過機器人?
A:我有放示範用的youtube影片,那個反應時間以人類手指和眼球速度應該無法超越。

Q:能開發軟體嗎?讓不懂程式的人也可以用?
A:
目前有執行檔,不懂程式的人可以在 Windows/Mac/Linux 平台上可以直接執行。
附註1:「搶票軟體」無法保證可以買的到票,也許還有很多其他人使用了機器人來搶票。
問題2:「搶票軟體」拓元網頁會改版,也許會造成功能不正常。

Q:有試著安裝及研究python,也成執行了webdriver幾個範例,但是還是不了解要如何寫出與您所說一支小程式來搶票?

A:請先學會如何透過python 程式自動去點畫面上的按鈕,或使用點2下就可以跑的執行檔。

Q:怎麼像你一樣在已開啟及已登入的視窗執行python?

A:python會全開啟一個全新的視窗,請使用該視窗去登入 Google/Facebook/Pixel Pin.

Q:你的教程中,電腦用的是Linux的系統,Windows系統是無法使用嗎?

A:Windows 也可以使用哦。而且語法相同。ChromeDriver目前有支援 Linux 64bit / macOS 64bit / Windows 32bit (64bit 也可以執行 32bit程式)檔案下載:
http://chromedriver.chromium.org/downloads

Q:如何判斷某一個網址,去做特定事情?
A:

while True:
    time.sleep(0.2)
    url = ""
    try:
        url = driver.current_url
    except Exception as exc:
        pass

    if url is None:
        continue
    else:
        if len(url) == 0:
            continue
    print(url)

Q:google提供的擴充附件外掛 官方是不是會查出來?

A:這不是google提供。不確定官方是不是查的出來,要查的出來的可能很低,難度也很高,難在伺服器端很難判斷用戶是否為機器人。

Q:環境是Win10 64bit 執行畫面顯示,我雙擊 chromedriver 後,畫面顯示「Only local connections are allowed.」,是不是這個機械人不設海外購票?

A:

Starting ChromeDriver 2.44(……) on port 9515
Only local connections are allowed.

直接執行 chromedriver.exe 是會顯示上面的訊息沒錯,ChromeDriver 的架構如下:

說明:上面三個正方形各代表一個執行檔,你雙擊 chromedriver.exe 是執行上面第二個正方形,最右邊的是chrome瀏覽器,最左邊的是我們寫的python應用程式,透過ChromeDriver可以認識的指令去操作ChromeDriver, ChromeDriver 進而在同一台電腦裡遠端去控制chrome瀏覽器。


實際範例:

如果有一個html 長的像醬子:

該按鈕的 html code:

<input class="btn btn-next" data-href="/ticket/area/18_RBTW/4029" name="yt0" type="button" value="立即訂購">

要取到該按鈕的 python code:

el = driver.find_element_by_css_selector('.btn-next')

讓按鈕產生點擊的事件的 python code:

el.click()

有可能會找不到element

How to use the try/except with Selenium Webdriver when having Exceptions on Python

To be able to use required exception you have to import it first with correct name (NoSuchElement -> NoSuchElementException):

from selenium.common.exceptions import NoSuchElementException

try:
    WebDriver.find_element_by_css_selector('div[class="..."')
except NoSuchElementException:
    ActionToRunInCaseNoSuchElementTrue

Using JavaScript

element = driver.execute_script("return $('.cheese')[0]")

設定 select 裡的值:

from selenium.webdriver.support.ui import Select
select = Select(driver.find_element_by_tag_name("select"))
select.select_by_visible_text("Edam")

勾選 checkbox :

You can “toggle” the state of checkboxes, and you can use “click” to set

html:

<input type="checkbox" value="1" name="TicketForm[agree]" id="TicketForm_agree">

python code:

el = driver.find_element(By.CSS_SELECTOR, '#TicketForm_agree')

or

driver.execute_script("$('#TicketForm_agree').prop('checked', true);")

附註:讓輸入框focus 請用 javascript:

$('#TicketForm_verifyCode').focus()

WebDriver Status
https://chromium.googlesource.com/chromium/src/+/master/docs/chromedriver_status.md

這些是目前 WebDriver 支援的指令。


從目前的 source code 可以清楚的看到如何去使用 webdriver 物件。

下面的這個 chromedriver 切換 frame 的功能,如果你要是搶「熱門」的票,是遇不到的,熱門的場次都是「自動畫位」,所以不必自己去選坐位,自動選坐位會彈出在 iframe 裡,可以使用下面這行指令即可切換到選位的 iframe:

driver.switch_to.frame(driver.find_element_by_xpath("//iframe[contains(@src,'/ticket/selectSeat/')]"))

selenium定位页面元素的时候会遇到定位不到的问题,明明元素就在那儿,用檢視原素也可以看到,就是定位不到,这种情况很有可能是frame在搞鬼。

frame标签有frameset、frame、iframe三种,frameset跟其他普通标签没有区别,不会影响到正常的定位,而frame与iframe对selenium定位而言是一样的,selenium有一组方法对frame进行操作。

1.怎麼切到frame中(switch_to.frame())

selenium提供了switch_to.frame()方法来切换frame

switch_to.frame(reference)

reference是传入的参数,用来定位frame,可以传入id、name、index以及selenium的WebElement对象,假设有如下HTML代码 index.html:

<html lang="en">
<head>
 <title>FrameTest</title>
</head>
<body>
<iframe src="a.html" id="frame1" name="myframe"></iframe>
</body>
</html>

想要定位其中的iframe并切进去,可以通过如下代码:

from selenium import webdriver
driver = webdriver.Firefox()
driver.switch_to.frame(0) # 1.用frame的index来定位,第一个是0
# driver.switch_to.frame("frame1") # 2.用id来定位
# driver.switch_to.frame("myframe") # 3.用name来定位
# driver.switch_to.frame(driver.find_element_by_tag_name("iframe")) # 4.用WebElement对象来定位

通常采用id和name就能够解决绝大多数问题。但有时候frame并无这两项属性,则可以用index和WebElement来定位:

index从0开始,传入整型参数即判定为用index定位,传入str参数则判定为用id/name定位
WebElement对象,即用find_element系列方法所取得的对象,我们可以用tag_name、xpath等来定位frame对象
举个例子:

<iframe src="myframetest.html" />

用xpath定位,传入WebElement对象:

driver.switch_to.frame(driver.find_element_by_xpath("//iframe[contains(@src,'myframe')]"))

2.從frame中切回主文件(switch_to.default_content())

切到frame中之后,我们便不能继续操作主文档的元素,这时如果想操作主文档内容,则需切回主文档。

driver.switch_to.default_content()

3.iframe的操作(switch_to.parent_frame())

有时候我们会遇到嵌套的frame,如下:

<html>
 <iframe id="frame1">
 <iframe id="frame2" / >
 </iframe>
</html>

1.從主文件切到frame2,一層層切進去

driver.switch_to.frame("frame1")
driver.switch_to.frame("frame2")

2.從frame2再切回frame1,這裡selenium給我們提供了一個方法能夠從子frame切回到父frame,而不用我們切回主文件再切進來。

driver.switch_to.parent_frame() # 如果当前已是主文档,则无效果

有了parent_frame()這個相當於後退的方法,我們可以隨意切換不同的frame,隨意的跳來跳去了。解法:

driver.switch_to.frame(reference)
driver.switch_to.parent_frame()
driver.switch_to.default_content()

如何在Server side(伺服器端)檢查使用者有沒開 chromedriver?

Can a website detect when you are using selenium with chromedriver?
https://stackoverflow.com/questions/33225947/can-a-website-detect-when-you-are-using-selenium-with-chromedriver

上面文章不用去看了,我檢查過新的版本的 selenium 完全不會多產生上面的 key 值。


拓元在 2018-05-12 之後更新的javascript 如下:

function order_check() {
 var count = valueCount(["WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek="]),
 maxQuota = 4;

if(!$("#TicketForm_agree").prop("checked")) {
 alert("\u8acb\u5148\u8a73\u95b1\u4e14\u540c\u610f\u6703\u54e1\u670d\u52d9\u689d\u6b3e\u5f8c\u518d\u884c\u9001\u51fa\u52d5\u4f5c\u3002");
 } else if (count > maxQuota) {
 alert("\u55ae\u7b46\u4ea4\u6613\u6700\u591a\u53ef\u8cb7 \" + maxQuota + \" \u5f35");
 } else if (count == 0) {
 alert("\u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u7a2e\u7968\u7a2e");
 
 } else {
 var ticketType = ["WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek="],
 ticketTypeSelector = $("[name=\"" + ticketType.join("\"], [name=\"") + "\"]");

ticketTypeSelector.each(function() {
 $(this).attr("name", "TicketForm[ticketPrice][" + $(this).get(0).name + "]");
 });
 return true;
 }

return false;
}

function valueCount(elements) {
 elements = countValById(elements);
 return elements.reduce(function(total, element) {
 return total + element;
 }, 0);
}

$("select[id=\"WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek=\"]").on("click", function(event) {
 if (!!event.originalEvent.isTrusted && !event.isTrigger) {
 $("#TicketForm_checked").attr("name", "TicketForm[ticketPrice][vQmwBD+sVq5AOWaOJrdiOQ5oIjAlhU38AxsBgnL1qkU=]");
 }
});
$("#TicketForm_agree").on("click", function(event) {
 if (!!event.originalEvent.isTrusted && !$(this).checked && !event.isTrigger) {
 $(this).attr("name", "TicketForm[agree][YnuMm9Vok/JcdY82p5pho4QaTg8m+p735VSWpPyjOfE=]");
 }
})

$("#TicketForm").on("change", function(event) {
 var ticketType = ["WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek="];

if (ticketType.indexOf(event.target.id) != -1) {
 var count = valueCount(ticketType),
 maxQuota = 4;

if (count > maxQuota) {
 var num = parseInt($(event.target).val()) + (maxQuota - count);

alert("\u55ae\u7b46\u4ea4\u6613\u6700\u591a\u53ef\u8cb7 \" + maxQuota + \" \u5f35\uff0c\u60a8\u5171\u9078\u64c7\u4e86 \" + count + \" \u5f35");

while ($(event.target).find("option[value=" + num + "]").length < 1 && num != 0) {
 num--;
 }

$(event.target).val(num);
 }

$("#ticketQuota").text(maxQuota - valueCount(ticketType));
 }
});

$("#TicketForm select").change();

 <select class="mobile-select" name="WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek=" id="WMeBWmQEOdoAKQq0wKU8kv4k5VcwA3GjyISDUmtPZek=">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
 </select>

針對上面的程式碼,如果使用

driver.execute_script("$('#TicketForm_agree').prop('checked', true);")

並不會觸發 onclick 事件。請改服用:

form_checkbox = None
try:
 form_checkbox = driver.find_element(By.ID, 'TicketForm_agree')
 if form_checkbox is not None:
 try:
 form_checkbox.click()
 except Exception as exc:
 print "click TicketForm_agree fail"
 pass
except NoSuchElementException:
 print "find TicketForm_agree fail"

附註, select box 比照 checkbox 的 code, 先產生 click 再去選取預期的張數即可。


2018年09月某一天 發現拓元的 javascript 又增加了幾個新的檢查點,javascript 如下:

function countValById(arr) {
    return arr.map(function(val) {
        return parseInt($("[id=\"" + val + "\"]").val());
    });
}

function order_check() {
    var count = valueCount(["gqQB0FXCgar2OyOvqUFZR1xujK9R1sO+OR6V6m\/unzY="]),
        maxQuota = 4;

    if(!$("#TicketForm_agree").prop("checked")) {
        alert("\u8acb\u5148\u8a73\u95b1\u4e14\u540c\u610f\u6703\u54e1\u670d\u52d9\u689d\u6b3e\u5f8c\u518d\u884c\u9001\u51fa\u52d5\u4f5c\u3002");
    } else if (count > maxQuota) {
        alert("單筆交易最多可買 " + maxQuota + " 張");
    } else if (count == 0) {
        alert("\u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u7a2e\u7968\u7a2e");
    
    } else {
        var ticketType = ["gqQB0FXCgar2OyOvqUFZR1xujK9R1sO+OR6V6m\/unzY="],
            ticketTypeSelector = $("[name=\"" + ticketType.join("\"], [name=\"") + "\"]");

        ticketTypeSelector.each(function() {
            $(this).attr("name", "TicketForm[ticketPrice][" + $(this).get(0).name + "]");
        });
        return true;
    }

    return false;
}

function valueCount(elements) {
    elements = countValById(elements);
    return elements.reduce(function(total, element) {
        return total + element;
    }, 0);
}

$(document).ready(function() {
    var ticketType = ["gqQB0FXCgar2OyOvqUFZR1xujK9R1sO+OR6V6m\/unzY="],
        ticketTypeSelector = "[id='" + ticketType.join("'], [id='") + "']";

    $("#TicketForm_agree").attr("name", "TicketForm[agrees]");
    $("#TicketForm_checked").attr("name", "TicketForm[ticketPrice][checks][ + $(ticketTypeSelector).length + ]");

    ravenCheck(ticketTypeSelector);

    $("#TicketForm").on("mousedown click touchstart", ticketTypeSelector, function(event) {
        $("#TicketForm_checked").attr("name", "s_" + event.originalEvent.isTrusted + "_" + event.isTrigger);
        if (event.originalEvent.isTrusted !== false && !event.isTrigger) {
            $("#TicketForm_checked").attr("name", "TicketForm[ticketPrice][5BQBWTBCQoAEPama/ehw8qJhA1lkdF0fH8J5eua5HSw=]");
        }
    }).on("mousedown click touchstart", "#TicketForm_agree", function(event) {
        $(this).attr("name", "s_" + event.originalEvent.isTrusted + "_" + event.isTrigger);
        if (event.originalEvent.isTrusted !== false && !$(this).checked && !event.isTrigger) {
            $(this).attr("name", "TicketForm[agree][Dbk4t/2b/yoQPK0qAnhBljhX0JBLgehzAlwuEOBdEik=]");
        }
    }).on("change", function(event) {
        if (ticketType.indexOf(event.target.id) != -1) {
            var count = valueCount(ticketType),
                maxQuota = 4;

            if (count > maxQuota) {
                var num = parseInt($(event.target).val()) + (maxQuota - count);

                alert("單筆交易最多可買 " + maxQuota + " 張,您共選擇了 " + count + " 張");

                while ($(event.target).find("option[value=" + num + "]").length < 1 && num != 0) {
                    num--;
                }

                $(event.target).val(num);
            }

            $("#ticketQuota").text(maxQuota - valueCount(ticketType));
        }
    });

    $("#TicketForm select").change();
});

jQuery(document).on('click', '#yw0', function(){
    jQuery.ajax({
        url: "\/ticket\/captcha?refresh=1",
        dataType: 'json',
        cache: false,
        success: function(data) {
            jQuery('#yw0').attr('src', data['url']);
            jQuery('body').data('captcha.hash', [data['hash1'], data['hash2']]);
        }
    });
    return false;
});

event.originalEvent.isTrusted

Event 介面的 isTrusted 唯讀屬性為一個布林值,若事件物件是由使用者操作而產生,則 isTrusted 值為 true。若事件物件是由程式碼所建立、修改,或是透過 EventTarget.dispatchEvent() 來觸發,則 isTrusted 值為 false。

Event.isTrusted

The isTrusted read-only property of the Event interface is a Boolean that is truewhen the event was generated by a user action such as mouse click, and false when the event was scripted or invoked via dispatchEvent.

This new property is intended primarily for use by browser extensions, to determine if an event was dispatched by a script running in the main world or not.

如果單純使用之前的程式,會讓 $(“#TicketForm_checked”).attr(“name”); 取得的結果為:

s_false_undefined

然後會造成搶票失敗,帳號會被登出。解法:

其實大多的情況下 Selenium 讓 Event.isTrusted 的結果是 True,調整一下網頁裡事件的執行順序就解決了,拓元很難透過網頁檢測是使用者透過機器人來輔助買票,如果你寫的程式可以被驗測到,代表調整一下順序應該就可以跳過檢查。


上面對很多沒學過 javascript 的應該看起來像是火星文,建議先學一下簡單的 javascript 和 jQuery.

不能確定下次開演唱會時,Max的搶票程式是否可以使用,因為拓元定期會改版。

selenium 的程式有一些限制,必需先知道:

  • 1:會開出新的「視窗」,所以需要重新登入帳號。
  • 2:不能使用新的「分頁」,程式只能控制第一個分頁,所以建議你開2~3個 selenium,放在背景,而且先都登入好會員,如果第一個視窗在搶票時遇到圈圈狂轉,這時候趕快派2號視窗上場救援。
    (附註:其實是可以控制其他分頁,會比較麻煩一點,難度也會比較高,只處理第一個分頁會簡單很多。)
  • 3:想看我的程式可以匯錢給我,歡迎與我聯絡:[email protected]

自動關掉 alert 對話框的工具:
https://chrome.google.com/webstore/detail/alert-control/ofjjanaennfbgpccfpbghnmblpdblbef

如果有討人厭的alert 彈出式的javascript 語法,可以節省大約一秒的時間。

要下載 chrome extension 可以使用 “Get CRX” extension 來取得:
https://chrome.google.com/webstore/detail/get-crx/dijpllakibenlejkbajahncialkbdkjc


在 selenium 裡載入 extension 程式碼:

extension_path = Root_Dir + "webdriver/Alert_Control.crx"
chrome_options = webdriver.ChromeOptions()
chrome_options.add_extension(extension_path)
chromedriver_path =Root_Dir+ "webdriver/chromedriver"
driver = webdriver.Chrome(chrome_options=chrome_options, executable_path=chromedriver_path)

Q:Selenium 支持那些程式語言?
A:參考看看https://www.selenium.dev/downloads/

  • Ruby
  • Java
  • Python
  • C#
  • JavaScript

當人們在網站註冊或購物時,經常會出現圖像驗證碼的輸入要求,為了區分「真人」與「機器人」,圖片會出現線條及扭曲分隔開的文字,讓電腦程式的機器人難以辨識,目前Max的搶票機器人沒有辦法處理「驗證碼」的部份,需要人工去輸入,機器人幫忙處理其他的欄位的輸入。

人外有人,天外有天,驗證碼的部份應該也可以透過程式識別,識別也是有分難度的,簡單的驗證碼可以透過去切割圖片,再把切割好的圖片採用深度學習(Deep learning)的方式去訓練就可以有效提升識別率。難一點的驗證碼就是字元會變形、重疊還有雜訊。拓元網站是屬於中上等級,只有變形和重疊。

沒有推薦的實作方向,因為我也沒有研究過。


相關文章:

Max的拓元搶票機器人
http://max-everyday.com/2018/03/tixcraft-bot/

Max自動掛號機器人
http://max-everyday.com/2018/08/max-auto-reg-bot/

Max 學習 tkinter:
https://stackoverflow.max-everyday.com/tag/tkinter/

Max 學習 selenium:
https://stackoverflow.max-everyday.com/tag/selenium/

[Python] 如何取出字串中的選項
https://stackoverflow.max-everyday.com/2018/12/python-regular-expression-findall/
這篇文章目是透過程式取得「驗證問題」裡的選項,再用程式來自動把選項用來回答問題。

實作基於CNN的台鐵訂票驗證碼辨識以及透過模仿及資料增強的訓練集產生器
(Simple captcha solver based on CNN and a training set generator by imitating the style of captcha and data augmentation)
https://github.com/JasonLiTW/simple-railway-captcha-solver

Comments

  1. 您好,請問能教我怎麼設定嗎 我看了您的YT影片 進來這看不太懂,

    謝謝您的回覆。

  2. 你好 不知道這個有沒有更好理解或操作的><
    因為完全沒有理工背景 全部都看不懂….

    1. 一個一個來吧,加油!我也不是讀理工的,我是我商科。

    1. 可正常操作,目前卡在:「欲購票者,建議於節目開賣前25小時,完成加入會員及手機號碼驗證。(驗證通過24小時後,才可購票)」,要過 24小時後,才能做新的影片出來,Demo 如何在改版後自動買票。

      Your mobile phone number has been verified at 2018/05/04 09:41. You may start to purchase tickets at 2018/05/05 09:41.
      系統說,我明天才能買票。

  3. 流程基本上應能正常操作,但聽說使用程式/機器人實際登入後購票好像會被強制登出

  4. 可以請問下您使用的Python版本為何?
    我使用您的Code無法正常運行

    1. hello, 我目前是使用 MacOS 內建的 Python 2.7.10,理論上 Python3 應該也可以執行。

  5. 請問輸入完程式碼後要怎麼儲存才能讓他自動跑呢?
    我要邊輸入程式碼網頁才會跟著動耶 還是我哪裡做錯了嗎?

    1. 你的問題太難,我無法理解。要執行python 的腳本,就是在命令列模式下去輸入 python your-filename.py

  6. 您好,

    感謝您的教學,我有一個小疑問請教
    我設定好了一個Selenium腳本,不過寫法都只會執行一次
    如果我想要每點開一個新頁面就讓它執行一次腳本(抓element 自動典擊等等)
    請問這邊是加入一個time函數讓他去重複跑?
    或是有什麼方法 可以達成這個目標?

  7. 請問如果需要答粉絲問題的話,有辦法讓他答完之後繼續跑嗎?

    1. 您的問題太抽像,看不懂。上次我搶安室的票中間有多一個步驟是要求客人多打入一串字,類似多問使用者一個問題。

  8. 您好:
    請問若是需要如購票驗證(進入選區域畫面之前) 這種狀況有辦法繞過嗎?
    謝謝您

    1. 可以寫程式去固定選取最前面的坐位,這個程式不難,需要跨frameset的寫法,我有描述在文章裡。通常需要手動去選位的活動,似乎訂票情況都不是很搶手。

  9. Max您好
    小弟自學Python之後也完成了類似的流程
    從登入到買票都ok,但最後按下確定購票時,果然被強制登出了(回覆中也有人提到)
    目前一直卡在最後一步,不知道拓元是怎麼判斷的
    是否方便提供您的程式碼讓我參考呢,感謝!

  10. Max您好
    小弟自學Python之後也寫好了類似的流程
    從登入到買票都ok,但最後按下確定購票時,果然被強制登出了(回覆中也有人提到)
    目前一直卡在最後一步
    是否方便提供您的程式碼讓我參考呢,感謝!

    1. 被登出的原因,只要看一下拓元的 javascript 就可以知道,是因為透過程式去觸發的事件較少,把 javascript 裡需要補足的事件補齊即可通過javascript的檢查。

  11. 不好意思 有個關於選區域的問題請要請教
    用seleuium 來寫的話
    如果我用像是 , get innerText的方式 去抓字元判斷
    但是這時候會發現他不支援中文編碼
    也就是很難用這個方式去鎖定區域選擇
    請問有較好的方式嗎? 或是如何讓他支援中文編碼?
    感謝~

  12. 你好:
    我在測試你給的範例Code時
    當我執行他會說:NameError: name ‘web’ is not defined
    請問這是什麼原因呢

    1. 因為「範例 2 號」 用的變數 web 沒有被定義,我已修改掉掉範例2號裡的變數名稱web,改成和範例1號使用同一個,這樣子就不會出錯了。

  13. 你好,根據你的範例,如果有很多個”立即訂購”按鈕,我要如何選擇指定的按鈕呢?(因為每個按鈕都是find_element_by_css_selector(‘.btn-next’)?)謝謝!

  14. 您好,想詢問程式購買需要多少呢?以及如購買後需修改內容可否直接幫忙呢?

    1. 文章裡有寫,是一次性服務,不確定未來官方改版後我還能夠找的到解法。

  15. 您好!請問如果搶票需要粉絲回答完問題正確以後,才能進入座位區選位,這個指令還適用嗎?謝謝!

    要粉絲為答問題例如:這位歌手的mv破億順序.隊長是誰之類的…

  16. 想請問一下 問什麼 範例一可以正常運行 但到了範例二卻不能呢

    1. 已重新修改範例2號,不能執行的原因是因為使用了 time.sleep(5) 卻沒有 import time, 解法是刪除 time 相關程式碼即可。

  17. 你好!使用python開網頁後進入訂票模式+驗證碼那邊都ok能順利運行
    但是想請教中間選擇區域該怎麼由上往下去挑選可以進入的做點擊
    可以給一點點提示嗎QQ

    1. 不知道什麼是「全網」,這個機器人只能客製化「特定」的網頁。

    1. 沒有提供代搶票的服務,因為沒搶到票,不好意思跟你拿錢。萬一跟你拿了錢又沒搶到票,可能被說是我偷懶沒做事還收錢。與其有爭議,多一事不如少一事。

  18. 請教一下 在選取特定的票種的張數,該如何去思考呢?能給點提示嗎?目前卡在這邊 感謝MAX大的教學

    1. 拓元只有一個下拉框去選張數。我猜測你問的應該是Kktix. 使用 For 迴圈一行一行取值出來判斷即可完成。

  19. 你好 請問現在自動同意可用的方法是什麼?
    attr prop trigger都被過濾了

      1. 試過用$(‘#TicketForm_agree’).click();
        發現執行之後還是會變成s_false_undefined
        我用的是tampermonkey 會不會是這個影響?

  20. 你好,請問這個BOT是通用的嗎?

    在不同網站購票,只需要更改相2網址跟設定就好了?

    還有請問價錢是多少?

    1. 不是通用,原理是要處理網站的HTML,需要一個一個網頁做「客製化」。價格我私下用email通知你。

  21. selenium.common.exceptions.WebDriverException: Message: unknown error: Failed to create Chrome process.

    [9420] Failed to execute script chrome_tixcraft

    您好~請問這段錯誤訊息是什麼原因發生的呢

    1. 沒裝chrome browser 在path 上,或chrome drive 版本有誤,後著的錯誤訊息會清楚寫出目前的chrome drive版本,所以前者的可能性高一點。實際原因我也不清楚,因為遇不到,呵呵。

  22. Max 你好:我在Kktix去搶員林往溪頭的車票要兩張, 但都只能訂到一張, 請問要如何解決?
    ps.我不會寫程式喔

  23. Max大大您好,想請問選擇使用FB登入,輸入完帳號密碼後顯示”無法處理你的請求”,可以幫我解惑看看為甚麼會這樣嗎? 謝謝。

  24. Max大大您好, 我參考你的範例嘗試連到HKTicketing, 但他會deny access, 請問你有解決方法嗎? 因為我看你的exe沒有這個問題, 感恩!

  25. 您好, KKTIX目前選完票後, 需要選擇自行選位或電腦配位, 我發現我的自動程式會停在這裡, 這可以在哪邊設定嗎?謝謝您

  26. HI Max ,
    請問執行後都會連到網頁版 url=”http://127.0.0.1:”的設定頁面,這樣這樣正確嗎? 因為先前看都是有一個視窗介面來來設定 ,謝謝

    1. 正確,settings.py : 編輯 settings.json 的 GUI 介面(http://127.0.0.1:16888/)。提供圖片 OCR 功能給 chrome 擴充功能。支援定時啟用/停用 MaxBot。
      settings_old.py : 舊版本的編輯 settings.json 的 GUI 介面,與 settings.py 的差別在介面一個是網頁形式,old 的用的是視窗形式。

  27. 提問Max:請用Maxbot的搶票功能後,
    是否建議先提前登入帳號?
    多帳號有辦法為每個帳號預先輸入做多個設定檔管理嗎(like視窗化版本)?
    Maxbot中的chrome擴充功能的用途?
    不在支援名單中的網站,是不是就無法自動化ex:遠大?
    謝謝Max從18年就無私分享心得,身為學生的我有動力學習

    1. 有設定檔用的管理介面,config_launcher.py : 設定檔管理, 方便對多個設定檔案搶票。
      如果遇到 github 上的網址已經變成錯誤: 404 不存在時。網路上還有其他網友的備份,在github 查詢關鍵字: tixcraft_bot 就可以看到其他 fork(分支)出來的專案。例如:
      git clone https://github.com/czdannyfeng/tixcraft_bot_newcopy

  28. 您好:我不是工程師是一個外行人,想詢問近期要搶拓元的門票,可以參考您哪一個網址的教學,讓我這個小白可以簡單上手,感謝您

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *