为什么xpath在html标签之外返回文本?

时间:2016-08-28 01:04:51

标签: html ruby parsing xpath nokogiri

我正在处理一个text<html>标记的文档。当我在body中读取数据时,它也会返回甚至不在html标记中的文本。

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

输出:

"WARC/1.0\nWARC-Type: response\nWARC-Date: 2012-02-11T04:48:01Z\nWARC-TREC-ID: clueweb12-0000tw-13-04988\nWARC-IP-Address: 184.85.26.15\nWARC-Payload-Digest: sha1:PNCB5NNAA766RLLISZ6ODV3FJZBCATKR\nWARC-Target-URI: http://www.allchocolate.com/health/basics/\nWARC-Record-ID: \nContent-Type: application/http; msgtype=response\nContent-Length: 14577\n\n\n\n\n sample document\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>

4 个答案:

答案 0 :(得分:2)

Nokogiri正在尝试将文件内容解析为HTML文档,但它不是有效的文档。它恰好包含一个HTML文档的文本文档。当然Nokogiri不知道这一点,并且它无法自己挑选出那个HTML部分,所以它试图解析整个事情。由于它不是有效的HTML,因此会产生错误。

在解析时,Nokogiri试图尽可能地修复这些错误,但在这种情况下这不起作用,并导致你在这里看到奇怪的输出。

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

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

您需要确保只提供有效的HTML(或尽可能接近有效 - 错误纠正可以解决小问题)。您可能需要以某种方式预处理文件,以便在开头删除额外的文本。

答案 1 :(得分:1)

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

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

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

对评论的回应:

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

顺便说一下,你可以看到Nokogiri如何处理标签之外的文本,解析一个只有文本的文件:hello world,然后打印出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>

这意味着你可以这样得到hello world

require 'nokogiri'

doc = Nokogiri::HTML(open('./html.html'))
title = doc.at_xpath('//title')
puts title.next.text.strip

--output:--
hello world

另一种方法是在使用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

这假设在非html内容和html内容之间总是有一个空行。

答案 2 :(得分:1)

首先,@ 7stud的答案就是你可以在\n\n打破你的文件 在我的文档集合中,在实际的html代码之前并不总是\n\n

因此,使用相同的想法,我提出了另一种解决方法,即使用正则表达式删除html开始标记之前的所有文本,然后将其传递给Nokogiri进行解析。

file = File.read(file_path).to_s
file = file.sub(/.*?(?=<html)/im,"")
page = Nokogiri::HTML(file)

现在工作正常。

答案 3 :(得分:0)

在将内容传递给Nokogiri之前预先处理内容很简单:

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"

诀窍是:

text[/<!DOCTYPE.+/m]

告诉Ruby查看文本并将所有文本从<!DOCTYPE返回到字符串的末尾,这是有效的HTML。