# 编写 XPath 表达式的技巧

## 被抓取文档与浏览器加载文档之间的 HTML 结构可能不同 <a href="#html-structure-may-differ-between-scraped-and-browser-loaded-document" id="html-structure-may-differ-between-scraped-and-browser-loaded-document"></a>

在编写 HTML 元素选择函数时， **请务必使用抓取到的文档，而不是浏览器中加载的实时网站版本**，因为这些文档可能不同。造成此问题的主要原因是 JavaScript 渲染。当网站打开时，浏览器负责加载额外的文档，例如 CSS 样式表和 JavaScript 脚本，这些内容可能会改变初始 HTML 文档的结构。在解析抓取到的 HTML 时，自定义解析器不会像浏览器那样加载 HTML 文档（解析器会忽略 JavaScript 指令），因此解析器渲染的 HTML 树与浏览器渲染的结果可能不同。

例如，请看下面这个 HTML 文档：

```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>
        <h3>这是一个产品</h3>
        <div id="price-container">
            <p>这是价格：</p>
        </div>
        <p>这里还有一些描述</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"]`:

<figure><img src="/files/4263a80fbbd4ef6f3fee91b1294e93b793d8cbe6" alt=""><figcaption></figcaption></figure>

现在如果你在浏览器中禁用 JavaScript 渲染，网站将如下所示：

<figure><img src="/files/5453d5f53fc12f8b4f0b8a90eeaee18e5409a25c" alt=""><figcaption></figcaption></figure>

同样的 `//p[@id="price"]` XPath 表达式将不再匹配价格，因为它没有被渲染出来。

## 请务必为目标元素编写所有可能的 HTML 选择器 <a href="#make-sure-to-write-all-possible-html-selectors-for-the-target-element" id="make-sure-to-write-all-possible-html-selectors-for-the-target-element"></a>

由于各种原因，同一页面抓取两次可能会有不同的布局（抓取时使用了不同的 User Agent、目标网站进行了 A/B 测试等）。

为了解决这个问题，我们建议为 `parsing_instructions` 最初抓取到的文档定义这些指令，并立即使用同一页面类型的多个其他抓取结果来测试这些指令。

HTML 选择器函数（`xpath`/`xpath_one`）支持 [**选择器回退**](/products/cn/web-scraper-api/features/custom-parser/writing-instructions-manually/list-of-functions/function-examples.md#xpath).

## 建议的 HTML 选择器编写流程 <a href="#suggested-html-selector-writing-flow" id="suggested-html-selector-writing-flow"></a>

1. 使用 网页爬虫API 抓取目标页面的 HTML 文档。
2. 在浏览器中禁用 JavaScript，并在本地打开抓取到的 HTML。如果 JavaScript 已被禁用 **之后** HTML 打开后，请务必重新加载页面，以便在没有 JavaScript 的情况下重新加载 HTML。
3. [**使用浏览器开发者工具**](https://www.computerhope.com/issues/ch002153.htm).

<figure><img src="/files/d47e2cc4081d7a450a67b7798ab7c0ac48f199cd" alt=""><figcaption></figcaption></figure>

### 如何编写解析指令  <a href="#how-to-write-parsing-instructions-inlineextension" id="how-to-write-parsing-instructions-inlineextension"></a>

假设你要解析下面这个页面：

```html
`<!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">这是一个很棒的产品</h1>
    <div id="description-container">
        <h2>这是产品描述</h2>
        <ul>
            <li class="description-item">耐用</li>
            <li class="description-item">不错</li>
            <li class="description-item">甜美</li>
            <li class="description-item">辛辣</li>
        </ul>
    </div>
    <div id="price-container">
        <h2>变体</h2>
        <div id="variants">
            <div class="variant">
                <p class="color">红色</p>
                <p class="price">99.99</p>
            </div>
            <div class="variant">
                <p class="color">绿色</p>
                <p class="price">87.99</p>
            </div>
            <div class="variant">
                <p class="color">蓝色</p>
                <p class="price">65.99</p>
            </div>
            <div class="variant">
                <p class="color">黑色</p>
                <p class="price">99.99</p>
            </div>
        </div>
    </div>
</div>
</body>
</html>
```

<figure><img src="/files/8381407529fe093572c59b4ccaf82cc6f6ed7763" alt=""><figcaption></figcaption></figure>

### 解析产品标题

创建一个新的 JSON 对象，并为其分配一个新字段。

你可以按自己喜欢的方式命名该字段，但有一些例外（用户定义的字段名不能以下划线开头 `_` ，例如 `"_title"`).&#x20;

该字段名会显示在解析结果中。

新字段必须持有 JSON 对象类型的值：

```json
{
    "title": {}  // 定义要解析的标题字段
} 
```

如果你将这些指令提供给自定义解析器，它可能什么都不会做，或者会提示你没有提供任何指令。

要真正将标题解析到 `title` 字段中，你必须在 `title` 对象内部使用保留的 `_fns` 属性定义一个数据处理管道（该属性始终为数组类型）：

```json
{
    "title": {
        "_fns": []  // 为标题字段定义数据处理管道
    }
}
```

为了让自定义解析器选择标题文本，你可以使用 HTML 选择器函数 `xpath_one`。要在 HTML 文档上使用该函数，应将其添加到数据处理管道中。该函数被定义为一个 JSON 对象，必须包含必需的 `_fn` （函数名）和必需的 `_args` （函数参数）字段。请查看完整的函数定义列表 [**这里**](/products/cn/web-scraper-api/features/custom-parser/writing-instructions-manually/list-of-functions.md).

```json
{
    "title": {
        "_fns": [
            {
                "_fn": "xpath_one",
                "_args": ["//h1/text()"]
            }
        ]
    }
}
```

上面的解析指令应产生如下结果：

```json
{
    "title": "这是一个很棒的产品"
}
```

### 解析描述

同样地，在解析指令中，你可以定义另一个字段，用于解析产品描述容器、描述标题和条目。为了让描述的标题和条目嵌套在 `描述` 对象下，指令结构应如下：

```json
{
    "title": {...},
    "description": { // 描述容器
        "title": {}, // 描述标题
        "items": {} // 描述条目
    } 
}
```

给定的解析指令结构意味着 `description.title` 和 `description.items` 将基于 `描述` 元素进行解析。你可以为 `描述` 字段定义一个管道。在这里，先这样做是因为这将简化描述标题的 XPath 表达式。

```json
{
    "title": {...},
    "description": {
        "_fns": [
            {
                "_fn": "xpath_one",
                "_args": ["//div[@id='description-container']"]
            }
        ],  // 管道结果将在解析 `title` 和 `items` 时使用。
        "title": {},
        "items": {}
    }
}
```

在这个示例中， `description._fns` 管道将选中 `description-container` HTML 元素，它将作为解析描述标题和条目的参考点。

要解析其余的描述字段，请为字段添加两个不同的管道 `description.items`，以及 `description.title`:

```json
{
    "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 表达式匹配的所有条目。

解析指令会产生如下结果：

```json
{
    "title": {...},
    "description": {
        "title": "这是关于该产品的描述",
        "items": [
            "Durable",
            "Nice",
            "Sweet",
            "Spicy"
        ]
    }
}
```

### 解析产品变体

下面的示例展示了如果你想把信息解析到 `product_variants` 字段中时，指令的结构，该字段将包含一个变体对象列表。在这种情况下，变体对象具有 `price` 和 `color` 字段。

```json
{
    "title": {...},
    "description": {...},
    "product_variants": [
        {
            "price": ...,
            "color": ...
        },
        {
            ...
        },
        ...
    ]
}
```

先从选择所有产品变体元素开始：

```json
{
    "title": {...},
    "description": {...},
    "product_variants": {
        "_fns": [
            {
                "_fn": "xpath",
                "_args": ["//div[@class='variant']"]
            }
        ]
    }
}
```

要让 `product_variants` 成为一个包含 JSON 对象的列表，你需要使用 `_items` 迭代器遍历找到的各个变体：

```json
{
    "title": {...},
    "description": {...},
    "product_variants": {
        "_fns": [
            {
                "_fn": "xpath",
                "_args": ["//div[@class='variant']"]
            }
        ],
        "_items": { // 使用这个，你是在指示逐个处理找到的元素
            // 字段指令将在这里描述
        } 
    }
}
```

最后，定义如何解析这些 `color` 和 `price` 字段的指令：

```json
{
    "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` 如上所述，最终指令将如下所示：

```json
{
    "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()"
                        ]
                    }
                ]
            }
        }
    }
}
```

这将产生如下输出：

```json
{
    "title": "这是一个很棒的产品",
    "description": {
        "title": "这是产品描述",
        "items": [
            "Durable",
            "Nice",
            "Sweet",
            "Spicy"
        ]
    },
    "product_variants": [
        {
            "color": "红色",
            "price": "99.99"
        },
        {
            "color": "绿色",
            "price": "87.99"
        },
        {
            "color": "蓝色",
            "price": "65.99"
        },
        {
            "color": "黑色",
            "price": "99.99"
        }
    ]
}
```

你可以在这里找到更多解析指令示例： [**解析指令示例**](/products/cn/web-scraper-api/features/custom-parser/writing-instructions-manually/parsing-instruction-examples.md).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developers.oxylabs.io/products/cn/web-scraper-api/features/custom-parser/writing-instructions-manually/tips-for-writing-xpath-expressions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
