当前位置: 首页 > news >正文

使用 chromedp 高效爬取 Bing 搜索结果

在数据采集领域,搜索引擎结果是重要的信息来源。但传统爬虫面对现代浏览器渲染的页面时,常因 JavaScript 动态加载、跳转链接加密等问题束手无策。本文将详细介绍如何使用 Go 语言的chromedp库,模拟真实浏览器行为爬取 Bing 搜索结果,并破解其跳转链接加密,最终获取真实目标地址。

一、需求背景与技术选型

1.1 爬取搜索引擎结果的痛点

在尝试获取 Bing 搜索结果时,我们会遇到两个核心问题:

  • 动态渲染障碍:Bing 搜索结果页通过 JavaScript 动态加载内容,传统基于http.Client的爬虫无法获取完整 DOM 结构
  • 跳转链接加密:搜索结果中的链接并非真实地址,而是经过 Base64 编码的 Bing 跳转链接(如https://www.bing.com/ck/a?!...&u=...

1.2 为何选择 chromedp?

chromedp是一个基于 Chrome DevTools Protocol(CDP)的 Go 语言库,相比其他方案有明显优势:

  • 真实浏览器环境:直接控制 Chrome/Chromium 浏览器,完美处理 JS 动态渲染
  • 无需额外依赖:无需安装 Selenium 或 ChromeDriver,简化部署流程
  • 强类型 API:基于 Go 语言的类型安全特性,减少运行时错误
  • 灵活的上下文控制:支持页面导航、元素等待、JS 执行等完整浏览器操作

二、环境准备

2.1 基础依赖

  • Go 1.18+(推荐使用最新稳定版)
  • Chrome/Chromium 浏览器(确保版本与 chromedp 兼容)
  • 依赖库安装:
go get github.com/chromedp/chromedp

2.2 核心配置说明

在代码初始化阶段,我们需要配置浏览器运行参数:

opts := append(chromedp.DefaultExecAllocatorOptions[:],chromedp.Flag("ignore-certificate-errors", true), // 忽略证书错误chromedp.Flag("headless", true),                  // 无头模式(生产环境推荐)
)
  • 无头模式(headless):不显示浏览器窗口,适合服务器环境运行,设为false可用于调试
  • 证书错误忽略:避免因 HTTPS 证书问题导致的爬取失败

三、核心功能实现

3.1 破解 Bing 跳转链接:unwrapBingURL 函数

Bing 搜索结果中的链接格式通常为:
https://www.bing.com/ck/a?!...&u=a1aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbQ==
其中u参数即为加密后的真实地址,解密步骤如下:

  1. 解析 URL 参数:提取u参数值
  2. 去除前缀标识:Bing 会在 Base64 字符串前添加a1前缀,需先移除
  3. Base64 URL 解码:使用 URL 安全的 Base64 解码算法还原真实地址

实现代码:

func unwrapBingURL(bing string) (real string, err error) {u, err := url.Parse(bing)if err != nil {return "", err}// 提取u参数(加密的真实地址)enc := u.Query().Get("u")if enc == "" {return bing, nil // 非跳转链接,直接返回}// 移除Bing添加的a1前缀if strings.HasPrefix(enc, "a1") {enc = enc[2:]}// Base64 URL解码dst := make([]byte, base64.URLEncoding.DecodedLen(len(enc)))n, err := base64.URLEncoding.Decode(dst, []byte(enc))if err != nil {return "", err}return string(dst[:n]), nil
}

3.2 分页爬取与去重机制

为获取更多搜索结果并避免重复,我们设计了分页爬取与去重逻辑:

3.2.1 分页控制

Bing 通过first参数控制分页(first=1为第 1 页,first=11为第 2 页,以此类推),实现代码:

pageSize := 10 // 每页预期结果数
for pageIndex := 0; len(unique) < maxResults; pageIndex++ {start := pageIndex*pageSize + 1searchURL := fmt.Sprintf("https://www.bing.com/search?q=%s&first=%d",url.QueryEscape(keyword), start)// 爬取当前页...
}
3.2.2 结果去重

使用map存储已获取的真实链接,确保最终结果唯一:

seen := make(map[string]bool)   // 记录已发现的链接
var unique []string             // 存储去重后的真实链接// 去重逻辑
if !seen[real] {seen[real] = trueunique = append(unique, real)newCount++
}

3.3 页面元素提取

通过chromedp.Evaluate执行 JavaScript 代码,提取搜索结果中的链接:

var rawLinks []string
if err := chromedp.Run(ctx,chromedp.Navigate(searchURL),                  // 导航到搜索页面chromedp.WaitVisible(`#b_content`, chromedp.ByID), // 等待结果区域加载完成chromedp.Sleep(2*time.Second),                 // 额外等待,确保JS渲染完成// 提取h2标题下的链接chromedp.Evaluate(`Array.from(document.querySelectorAll('#b_content h2 a')).map(a => a.href)`, &rawLinks),
); err != nil {log.Printf("第 %d 页加载失败: %v", pageIndex+1, err)break
}
  • WaitVisible:等待结果容器#b_content可见,避免过早提取导致数据缺失
  • Sleep 延迟:应对 Bing 的动态加载机制,确保结果完全渲染
  • JS 选择器:通过#b_content h2 a精准定位搜索结果链接

四、完整代码解析

4.1 代码结构总览

整个程序分为三个核心部分:

  1. unwrapBingURL:解密 Bing 跳转链接
  2. main 函数初始化:配置 chromedp 上下文、初始化去重容器
  3. 分页爬取循环:控制分页、提取链接、去重存储

4.2 关键细节说明

  • 上下文管理:使用defer cancel()确保资源释放,避免内存泄漏
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
  • 异常处理

    • 捕获页面加载错误,避免程序崩溃
    • 过滤无效链接(空链接、Microsoft 官方链接)
    • 处理 Base64 解码失败的情况
  • 终止条件

    • 已获取足够数量的结果(达到maxResults
    • 当前页无新结果(newCount == 0),说明已爬取所有结果

五、完整代码

package mainimport ("context""encoding/base64""fmt""log""net/url""strings""time""github.com/chromedp/chromedp"
)// 从 Bing 跳转链中提取真实地址
func unwrapBingURL(bing string) (real string, err error) {u, err := url.Parse(bing)if err != nil {return "", err}// 取 u= 参数enc := u.Query().Get("u")if enc == "" {return bing, nil // 不是跳转链,原样返回}// 去掉前缀if strings.HasPrefix(enc, "a1") {enc = enc[2:]}// base64 解码dst := make([]byte, base64.URLEncoding.DecodedLen(len(enc)))n, err := base64.URLEncoding.Decode(dst, []byte(enc))if err != nil {return "", err}return string(dst[:n]), nil
}func main() {keyword := "印度大幅下调消费税应对经济压力"maxResults := 100 // 你想拿多少条opts := append(chromedp.DefaultExecAllocatorOptions[:],chromedp.Flag("ignore-certificate-errors", true),chromedp.Flag("headless", true), // 调试可改 false)allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)defer cancel()ctx, cancel := chromedp.NewContext(allocCtx)defer cancel()seen := make(map[string]bool)var unique []stringpageSize := 10for pageIndex := 0; len(unique) < maxResults; pageIndex++ {start := pageIndex*pageSize + 1searchURL := fmt.Sprintf("https://www.bing.com/search?q=%s&first=%d",url.QueryEscape(keyword), start)var rawLinks []stringif err := chromedp.Run(ctx,chromedp.Navigate(searchURL),chromedp.WaitVisible(`#b_content`, chromedp.ByID),chromedp.Sleep(2*time.Second),chromedp.Evaluate(`Array.from(document.querySelectorAll('#b_content h2 a')).map(a => a.href)`, &rawLinks),); err != nil {log.Printf("第 %d 页加载失败: %v", pageIndex+1, err)break}newCount := 0for _, l := range rawLinks {if l == "" || strings.Contains(l, "go.microsoft.com") {continue}real, err := unwrapBingURL(l)if err != nil || real == "" {continue}if !seen[real] {seen[real] = trueunique = append(unique, real)newCount++}if len(unique) >= maxResults {break}}if newCount == 0 { // 没新结果就停break}}fmt.Printf("共拿到 %d 条真实链接:\n", len(unique))for i, u := range unique {fmt.Printf("%2d. %s\n", i+1, u)}
}

六、优化建议与注意事项

6.1 性能优化

  1. 调整 Sleep 时间:2 秒等待可能过长,可根据网络情况调整为 1-1.5 秒
  2. 并发爬取:在合规前提下,可使用chromedp的多上下文特性实现并发爬取
  3. 结果缓存:将已爬取的链接存储到本地文件,避免重复爬取

6.2 反爬应对

  1. 添加随机延迟:在分页请求之间添加随机延迟(1-3 秒),模拟人类操作
  2. 设置 User-Agent:在 chromedp 选项中添加真实的 User-Agent,避免被识别为爬虫
chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
  1. IP 轮换:若爬取量大,建议使用代理 IP 轮换,避免 IP 被封禁

6.3 合规性提醒

  • 遵守 Robots 协议:查看 Bing 的/robots.txt文件,了解爬取限制
  • 控制爬取频率:避免给服务器造成过大压力
  • 尊重版权:爬取的结果仅用于合法用途,不得侵犯他人权益
http://www.xdnf.cn/news/1466425.html

相关文章:

  • 安装Codex(需要用npm)
  • Chrome 插件开发入门指南:从基础到实践
  • 达梦数据守护集群监视器详解与应用指南
  • vsan高可用:确保可访问性、全部数据迁移,两种类型权衡
  • 软件启动时加配置文件 vs 不加配置文件
  • Go 1.25.1基本包
  • 凌力尔特(LINEAR)滤波器LTC1068的二阶滤波器模块设计
  • STM32 USBx Device HID standalone 移植示例 LAT1466
  • 全球企业内容管理ECM市场规模增长趋势与未来机遇解析
  • (4)什么时候引入Seata‘‘
  • 黄金上门回收小程序开发
  • 多路转接介绍及代码实现
  • Rust 基础语法
  • 设计模式笔记
  • 从技术选型到现场配置:DDC 楼宇自控系统全流程落地方案(2025 版)
  • 织信低代码:用更聪明的方式,把想法变成现实!
  • 多语言Qt Linguist
  • 职场礼仪实训室:健康管理专业人才培养的核心支柱与创新实践
  • Springboot实现国际化(MessageSource)
  • AI Compass前沿速览:Kimi K2、InfinityHuman-AI数字人、3D-AI桌面伴侣、叠叠社–AI虚拟陪伴
  • 查询语言的进化:SQL之后,为什么是GQL?数据世界正在改变
  • 生态 | 华院计算与深至科技达成战略合作,携手推动AI+医学影像算法升级迭代
  • 代码随想录70期day3
  • 算法(keep learning)
  • 外包干了3年,技术退步太明显了。。。。。
  • 计算机网络1 第一章 概述——以寄邮件比喻整个流程
  • threeJS 实现开花的效果
  • 概率论第三讲——多维随机变量及其分布
  • 要搞清楚你为什么上班
  • 大型语言模型SEO(LLM SEO)完全手册:驾驭搜索新范式