《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)
}
}
编译后,就可以使用