Documentation has been updated: see help center and changelog in one place.

编写 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选择器编写流程

  1. 使用网页爬虫API抓取目标页面的HTML文档。

  2. 禁用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.titledescription.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"
        }
    ]
}

你可以在此处找到更多解析指令示例: 解析指令示例.

最后更新于

这有帮助吗?