Nokogiri анализирует вырезанный контент между элементами

В моем случае я прогуглил половину интернет-поиска.

Итак, что мне нужно:

У меня есть структура HTML для разбора следующим образом:

<div class="foo">
  <div class='bar' dir='ltr'>
    <div id='p1' class='par'>
      <p class='sb'>
        <span id='dc_1_1' class='dx'>
          <a href='/bar32560'>1</a>
        </span>
        Neque porro 
        <a href='/xyz' class='mr'>+</a>
        quisquam est 
        <a href='/xyz' class='mr'>+</a>
        qui. 
      </p>
    </div>
    <div id='p2' class='par'>
      <p class='sb'>
        <span id='dc_1_2' class='dx'>
          <a href='/foo12356'>2</a>
        </span>
        dolorem ipsum 
        <a href='/xyz' class='mr'>+</a>
        quia dolor sit amet, 
        <a href='/xyz' class='mr'>+</a>
        consectetur, adipisci velit.
      </p>
    </div>
    <div id='p3' class='par'>
      <p class='sb'>
        <span id='dc_1_3' class='dx'>
          <a href='/foobar4586'>3</a>
        </span>
        Neque porro quisquam 
        <a href='/xyz' class='mr'>+</a>
        est qui dolorem ipsum quia dolor sit 
        <a href='/xyz' class='mr'>+</a>
        amet, t.
        <a href='/xyz' class='mr'>+</a>
        <span id='dc_1_4' class='dx'>
          <a href='/barefoot4135'>4</a>
        </span>
        consectetur, 
        <a href='/xyz' class='mr'>+</a>
        adipisci veli.
        <span id='dc_1_5' class='dx'>
          <a href='/barfoo05123'>5</a>
       </span>
       Neque porro 
       <a href='/xyz' class='mr'>+</a>
       quisquam est
       <a href='/xyz' class='mr'>+</a>
       qui.
     </p>
   </div>
 </div>
</div>

Что мне нужно (НА АНГЛИЙСКОМ ЯЗЫКЕ): очистить каждый абзац, НО мне нужно окончательное очищенное содержимое текстового объекта в форме:

scraped_body 1 => 1 Neque porro quisquam est qui.
scraped_body 2 => 2 dolorem ipsum quia dolor sit amet, consectetur, adipisci velit
scraped_body 3 => 3 Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t.
scraped_body 4 => 4 consectetur, adipisci veli.
scraped_body 5 => 5 Neque porro quisquam est qui.

Код, который я использую сейчас:

page = Nokogiri::HTML(open(url))
x = page.css('.mr').remove
x.xpath("//div[contains(@class, 'par')]").map do |node|
  body = node.text
end

Мой результат такой:

scraped_body 1 => 1 Neque porro quisquam est qui.
scraped_body 2 => 2 dolorem ipsum quia dolor sit amet, consectetur, adipisci velit
scraped_body 3 => 3 Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t. 4 consectetur, adipisci veli. 5 Neque porro quisquam est qui.

Таким образом, это очищает весь текст из класса абзаца div 'par'. Мне нужно очищать весь текст после каждого диапазона с его содержимым - числами. Или вырезать эти div'ы перед каждым диапазоном.

Мне нужно что-то вроде:

SPAN.text + P.text - a.mr

я не знаю... как это сделать

Помогите пожалуйста с этим разбором. Думаю, мне нужно очищать после/перед каждым пролетом.

Пожалуйста, помогите, я перепробовал все, что нашел.


РЕДАКТИРОВАТЬ УТКУ @ Duck1337:

Я использую следующий код:

def verses
    page = Nokogiri::HTML(open(url))
    i=0
    x = page.css("p").text.gsub("+", " ").split.join(" ").gsub(". ", ". HAM").split(" HAM").map do |node|
    i+=1
    body = node
    VerseSource.new(body, book_num, number, i)
  end
end

Мне это нужно, потому что я анализирую большой веб-сайт с текстом. Есть еще несколько методов. Итак, мой окончательный вывод выглядит так:

Saved record with: book: 1, chapter: 1, verse: 1, body: 1 Neque porro quisquam est qui.

Но если у меня есть одно предложение с несколькими предложениями, тогда ваш код разделит его на каждое предложение. Так что это слишком много.

Например:

    <div id='p1' class='par'>
      <p class='sb'>
        <span id='dc_1_3' class='dx'>
          <a href='/foobar4586'>1</a>
        </span>
        Neque porro quisquam. Est qui dolorem
        <a href='/xyz' class='mr'>+</a>
        <span id='dc_1_3' class='dx'>
          <a href='/foobar4586'>2</a>
        </span>
        est qui dolorem ipsum quia dolor sit. 
        <a href='/xyz' class='mr'>+</a>
        amet, t.

Ваш код разделен так:

Saved record with: book: 1, chapter: 1, verse: 1, body: 1 Neque porro quisquam.
Saved record with: book: 1, chapter: 1, verse: 2, body: Est qui dolorem
Saved record with: book: 1, chapter: 1, verse: 3, body: 2 est qui dolorem ipsum quia dolor sit.

Надеюсь, вы, что я имею в виду. Действительно БОЛЬШОЕ спасибо вам за это. Если вы можете изменить это, это будет здорово!


РЕДАКТИРОВАТЬ: @KARDEIZ

Спасибо за ответ! Когда я использую ваш код внутри своего метода: он анализирует действительно забавные вещи.

def verses
  page = Nokogiri::HTML(open(url))
  i=0
  #page.css(".mr").remove
  page.xpath("//div[contains(@class, 'par')]//span").map do |node|
    node.content.strip.tap do |out|
      while nn = node.next
        break if nn.name == 'span'
        out << ' ' << nn.content.strip if nn.text? && !nn.content.strip.empty?
        node = nn
      end
    end
    i+=1
    body = node
    VerseSource.new(body, book_num, number, i)
  end
end

Вывод такой:

Saved record with: book: 1, chapter: 1, verse: 1, body:  <here is last part of last sentence in first paragraph after "+" sign(href) and before last "+"(href)>
Saved record with: book: 1, chapter: 1, verse: 2, body:  <here is last part of last sentence in second paragraph after "+" sign(href) and before last "+"(href)>
Saved record with: book: 1, chapter: 1, verse: 3, body:
Saved record with: book: 1, chapter: 1, verse: 4, body:
Saved record with: book: 1, chapter: 1, verse: 5, body:  <here is last sentence in third paragraph. It is after last "+" in this paragraph and have no more "+" signs(href)

Как видишь, я не знаю, как это устроило такой беспорядок ;] Можешь сделать с этим что-нибудь еще? Большое спасибо!


С Уважением!


person hash4di    schedule 15.07.2014    source источник


Ответы (3)


Попробуйте что-то вроде:

x.xpath("//div[contains(@class, 'par')]//span").map do |node|
  out = node.content.strip
  if following = node.at_xpath('following-sibling::text()')
    out << ' ' << following.content.strip
  end
  out
end

following-sibling::text() XPATH получит первый текстовый узел после диапазона.

РЕДАКТИРОВАТЬ

Я думаю, что это делает то, что вы хотите:

html.xpath("//div[contains(@class, 'par')]//span").map do |node|
  node.content.strip.tap do |out|
    while nn = node.next
      break if nn.name == 'span'
      out << ' ' << nn.content.strip if nn.text? && !nn.content.strip.empty?
      node = nn
    end
  end  
end

выходы:

[
  "1 Neque porro quisquam est qui.",
  "2 dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.",
  "3 Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t.",
  "4 consectetur, adipisci veli.",
  "5 Neque porro quisquam est qui."
]

Это также можно сделать с помощью чистого XPath (см. ось XPath, получить все следующие узлы пока), но это решение проще с точки зрения кодирования.

РЕДАКТИРОВАТЬ 2

Попробуй это:

def verses
  page = Nokogiri::HTML(open(url))
  i=0
  page.xpath("//div[contains(@class, 'par')]//span").map do |node|
    body = node.content.strip.tap do |out|
      while nn = node.next
        break if nn.name == 'span'
        out << ' ' << nn.content.strip if nn.text? && !nn.content.strip.empty?
        node = nn
      end
    end
    i+=1
    VerseSource.new(body, book_num, number, i)
  end
end
person Jacob Brown    schedule 15.07.2014
comment
Спасибо @kardeiz. Извините, но я забыл об очень важной вещи в структуре HTML. В каждом абзаце у меня есть ссылки a_href с классом .mr, поскольку + поет, что такое ссылки на словарь после некоторой части текста - для объяснения этой части. Когда я использую ваше решение, я получаю только первый элемент абзаца после span - я тоже пробовал это раньше. Это не то, что мне нужно, потому что это царапины, например, только в первом абзаце: Neque porro - person hash4di; 16.07.2014
comment
Я отредактировал свой вопрос, чтобы он был более точным и полным. Пожалуйста, посмотрите еще раз. Еще раз спасибо! - person hash4di; 16.07.2014
comment
спасибо за ответ и обновление. Я пробовал ваш код, и у меня были проблемы. Пожалуйста, ознакомьтесь с моим РЕДАКТИРОВАТЬ: KARDEIZ. Надеюсь, что так понятно и легко читается. Спасибо! - person hash4di; 16.07.2014
comment
@ hash4di, я обновил свой ответ. Должен ли body быть узлом или строкой? В моем обновленном ответе body будет установлено строковое значение, упомянутое ранее, например: 1 Neque porro quisquam est qui. - person Jacob Brown; 16.07.2014
comment
ИДЕАЛЬНО!!! Сейчас 23 часа дня по моему часовому поясу, так что я не очень в хорошем состоянии, но это «выглядит нормально». Спасибо!!! пока :) Я проверю это завтра. Ваше здоровье! - person hash4di; 17.07.2014

Я сохранил ваш ввод как "temp.html" на своем рабочем столе.

require 'open-uri'
require 'nokogiri'

$page_html = Nokogiri::HTML.parse(open("/home/user/Desktop/temp.html"))

output = $page_html.css("p").text.gsub("+", " ").split.join(" ").gsub(". ", ". HAM").split(" HAM")

# I found the pattern ". " in every line, so i replaced ". " with (". HAM")
# I did that by using gsub(". ", ". HAM") this means replace ". " with ". HAM"

# then i split up the string with " HAM" so it preserved the "." in each item in the array


output = ["1 Neque porro quisquam est qui.", "2 dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.", "3 Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t.", "4 consectetur, adipisci veli.", "5 Neque porro quisquam est qui."]

РЕДАКТИРОВАТЬ:

 %w[nokogiri open-uri].each{|gem| require gem}     

 $url = "/home/user/Desktop/temp.html"
 def verses
     page = Nokogiri::HTML(open($url))
     i=0
     x = page.css("p").text.gsub("+", " ").split.join(" ").gsub(". ", ".    HAM").split(" HAM") do |node|
         i+=1
         body = node
         VerseSource.new(body, book_num, number, i)
    end
 end
person Duck1337    schedule 15.07.2014
comment
Спасибо @ Duck1337 за ответ. RLY Извините, но я забыл об очень важной части структуры HTML. В каждом разделе абзаца, кроме элемента span, у меня есть href в качестве знака +, что является ссылкой на словарь, объясняющий предыдущую часть текста. Так что паттерн сложнее, потому что этот a_href находится в случайных местах. Я отредактировал свой вопрос, чтобы он был более точным и полным. - person hash4di; 16.07.2014
comment
Я добавил еще один .gsub(+, ), чтобы удалить ссылки из a_href - person Duck1337; 16.07.2014
comment
Спасибо @Duck1337. Но все же у меня проблема. Пожалуйста, просмотрите РЕДАКТИРОВАТЬ в моем вопросе: РЕДАКТИРОВАТЬ УТКУ. Большое спасибо! - person hash4di; 16.07.2014
comment
Попробуйте, x = page.css(p).text.gsub(+, ).split.join( ).gsub(. , . HAM).split( HAM) do |node| вместо x = page.css(p).text.gsub(+, ).split.join( ).gsub(. , . HAM).split( HAM).map do |node| - person Duck1337; 17.07.2014

require 'nokogiri'

your_html =<<END_OF_HTML
<your html here>
END_OF_HTML

doc  = Nokogiri::HTML(your_html)
text_nodes = doc.xpath("//div[contains(@class, 'par')]/p/child::text()")

results = text_nodes.reject do |text_node| 
  text_node.text.match /\A \s+ \z/x  #Eliminate whitespace nodes
end

results.each_with_index do |node, i|
  puts "scraped_body#{i+1} => #{node.text.strip}"
end


--output:--
scraped_body1 => Neque porro quisquam est qui.
scraped_body2 => dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.
scraped_body3 => Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t.
scraped_body4 => consectetur, adipisci veli.
scraped_body5 => Neque porro quisquam est qui.

Ответ для нового HTML:

require 'nokogiri'

html = <<END_OF_HTML
your new html here
END_OF_HTML

html_doc  = Nokogiri::HTML(html)
current_group_number = nil
non_ws_text = []  #non_whitespace_text for each group

html_doc.css("div.par > p").each do |p|   #p's that are direct children of <div class="par">
  p.xpath("./node()").each do |node|  #All Text and Element nodes that are direct children of p tag.
    case node
    when  Nokogiri::XML::Element
      if node.name == 'span'
        node.xpath(".//a").each do |a|  #Step through all the <a> tags inside the <span>
          md = a.text.match(/\A (\d+) \z/xm)  #Check for numbers

          if md  #Then found a number, so it's the start of the next group
            if current_group_number  #then print the results for the current group
              print "scraped_body #{current_group_number} => "
              puts "#{current_group_number} #{non_ws_text.join(' ')}"
              non_ws_text = []
            end
            current_group_number = md[1] #Record the next group number 
            break  #Only look for the first <a> tag containing a number
          end

        end
      end

    when Nokogiri::XML::Text
      text = node.text
      non_ws_text << text.strip if text !~ /\A \s+ \z/xm 
    end

  end
end

#For the last group: 
print "scraped_body #{current_group_number} => "
puts "#{current_group_number} #{non_ws_text.join(' ')}"

--output:--
scraped_body 1 => 1 Neque porro quisquam est qui.
scraped_body 2 => 2 dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.
scraped_body 3 => 3 Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, t.
scraped_body 4 => 4 consectetur, adipisci veli.
scraped_body 5 => 5 Neque porro quisquam est qui.
person 7stud    schedule 15.07.2014
comment
Спасибо @7stud за ответ. RLY Извините, но я забыл об очень важной части структуры HTML. В каждом разделе абзаца, кроме элемента span, у меня есть href в качестве знака +, что является ссылкой на словарь, объясняющий предыдущую часть текста. Так что паттерн сложнее, потому что этот a_href находится в случайных местах. Я отредактировал свой вопрос, чтобы он был более точным и полным. Но когда я использую ваше решение, я ничего не получаю. В вашем REGEXP нет опечатки? - person hash4di; 16.07.2014
comment
@ hash4di, я добавил исправленный ответ в свой пост. - person 7stud; 02.08.2014