在本文中,我们将探索如何利用两个强大的Go语言包------goquery
和chromedp
------来爬取网页文章。goquery
是一个轻量级且易于使用的库,它提供了基本的HTTP请求功能,允许我们直接向目标URL发起请求并获取页面内容。相比之下,chromedp
则提供了更为高级的功能,它能够模拟一个完整的Chrome浏览器实例,支持后台运行,并能够执行复杂的用户交互操作,如鼠标点击和页面滚动。
通过结合使用这两个包,我们不仅能够高效地获取网页数据,还能够模拟用户行为,深入挖掘那些仅通过静态请求无法触及的网页内容。
使用 goquery 爬取静态网页内容
goquery
包提供了一种方便的方式来处理HTML文档,它借鉴了jQuery的用法,使得DOM选择和操作变得简单直观。所以说如果想要会爬虫,需要知道什么是css选择器。用它告诉浏览器哪些元素需要应用样式或者在像goquery
这样的库中用来选取特定的DOM元素。以下是几个例子
html
<!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元素。
- 提取数据:从选定的元素中提取文本或属性。
go
package main
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
)
func main() {
// 发起HTTP GET请求到指定的URL
resp, 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)
输出获取到的元素文本内容。
go
package main
import (
"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" 需要替换成你想要获取内容的元素的实际ID
chromedp.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。
go
// 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"),
// 防止监测webdriver
chromedp.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() // 确保在函数结束时释放资源
// 启动浏览器并导航到指定URL
err := 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
}
// 将字符串转换为 []byte
b := []byte(adStr)
// 使用goquery解析HTML
doc, 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应用提供了强大的工具,使得我们可以从复杂的网页中提取有价值的数据。