《GO程序設計語言》設計中案例,僅作爲筆記進行收藏。Web 爬蟲 只是簡單獲取頁面屬性href中鏈接。
package main
import (
"fmt"
"log"
"net/http"
"os"
"golang.org/x/net/html"
)
func main() {
// 可能有重複的 URL 列表
worklist := make(chan []string)
// 去重後的 URL 列表
unseenLinks := make(chan string)
// 向任務列表中添加命令行參數
go func() { worklist <- os.Args[1:] }()
// 創建20個爬蟲 goroutine 來獲取每個不可見鏈接
for i := 0; i < 20; i++ {
go func() {
for link := range unseenLinks {
foundLinks := crawl(link)
go func() { worklist <- foundLinks }()
}
}()
}
// 主 goroutine 對 URL 列表進行去重
// 並把沒有爬過的條目發送給爬蟲程序
seen := make(map[string]bool)
for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
unseenLinks <- link
}
}
}
}
func crawl(url string) []string {
fmt.Println(url)
list, err := Extract(url)
if err != nil {
log.Print(err)
}
return list
}
// Extract 函數向給定 URL 發起 HTTP GET 請求
// 解析 HTML 並返回 HTML 文檔中存在的鏈接
// 網頁爬蟲的核心是解決圖的遍歷
func Extract(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("getting %s:%s", url, resp.Status)
}
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML:%v", url, err)
}
var links []string
visitNode := func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key != "href" {
continue
}
link, err := resp.Request.URL.Parse(a.Val)
if err != nil {
continue // 忽略不合法的 URL
}
fmt.Println(link)
links = append(links, link.String())
}
}
}
forEachNode(doc, visitNode, nil)
return links, nil
}
// forEachNode 調用 pre(x) 和 post(x) 遍歷以n爲根的樹種的每個節點x
// 兩個函數是可選的
// pre 在子節點被訪問前(前序)調用
// post 在訪問後(後序)調用
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
編譯後,就可以使用