กำลังลองเล่น package chromedp https://github.com/chromedp/chromedp ซึ่งเป็น package ช่วยให้เราเขียน Go ไปต่อกับ Chrome Engine แล้วทำอะไรต่างๆได้แบบ browser ทำ โดยผ่านการเขียนโปรแกรมเอง ประโยชน์ก็ใช้สำหรับทำ web bot ทำ web crawler/scrapper ต่างๆนั่นเอง
ประเด็นของโพสต์นี้คือ กำลังพยายามเอามันไปรันภายใต้ Docker ซึ่งไม่มี Chrome อยู่ในนั้น อย่างไรก็ตาม Chrome มีวิธีให้เชื่อมต่อหามันผ่าน remote โดยใช้ Chrome DevTools Websocket endpoint
ประเด็นต่อมา เราต้องรัน Chrome headless ซึ่งเป็น Chrome engine แบบไม่ต้องติดตั้ง Chrome application นั่นเอง ดีที่มีคนทำ docker image เอาไว้แล้วที่นี่ https://hub.docker.com/r/chromedp/headless-shell/
ดังนั้นเราจะใช้งาน headless-shell ก็รันผ่าน docker แบบนี้
docker run -d -p 9222:9222 --rm --name headless-shell chromedp/headless-shell
ต่อมาผมจะลองเล่นโค้ดตัวอย่างของ chromedp จากโค้ดนี้ https://github.com/chromedp/examples/blob/master/remote/main.go
// Command remote is a chromedp example demonstrating how to connect to an
// existing Chrome DevTools instance using a remote WebSocket URL.
package main
import (
"context"
"flag"
"log"
"github.com/chromedp/chromedp"
)
func main() {
devtoolsWsURL := flag.String("devtools-ws-url", "", "DevTools WebSsocket URL")
flag.Parse()
if *devtoolsWsURL == "" {
log.Fatal("must specify -devtools-ws-url")
}
// create allocator context for use with creating a browser context later
allocatorContext, cancel := chromedp.NewRemoteAllocator(context.Background(), *devtoolsWsURL)
defer cancel()
// create context
ctxt, cancel := chromedp.NewContext(allocatorContext)
defer cancel()
// run task list
var body string
if err := chromedp.Run(ctxt,
chromedp.Navigate("https://duckduckgo.com"),
chromedp.WaitVisible("#logo_homepage_link"),
chromedp.OuterHTML("html", &body),
); err != nil {
log.Fatalf("Failed getting body of duckduckgo.com: %v", err)
}
log.Println("Body of duckduckgo.com starts with:")
log.Println(body[0:100])
}
โดยเอามาใส่ go.mod เพื่อจัดการ package ให้ ซึ่ง go.mod มีโค้ดแบบนี้อยู่
module example-chromedp-remote
go 1.16
require github.com/chromedp/chromedp v0.7.1
เสร็จแล้วก็สร้าง Dockerfile ให้มันแบบนี้
FROM golang:1.16.4-alpine as builder
WORKDIR /app
COPY go.mod /app
COPY main.go /app
RUN go mod download
RUN go build -o app
FROM alpine
COPY --from=builder /app/app /app
ENTRYPOINT ["/app"]
จากนั้นทำการ build docker ด้วยคำสั่ง
docker build -t example-chromedp-remote .
สุดท้าย เราจะรัน example-chromedp-remote โดยส่ง ws url ของ headless ไปเป็น option ให้มัน ส่วนวิธีหา ws url นั้นทำแบบนี้
curl 127.0.0.1:9222/json/version
{
"Browser": "Chrome/90.0.4430.93",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
"V8-Version": "9.0.257.23",
"WebKit-Version": "537.36 (@4df112c29cfe9a2c69b14195c0275faed4e997a7)",
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/68424ed8-a10a-4633-9618-f93b443e0aa9"
}
เราจะเอา URL ตรง "webSocketDebuggerUrl"
ไปใช้งานนั่นเอง
ต่อไปก็สั่งรัน example-chromedp-remote แบบนี้
docker run --rm --network="container:headless-shell" example-chromedp-remote -devtools-ws-url="ws://127.0.0.1:9222/devtools/browser/68424ed8-a10a-4633-9618-f93b443e0aa9"
จุดที่ทำให้รันโดยเชื่อมต่อไปให้มันเป็น network เดียวกัน IP เดียวกันได้คือ option --network="container:headless-shell"
นั่นเอง pattern มันคือมี container:
ด้านหน้า ตามด้วยชื่อ container ที่จะเอาไปเชื่อมต่อ (ref: https://docs.docker.com/engine/reference/run/#network-container)
แถมท้าย ถ้าใช้ docker compose แทนที่จะรันเองตรงๆนั้น จะใช้ config key ที่ชื่อ network_mode แต่ว่าแทนที่จะกำหนดชื่อ container ตรงๆ เราใช้ชื่อ service แทนได้โดยใช้ prefix service:
ตามด้วยชื่อ service ตัวอย่างเช่น
---
version: "3.8"
services:
chrome-headless:
image: "chromedp/headless-shell"
ports:
- "9222:9222"
example-chromedp-remote:
build:
context: .
network_mode: "service:chrome-headless"
จากนั้นก็สั่งรัน chrome-headless ผ่าน docker compose แบบนี้
docker compose up -d chrome-headless
แล้วก็รัน example-chromedp-remote แบบนี้
docker compose run --rm example-chromedp-remote -devtools-ws-url="ws://127.0.0.1:9222/devtools/browser/cdaa6d7f-3d08-4593-ad4d-0d630abcd627"
สรุป
ถ้าจะเชื่อมต่อไปใช้ network ของ container อื่นๆ ผ่าน docker run ให้ใช้ option --network ค่าที่กำหนดคือ container:
ตามด้วยชื่อ container ที่จะไปต่อ เช่น --network="conatiner:headless"
แต่ถ้าใช้ docker compose ให้กำหนดโดยคีย์ที่ชื่อว่า network_mode
ค่าที่กำหนดคือ service:
ตามด้วยชื่อ service ที่จะไปต่อ เช่น network_mode: "service:chrome-headless"
ขอฝาก Buy Me a Coffee
สำหรับท่านใดที่อ่านแล้วชอบโพสต์ต่างๆของผมที่นี่ ต้องการสนับสนุนค่ากาแฟเล็กๆน้อยๆ สามารถสนับสนุนผมได้ผ่านทาง Buy Me a Coffee คลิ๊กที่รูปด้านล่างนี้ได้เลยครับ
ส่วนท่านใดไม่สะดวกใช้บัตรเครดิต หรือ Paypal สามารถสนับสนุนผมได้ผ่านทาง PromptPay โดยดู QR Code ได้จากโพสต์ที่พินเอาไว้ได้ที่ Page DevDose ครับ https://web.facebook.com/devdoseth
Top comments (0)