2个不稳定版本
0.2.0 | 2024年6月22日 |
---|---|
0.1.0 | 2024年6月17日 |
#120 in HTTP客户端
1.5MB
2K SLoC
用户代理解析器
此模块实现了browserscope / uap标准,允许从用户代理中提取各种元数据。
browserscope标准是面向数据的,其中regexes.yaml
指定了从用户代理字符串中进行匹配和提取。此库实现了匹配协议并提供各种类型以简化数据集的加载,但它不提供数据本身,以避免对序列化库的依赖或加载限制。
数据集加载
该crate不提供任何预编译的数据文件或专用加载器,但是Regexes
实现了serde::Deserialize
并可以加载一个regexes.yaml
文件或任何格式保留的转换(例如,如果应用程序已经依赖于其中之一,则可能更喜欢从json或cbor加载)
# let ua_str = "";
let f = std::fs::File::open("regexes.yaml")?;
let regexes: ua_parser::Regexes = serde_yaml::from_reader(f)?;
let extractor = ua_parser::Extractor::try_from(regexes)?;
# Ok::<(), Box<dyn std::error::Error>>(())
所有数据描述结构也都是纯旧数据,因此可以直接嵌入到应用程序中,例如通过构建脚本
let parsers = vec![
ua_parser::user_agent::Parser {
regex: "foo".into(),
family_replacement: Some("bar".into()),
..Default::default()
}
];
提取
该crate提供提取单个信息集(用户代理 — 浏览器、操作系统和设备)或在一次调用中提取所有三个的能力。
这三个信息集是独立且不重叠的,因此如果只需要其中一个,完整的提取可能是方便的,但也是不必要的开销,提取器本身也相对昂贵,并且占用内存。
完整提取器
完整的提取器简单地将Regexes
结构体转换为。生成的Extractor
将三个模块级别的提取器作为属性嵌入,并将Extractor::extract
转换为一个包含三个ValueRef
的元组。
单独的提取器
单独的提取器位于user_agent
、os
和device
模块中,这三个模块遵循完全相同的模型
- 一个
Parser
结构体,用于指定单个解析器配置,作为Builder
的输入 - 一个
Builder
,可以将相关解析器push
到其中 - 从
Builder
创建的Extractor
,用户可以从其中extract
一个ValueRef
- 数据提取的结果
ValueRef
,可能从(因此与其相关)Parser
替换数据以及从中提取的用户代理字符串借用 - 为了方便,
ValueRef
的拥有者Value
变体
use ua_parser::os::{Builder, Parser, ValueRef};
let e = Builder::new()
.push(Parser {
regex: r"(Android)[ \-/](\d+)(?:\.(\d+)|)(?:[.\-]([a-z0-9]+)|)".into(),
..Default::default()
})?
.push(Parser {
regex: r"(Android) Donut".into(),
os_v1_replacement: Some("1".into()),
os_v2_replacement: Some("2".into()),
..Default::default()
})?
.push(Parser {
regex: r"(Android) Eclair".into(),
os_v1_replacement: Some("2".into()),
os_v2_replacement: Some("1".into()),
..Default::default()
})?
.push(Parser {
regex: r"(Android) Froyo".into(),
os_v1_replacement: Some("2".into()),
os_v2_replacement: Some("2".into()),
..Default::default()
})?
.push(Parser {
regex: r"(Android) Gingerbread".into(),
os_v1_replacement: Some("2".into()),
os_v2_replacement: Some("3".into()),
..Default::default()
})?
.push(Parser {
regex: r"(Android) Honeycomb".into(),
os_v1_replacement: Some("3".into()),
..Default::default()
})?
.push(Parser {
regex: r"(Android) (\d+);".into(),
..Default::default()
})?
.build()?;
assert_eq!(
e.extract("Android Donut"),
Some(ValueRef {
os: "Android".into(),
major: Some("1".into()),
minor: Some("2".into()),
..Default::default()
}),
);
assert_eq!(
e.extract("Android 15"),
Some(ValueRef { os: "Android".into(), major: Some("15".into()), ..Default::default()}),
);
assert_eq!(
e.extract("ZuneWP7"),
None,
);
# Ok::<(), Box<dyn std::error::Error>>(())
性能
该包尚未进行性能分析或优化,但与uap-cpp(在M1 Pro MBP上测试)相当有竞争力
> ./UaParserBench ../uap-core/regexes.yaml benchmarks/useragents.txt 10
25.13s user 0.07s system 99% cpu 25.279 total
> ./UaParserBench ../uap-core/regexes.yaml ../uap-python/samples/useragents.txt 100
246.49s user 0.47s system 99% cpu 4:07.55 total
> target/release/examples/bench -r 10 ../uap-core/regexes.yaml ../uap-cpp/benchmarks/useragents.txt
10.10s user 0.04s system 99% cpu 10.169 total
> target/release/examples/bench -r 100 ../uap-core/regexes.yaml ../uap-python/samples/useragents.txt
98.46s user 0.04s system 99% cpu 1:38.73 total
依赖项
约4–6MB
约107K SLoC