總結
需要做的事情
1. GCP帳號
2. 開VM
3. 多台VM 指定一台為母機
4. 其餘為子機
5. 安裝DOCKER 並在子母機上設定關聯
Prerequisites
申請Google帳號及GCP免費使用額度
- Google Cloud Platform(GCP)免費試用額度。 申請網址如下:https://cloud.google.com/free/?hl=zh-tw
- GCP若已無額度,可另外申請 Gmail 帳號後,再用以登入GCP即取得新的試用額度。
GCP上建立 container環境
1. 建立VM執行個體
1. 建立防火牆

2. 建立VM執行個體

記得要開防火牆 因為預設只有80跟443
這個下面的PORT基本上都不是這個PORT
所以要去防火牆那邊default改掉
以下前提全在linux centos7底下執行
懶人包
基礎環境
“` linux=
sudo su –
yum -y install wget docker unzip
chkconfig docker on
service docker start
service firewalld start
firewall-cmd –permanent –zone=trusted –change- interface=docker0
firewall-cmd –reload
firewall-cmd –list-all
echo “net.ipv4.ip_forward=1” >> /etc/sysctl.conf
service network restart
service docker restart
service docker status
setenforce 0
sed -i \’s/SELINUX=enforcing/SELINUX=disabled/g\’ /etc/selinux/config
啟動 swap 設定4G
``` linux=
dd if=/dev/zero of=/etc/swapfile bs=1024 count=4096000
mkswap /etc/swapfile
swapon /etc/swapfile
“` linux=
crontab
0 0 * * 5 /bin/bash /home/tonic16888/work.sh
work.sh
母
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker run -d -p 4442-4444:4442-4444 –name selenium-hub selenium/hub:4.13.0
子
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker run -d -p 5555:5555 -e SE_EVENT_BUS_HOST=<母機IP> -e SE_NODE_HOST=<子雞IP> –shm-size=”2g” -e SE_NODE_MAX_SESSIONS=4 -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 selenium/node-chrome:4.13.0
IP建議外網IP
## 使用 standalone啟動(單機模式)
分界線
用簡單啟動法
```linux=
docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-chrome:4.4.0
使用子母機啟動(hub-node)
同網域下啟動法 (設定grid)
“` linux=
docker network create grid
启动 Hub
docker run -d -p 4442-4444:4442-4444 –net grid –name selenium-hub selenium/hub:4.4.0
docker run -d -p 7901:7900 –net grid -e SE_EVENT_BUS_HOST=selenium-hub –shm-size=”2g” -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 selenium/node-edge:4.4.0
docker run -d -p 7900:7900 –net grid -e SE_EVENT_BUS_HOST=selenium-hub –shm-size=”3g” -e SE_NODE_MAX_SESSIONS=4 -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 selenium/node-chrome:4.4.0 -e SE_NODE_OVERRIDE_MAX_SESSIONS=true
docker run -d -p 7902:7900 –net grid -e SE_EVENT_BUS_HOST=selenium-hub –shm-size=”2g” -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 selenium/node-firefox:4.4.0
### 多台VM的情況
#### 母機設定
``` linux=
docker run -d -p 4442-4444:4442-4444 --name selenium-hub selenium/hub:4.13.0
子機設定
“` linux=
docker run -d -p 5555:5555 -e SE_EVENT_BUS_HOST=<母機IP> -e SE_NODE_HOST=<子雞IP> –shm-size=”2g” -e SE_NODE_MAX_SESSIONS=4 -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 -e SE_NODE_SESSION_TIMEOUT=1000 selenium/node-chrome:4.13.0
SE_NODE_MAX_SESSIONS << 這個要看機器的CPU而定 你寫多少不一定是多少
子母機IP可以是內網IP也可以是外網IP
SE_EVENT_BUS_PUBLISH_PORT跟 SE_EVENT_BUS_SUBSCRIBE_PORT 跟著母機設定的Port走
# 問題
## Docker自己跑到死
https://stackoverflow.com/questions/62470244/docker-compose-starting-already-created-containers
/usr/bin/docker-current: Error response from daemon: Conflict. The container name “/selenium-hub” is already in use by container 630fa4d89ad42d1ee930740e3bdda00617353d6ff0bad3284cc3ef74b14bf93b. You have to remove (or rename) that container to be able to reuse that name..
docker rm -v $(docker ps -aq -f \’status=exited\’)
## 抓取網頁要很久
需分清楚是抓網頁回應久,還是Xpath或是classname的久
因Xpath是遍歷,所以如果抓回來的網頁字元很多 xpath就會很久
可是當的配合JSOUP 把網頁內容抓取到JSOUP再進行XPATH
遍歷小很多 但是相對的PATH也不一樣

### 舉個MOMO的案例
比方說
MOMO的網頁總共要 100萬個字元每一次PATH都在這100萬中跑

其中有30個商品 要抓品名、價格等等
每一個都要XPATH 等於要90次以上
導致我雖然抓網頁要20秒 可是自己處理也要30秒
(這點機器增加記憶體有解 就是靠$$ 處理就會變快)
但是如果是按照上面SWAP的模式下 速度起不來,就要靠縮小文本減少遍歷
所以要靠JSOUP在JOB機中產生物件 並且對JSOUP進行小空間的XPATH處理
可從30秒變成5秒內


**一百萬跑完跟五千跑完還是有差的**
### 最終成果
因k8s的機器有多少台 selenium對應的就要有多少台
因此如果queue卡住 需要開多台機器時 這裡就要有對應的策略
最早的子母機合併的方式 因只有兩個CPU 限制住只能有兩台
後來的HUB-NODE模式 可多開 可相對應的經費問題跟記憶體問題隨之而來
原本需要 1台母機 +3台子機(14+25+25+25)=89美金的機器
經過上面這些調整
可以使用 1台母機 +3台子機(14+14+14+14)=56美金的機器
當然還有地區跟硬碟的差異 但是上下不大
並且每擴增一台子機的COST不高
代價就是要先開SWAP跟SELENIUM+JSOUP的處理

## Hub-node模式底下一個特殊案例
以往在本地模式 selenium卡住可以用processKill的方式
但是在hub-node模式下
無法用chromeDriveService這個方法來處理服務了
但是取而代之的是hub有/status
可以得知所有的nodes的狀態
因此需要自己進行控制
如果時間太久 要自己殺
使用
``` curl=
cURL --request DELETE \'http://<node-URL>/se/grid/node/session/<session-id>\' --header \'X-REGISTRATION-SECRET;\'
另外 因為這樣的關係
上面的子機IP跟母機IP 必須要用外部IP,否則打/status的時候會取得內網 你是無法使用http去刪除session的(kill process)
WARNING: 會發現這個是因為
爬蟲機的基本需求就是大量的啟動、關閉
這樣會造成SELENIUM有點秀斗
正常的close quit 不一定會真的可以關掉
本地端的時候可以用kill procress 的方法 或是chromeDriveService
但是remote模式下 無法下這種指令
然後只能等selenium慢慢讓它死 (通常要十分鐘以上) 這樣會造成另外的問題
K8S機器反被GCP限制
因為爬蟲其實不一定都是SELENIUM,但是這種模式之下變成我的JOB機的上限就是SELENIUM的上限數
我有十家平台但是每家爬法不同 不一定都要用網頁式,可是最慘的情況下是我所有JOB機可能同時都要用selenium,因此衍伸了
只要是selenium的JOB 都先呼叫grid api 查詢一下狀態 看看是不是workable的
http://Hubip:port/status
“` json =
{“value”:{“ready”:true,”message”:”Selenium Grid ready.”,”nodes”:[{“id”:”61205c84-aee7-4958-ab76-98a18d3bb842″,”uri”:”http://10.140.0.13:5555″,”maxSessions”:2,”osInfo”:{“arch”:”amd64″,”name”:”Linux”,”version”:”3.10.0-1160.108.1.el7.x86_64″},”heartbeatPeriod”:60000,”availability”:”UP”,”version”:”4.13.0 (revision ba948ece5b)”,”slots”:[{“id”:{“hostId”:”61205c84-aee7-4958-ab76-98a18d3bb842″,”id”:”2a1ceb42-2525-4708-abeb-5ae2fc9bddc0″},”lastStarted”:”2024-03-02T12:07:13.419922Z”,”session”:null,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}},{“id”:{“hostId”:”61205c84-aee7-4958-ab76-98a18d3bb842″,”id”:”f81056ad-e248-4b3e-9277-54386444a200″},”lastStarted”:”2024-03-02T12:08:47.884344Z”,”session”:null,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}}]},{“id”:”f659c4f4-0dfb-48ee-9a4f-28e3a81558d3″,”uri”:”http://10.140.0.11:5555″,”maxSessions”:2,”osInfo”:{“arch”:”amd64″,”name”:”Linux”,”version”:”3.10.0-1160.108.1.el7.x86_64″},”heartbeatPeriod”:60000,”availability”:”UP”,”version”:”4.13.0 (revision ba948ece5b)”,”slots”:[{“id”:{“hostId”:”f659c4f4-0dfb-48ee-9a4f-28e3a81558d3″,”id”:”57052106-1ce4-4e7d-87f0-706e54c44d76″},”lastStarted”:”2024-03-02T12:08:49.601586Z”,”session”:null,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}},{“id”:{“hostId”:”f659c4f4-0dfb-48ee-9a4f-28e3a81558d3″,”id”:”c0ad4ea5-0030-42e4-b075-6ffff7ea6bde”},”lastStarted”:”2024-03-02T12:07:14.020095Z”,”session”:null,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}}]},{“id”:”66aac391-13d8-4900-878c-0673dd4d0363″,”uri”:”http://10.140.0.12:5555″,”maxSessions”:2,”osInfo”:{“arch”:”amd64″,”name”:”Linux”,”version”:”3.10.0-1160.108.1.el7.x86_64″},”heartbeatPeriod”:60000,”availability”:”UP”,”version”:”4.13.0 (revision ba948ece5b*)”,”slots”:[{“id”:{“hostId”:”66aac391-13d8-4900-878c-0673dd4d0363″,”id”:”9e7a80bf-46e3-4d5e-9403-c45766e6ed3a”},”lastStarted”:”2024-03-02T12:39:28.621087Z”,”session”:{“capabilities”:{“acceptInsecureCerts”:false,”browserName”:”chrome”,”browserVersion”:”117.0.5938.132″,”chrome”:{“chromedriverVersion”:”117.0.5938.92 (67649b10b92bb182fba357831ef7dd6a1baa5648-refs/branch-heads/5938_62@{#14})”,”userDataDir”:”/tmp/.org.chromium.Chromium.3dU9aD”},”fedcm:accounts”:true,”goog:chromeOptions”:{“debuggerAddress”:”localhost:36448″},”networkConnectionEnabled”:false,”pageLoadStrategy”:”normal”,”platformName”:”linux”,”proxy”:{},”se:bidiEnabled”:false,”se:cdp”:”ws://172.17.0.2:4444/session/d76c84411f124004aac70f206ca12118/se/cdp”,”se:cdpVersion”:”117.0.5938.132″,”se:vnc”:”ws://172.17.0.2:4444/session/d76c84411f124004aac70f206ca12118/se/vnc”,”se:vncEnabled”:true,”se:vncLocalAddress”:”ws://172.17.0.2:7900″,”setWindowRect”:true,”strictFileInteractability”:false,”timeouts”:{“implicit”:0,”pageLoad”:300000,”script”:30000},”unhandledPromptBehavior”:”dismiss and notify”,”webauthn:extension:credBlob”:true,”webauthn:extension:largeBlob”:true,”webauthn:extension:minPinLength”:true,”webauthn:extension:prf”:true,”webauthn:virtualAuthenticators”:true},”sessionId”:”d76c84411f124004aac70f206ca12118″,”start”:”2024-03-02T12:39:28.621087Z”,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true},”uri”:”http://10.140.0.12:5555″},”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}},{“id”:{“hostId”:”66aac391-13d8-4900-878c-0673dd4d0363″,”id”:”ebb4194e-3989-4eb5-810a-8b0de33b7fe3″},”lastStarted”:”2024-03-02T12:03:37.258575Z”,”session”:null,”stereotype”:{“browserName”:”chrome”,”browserVersion”:”117.0″,”platformName”:”linux”,”se:noVncPort”:7900,”se:vncEnabled”:true}}]}]}}
比較值得注意的是
其中ready 要全部機器都開滿才會是false
因此如果其中有閒置機器 他會是true
要自行去nodes>>slots>>session 來判斷
空的會是null
因此衍伸出來 kafka>k8s job> selenium的模式會是
``` mermaid
flowchart TD
kafka--> job機
job機 --判斷要不要丟回去-是-->丟回kafka
job機 --判斷要不要丟回去-不是-->做工作
做工作 --是否需要selenium-是-->s-job
做工作 --是否需要selenium-不是-->n-job
s-job --問hub狀態有機器-有-->做事情
s-job --問hub狀態有機器-沒有-->判斷機器有沒有卡住
判斷機器有沒有卡住--有-->deleteSession
deleteSession-->丟回kafka
判斷機器有沒有卡住--無-->丟回kafka
做事情 --做完後檢查-閒置機器只有一台-->正常END
n-job -->正常END
做事情 --做完後檢查-閒置機器超過一台-->下一個取的queue要直接丟回kafka
下一個取的queue要直接丟回kafka -->job機的丟回狀態改是
丟回kafka --丟回的狀態改不是-->kafka
發佈留言