BrowseCrawler — 整树离线爬取
概述
IDE 启动 / HMI 联机时常需要把服务端 AddressSpace 子树拉到本地, 后续节点搜索 / 自动补全无需再发 Browse RPC。BrowseCrawler 用 BFS 队列遍历, 已访问 set 去重 (服务端节点图允许有环), 双重保护 (maxDepth + maxNodes) 防爆。
对应规范段: Part 4 §5.8 (Browse 服务集)。
API
| 成员 | 类别 | 读写 | 说明 |
|---|---|---|---|
| new BrowseCrawler(ua, maxDepth, maxNodes, nodeClassFilter) | 构造 | — | 构造爬取器 |
| CrawlAsync(rootNodeId, progress, ct) | 方法 | 读 | 异步 BFS 爬取, 返回 CrawlResult |
| CrawlResult.AllNodes | 属性 | 读 | 扁平节点列表 (List<CrawlNode>) |
| CrawlResult.ChildrenByParent | 属性 | 读 | 父子关系字典 |
| CrawlResult.Elapsed | 属性 | 读 | 总耗时 |
| CrawlNode.NodeId / BrowseName / DisplayName / NodeClass / Depth | 属性 | 读 | 节点字段 |
构造参数:
| 参数 | 默认 | 说明 |
|---|---|---|
| maxDepth | 10 | 最大递归深度 (防深度爆) |
| maxNodes | 100,000 | 最大节点数 (防总数爆) |
| nodeClassFilter | Unspecified | 仅返回此 NodeClass (BFS 仍遍历, 不写入结果) |
代码示例
using DarraOpcUa_Client;
using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Connect();
// 1) 爬整棵 ObjectsFolder
var crawler = new BrowseCrawler(ua, maxDepth: 8, maxNodes: 50_000);
var result = await crawler.CrawlAsync(
WellKnownNodes.ObjectsFolder,
progress: new Progress<(int count, string nid)>(p =>
Console.Write($"\r已抓 {p.count} 个, 当前 {p.nid}")));
Console.WriteLine($"\n共 {result.AllNodes.Count} 节点, 耗时 {result.Elapsed.TotalSeconds:F1}s");
// 2) 仅抓 Variable 节点 (建本地 Tag 字典)
var c2 = new BrowseCrawler(ua, nodeClassFilter: NodeClass.Variable);
var r2 = await c2.CrawlAsync("ns=2;s=Boilers");
foreach (var n in r2.AllNodes)
Console.WriteLine($" {n.BrowseName} ({n.NodeId})");
// 3) 取消支持 (用户关闭对话框)
using var cts = new CancellationTokenSource();
cancelButton.Click += (_, _) => cts.Cancel();
var r3 = await crawler.CrawlAsync("i=85", null, cts.Token);
Console.WriteLine($"取消时已抓 {r3.AllNodes.Count} 个");
实现细节
- 用
BrowseSafe调底层, 单点失败不中断整棵爬取 - 每抓 100 个节点汇报一次
Progress, 避免 UI 线程被刷屏 - 已访问 set 用
StringComparer.Ordinal严格匹配 NodeId Task.Run跑后台, 不阻塞调用线程
性能基准
| 节点规模 | 耗时 (千兆 LAN) |
|---|---|
| 1,000 | ~0.5s |
| 10,000 | ~5s |
| 50,000 | ~25s |
实际取决于服务端节点图深度 + Browse RPC 单次返回量。
最佳实践
- 限定 root — 不要从
i=84(Root) 开始, 改从i=85(Objects) 或具体子树 - 用
nodeClassFilter— 只关心 Variable 时过滤, 节省内存 - 启动时一次性爬 — 后续编辑器 / 树面板从
AllNodes本地查 - 取消支持 — 给用户"取消加载"按钮,
CrawlAsync会立即返回当前已抓部分 - 不要爬死循环 —
maxDepth/maxNodes必设, 防"无限节点服务端"卡死
跨语言对照
| C# | Python | Java | C++ | Rust | C |
|---|---|---|---|---|---|
| BrowseCrawler | BrowseCrawler | BrowseCrawler | BrowseCrawler | BrowseCrawler | DarraUa_BrowseCrawler_* |
| CrawlAsync | crawl_async | crawlAsync | CrawlAsync | crawl_async | DarraUa_BrowseCrawler_Crawl |