编写 XPath 表达式的技巧
学习为自定义解析器编写有效 XPath 表达式的技巧与最佳实践,以确保准确的数据提取。
被抓取的文档与浏览器加载的文档之间的HTML结构可能不同
在编写HTML元素选择函数时, 请确保针对抓取到的文档工作,而不是针对你浏览器中加载的线上网站版本,因为两者可能不同。造成该问题的主要原因是JavaScript渲染。网站打开时,浏览器会负责加载额外的文档,如CSS样式表和JavaScript脚本,这些都会改变初始HTML文档的结构。在解析抓取到的HTML时,Custom Parser并不会像浏览器那样加载HTML文档(解析器会忽略JavaScript指令),因此解析器和浏览器渲染出的HTML树可能不同。
例如,请看下面的HTML文档:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<h3>This is a product</h3>
<div id="price-container">
<p>This is the price:</p>
</div>
<p>And here is some description</p>
</div>
<script>
const priceContainer = document.querySelector("#price-container");
const priceElement = document.createElement("p");
priceElement.textContent = "123";
priceElement.id = "price"
priceContainer.insertAdjacentElement("beforeend", priceElement);
</script>
</body>
</html>如果你通过浏览器打开该文档,它会显示价格,你可以使用以下XPath表达式选择它 //p[@id="price"]:

现在如果你在浏览器中禁用JavaScript渲染,网站将呈现如下:

同一个 //p[@id="price"] XPath表达式将不再匹配价格,因为它未被渲染。
确保为目标元素编写所有可能的HTML选择器
由于各种原因,同一页面被抓取两次可能会有不同的布局(抓取时使用的User-Agent不同、目标网站进行A/B测试等)。
为解决该问题,我们建议为最初抓取的文档定义 parsing_instructions ,并立即使用同类型页面的多个其他抓取任务结果来测试这些指令。
HTML选择器函数(xpath/xpath_one)支持 选择器回退(selector fallbacks).
推荐的HTML选择器编写流程
使用网页爬虫API抓取目标页面的HTML文档。
禁用JavaScript,并在本地用浏览器打开抓取到的HTML。如果是在 之后 禁用JavaScript再打开HTML,请确保重新加载页面,以便HTML在无JavaScript的情况下重新加载。

如何编写解析指令
假设你有如下页面需要解析:
`<!doctype html>
<html lang="en">
<head></head>
<body>
<style>
.variant {
display: flex;
flex-wrap: nowrap;
}
.variant p {
white-space: nowrap;
margin-right: 20px;
}
</style>
<div>
<h1 id="title">This is a cool product</h1>
<div id="description-container">
<h2>This is a product description</h2>
<ul>
<li class="description-item">Durable</li>
<li class="description-item">好</li>
<li class="description-item">Sweet</li>
<li class="description-item">Spicy</li>
</ul>
</div>
<div id="price-container">
<h2>Variants</h2>
<div id="variants">
<div class="variant">
<p class="color">Red</p>
<p class="price">99.99</p>
</div>
<div class="variant">
<p class="color">Green</p>
<p class="price">87.99</p>
</div>
<div class="variant">
<p class="color">Blue</p>
<p class="price">65.99</p>
</div>
<div class="variant">
<p class="color">Black</p>
<p class="price">99.99</p>
</div>
</div>
</div>
</div>
</body>
</html>
解析产品标题
创建一个新的JSON对象,并为其赋予一个新字段。
你可以按喜好为该字段命名,但有一些例外(用户自定义字段名不能以下划线开头 _ , 例如, "_title").
字段名会显示在解析结果中。
新字段必须保存JSON对象类型的值:
{
"title": {} // 定义要解析的title字段
} 如果你将这些指令提供给Custom Parser,它不会执行任何操作,或者会提示你未提供任何指令。
要将标题实际解析到 title 字段中,你必须在该 title 对象内使用保留的 _fns 属性(其类型始终为array)来定义数据处理流水线:
{
"title": {
"_fns": [] // 为title字段定义数据处理流水线
}
}为了让Custom Parser选中标题文本,你可以使用HTML选择器函数 xpath_one。要在HTML文档上使用该函数,需要把它加入数据处理流水线。该函数以JSON对象形式定义,必须包含 _fn (函数名)以及必须包含 _args (函数参数)字段。查看完整函数定义列表 此处.
{
"title": {
"_fns": [
{
"_fn": "xpath_one",
"_args": ["//h1/text()"]
}
]
}
}上述解析指令应产生以下结果:
{
"title": "This is a cool product"
}解析描述
类似地,在解析指令中,你可以定义另一个字段,用于解析产品描述容器、描述标题和条目。为了让描述的标题和条目嵌套在 description 对象下,指令的结构应如下:
{
"title": {...},
"description": { // 描述容器
"title": {}, // 描述标题
"items": {} // 描述条目
}
}给出的解析指令结构意味着 description.title 和 description.items 将基于 description 元素进行解析。你可以为 description 字段定义一个流水线。在此情况下先这样做,可以简化描述标题的XPath表达式。
{
"title": {...},
"description": {
"_fns": [
{
"_fn": "xpath_one",
"_args": ["//div[@id='description-container']"]
}
], // 在解析`title`和`items`时会使用该流水线的结果。
"title": {},
"items": {}
}
}在该示例中, description._fns 流水线会选中 description-container 这个HTML元素,它将作为解析描述标题和条目的参考点。
要解析剩余的描述字段,为字段添加两个不同的流水线 description.items,以及 description.title:
{
"title": {...},
"description": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
"//div[@id='description-container']"
]
}
],
"title": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
"//h2/text()"
]
}
]
},
"items": {
"_fns": [
{
"_fn": "xpath",
"_args": [
"//li/text()"
]
}
]
}
}
}注意 xpath 函数用于替代 xpath_one 以提取所有匹配该XPath表达式的条目。
这些解析指令会产生以下结果:
{
"title": {...},
"description": {
"title": "This is description about the product",
"items": [
"Durable",
"Nice",
"Sweet",
"Spicy"
]
}
}解析产品变体
以下示例展示了当你希望将信息解析到 product_variants 字段(其中将包含变体对象列表)时的指令结构。在该情况下,变体对象具有 价格 和 color 字段。
{
"title": {...},
"description": {...},
"product_variants": [
{
"price": ...,
"color": ...
},
{
...
},
...
]
}首先选中所有产品变体元素:
{
"title": {...},
"description": {...},
"product_variants": {
"_fns": [
{
"_fn": "xpath",
"_args": ["//div[@class='variant']"]
}
]
}
}要将 product_variants 变为包含JSON对象的列表,你需要使用 _items 迭代器来遍历找到的变体:
{
"title": {...},
"description": {...},
"product_variants": {
"_fns": [
{
"_fn": "xpath",
"_args": ["//div[@class='variant']"]
}
],
"_items": { // 通过此设置,你在指示逐个处理找到的元素
// 这里填写字段指令
}
}
}最后,定义如何解析 color 和 价格 字段的指令:
{
"title": {...},
"description": {...},
"product_variants": {
"_fns": [
{
"_fn": "xpath",
"_args": [
"//div[@class='variant']"
]
}
],
"_items": {
"color": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
// 由于我们使用相对XPath表达式,
// 确保XPath以点(.)开头
".//p[@class='color']/text()"
]
}
]
},
"price": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
".//p[@class='price']/text()"
]
}
]
}
}
}
}在描述完 product_variants 之后,最终指令如下所示:
{
"title": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
"//h1/text()"
]
}
]
},
"description": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
"//div[@id='description-container']"
]
}
],
"title": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
"//h2/text()"
]
}
]
},
"items": {
"_fns": [
{
"_fn": "xpath",
"_args": [
"//li/text()"
]
}
]
}
},
"product_variants": {
"_fns": [
{
"_fn": "xpath",
"_args": [
"//div[@class='variant']"
]
}
],
"_items": {
"color": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
".//p[@class='color']/text()"
]
}
]
},
"price": {
"_fns": [
{
"_fn": "xpath_one",
"_args": [
".//p[@class='price']/text()"
]
}
]
}
}
}
}它将产生如下输出:
{
"title": "This is a cool product",
"description": {
"title": "This is a product description",
"items": [
"Durable",
"Nice",
"Sweet",
"Spicy"
]
},
"product_variants": [
{
"color": "Red",
"price": "99.99"
},
{
"color": "Green",
"price": "87.99"
},
{
"color": "Blue",
"price": "65.99"
},
{
"color": "Black",
"price": "99.99"
}
]
}你可以在此处找到更多解析指令示例: 解析指令示例.
最后更新于
这有帮助吗?

