问题
当给定确切的 HTML 作为字符串时,我需要在给定的网页中搜索特定节点。例如,如果给定:
url = "https://www.wikipedia.org/"
node_to_find = "<title>Wikipedia</title>"
我想"选择"页面上的节点(并最终返回其子节点和同级节点)。我在使用Nokogiri文档时遇到了麻烦,以及如何做到这一点。似乎大多数时候,人们希望使用 Xpath 语法或 #css 方法来查找满足一组条件的节点。我想使用 HTML 语法,只在网页中找到完全匹配。
解决方案的可能开始?
如果我创建两个Nokogiri::HTML::D ocumentFragment对象,它们看起来相似,但由于内存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::D ocument对象。所以 - 这将可以进行比较。
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
结束
正如评论者@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(例如,根据我上面的原型,不限于只有一个文本子片段的人),但我会把它留给读者练习;-)