你今天網路投票了嗎?

前言

看某新聞網的新聞,看完之後都會有一個類似如下的頁面:

從上圖得知,是不是有個驗證碼與一個投票的頻率?這意思是要在限期期限之內,就可以去投下神聖的一票。且頻率為一天一次。

本文章,是要演示讓我們可以一天之內投好幾次的神聖的票。

環境建置

首先,我們要先有一個可以,進行發送請求的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

參考文獻