以下是基于 Go 1.19 的站点模板爬虫教程。我们将使用 Go 编程语言创建一个简单的网页爬虫,爬取指定网站的内容。我们将使用一些常见的 Go 库,例如 net/http
和 golang.org/x/net/html
,来处理 HTTP 请求和解析 HTML。
第一步:安装和设置
-
安装 Go:
- 如果你还没有安装 Go,请先从 Go 官方网站 下载并安装最新版本。
-
创建项目目录:
mkdir go-web-crawler cd go-web-crawler
-
初始化 Go 模块:
go mod init go-web-crawler
第二步:编写爬虫代码
-
创建主程序文件:
在项目目录中创建一个名为main.go
的文件。 -
编写爬虫逻辑:
package main import ( "fmt" "net/http" "golang.org/x/net/html" "log" ) // fetch makes an HTTP request and returns the response body as a string func fetch(url string) (string, error) { resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("error: status code %d", resp.StatusCode) } var body []byte body, err = io.ReadAll(resp.Body) if err != nil { return "", err } return string(body), nil } // parseHTML parses the HTML and prints the links found func parseHTML(body string) { doc, err := html.Parse(strings.NewReader(body)) if err != nil { log.Fatal(err) } var f func(*html.Node) f = func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "a" { for _, a := range n.Attr { if a.Key == "href" { fmt.Println(a.Val) break } } } for c := n.FirstChild; c != nil; c = c.NextSibling { f(c) } } f(doc) } func main() { url := "http://example.com" body, err := fetch(url) if err != nil { log.Fatal(err) } parseHTML(body) }
第三步:运行爬虫
-
运行程序:
在项目目录中执行以下命令运行爬虫:go run main.go
你应该会看到控制台输出网站中所有的链接(
<a href="...">
标签的href
属性值)。
第四步:扩展爬虫功能
-
处理相对链接:
解析链接时,你可能需要将相对链接转换为绝对链接。你可以使用net/url
包中的ResolveReference
方法来实现这一点。 -
并发请求:
为了提高效率,你可以使用 Goroutines 来并发地处理多个 URL。 -
避免重复抓取:
使用map
数据结构来记录已经抓取过的 URL,避免重复抓取。 -
错误处理:
添加更健壮的错误处理机制,确保程序在遇到错误时不会崩溃。
示例代码:处理相对链接和并发请求
package main
import (
"fmt"
"log"
"net/http"
"golang.org/x/net/html"
"io"
"strings"
"net/url"
"sync"
)
var visited = make(map[string]bool)
var mu sync.Mutex
func fetch(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("error: status code %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func parseHTML(baseURL *url.URL, body string, ch chan string) {
doc, err := html.Parse(strings.NewReader(body))
if err != nil {
log.Fatal(err)
}
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
link, err := baseURL.Parse(a.Val)
if err != nil {
continue
}
mu.Lock()
if !visited[link.String()] {
visited[link.String()] = true
ch <- link.String()
}
mu.Unlock()
break
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
}
func worker(ch chan string, wg *sync.WaitGroup) {
defer wg.Done()
for url := range ch {
body, err := fetch(url)
if err != nil {
log.Println(err)
continue
}
u, err := url.Parse(url)
if err != nil {
log.Println(err)
continue
}
parseHTML(u, body, ch)
}
}
func main() {
startURL := "http://example.com"
ch := make(chan string)
var wg sync.WaitGroup
wg.Add(1)
go worker(ch, &wg)
ch <- startURL
go func() {
wg.Wait()
close(ch)
}()
for link := range ch {
fmt.Println(link)
}
}
以上示例代码使用 Goroutines 和 Channel 来并发地处理 URL,并通过 sync.Mutex
确保对 visited
map 的并发访问是安全的。你可以根据需要进一步扩展和优化爬虫的功能。