chromedp 笔记
📚 一、XPath 核心内置函数(基于 XPath 1.0,主流浏览器支持)
1. 节点集函数
count(node-set):返回节点数量(如 count(//div) 统计所有 <div> 数量)
position():返回当前节点在上下文中的位置(如 //li[position()=1] 定位第一个 <li>)
last():返回节点集中最后一个节点的位置(如 //tr[last()] 选中表格最后一行)
2. 字符串处理函数
contains(string1, string2):判断是否包含子串(如 //a[contains(@href, 'example')])
starts-with(string1, string2):检查字符串开头(如 //input[starts-with(@id, 'user_')])
substring(string, start, length):截取子串(如 substring('Hello', 1, 3) → "ell")
normalize-space(string):去除首尾空格并合并连续空格(如 //div[normalize-space(text())='登录'])
concat(string1, string2, ...):拼接字符串(如 concat('ID-', @id))
3. 布尔逻辑函数
not(boolean):逻辑取反(如 //input[not(@disabled)] 排除禁用输入框)
true() / false():返回布尔常量
4. 数值运算函数
sum(node-set):对节点值求和(如 sum(//span[@class='price']) 计算总价)
floor(number) / ceiling(number):向下/向上取整(如 floor(3.7) → 3)
round(number):四舍五入
🧩 二、高阶函数(XPath 2.0+,部分场景需工具支持)
matches(string, pattern):正则表达式匹配(如 //*[matches(@id, '^section\d+$')])
ends-with(string1, string2):检查字符串结尾(需 XPath 2.0 支持)
tokenize(string, separator):按分隔符拆分字符串
🎯 三、XPath 匹配教程推荐(从入门到精通)
1. 基础语法与实战
MDN XPath 文档:权威标准函数库说明,覆盖全部核心函数语法及示例 。
《UI自动化测试中XPath定位的终极指南》:
含轴定位(following::、preceding-sibling::)、动态加载处理、跨 iframe 定位等高级技巧。
优化建议:避免冗长路径,优先用 CSS Selector 提升性能。
2. 函数深度解析
《【Xpath合集】函数与操作符详解》:
结合影刀 RPA 案例,演示 contains() 匹配动态文本、count() 统计元素数量等场景。
附练习题与 HTML 结构,适合动手实践(如定位多 class 元素、排除隐藏元素)。
3. 爬虫专项教程
《Python3爬虫:使用XPath解析数据(27种典型用法)》:
覆盖节点关系(父子、兄弟)、属性提取(@href)、文本捕获(text())等爬虫高频需求。
提供 lxml 库操作代码示例(如修正 HTML 结构、分步定位调试)。
4. 避坑指南
《XPath定位实用技巧》:
解决动态属性、嵌套文本、不可见元素等疑难问题(如 //div[not(contains(@class,'hidden'))])。
调试技巧:浏览器控制台用 $x("//div") 验证表达式。
💎 总结:函数使用场景对比
场景 推荐函数 示例
动态 ID 匹配 starts-with(), contains() //button[starts-with(@id, 'btn_')]
多 class 元素筛选 contains() + 空格边界模拟 //div[contains(concat(' ', @class, ' '), ' btn ')]
文本精准提取 normalize-space() //span[normalize-space()='提交']
表格数据汇总 sum(), position() sum(//td[@class='price'])
条件排除 not() //input[not(@disabled)]
🧩 1. //input[not(@disabled)] 的作用
核心功能:此表达式会匹配所有 不包含 disabled 属性 的 <input> 元素。
是否排除 disabled="true"?
✅ 是的。无论 disabled 属性的值是 true、false 还是空值(如 disabled="" 或 disabled),只要该属性存在,都会被 not(@disabled) 排除。
例如:
<input disabled>(无值)
<input disabled="true">
<input disabled="disabled">
均会被排除。
⚙️ 2. disabled 属性的值是否加引号的区别
加引号与否不影响禁用效果:
HTML 中 disabled 是布尔属性,只要存在即生效,无论值是什么(包括空值或任意字符串)。以下写法效果相同:
<input disabled> <!-- 生效,输入框被禁用 -->
<input disabled="true"> <!-- 生效,输入框被禁用 -->
<input disabled="false"> <!-- 生效!值"false"无效,属性存在即禁用 -->
<input disabled=""> <!-- 生效,空值仍被识别为禁用 -->
readonly属性也类似,都是布尔类型,存在即禁止
对于xpath选择器来说,标签里面的所有都是属性,都通过//tag[@attr="value"]来匹配;或者//tag[contains(@attr, "value")], =等于号需要完全匹配,contains包含即可
chromedp.Click(`//button[contains(@class,"submit") and text()="确认"]`, chromedp.BySearch)
而对于css/js选择器,id和class属于专有属性,有自己的标识,#表示id, .表示class:
chromedp.Click(`#form-container > button.submit`, chromedp.ByQuery)
Chromedp支持的选择器类型
选择器类型 常量名称 使用示例 适用场景
CSS选择器 chromedp.ByQuery chromedp.Click("button.submit", chromedp.ByQuery) 常规元素定位,语法简洁,定位class包含submit的button按钮
XPath chromedp.BySearch chromedp.Click(//button[text()="Submit"], chromedp.BySearch) 复杂层级关系或属性匹配,定位<button>submit</submit>
JS路径 chromedp.ByJSPath chromedp.OuterHTML("document.body", &html, chromedp.ByJSPath) 通过JavaScript表达式定位元素
ID chromedp.ByID chromedp.WaitVisible("#username", chromedp.ByID) 快速定位具有唯一ID的元素
节点ID(底层) chromedp.ByNodeID 需结合DevTools协议,较少直接使用 高级调试或底层操作
注:若不显式指定 By* 常量,Chromedp 默认使用 ByQuery(即CSS选择器)。
除OuterHTML(包含标签本身)和InnerHTML(标签子元素,不含标签本身)外,chromedp提供多种内容提取方式:
chromedp.Text(selector, &result)
功能:提取元素的纯文本(忽略所有HTML标签)
示例:
var text string
chromedp.Text("#title", &text) // 获取id="title"元素的文本
chromedp.Value(selector, &result)
功能:获取表单元素(如<input>、<textarea>)的值
示例:
var inputVal string
chromedp.Value("#search-box", &inputVal)
chromedp.AttributeValue(selector, attrName, &value, &exists)
功能:提取元素的特定属性值(如href、src)
示例:
var link string
chromedp.AttributeValue("a#home", "href", &link, nil)
chromedp.Evaluate(jsExpr, &result)
功能:执行JavaScript表达式并返回结果(支持复杂逻辑)
示例:
var width float64
chromedp.Evaluate(`window.innerWidth`, &width)
🛠️ 三、chromedp中的高级DOM操作
通过Chrome DevTools Protocol(CDP),chromedp支持更底层的DOM操作:
节点查询
chromedp.Query():通过选择器查找单个节点
chromedp.QueryAll():查找所有匹配节点
节点属性操作
chromedp.SetAttributeValue():动态修改属性
chromedp.RemoveAttribute():删除属性
样式获取
chromedp.ComputedStyle():提取元素计算后的CSS样式
节点结构操作
chromedp.RemoveNode():删除DOM节点
chromedp.InsertBefore():在指定位置插入节点
事件监听
chromedp.ListenTarget():监听DOM变更事件(如节点新增/删除)
chromedp.ListenTarget(ctx, func(ev interface{}) {
if ev, ok := ev.(*dom.ChildNodeInserted); ok {
log.Println("新增节点:", ev.Node.NodeName)
}
})
获取cookie
import "github.com/chromedp/cdproto/network"
func getCookies(ctx context.Context) ([]*network.Cookie, error) {
cookies, err := network.GetAllCookies().Do(ctx)
if err != nil {
return nil, err
}
return cookies, nil
}
// 在任务链中调用
var cookies []*network.Cookie
err := chromedp.Run(ctx,
chromedp.Navigate(targetURL),
chromedp.ActionFunc(func(ctx context.Context) error {
cookies, err = getCookies(ctx)
return err
}),
)
Chromedp被识别的风险
Chromedp默认特征可能被服务器拦截:
自动化标识:
navigator.webdriver属性为true。
请求头包含HeadlessChrome标识。
指纹特征:
固定窗口尺寸(无头模式默认800×600)。
缺少常见浏览器插件(如Flash、PDF Viewer)。
行为模式:
无鼠标移动轨迹或固定操作间隔。
无滚动行为模拟。
🛡️ 三、降低拦截的反爬措施
1. 基础伪装
措施 实现方式 效果
禁用自动化标志 chromedp.Flag("disable-blink-features", "AutomationControlled") 隐藏navigator.webdriver
随机化窗口尺寸 chromedp.WindowSize(1920+rand.Intn(200), 1080+rand.Intn(200)) 模拟真实用户分辨率
修改语言指纹 chromedp.Flag("lang", "zh-CN,zh;q=0.9") 避免语言特征异常
注入JS修改Navigator 通过chromedp.Evaluate()覆盖plugins/languages属性 伪装插件和语言列表
2. 请求特征伪装
随机化请求头顺序:重排请求头字段(如Accept、User-Agent)的顺序。
添加伪装的Sec头:
req.Headers["Sec-Fetch-Dest"] = "document"
req.Headers["Sec-Fetch-Mode"] = "navigate"
使用动态User-Agent:轮换不同浏览器UA字符串。
3. 行为模式模拟
随机操作延迟:在关键步骤间添加随机休眠:
chromedp.Sleep(time.Duration(500+rand.Intn(1500)) * time.Millisecond)
模拟鼠标轨迹:通过input.DispatchMouseEvent生成人类移动轨迹。
滚动页面:触发懒加载并分散请求密度:
chromedp.Evaluate(`window.scrollBy(0, 500)`, nil)
4. 高级指纹对抗
对Canvas/WebGL等高级指纹进行动态修改:
chromedp.Evaluate(fmt.Sprintf(`
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) { // VENDOR
return '%s'; // 随机显卡厂商
}
return this.__proto__.getParameter(parameter);
};
`, randomVendor), nil)
err := chromedp.Run(ctx,
// 使用CSS选择器等待搜索框可见
chromedp.WaitVisible(`input[name="q"]`, chromedp.ByQuery),
// 使用XPath点击按钮
chromedp.Click(`//button[contains(@class, "submit-btn")]`, chromedp.BySearch),
// 使用JS路径获取页面标题
chromedp.Evaluate(`document.title`, &title, chromedp.ByJSPath),
)
if err != nil { ... }
func worker(tab *chromedp.Target) {
ctx := tab.CDPContext() // 从标签页创建独立Context
chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.WaitVisible("body"),
chromedp.Text("#title", &text),
)
}
func main() {
// 1. 初始化浏览器
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 创建独立标签页
tab, err := chromedp.NewTarget(ctx, "")
if err != nil {
log.Fatal(err)
}
worker(tab)
}()
}
wg.Wait()
}
///
package main
import (
"context"
"io/ioutil"
"log"
"github.com/chromedp/chromedp"
)
func main() {
// 1. 配置Headless参数
opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.Flag("headless", true),
chromedp.Flag("disable-gpu", true),
chromedp.Flag("no-sandbox", true),
)
allocCtx, allocCancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer allocCancel()
// 2. 创建浏览器上下文
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
// 3. 执行任务链:导航→等待元素→截图
var buf []byte
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.WaitVisible("body", chromedp.ByQuery), // 等待body加载
chromedp.FullScreenshot(&buf, 90), // 全屏截图(质量90%)
)
if err != nil {
log.Fatal(err)
}
// 4. 保存截图
if err := ioutil.WriteFile("screenshot.png", buf, 0644); err != nil {
log.Fatal(err)
}
log.Println("截图保存成功!")
}
///
var htmlContent string
err := chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.WaitReady("body", chromedp.ByQuery), // 等待基础元素
chromedp.OuterHTML("html", &htmlContent), // 获取完整HTML
)
///
err := chromedp.Run(ctx,
chromedp.Navigate("https://example.com/form-page"),
// 1. 等待表单加载后保存其HTML
chromedp.WaitVisible(`#form-container`, chromedp.ByID),
chromedp.OuterHTML(`#form-container`, &formHTML, chromedp.ByID),
// 2. 继续操作:填写并提交表单
chromedp.SendKeys(`#email`, "user@example.com", chromedp.ByID),
chromedp.Click(`#submit-btn`, chromedp.ByID),
// 3. 等待结果出现并获取文本
chromedp.WaitVisible(`.result-message`, chromedp.ByQuery),
chromedp.Text(`.result-message`, &resultText, chromedp.ByQuery),
)
///
chromedp.Run(ctx,
chromedp.ScrollIntoView(`img`, chromedp.ByQuery), // 滚动到图片位置
chromedp.Nodes(`img`, &nodes),
)
// 存储所有图片节点
"github.com/chromedp/cdproto/cdp"
var nodes []*cdp.Node
err := chromedp.Run(ctx,
chromedp.Navigate("https://example-news-site.com/article"),
chromedp.WaitVisible(`body`), // 等待页面加载
// 获取所有<img>标签
chromedp.Nodes(`img`, &nodes, chromedp.ByQueryAll),
)
方法一:手动遍历元组
将连续的奇偶索引分别作为 Name 和 Value:
func parseAttributes(attrs []string) map[string]string {
result := make(map[string]string)
for i := 0; i < len(attrs); i += 2 {
if i+1 < len(attrs) {
result[attrs[i]] = attrs[i+1]
}
}
return result
}
// 示例:获取节点的 class 属性
attrs := parseAttributes(node.Attributes)
class := attrs["class"]
方法二:使用 chromedp.Evaluate 执行 JS 获取
通过注入 JavaScript 直接获取结构化属性:
var attributes map[string]string
chromedp.EvaluateAsDevTools(`
const elem = document.querySelector("...");
const attrs = {};
for (const attr of elem.attributes) {
attrs[attr.name] = attr.value;
}
attrs;
`, &attributes)
安装浏览器:sudo apt-get install google-chrome-stable(Ubuntu/Debian)。
安装依赖库:如 libnss3、libxss1 等(避免启动失败):
sudo apt-get install -y libnss3 libxss1 libasound2 libgbm-dev
apt-get install -y google-chrome-stable
常见问题解决
浏览器启动失败
依赖缺失:运行ldd $(which google-chrome)检查缺失库,通过apt安装。
权限问题:添加chromedp.Flag("no-sandbox", true)(仅限可信环境)。
package mainimport ("context""log""time""github.com/chromedp/cdproto/cdp""github.com/chromedp/chromedp""github.com/chromedp/chromedp/kb"
)func main() {// 禁用chrome headlessopts := append(chromedp.DefaultExecAllocatorOptions[:],chromedp.Flag("headless", false),chromedp.Flag("disable-gpu", true),chromedp.Flag("no-sandbox", true),chromedp.Flag("disable-plugins", true),chromedp.Flag("disable-blink-features", "AutomationControlled"),chromedp.Flag("ignore-certificate-errors", true),//chromedp.Flag("user-agent", userAgent),)allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)defer cancel()// create chrome instancectx, cancel := chromedp.NewContext(allocCtx,chromedp.WithLogf(log.Printf),)defer cancel()// create a timeout//ctx, cancel = context.WithTimeout(ctx, 5*time.Second)//defer cancel()// navigate to a page, wait for an element, clickvar example stringvar text stringvar nodes []*cdp.Nodesel := `//textarea[@aria-label="搜索"]`err := chromedp.Run(ctx,chromedp.Navigate(`https://www.google.com/search`),chromedp.WaitVisible(sel, chromedp.BySearch), // 等待搜索框chromedp.OuterHTML("body", &example),chromedp.Text("#title", &text, chromedp.ByQuery),chromedp.Nodes(`img`, &nodes, chromedp.ByQueryAll),//缓一缓chromedp.Sleep(2*time.Second),chromedp.SendKeys(sel, "chromedp"+kb.Enter, chromedp.BySearch),)if err != nil {log.Fatal(err)}log.Printf("Go's time.After example:\n%s", example)time.Sleep(time.Minute)
}
常用的几个方法:chromedp.NewContext() 初始化chromedp的上下文,后续这个页面都使用这个上下文进行操作chromedp.Run() 运行一个chrome的一系列操作chromedp.Navigate() 将浏览器导航到某个页面chromedp.WaitVisible() 等候某个元素可见,再继续执行。chromedp.Value() 获取某个元素的value值chromedp.ActionFunc() 再当前页面执行某些自定义函数chromedp.OuterHTML() 获取元素的outer htmlchromedp.InnerHTML() 获取元素的inner htmlchromedp.Text() 读取某个元素的text值chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组chromedp.Evaluate() 执行某个js,相当于控制台输入jsnetwork.SetExtraHTTPHeaders() 截取请求,额外增加header头chromedp.SendKeys() 模拟键盘操作,输入字符chromedp.Screenshot() 根据某个元素截图page.CaptureScreenshot() 截取整个页面的元素chromedp.Click() 模拟鼠标点击某个元素chromedp.Submit() 提交某个表单chromedp.WaitNotPresent() 等候某个元素不存在