我使用Go与gotk3和webkit2库来尝试构建一个可以在WebKitWebView上下文中解析JavaScript的网络爬虫。
考虑到性能,我试图找出使用所有可用资源同时抓取(如果不是并行,使用多个处理器)的最佳方法。
GTK以及所有带线程和goroutines的东西对我来说都是新手。从gotk3 goroutines example读取,它指出:
当我尝试在goroutine中运行一个创建新WebView的函数时,Go会发生恐慌并显示堆栈跟踪。我不确定为什么会发生这种情况,但我认为这与此评论有关。示例如下所示。原生GTK不是线程安全的,因此,其他goroutine可能无法使用gotk3的GTK绑定。相反,glib.IdleAdd()必须用于添加一个函数,以便在GTK主循环处于空闲状态时运行。
这是我目前的代码,已经改编自webkit2 example:
package main
import (
"fmt"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/sourcegraph/go-webkit2/webkit2"
"github.com/sqs/gojs"
)
func crawlPage(url string) {
web := webkit2.NewWebView()
web.Connect("load-changed", func(_ *glib.Object, i int) {
loadEvent := webkit2.LoadEvent(i)
switch loadEvent {
case webkit2.LoadFinished:
fmt.Printf("Load finished for: %v\n", url)
web.RunJavaScript("window.location.hostname", func(val *gojs.Value, err error) {
if err != nil {
fmt.Println("JavaScript error.")
} else {
fmt.Printf("Hostname (from JavaScript): %q\n", val)
}
//gtk.MainQuit()
})
}
})
glib.IdleAdd(func() bool {
web.LoadURI(url)
return false
})
}
func main() {
gtk.Init(nil)
crawlPage("https://www.google.com")
crawlPage("https://www.yahoo.com")
crawlPage("https://github.com")
crawlPage("http://deelay.me/2000/http://deelay.me/img/1000ms.gif")
gtk.Main()
}
似乎为每个URL创建一个新的WebView允许它们同时加载。根据gotk3示例,让glib.IdleAdd()
在goroutine中运行,似乎没有任何效果(尽管我只做了视觉基准测试):
go glib.IdleAdd(func() bool { // Works
web.LoadURI(url)
return false
})
但是,尝试为每个crawlPage()
电话创建一个goroutine会以恐慌结束:
go crawlPage("https://www.google.com") // Panics and shows stack trace
我可以毫无问题地在goroutine中运行web.RunJavaScript()
:
switch loadEvent {
case webkit2.LoadFinished:
fmt.Printf("Load finished for: %v\n", url)
go web.RunJavaScript("window.location.hostname", func(val *gojs.Value, err error) { // Works
if err != nil {
fmt.Println("JavaScript error.")
} else {
fmt.Printf("Hostname (from JavaScript): %q\n", val)
}
//gtk.MainQuit()
})
}
我能想到的当前方法是:
如果可能的话,实际上同时运行此代码的最佳方法是什么,并最大限度地提高性能?
方法1和方法2可能不合适,因为我通过生成~100个WebViews进行测试,它们似乎同步加载。