مرورگرهای Headless در سالهای اخیر به یکی از ابزارهای اصلی توسعهدهندگان تبدیل شدهاند. این نوع مرورگرها بدون رابط گرافیکی اجرا میشوند و امکاناتی مانند رندر صفحات وب، اجرای جاوااسکریپت، پیمایش، اسکرینشات و تعامل با DOM را در اختیار برنامههای سمت سرور قرار میدهند. در این مقاله، نحوه ساخت یک مرورگر Headless ساده با استفاده از زبان Go و کتابخانه Chromedp بررسی میشود و در نهایت یک API کاربردی برای گرفتن اسکرینشات ارائه خواهد شد.
در اصل پیش زمینه ای برای ساختن مرورگرهای ابری روی سرور فراهم میآورند و بدون داشتن صفحه مرورگر اطلاعات وب سایت و ... را پردازش میکنند.
کتابخانهٔ Chromedp یک ابزار قدرتمند برای تعامل برنامهنویسی با مرورگر Google Chrome از طریق Chrome DevTools Protocol است. این کتابخانه امکان انجام عملیاتهایی مانند موارد زیر را فراهم میکند:
باز کردن صفحات وب
اجرای اسکریپتهای جاوااسکریپت
استخراج داده از DOM
اسکرول، کلیک و شبیهسازی رفتار کاربر
گرفتن اسکرینشات در اندازه کامل یا بخش خاصی از صفحه
تبدیل صفحه به PDF
Chromedp کاملاً در Go نوشته شده و نیاز به نصب selenium یا webdriver ندارد.
برای مدیریت یک مرورگر Headless ابتدا یک ساختار معرفی میشود که context و تابع لغو (cancel) را نگهداری میکند:
type BrowserInstance struct { ctx context.Context cancel context.CancelFunc }
ctx مسئول نگهداری اطلاعات Session مرورگر و کانال ارتباطی با DevTools است.
cancel امکان متوقفسازی و آزادسازی منابع مرورگر را فراهم میکند.
با استفاده از تابع chromedp.NewContext یک مرورگر Headless ساخته میشود:
func NewBrowserInstance() (*BrowserInstance, error) { ctx, cancel := chromedp.NewContext(context.Background()) return &BrowserInstance{ ctx: ctx, cancel: cancel, }, nil }
ساخت context پایه (context.Background)
ساخت context اختصاصی مرورگر و آغاز بهکار Chrome Headless
بازگرداندن ساختار مدیریتکننده مرورگر
برای باز کردن یک آدرس اینترنتی از تابع زیر استفاده میشود:
func (b *BrowserInstance) LoadURL(url string) error { err := chromedp.Run(b.ctx, chromedp.Navigate(url), chromedp.WaitReady("body", chromedp.ByQuery), ) return err }
Navigate آدرس موردنظر را در مرورگر باز میکند.
WaitReady تا زمانی که تگ <body> آماده باشد صبر میکند و از رندر کامل محتوای صفحه مطمئن میشود.
تابع زیر محتوای صفحه را بهصورت PNG باز میگرداند:
func (b *BrowserInstance) Screenshot() ([]byte, error) { var buf []byte err := chromedp.Run(b.ctx, chromedp.CaptureScreenshot(&buf), chromedp.Sleep(500*time.Millisecond), ) return buf, err }
CaptureScreenshot تصویر کامل صفحه را گرفته و در متغیر buf ذخیره میکند.
Sleep برای اطمینان از رندر کامل عناصر پویا استفاده میشود.
در این مرحله یک هندلر HTTP معرفی میشود که با گرفتن یک URL از کاربر، تصویر صفحه را بازگردانی میکند.
func screenshotHandler(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get("url") if url == "" { http.Error(w, "url is required", http.StatusBadRequest) return } browser, err := NewBrowserInstance() if err != nil { http.Error(w, "failed to create browser", http.StatusInternalServerError) return } defer browser.Close() if err := browser.LoadURL(url); err != nil { http.Error(w, "failed to load url", http.StatusInternalServerError) return } img, err := browser.Screenshot() if err != nil { http.Error(w, "failed to capture screenshot", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "image/png") w.Write(img) }
func main() { http.HandleFunc("/screenshot", screenshotHandler) fmt.Println("Server running at :8081") http.ListenAndServe(":8081", nil) }
پس از اجرای برنامه:
http://localhost:8081/screenshot?url=https://example.com
مرورگر Headless:
صفحهٔ example.com را باز میکند
آن را کامل رندر میکند.
یک تصویر PNG بازمیگرداند
در بخش بعدی به توسعه کامل تر میپردازیم و کدهای نوشته شده در یک مخزن گیت نگهداری میکنیم.