如何使用自定义解析器
介绍
如需使用自定义解析器,请在创建作业时提供一组parsing_instructions
。
比如,您想解析在Bing搜索中搜索测试
所得出总结果的数量:
一个作业参数的示例如下:
Copy {
"source" : "bing_search" ,
"query" : "test" ,
"parse" : true ,
"parsing_instructions" : {
"number_of_results" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ ".//span[@class='sb_count']/text()" ]
}
]
}
}
}
第1步. 您必须提供"parse": true
参数。
第2步. 解析指令应在"parsing_instructions"
字段中进行描述。
上述解析说明样本指定的目的是解析抓取文档中的搜索结果数量,并将结果放在number_of_results
字段中。关于如何通过定义“pipeline”来解析字段的指令如下:
Copy "_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ ".//span[@class='sb_count']/text()" ]
}
]
管线是指一个要执行的数据处理函数的列表。这些函数将按照它们在列表中出现的顺序执行,并将前一个函数的输出作为输入。
在上面的示例管线中,使用了xpath_one
函数(可用函数的完整列表 )。它允许您使用XPath表达式和XSLT函数来处理HTML文档。作为一个函数参数,指定可以找到目标元素的确切路径:.//span[@class='sb_count']
。您也可以指示解析器选择在目标元素中发现的text()
。
上述样本作业的解析结果应如下所示:
Copy {
"results" : [
{
"content" : {
"number_of_results" : "About 35.700.000.000 results" ,
"parse_status_code" : 12000
} ,
"created_at" : "2023-03-24 08:27:16" ,
"internal" : [] ,
"job_id" : "7044947765926856705" ,
"page" : 1 ,
"parser_type" : "" ,
"status_code" : 200 ,
"updated_at" : "2023-03-24 08:27:21" ,
"url" : "https://www.bing.com/search?form=QBLH&q=test"
}
]
}
自定义解析器不仅提供了从抓取的HTML中提取文本的功能,而且还可以执行基本的数据处理功能。
例如,前文中描述的解析指令在提取number_of_results
为文本的同时会带有您可能不需要的额外关键词。如果您想得到数字数据类型的指定query=test
结果数量,则您可以重复使用相同的解析指令,并在现有的管线中添加amount_from_string
函数:
Copy {
"source" : "bing_search" ,
"query" : "test" ,
"parse" : true ,
"parsing_instructions" : {
"number_of_results" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ ".//span[@class='sb_count']/text()" ]
} ,
{
"_fn" : "amount_from_string"
}
]
}
}
}
上述样本作业的解析结果应如下所示:
Copy {
"results" : [
{
"content" : {
"number_of_results" : 2190000000 ,
"parse_status_code" : 12000
} ,
"created_at" : "2023-03-24 08:52:21" ,
"internal" : [] ,
"job_id" : "7044954079138679809" ,
"page" : 1 ,
"parser_type" : "" ,
"status_code" : 200 ,
"updated_at" : "2023-03-24 08:52:25" ,
"url" : "https://www.bing.com/search?form=QBLH&q=test"
}
]
}
我们可以看到,它已准确地解析了该文件:
编写XPath表达式的技巧
抓取的文件和浏览器加载的文件之间的HTML结构可能不同
在编写HTML元素选择函数时,要确保使用抓取的文件,而不是浏览器上加载的实时网站版本,因为这些文件可能有所不同。这个问题背后的主要原因在于JavaScript的渲染。当一个网站被打开时,您的浏览器负责加载额外的文件,如CSS样式表和JavaScript脚本,它们可以改变初始HTML文件的结构。当解析抓取的HTML时,自定义解析器并不像浏览器那样加载HTML文档(解析器忽略了JavaScript指令),因此HTML树在解析器和浏览器呈现的内容可能有所不同。
如下所示,请查看下列HTML文档:
Copy <! 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选择器
由于各种原因,同一个页面被抓取两次可能会有不同的布局(抓取时使用的用户代理不同,目标网站做A/B测试等等)。
为了解决这个问题,我们建议为最初抓取的文档定义 parsing_instructions
, 并立即用相同页面类型的其他多个抓取作业结果测试这些指令。
HTML选择器函数支持(xpath
/xpath_one
)选择器回退 。
建议的HTML选择器编写流程
禁用JavaScript,在您的浏览器上本地打开抓取的HTML。如果在打开HTML后 禁用了JavaScript,请务必重新加载页面,以便在没有JavaScript的情况下重新加载HTML。
如何编写解析指令
假设您有以下页面需要解析:
Copy `<! doctype html >
< html lang = "en" >
< head ></ head >
< body >
< style >
.variant {
display : flex ;
flex-wrap : nowrap ;
}
.variant p {
white-space : nowrap ;
margin-right : 20 px ;
}
</ 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" >Nice</ 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对象类型值:
Copy {
"title" : {} // defining a title field to be parsed
}
如果您向自定义解析器提供这些指令,它不会执行任何操作,或者发送一个投诉表示您没有提供任何指令。
为了真正将标题解析到标题段中,则您必须在标题对象内部使用保留的_fns
属性(其始终为数组类型)定义一个数据处理管线:
Copy {
"title" : {
"_fns" : [] // defining data processing pipeline for the title field
}
}
对于由自定义解析器选择标题的文本,您可以利用HTML选择器函数xpath_one
。如需在HTML文档上使用该函数,则应将其添加到数据处理管线中。该函数被定义为一个JSON对象,具有必要的_fn
(函数名称)和必要的_args
(函数参数)字段。在此 查看完整的函数定义列表。
Copy {
"title" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ "//h1/text()" ]
}
]
}
}
上述的解析指令应该得出以下结果:
Copy {
"title" : "This is a cool product"
}
解析描述
同样,在解析指令中,您可以定义另一个字段,需要进行解析的产品描述容器、描述标题和项目。如果标题和项目的描述要嵌套在description
对象下,则指令的结构应该如下:
Copy {
"title" : {...} ,
"description" : { // description container
"title" : {} , // description title
"items" : {} // description items
}
}
给定的解析指令结构意味着description.title
和description.items
将根据description
的元素进行解析。您可以为描述字段定义一个管线。在这种情况下,它会被优先完成,因为它将简化对标题描述的XPath表达式。
Copy {
"title" : {...} ,
"description" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ "//div[@id='description-container']" ]
}
] , // Pipeline result will be used when parsing `title` and `items`.
"title" : {} ,
"items" : {}
}
}
在这个示例中,description._fns
管线将选择description-container
HTML元素,并将被用作解析描述标题和项目的参考点。
为了解析其余的描述字段,须为字段description.items
和description.title
添加两个不同的管线:
Copy {
"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
表达式的所有项目。
解析指令产生的结果如下:
Copy {
"title" : {...} ,
"description" : {
"title" : "This is description about the product" ,
"items" : [
"Durable" ,
"Nice" ,
"Sweet" ,
"Spicy"
]
}
}
解析产品变体
下面的示例中显示了如果您想把信息解析到product_variants
字段中的指令结构,其将包含一个变体对象的列表。在这种情况下,其变体对象拥有价格和颜色字段。
Copy {
"title" : {...} ,
"description" : {...} ,
"product_variants" : [
{
"price" : ... ,
"color" : ...
} ,
{
...
} ,
...
]
}
首先选择所有的产品变体元素:
Copy {
"title" : {...} ,
"description" : {...} ,
"product_variants" : {
"_fns" : [
{
"_fn" : "xpath" ,
"_args" : [ "//div[@class='variant']" ]
}
]
}
}
为了使product_variants
成为一个包含JSON对象的列表,您将不得不使用_items
迭代器来迭代找到变体:
Copy {
"title" : {...} ,
"description" : {...} ,
"product_variants" : {
"_fns" : [
{
"_fn" : "xpath" ,
"_args" : [ "//div[@class='variant']" ]
}
] ,
"_items" : { // with this, you are instructing to process found elements one by one
// field instructions to be described here
}
}
}
最后,定义关于如何解析颜色和价格字段的说明:
Copy {
"title" : {...} ,
"description" : {...} ,
"product_variants" : {
"_fns" : [
{
"_fn" : "xpath" ,
"_args" : [
"//div[@class='variant']"
]
}
] ,
"_items" : {
"color" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [
// As we are using relative XPath expressions,
// make sure XPath starts with a dot (.)
".//p[@class='color']/text()"
]
}
]
} ,
"price" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [
".//p[@class='price']/text()"
]
}
]
}
}
}
}
有了product_variants
的描述,其最终的指令将如下:
Copy {
"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()"
]
}
]
}
}
}
}
其将产生以下输出内容:
Copy {
"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"
}
]
}
您可以在这里找到更多解析指令的示例:解析指令的示例 。
用管道函数的
解析结果函数列表
使用自定义解析器时,如果解析失败会发生什么?
如果自定义解析器无法处理客户定义的解析指令,我们将返回12005状态代码(解析后有警告)。
Copy {
"source" : "bing_search" ,
"query" : "test" ,
"parse" : true ,
"parsing_instructions" : {
"number_of_results" : {
"_fns" : [
{
"_fn" : "xpath_one" ,
"_args" : [ ".//span[@class='sb_count']/text()" ]
} ,
{
"_fn" : "amount_from_string"
}
]
} ,
"number_of_organics" : {
"_fns" : [
{
"_fn" : "xpath" ,
"_args" : [ "//this-will-not-match-anything" ]
} ,
{
"_fn" : "length"
}
]
}
}
}
对于此类结果客户将被收取费用:
Copy {
"results" : [
{
"content" : {
"_warnings" : [
{
"_fn" : "xpath" ,
"_fn_idx" : 0 ,
"_msg" : "XPath expressions did not match any data." ,
"_path" : ".number_of_organics"
}
] ,
"number_of_organics" : null ,
"number_of_results" : 18000000000 ,
"parse_status_code" : 12005
} ,
"created_at" : "2023-03-24 09:46:46" ,
"internal" : [] ,
"job_id" : "7044967772475916289" ,
"page" : 1 ,
"parser_type" : "" ,
"status_code" : 200 ,
"updated_at" : "2023-03-24 09:46:48" ,
"url" : "https://www.bing.com/search?form=QBLH&q=test"
}
]
}
如果自定义解析器在解析操作中遇到异常并中断,则它将返回这些状态代码:12002
、12006
、12007
。您不会因为这些意外的错误而被收费。
状态代码
请查看此处 所述的状态代码。