答案:使用Golang实现爬虫需先用net/http发送请求并处理错误、超时和重定向,再通过goquery结合CSS选择器解析HTML提取数据,最后利用goroutine和channel实现并发抓取,配合WaitGroup同步,数据可存为文件或数据库。

用Golang实现一个简单的爬虫,核心思路其实就是两步:先用标准库
net/http
goquery
说实话,第一次用Go写爬虫,
net/http
goquery
下面是一个基础的实现,展示了如何抓取一个网页的标题和所有链接:
package main
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
)
func main() {
// 目标URL,这里以一个示例网站为例,实际使用时请替换
url := "http://example.com" // 请替换成实际可访问的URL
// 发送HTTP GET请求
resp, err := http.Get(url)
if err != nil {
log.Fatalf("请求URL失败: %v", err)
return
}
defer resp.Body.Close() // 确保在函数结束时关闭响应体
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
log.Fatalf("HTTP请求失败,状态码: %d %s", resp.StatusCode, resp.Status)
return
}
// 使用goquery解析HTML文档
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Fatalf("解析HTML文档失败: %v", err)
return
}
// 提取网页标题
title := doc.Find("title").Text()
fmt.Printf("网页标题: %s\n", title)
fmt.Println("\n所有链接:")
// 遍历所有a标签,提取href属性和链接文本
doc.Find("a").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists {
linkText := strings.TrimSpace(s.Text())
// 简单过滤空链接文本,或只显示非锚点链接
if linkText != "" && !strings.HasPrefix(href, "#") {
fmt.Printf("- 链接 %d: %s (%s)\n", i+1, linkText, href)
}
}
})
// 尝试提取某个特定元素,比如第一个段落
firstParagraph := doc.Find("p").First().Text()
if firstParagraph != "" {
fmt.Printf("\n第一个段落内容: %s\n", strings.TrimSpace(firstParagraph))
} else {
fmt.Println("\n未找到任何段落。")
}
}这段代码展示了最基本的爬取和解析流程。从请求到错误处理,再到用
goquery
立即学习“go语言免费学习笔记(深入)”;
在实际的爬虫开发中,网络波动或者目标网站的反爬机制,常常让请求变得不可靠。光是简单的
http.Get()
首先是错误处理。除了检查
http.Get()
resp.StatusCode
超时设置也是个关键点。默认的HTTP请求可能不会设置超时,导致程序长时间阻塞。我们可以创建一个自定义的
http.Client
client := &http.Client{
Timeout: 10 * time.Second, // 设置10秒的请求超时
}
resp, err := client.Get(url)
// ... 后续处理User-Agent的设置也挺重要。很多网站会根据User-Agent来判断请求来源,如果发现是爬虫,可能会直接拒绝。模拟浏览器行为,设置一个常见的User-Agent头是个好习惯:
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatalf("创建请求失败: %v", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
client := &http.Client{}
resp, err := client.Do(req)
// ... 后续处理至于重定向,
net/http
http.Client
http.Client
CheckRedirect
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 { // 限制重定向次数,防止无限循环
return errors.New("stopped after 10 redirects")
}
fmt.Printf("重定向到: %s (原URL: %s)\n", req.URL.String(), via[0].URL.String())
return nil // 返回nil表示允许重定向
},
}
// 如果想禁用重定向,直接返回http.ErrUseLastResponse即可
// CheckRedirect: func(req *http.Request, via []*http.Request) error {
// return http.ErrUseLastResponse
// }这些细节的考量,能让你的爬虫在面对复杂网络环境时,显得更加健壮和可靠。
说完了请求,接下来的重头戏自然是数据的提取。
goquery
goquery
要精准定位元素,关键在于灵活运用CSS选择器。
tagName
"div"
"p"
.className
".product-title"
#idName
"#main-content"
parent child
parent
child
"div p"
div
p
parent > child
parent
child
"ul > li"
tag.className
"span.price"
[attribute=value]
"a[target=_blank]"
:nth-child(n)
:first-child
:last-child
goquery
Find()
*goquery.Selection
Selection
Find()
举个例子,假设你有一个产品列表,每个产品在一个
div
div
class="product-item"
h3
h3
a
Awesome Product
$19.99
要提取产品标题和价格,你可以这么做:
doc.Find(".product-item").Each(func(i int, s *goquery.Selection) {
// 在当前产品项的Selection中查找标题和价格
title := s.Find(".product-title a").Text()
href, _ := s.Find(".product-title a").Attr("href")
price := s.Find(".price").Text()
fmt.Printf("产品 %d: 标题=%s, 链接=%s, 价格=%s\n", i+1, title, href, price)
})这里的关键是
s.Find()
product-item
此外,
goquery
First()
Last()
Eq(index)
Selection
Text()
Attr(name)
一个简单的爬虫可能只抓取一两个页面,但如果面对成千上万的页面,甚至需要持续抓取,那么并发和数据存储就成了绕不开的话题。
Go语言天生就是为并发而设计的,
goroutine
channel
基本的并发抓取思路是:
goroutine
channel
goroutine
channel
channel
goroutine
sync.WaitGroup
goroutine
// 这是一个简化的并发抓取框架示例
func worker(id int, urls <-chan string, results chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
for url := range urls {
fmt.Printf("工作者 %d 正在抓取: %s\n", id, url)
// 模拟抓取和解析
// resp, err := http.Get(url)
// doc, err := goquery.NewDocumentFromReader(resp.Body)
// ... 实际的抓取解析逻辑
time.Sleep(time.Millisecond * 500) // 模拟网络延迟和处理时间
results <- fmt.Sprintf("抓取完成: %s", url)
}
}
func main() {
// ... 前面省略的导入和主函数开头
urlsToCrawl := []string{
"http://example.com/page1",
"http://example.com/page2",
"http://example.com/page3",
// ... 更多URL
}
numWorkers := 5 // 设定并发工作者数量
urls := make(chan string, len(urlsToCrawl))
results := make(chan string, len(urlsToCrawl))
var wg sync.WaitGroup
// 启动工作者goroutine
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, urls, results, &wg)
}
// 将URL发送到urls channel
for _, url := range urlsToCrawl {
urls <- url
}
close(urls) // 关闭urls channel,通知worker没有更多URL了
// 等待所有worker完成
wg.Wait()
close(results) // 关闭results channel
// 收集并处理结果
for res := range results {
fmt.Println(res)
}
fmt.Println("所有抓取任务完成。")
}当然,并发抓取还需要考虑速率限制(避免对目标网站造成过大压力,甚至被封禁IP)、错误重试机制、IP代理池等,这些都是让爬虫更健壮的进阶话题。
数据存储方面,取决于你的需求和数据量:
encoding/csv
encoding/json
database/sql
选择哪种存储方式,最终还是取决于你爬取的数据量、数据结构以及后续如何使用这些数据。
以上就是Golang实现简单爬虫怎么做 组合net/http与goquery解析HTML的详细内容,更多请关注php中文网其它相关文章!
HTML怎么学习?HTML怎么入门?HTML在哪学?HTML怎么学才快?不用担心,这里为大家提供了HTML速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号