📚 一、XPath 核心内置函数(基于 XPath 1.0,主流浏览器支持)
- 节点集函数
count(node-set):返回节点数量(如 count(//div) 统计所有 <div> 数量)
position():返回当前节点在上下文中的位置(如 //li[position()=1] 定位第一个 <li>)
last():返回节点集中最后一个节点的位置(如 //tr[last()] 选中表格最后一行)
- 字符串处理函数
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))
- 布尔逻辑函数
not(boolean):逻辑取反(如 //input[not(@disabled)] 排除禁用输入框)
true() / false():返回布尔常量
- 数值运算函数
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 匹配教程推荐(从入门到精通)
- 基础语法与实战
MDN XPath 文档:权威标准函数库说明,覆盖全部核心函数语法及示例 。
《UI自动化测试中XPath定位的终极指南》:
含轴定位(following::、preceding-sibling::)、动态加载处理、跨 iframe 定位等高级技巧。
优化建议:避免冗长路径,优先用 CSS Selector 提升性能。
- 函数深度解析
《【Xpath合集】函数与操作符详解》:
结合影刀 RPA 案例,演示 contains() 匹配动态文本、count() 统计元素数量等场景。
附练习题与 HTML 结构,适合动手实践(如定位多 class 元素、排除隐藏元素)。
- 爬虫专项教程
《Python3爬虫:使用XPath解析数据(27种典型用法)》:
覆盖节点关系(父子、兄弟)、属性提取(@href)、文本捕获(text())等爬虫高频需求。
提供 lxml 库操作代码示例(如修正 HTML 结构、分步定位调试)。
- 避坑指南
《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)。
行为模式:
无鼠标移动轨迹或固定操作间隔。
无滚动行为模拟。
🛡️ 三、降低拦截的反爬措施
- 基础伪装
措施 实现方式 效果
禁用自动化标志 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属性 伪装插件和语言列表
- 请求特征伪装
随机化请求头顺序:重排请求头字段(如Accept、User-Agent)的顺序。
添加伪装的Sec头:
req.Headers["Sec-Fetch-Dest"] = "document"
req.Headers["Sec-Fetch-Mode"] = "navigate"
使用动态User-Agent:轮换不同浏览器UA字符串。
- 行为模式模拟
随机操作延迟:在关键步骤间添加随机休眠:
chromedp.Sleep(time.Duration(500+rand.Intn(1500)) * time.Millisecond)
模拟鼠标轨迹:通过input.DispatchMouseEvent生成人类移动轨迹。
滚动页面:触发懒加载并分散请求密度:
chromedp.Evaluate(`window.scrollBy(0, 500)`, nil)
- 高级指纹对抗
对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)(仅限可信环境)。
Go
package main
import (
"context"
"log"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
"github.com/chromedp/chromedp/kb"
)
func main() {
// 禁用chrome headless
opts := 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 instance
ctx, 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, click
var example string
var text string
var nodes []*cdp.Node
sel := `//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)
}
javascript
常用的几个方法:
chromedp.NewContext() 初始化chromedp的上下文,后续这个页面都使用这个上下文进行操作
chromedp.Run() 运行一个chrome的一系列操作
chromedp.Navigate() 将浏览器导航到某个页面
chromedp.WaitVisible() 等候某个元素可见,再继续执行。
chromedp.Value() 获取某个元素的value值
chromedp.ActionFunc() 再当前页面执行某些自定义函数
chromedp.OuterHTML() 获取元素的outer html
chromedp.InnerHTML() 获取元素的inner html
chromedp.Text() 读取某个元素的text值
chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组
chromedp.Evaluate() 执行某个js,相当于控制台输入js
network.SetExtraHTTPHeaders() 截取请求,额外增加header头
chromedp.SendKeys() 模拟键盘操作,输入字符
chromedp.Screenshot() 根据某个元素截图
page.CaptureScreenshot() 截取整个页面的元素
chromedp.Click() 模拟鼠标点击某个元素
chromedp.Submit() 提交某个表单
chromedp.WaitNotPresent() 等候某个元素不存在