前言
看某新聞網的新聞,看完之後都會有一個類似如下的頁面:
從上圖得知,是不是有個驗證碼與一個投票的頻率?這意思是要在限期期限之內,就可以去投下神聖的一票。且頻率為一天一次。
本文章,是要演示讓我們可以一天之內投好幾次的神聖的票。
環境建置
首先,我們要先有一個可以,進行發送請求的HTTP Client的環境,我們在這邊使用的是Docker上面的一個image。
此環境皆為PHP相關的程式以及函式庫。我們可以用下列的方式把環境下載下來:
若沒有Docker指令或是沒有安裝的,可以參考這個網址進行安裝。安裝好之後,就會有docker指令了,並可以執行下列的指令將此文章預先要用的Docker image給下載回來:
docker pull peter279k/crawler-lab-coscup:latest
拉回來Docker image之後,可以用下列的指令來檢查相關的套件是否有存在,以及相關文章要用的套件是否已經有安裝了:
# 進入此環境 docker run -it peter279k/crawler-lab-coscup:latest bash # 檢查Tesseract套件是否存在 dpkg -l | grep tess # 檢查Guzzlehttp/client相關套件是否存在 php composer.phar show | grep guzzle # 檢查symfony/css-selector套件是否存在 php composer.phar show | grep css-selector # 檢查dom-crawler套件是否存在 php composer.phar show | grep dom-crawler
如執行上述指令,大多都會跑出相關的套件資訊清單等結果。
分析網頁
接著,我們先分析投票網頁,發現投票頁面有一個特點,就是會用act_code參數帶在網址後面並帶一個值,像是「v855」這樣,每個投票頁面都會不同,這樣的頁面進去之後,就會看到下面的頁面了:
接著,我們會發現幾個重點,相關的重點如下:
- 剩餘時間:28天 18小時
- 投票頻率:1天一次
- 驗證碼與驗證碼圖示
- 「觀看投票結果」按鈕
所以總結上面的特性,我們可以得到幾個重點:
- 每個投票頁面都有期限的,期限到了即不能投票
- 每個投票只能1天1次,超過即不能再投
- 每次投票都會有驗證碼以及驗證碼的圖示
所以要用HTTP Client來送出一張選票,有幾個重點:
- 用GET的HTTP方法取得當前投票頁面,並將當前的相關Cookie給記錄下來
- 接著將上述請求的Cookie加入到Header並發送GET方法請求到https://udn.com/funcap/keyimg以取得上述投票頁面的驗證碼
- 接著,將驗證碼進行OCR解析拿到驗證碼的數字
- 接著選好要投的選項,並發送POST方法請求到https://udn.com/funcap/vote/votingJson.jsp進行投票,相關的JSON payload的PHP關聯陣列如下:
$json = [ 'code' => $code, 'choice' => [2], 'id' => 'v855', 'email' => '', 'g_token' => null, ];
從上述的關聯陣列得知,code為驗證碼,choice為投票的選項,id為投票頁面的「act_code」參數值,email與g_token皆維持空字串與null值即可。
開發程式
我們將上面的步驟與想法轉換成程式碼如下:
上述程式碼重點如下:
- 第7行投票頁面可以更改,改成同樣的樣式即可,且只有act_code後面帶的參數不同,即代表不同的投票頁面
- 第30行的JSON payload中,裡面的id需要與投票頁面中的act_code相同,這樣才可以進行投票
- 第29行的JSON payload中,裡面的choice需要是投票頁面中要投的選項,選項從上面第一個為1,下一個為2,以此類推
- 最後一行印出上述請求之後的HTTP response訊息
開發好程式之後,我們可以將此程式取名叫「vote.php」並將此檔案複製到上述正在運行的Docker container
可以用下列指令將此檔案複製到運行的Docker container中:
docker cp /path/to/vote.php container_id:/root/vote.php
接著,可以在Docker container執行此vote.php:
php vote.php
接著,有可能會出現以下的response錯誤訊息:
string(64) "{ "state" : false , "message" : "您輸入檢核碼錯誤!!"}
出現上述這個錯誤,則代表使用Tesseract辨識出的驗證碼有問題,所以我們可以再試一遍執行vote.php或是用迴圈將這整個流程框在迴圈區塊內,並當失敗時候,再自動執行幾次已達到重試的動作。
若投票成功之後,會出現以下的成功訊息:
string(545) " {"state":true,"id":"v855","options":[{"title":"支持,守住第一道防線,每日花420萬元值得","count":3204,"link":"","value":1,"img":""},{"title":"支持,入境者應自費普篩減輕社會負擔","count":7998,"link":"","value":2,"img":""},{"title":"不支持,每日成本需要420萬元太高","count":373,"link":"","value":3,"img":""},{"title":"不支持,若出現偽陰性會鬆懈居家檢疫","count":1319,"link":"","value":4,"img":""},{"title":"沒意見/不知道","count":121,"link":"","value":5,"img":""}]} "
有看到上面的訊息之後,代表送出投票已經成功了,那我們會想到另一個更進階的問題,可否再送幾次投下更多的票數?如果我們看到上面的回應訊息之後,我們再次執行vote.php。
若沒有驗證碼辨識錯誤的話,則會看到下面的HTTP Response訊息:
string(64) "{ "state" : false , "message" : "請依正常操作程序!!"} "
上述這個回應訊息,則代表後端會記住發送的IP位址以達到一天一票的效果,那如果使用proxy代理呢?相關的架構圖如下:
由上述的架構得知,我們可以透過外部的某一台proxy server進行發送請求,讓我們目標請求的server以為是Proxy Server所發出的請求,以達到再投出某更多張的票。
因此,假設我們可以拿到一些proxy IP位址為:http://85.173.165.36:46330,那我們可以修改一下上面的vote.php程式,改成如下:
我們可以注意到,上述的程式碼第10行加入了Proxy IP位址,並達成上面架構的方式送出請求。
接著,我們再次的執行vote.php則可以又再得到下面的HTTP Response回應訊息:
string(545) " {"state":true,"id":"v855","options":[{"title":"支持,守住第一道防線,每日花420萬元值得","count":3207,"link":"","value":1,"img":""},{"title":"支持,入境者應自費普篩減輕社會負擔","count":8002,"link":"","value":2,"img":""},{"title":"不支持,每日成本需要420萬元太高","count":373,"link":"","value":3,"img":""},{"title":"不支持,若出現偽陰性會鬆懈居家檢疫","count":1319,"link":"","value":4,"img":""},{"title":"沒意見/不知道","count":121,"link":"","value":5,"img":""}]} "
結論
從這次的投票請求發送與防護機制,可以得到幾個重點
- 有時候OCR辨識引擎會辨識錯誤,因此需要再次進行發送一次請求(retry sending HTTP request)
- 網路投票可以投下「好幾張神聖的票數」,而這取決於Proxy IP位址的多寡
- 有可能用了Proxy IP位址發送請求仍會發生「請依正常操作程序!!」錯誤訊息,其原因有可能為:
- 這個Proxy IP 位址被人拿去用過了,請下次請早,用下一個Proxy IP位址再送一次吧XD
參考文獻
- http://docs.guzzlephp.org/en/stable/request-options.html?highlight=proxy#proxy
- https://docs.docker.com/engine/install/ubuntu/
- https://hub.docker.com/repository/docker/peter279k/crawler-lab-coscup
- https://free-proxy-list.net/
- http://spys.one/en/
- http://free-proxy.cz/en/
- http://www.freeproxylists.net/zh/