提问人:rosswgray 提问时间:5/31/2022 更新时间:6/1/2022 访问量:66
如何使用 Nokogiri 捕获同级标签之间的连续元素?
How do I use Nokogiri to capture consecutive elements between sibling tags?
问:
我有类似以下 HTML 的东西,它代表了多项选择题。模式通常是标签(问题),然后是 和 和 四个 s(答案选项)。但是,只是偶尔,一个问题会超过一个标签。<p>
<ol>
<li>
<p>
<ol />
<p class="Question-Stem">Which choice is best?</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
<ol />
<p class="Question-Stem">Which choice is best?</p>
<p class="Indented-Sentence">more text</p>
<p class="Question-Stem">more text</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
<ol />
<p class="Question-Stem">null</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
我正在将问题解析为哈希数组,每个问题一个哈希值
questions_array = []
questions_doc.css('p.Question-Stem').each do |p|
# skip this iteration if the previous tag is not an ol tag
# (i.e. it's not the beginning of the stem)
next if p.previous_element.name != "ol"
question = { :stem => "", :answer_choices => [] }
p.inner_html == "null" ? question[:stem] = "" : question[:stem] = p.inner_html
p.next_element.element_children.each do |child|
question[:answer_choices] << child.inner_html
end
questions_array << question
end
这完全按照我的意愿进行解析,除了问题词干连续三个标签的少数情况。在这些情况下,我希望将所有三个标签的 html 一起推入 .有什么想法如何实现吗?
我已经阅读了如何使用 Nokogiri 解析连续标签? 但没有找到适用于这种情况的解决方案。p
question[:stem]
答:
1赞
Konstantin Strukov
5/31/2022
#1
当文档具有常规结构时,仅使用声明性选择器(css、xpath)处理文档是很好的。
如果没有,并且您必须引入显式控制流(循环、条件等),那么您可能需要执行命令式并逐节点显式遍历文档树。
在这种情况下,它可能非常简单,如下所示:
- 找到第一个问题节点,
- 检查下一个同级
- 如果它是另一个节点 - 将其内容添加到当前正在解析的问题中,
p
- 否则,如果它是节点,则完成当前问题并为其提取答案,
ol
- 否则,如果它是其他内容,请跳过它(例如 nokogiri 识别的换行符的空文本节点)
- 重复 2,而下一个同级存在
该算法的幼稚(且相当脆弱和肮脏)的实现:
def extract_questions(doc)
questions = []
current_node = doc.xpath("//p[contains(@class, 'Question-Stem')]").first
current_question = [current_node&.text].compact
loop do
current_node = current_node&.next_sibling
break if current_node.nil?
case current_node.name
when "p"
current_question << current_node.text
when "ol"
answers = current_node.children.select { |c| c.name == "li" }.map(&:text)
questions << { stem: current_question.join("\n"), answer_choices: answers}
current_question = []
end
end
questions
end
现在
html = <<~HTML
<p class="Question-Stem">Which choice is best?</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
<p class="Question-Stem">Which choice is best?</p>
<p class="Indented-Sentence">more text</p>
<p class="Question-Stem">more text</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
<p class="Question-Stem">null</p>
<ol>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
<li class="Answer-Choice">text</li>
</ol>
HTML
doc = Nokogiri::HTML(html)
extract_questions(doc) # => [{:stem=>"Which choice is best?", :answer_choices=>["text", "text", "text", "text"]},{:stem=>"Which choice is best?\nmore text\nmore text", :answer_choices=>["text", "text", "text", "text"]},{:stem=>"null", :answer_choices=>["text", "text", "text", "text"]}]
评论
0赞
rosswgray
6/1/2022
是的!这很好用。除了有一堆自闭合标签会搞砸循环,所以我需要在里面添加<ol />
case "ol"
next if current_node.children.empty?
评论