在本文中,我们将探索如何利用两个强大的Go语言包——goquery
和chromedp
——来爬取网页文章。goquery
是一个轻量级且易于使用的库,它提供了基本的HTTP请求功能,允许我们直接向目标URL发起请求并获取页面内容。相比之下,chromedp
则提供了更为高级的功能,它能够模拟一个完整的Chrome浏览器实例,支持后台运行,并能够执行复杂的用户交互操作,如鼠标点击和页面滚动。
通过结合使用这两个包,我们不仅能够高效地获取网页数据,还能够模拟用户行为,深入挖掘那些仅通过静态请求无法触及的网页内容。
使用 goquery 爬取静态网页内容
goquery
包提供了一种方便的方式来处理HTML文档,它借鉴了jQuery的用法,使得DOM选择和操作变得简单直观。所以说如果想要会爬虫,需要知道什么是css选择器。用它告诉浏览器哪些元素需要应用样式或者在像goquery
这样的库中用来选取特定的DOM元素。以下是几个例子
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Selector Examples</title><style>/* 元素选择器:选择所有的<p>元素 */p {color: blue;}/* 类选择器:选择所有class="highlight"的元素 */.highlight {background-color: yellow;}/* ID选择器:选择id="special"的元素 */#special {font-weight: bold;color: red;}/* 属性选择器:选择所有type="email"的<input>元素 */input[type="email"] {border: 2px solid green;}</style>
</head>
<body><!-- 元素选择器示例:所有<p>元素都将显示为蓝色文本 --><p>This paragraph is styled by an element selector.</p><!-- 类选择器示例:这个段落有一个黄色背景 --><p class="highlight">This paragraph is styled by a class selector.</p><!-- ID选择器示例:这个段落将显示为红色加粗文本 --><p id="special">This paragraph is styled by an ID selector.</p><!-- 属性选择器示例:这个输入框将有绿色边框 --><form><input type="email" placeholder="Enter your email"></form></body>
</html>
在这个HTML文档中:
- 所有的
<p>
元素将显示为蓝色文本,这是通过元素选择器实现的。 - 具有
class="highlight"
的<p>
元素将有一个黄色背景,这是通过类选择器实现的。 - 具有
id="special"
的<p>
元素将显示为红色加粗文本,这是通过ID选择器实现的。 - 类型为
email
的<input>
元素将有绿色边框,这是通过属性选择器实现的。
当你将这个HTML代码保存为文件并在浏览器中打开时,你将看到不同选择器对元素样式的影响。
打开后通过 1、鼠标右键+点击检查。2、点击元素
即可看到该页面的html文件
这里一个小tips就是将鼠标指针置于检查的元素中通过Ctrl+f可以输入css选择器来查找对应的dom对象的位置。
以下是使用goquery
获取网页内容的基本步骤:
- 发送HTTP请求:使用
net/http
包向目标URL发送请求。 - 解析HTML:将响应的HTML内容解析为
goquery
文档对象。 - 选择元素:使用刚才教学的CSS选择器选取所需的HTML元素。
- 提取数据:从选定的元素中提取文本或属性。
package mainimport ("fmt""log""net/http""strings""github.com/PuerkitoBio/goquery"
)func main() {// 发起HTTP GET请求到指定的URLresp, err := http.Get("http://example.com")if err != nil {log.Fatal(err) // 如果请求失败,记录错误并退出}defer resp.Body.Close() // 确保在函数结束时关闭响应体// 使用goquery.NewDocumentFromReader解析HTML文档doc, err := goquery.NewDocumentFromReader(resp.Body)if err != nil {log.Fatal(err) // 如果解析失败,记录错误并退出}// 使用CSS选择器找到class为highlight的元素// 这里假设我们只关心body中的第一个匹配元素content := ""doc.Find("body .highlight").Each(func(i int, s *goquery.Selection) {// .Text() 获取当前选择器的纯文本内容content = s.Text()return // 因为我们只关心第一个匹配元素,所以找到后就返回})// 输出获取到的内容fmt.Println("Extracted content:", content)
}
使用 chromedp 模拟浏览器操作
chromedp
包提供了一种方式来控制Chrome浏览器,执行复杂的用户交互操作。以下是使用chromedp
执行浏览器自动化的基本步骤:
- 创建上下文:
chromedp.NewContext(context.Background())
创建一个新的浏览器上下文,这个上下文用于控制浏览器实例。defer cancel()
确保在函数结束时释放资源。 - 定义动作序列:使用
chromedp.Run(ctx, ...)
定义一系列自动化操作。chromedp.Navigate(
https://example.com)
:导航到指定的URL。chromedp.WaitVisible(
#someElementID, chromedp.ByQuery)
:等待页面上的某个元素变得可见。这里的#someElementID
需要替换成你想要获取内容的元素的实际ID。chromedp.Text(
#someElementID, &text, chromedp.NodeVisible)
:获取该元素的文本内容,并将其存储在变量text
中。
- 错误处理:
if err != nil
检查执行过程中是否出现错误,并在出现错误时终止程序。 - 输出结果:
log.Printf("Element text: %s\n", text)
输出获取到的元素文本内容。
package mainimport ("context""log""time""github.com/chromedp/chromedp"
)func main() {// 创建上下文,用于控制浏览器实例ctx, cancel := chromedp.NewContext(context.Background())defer cancel() // 确保在函数结束时释放资源// 定义要执行的动作序列// 1. 导航到指定的URL// 2. 等待页面上的某个元素变得可见// 3. 获取该元素的文本内容var text string // 用于存储元素的文本内容err := chromedp.Run(ctx,// 导航到 "https://example.com" 网站chromedp.Navigate(`https://example.com`),// 等待ID为 "someElementID" 的元素在页面上变得可见// 这里的 "someElementID" 需要替换成你想要获取内容的元素的实际IDchromedp.WaitVisible(`#someElementID`, chromedp.ByQuery),// 获取ID为 "someElementID" 的元素的文本内容// 并将内容存储在变量 "text" 中chromedp.Text(`#someElementID`, &text, chromedp.NodeVisible),)// 检查执行过程中是否出现错误if err != nil {log.Fatal(err)}// 输出获取到的元素文本内容log.Printf("Element text: %s\n", text)
}
chromedp+goquery
我习惯于将chromedp和goquery接合使用,chromedp可以通过延迟加载可以避免被网页判断是否为真人而拒绝返回内容。
goquery去操作dom树用的比较熟悉。我将其封装成了一个函数,可以借鉴一下:使用 chromedp
库来启动一个无头浏览器会话,导航到指定的 URL,并等待页面上的某个元素变得可见,然后获取该页面的 HTML 内容,并使用 goquery
库来解析 HTML。
// GetDocumentWithSelectWaiting 函数用于获取指定 URL 的页面内容,并等待特定元素可见
func GetDocumentWithSelectWaiting(docSelect string, url string) (*goquery.Document, []byte, error) {// 设置Chrome会话上下文和超时时间ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)defer cancel() // 确保在函数结束时释放资源// 创建Chrome会话的选项opts := append(chromedp.DefaultExecAllocatorOptions[:],// 打开无头模式chromedp.Flag("headless", true),// 设置用户代理,模拟浏览器访问chromedp.Flag("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"),// 设置浏览器窗口大小chromedp.WindowSize(1150, 1000),// 设置语言chromedp.Flag("lang", "en-US"),// 防止监测webdriverchromedp.Flag("enable-automation", false),// 禁用blink特征,减少自动化检测chromedp.Flag("disable-blink-features", "AutomationControlled"),// 忽略证书错误chromedp.Flag("ignore-certificate-errors", true),// 关闭浏览器声音chromedp.Flag("mute-audio", false),// 再次设置浏览器窗口大小,确保覆盖默认值chromedp.WindowSize(1150, 1000),)allocCtx, allocCancel := chromedp.NewExecAllocator(ctx, opts...)defer allocCancel() // 确保在函数结束时释放资源// 用于执行具体的浏览器操作taskCtx, taskCancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))defer taskCancel() // 确保在函数结束时释放资源// 启动浏览器并导航到指定URLerr := chromedp.Run(taskCtx,// 打开该网站chromedp.Navigate(url),// 等待5秒,确保页面加载完成chromedp.Sleep(5*time.Second),)if err != nil {return nil, nil, err}var visible bool// 检查指定元素是否在页面上err = chromedp.Run(taskCtx,chromedp.Evaluate(fmt.Sprintf(`document.querySelector("%s") !== null`, docSelect), &visible),)if err != nil {return nil, nil, err}if !visible {// 如果元素不可见,返回一个自定义错误return nil, nil, errors.New("element not visible")}var adStr string// 等待元素可见并获取页面的HTML内容err = chromedp.Run(taskCtx,chromedp.WaitVisible(docSelect),chromedp.OuterHTML("html", &adStr),)if err != nil {// 判断是否是超时错误if err == context.DeadlineExceeded {log.Println("Operation timed out url:", url)} else {log.Println("Error navigating to URL:", err)}return nil, nil, err}// 将字符串转换为 []byteb := []byte(adStr)// 使用goquery解析HTMLdoc, err := goquery.NewDocumentFromReader(strings.NewReader(adStr))if err != nil {log.Fatalf("Error creating goquery document: %v", err)}defer taskCtx.Done() // 确保在函数结束时释放资源return doc, b, nil
}
- 无头模式:使用无头浏览器,可以在后台运行,不需要显示界面。
- 自定义用户代理:模拟特定浏览器的访问,有助于绕过一些简单的反爬虫机制。
- 窗口大小设置:可以设置浏览器窗口的大小,有时这对于页面渲染是必要的。
- 防检测:通过设置
enable-automation
和disable-blink-features
来减少被网站检测为自动化脚本的风险。 - 错误处理:代码中有详细的错误处理,可以区分超时错误和其他类型的错误。
- 元素可见性检查:在获取元素内容之前检查元素是否可见,确保元素已经加载完成。
- 资源管理:使用
defer
确保上下文和资源在函数结束时被正确释放。
通过结合goquery
和chromedp
,我们可以创建强大的爬虫,它们不仅能够处理静态内容,还能够与JavaScript生成的动态内容交互。这种组合为爬取现代Web应用提供了强大的工具,使得我们可以从复杂的网页中提取有价值的数据。