问题
当给定确切的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
端
答案 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(例如,不限于那些只有一个文本孩子的片段,根据我上面的原型)但是我会把它留作练习为读者; - )