如何在给定确切的HTML标记作为字符串的情况下找到节点(使用Nokogiri)?

时间:2015-01-07 21:14:13

标签: ruby xpath nokogiri

问题

当给定确切的HTML作为字符串时,我需要在给定的网页中搜索特定节点。例如,如果给出:

url = "https://www.wikipedia.org/"
node_to_find = "<title>Wikipedia</title>"

我想“选择”页面上的节点(并最终返回其子节点和兄弟节点)。我在使用Nokogiri文档时遇到了麻烦,以及如何解决这个问题。似乎在大多数情况下,人们希望使用Xpath语法或#css方法来查找满足一组条件的节点。我想使用HTML语法,只是在网页中找到完全匹配。


可能启动解决方案?

如果我创建两个Nokogiri :: HTML :: DocumentFragment对象,它们看起来相似但由于内存ID不同而不匹配。我认为这可能是解决它的先兆?

irb(main):018:0> n = Nokogiri::HTML::DocumentFragment.parse(<title>Wikipedia</title>").child

=> #<Nokogiri::XML::Element:0x47e7e4 name="title" children=[ <Nokogiri::XML::Text:0x47e08c "Wikipedia">]>

irb(main):019:0> n.class

=> Nokogiri::XML::Element

然后我使用完全相同的参数创建第二个。比较它们 - 它返回false:

irb(main):020:0> x = Nokogiri::HTML::DocumentFragment.parse("<title>Wikipedia</title>").child 

=> #<Nokogiri::XML::Element:0x472958 name="title" children=[#<Nokogiri::XML::Text:0x4724a8 "Wikipedia">]>

irb(main):021:0> n == x

=> false

所以我想如果我能以某种方式创建一个可以找到这样的匹配的方法,那么我可以执行该节点的操作。特别是 - 我想找到后代(孩子和下一个兄弟姐妹)。

编辑:我应该提一下,我的代码中有一个方法可以从给定的URL创建Nokogiri :: HTML :: Document对象。所以 - 可以与之比较。

class Page
attr_accessor :url, :node, :doc, :root

def initialize(params = {})
  @url = params.fetch(:url, "").to_s
  @node = params.fetch(:node, "").to_s
  @doc = parse_html(@url)
end

def parse_html(url)
  Nokogiri::HTML(open(url).read)  
end

1 个答案:

答案 0 :(得分:0)

根据评论者@August的建议,您可以使用Node#traverse查看任何节点的字符串表示是否与目标节点的字符串形式匹配。

def find_node(html_document, html_fragment)
  matching_node = nil
  html_document.traverse do |node|
    matching_node = node if node.to_s == html_fragment.to_s
  end
  matching_node
end

当然,这种方法充满了问题,归结为数据的规范表示(您是否关心属性排序?特定的语法项目,如引号?空格?)。

[编辑] 这是将任意HTML元素转换为XPath表达式的原型。它需要一些工作,但基本的想法(匹配任何元素与节点名称,特定属性,可能文本孩子)应该是一个良好的起点。

def html_to_xpath(html_string)
  node = Nokogiri::HTML::fragment(html_string).children.first
  has_more_than_one_child = (node.children.size > 1)
  has_non_text_child = node.children.any? { |x| x.type != Nokogiri::XML::Node::TEXT_NODE }
  if has_more_than_one_child || has_non_text_child
    raise ArgumentError.new('element may only have a single text child')
  end
  xpath = "//#{node.name}"
  node.attributes.each do |_, attr|
    xpath += "[#{attr.name}='#{attr.value}']" # TODO: escaping.
  end
  xpath += "[text()='#{node.children.first.to_s}']" unless node.children.empty?
  xpath
end
html_to_xpath('<title>Wikipedia</title>') # => "//title[text()='Wikipedia']"
html_to_xpath('<div id="foo">Foo</div>')  # => "//div[id='foo'][text()='Foo']"
html_to_xpath('<div><br/></div>') # => ArgumentError: element may only have a single text child

似乎你可以从任何 HTML片段构建一个XPath(例如,不限于那些只有一个文本孩子的片段,根据我上面的原型)但是我会把它留作练习为读者; - )