为什么xpath返回html标记之外的文本?

为什么xpath返回html标记之外的文本?,html,ruby,parsing,xpath,nokogiri,Html,Ruby,Parsing,Xpath,Nokogiri,我正在处理一个在标签外有一些文本的文档。当我读取主体内的数据时,它还返回html标记中甚至没有的文本 page_text = Nokogiri::HTML(open(file_path)).xpath("//body").text p page_text 输出: “WARC/1.0\nWARC类型:响应\nWARC日期:2012-02-11T04:48:01Z\nWARC树ID:clueweb12-0000tw-13-04988\nWARC IP地址:184.85.26.15\nWARC有效负

我正在处理一个在
标签外有一些
文本的文档。当我读取主体内的数据时,它还返回html标记中甚至没有的文本

page_text = Nokogiri::HTML(open(file_path)).xpath("//body").text
p page_text
输出:

“WARC/1.0\nWARC类型:响应\nWARC日期:2012-02-11T04:48:01Z\nWARC树ID:clueweb12-0000tw-13-04988\nWARC IP地址:184.85.26.15\nWARC有效负载摘要:sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR\nWARC目标URI:http://www.allchocolate.com/health/basics/\nWARC记录ID:\n内容类型:应用程序/http;msgtype=response\n内容长度:14577\n\n\n\n示例文档\n\n\n hello world\n\n“

文档:

WARC/1.0
WARC-Type: response
WARC-Date: 2012-02-11T04:48:01Z
WARC-TREC-ID: clueweb12-0000tw-13-04988
WARC-IP-Address: 184.85.26.15
WARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC-Target-URI: http://www.allchocolate.com/health/basics/
WARC-Record-ID: <urn:uuid:ff32c863-5066-4f51-802a-f31d4af074d5>
Content-Type: application/http; msgtype=response
Content-Length: 14577

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>sample document</title>
</head>
<body>
    hello world
</body>
</html>
WARC/1.0
WARC类型:响应
WARC日期:2012-02-11T04:48:01Z
WARC-TREC-ID:clueweb12-0000tw-13-04988
WARC IP地址:184.85.26.15
WARC有效载荷摘要:sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC目标URI:http://www.allchocolate.com/health/basics/
WARC记录ID:
内容类型:应用程序/http;msgtype=response
内容长度:14577
样本文件
你好,世界

很明显,前导文本是一个问题,而不是尾随文本。XML是一种高度结构化的语言,对HTML应用XML解析器至少意味着您必须拥有有效的HTML。如果您没有有效的HTML,那么您可以得到Nokogiri吐出的任何内容

在我看来,Nokogiri将整个内容包装在一个默认根节点中,然后返回其中的所有文本节点,基本上忽略了
//body
xpath。有趣的是,如果您将文本包装在
div
中并搜索xpath
//div
,则不会出现问题,因此可能会提出解决方案

似乎Nokogiri认为
//body
等于根节点。啊!也许Nokogiri使用
作为根节点。不:xpath
/body//body
不起作用

评论回复:

您可以使用正则表达式搜索
标记,然后插入div标记。但是使用简单的正则表达式搜索html将是一个脆弱的解决方案,并且它不会在所有情况下都起作用

顺便说一下,您可以通过解析只有文本:hello world的文档,然后打印出Nokogiri找到的所有节点,来了解Nokogiri如何处理标记之外的文本:

require 'nokogiri'

nodes = Nokogiri::HTML(open('html.html')).xpath('//*')

nodes.each do |node|
  puts node.name
end

--output:--
html
body
p
所以Nokogiri将文本包装在三个标记中

或者,更好的是,您可以解析文档并将其打印为html:

require 'nokogiri'

doc = Nokogiri::HTML(open('./html.html'))
puts doc.to_html

--output:--
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><body><p>WARC/1.0
WARC-Type: response
WARC-Date: 2012-02-11T04:48:01Z
WARC-TREC-ID: clueweb12-0000tw-13-04988
WARC-IP-Address: 184.85.26.15
WARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC-Target-URI: http://www.allchocolate.com/health/basics/
WARC-Record-ID: <uuid:ff32c863-5066-4f51-802a-f31d4af074d5>
Content-Type: application/http; msgtype=response
Content-Length: 14577




    <title>sample document</title>


    hello world


</uuid:ff32c863-5066-4f51-802a-f31d4af074d5></p></body></html>
另一种方法是在使用Nokogiri解析之前除去非html内容:

require 'nokogiri'

infile = File.open('html.html')
non_html = infile.gets(sep="\n\n")
html = infile.gets(nil)  #Slurp the rest of the file

doc = Nokogiri::HTML(html)
puts doc.at_xpath('//body').text.strip

--output:--
hello world
require 'nokogiri'

text = '
WARC/1.0
WARC-Type: response
WARC-Date: 2012-02-11T04:48:01Z
WARC-TREC-ID: clueweb12-0000tw-13-04988
WARC-IP-Address: 184.85.26.15
WARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC-Target-URI: http://www.allchocolate.com/health/basics/
WARC-Record-ID: <urn:uuid:ff32c863-5066-4f51-802a-f31d4af074d5>
Content-Type: application/http; msgtype=response
Content-Length: 14577

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>sample document</title>
</head>
<body>
    hello world
</body>
</html>
'

doc = Nokogiri::HTML(text[/<!DOCTYPE.+/m])
doc.to_html # => "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>sample document</title>\n</head>\n<body>\n    hello world\n</body>\n</html>\n"

这假设非html内容和html内容之间总是有一条空行分隔。

很明显,前导文本是一个问题,而不是尾随文本。XML是一种高度结构化的语言,对html应用XML解析器意味着至少必须有有效的html。如果没有有效的html,那么就可以得到任何Noko吉瑞吐了出来

在我看来,Nokogiri将整个内容包装在一个默认根节点中,然后返回其中的所有文本节点,基本上忽略了
//body
xpath。有趣的是,如果您将文本包装在
div
中并搜索xpath
//div
,则不会出现问题,因此可能会提出解决方案

似乎Nokogiri认为
//body
等于根节点。啊!也许Nokogiri使用
作为根节点。不:xpath
/body//body
不起作用

评论回复:

您可以使用正则表达式搜索
标记,然后插入div标记。但是使用简单的正则表达式搜索html将是一个脆弱的解决方案,并且它不会在所有情况下都起作用

顺便说一下,您可以通过解析只有文本:hello world的文档,然后打印出Nokogiri找到的所有节点,来了解Nokogiri如何处理标记之外的文本:

require 'nokogiri'

nodes = Nokogiri::HTML(open('html.html')).xpath('//*')

nodes.each do |node|
  puts node.name
end

--output:--
html
body
p
所以Nokogiri将文本包装在三个标记中

或者,更好的是,您可以解析文档并将其打印为html:

require 'nokogiri'

doc = Nokogiri::HTML(open('./html.html'))
puts doc.to_html

--output:--
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html><body><p>WARC/1.0
WARC-Type: response
WARC-Date: 2012-02-11T04:48:01Z
WARC-TREC-ID: clueweb12-0000tw-13-04988
WARC-IP-Address: 184.85.26.15
WARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC-Target-URI: http://www.allchocolate.com/health/basics/
WARC-Record-ID: <uuid:ff32c863-5066-4f51-802a-f31d4af074d5>
Content-Type: application/http; msgtype=response
Content-Length: 14577




    <title>sample document</title>


    hello world


</uuid:ff32c863-5066-4f51-802a-f31d4af074d5></p></body></html>
另一种方法是在使用Nokogiri解析之前除去非html内容:

require 'nokogiri'

infile = File.open('html.html')
non_html = infile.gets(sep="\n\n")
html = infile.gets(nil)  #Slurp the rest of the file

doc = Nokogiri::HTML(html)
puts doc.at_xpath('//body').text.strip

--output:--
hello world
require 'nokogiri'

text = '
WARC/1.0
WARC-Type: response
WARC-Date: 2012-02-11T04:48:01Z
WARC-TREC-ID: clueweb12-0000tw-13-04988
WARC-IP-Address: 184.85.26.15
WARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR
WARC-Target-URI: http://www.allchocolate.com/health/basics/
WARC-Record-ID: <urn:uuid:ff32c863-5066-4f51-802a-f31d4af074d5>
Content-Type: application/http; msgtype=response
Content-Length: 14577

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>sample document</title>
</head>
<body>
    hello world
</body>
</html>
'

doc = Nokogiri::HTML(text[/<!DOCTYPE.+/m])
doc.to_html # => "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>sample document</title>\n</head>\n<body>\n    hello world\n</body>\n</html>\n"

这假设非html内容和html内容之间总是有一条空行分隔。

Nokogiri试图将文件内容解析为html文档,但它不是有效的文档。它是一个文本文档,恰好包含一个html文档。当然,Nokogiri不知道这一点,也无法识别这部分本身就是HTML,所以它试图解析整个内容。因为它不是有效的HTML,所以会产生错误

在解析过程中,Nokogiri试图尽可能地修复这些错误,但在本例中这不起作用,并导致您在此处看到的奇怪输出

特别是,当Nokogiri看到HTML之前的文本时,它假定它应该是HTML文档正文的一部分。因此,它在将文本作为此
正文的子项添加之前,创建并向文档中注入
HTML
body
元素

稍后,它会看到实际的
标记,但由于它知道它已经有一个
body
元素,并且只能有一个这样的元素,所以它会忽略它


您需要确保只提供有效的HTML(或者尽可能接近有效的HTML,错误更正可以解决一些小问题)。您可能需要以某种方式对文件进行预处理,以便在开始时删除多余的文本。

Nokogiri正在尝试将文件内容解析为HTML文档,但它不是有效的文档。它是一个文本文档,恰好包含HTML文档。当然,Nokogiri不知道这一点,并且无法将其转换为HTML文档勾选那个本身就是HTML的部分,所以它会尝试解析整个内容。因为它不是有效的HTML,所以会产生错误

在解析过程中,Nokogiri试图尽可能地修复这些错误,但在本例中这不起作用,并导致您在此处看到的奇怪输出

特别是当没有