Почему я получаю пустую запись для каждой строки таблицы? - PullRequest
0 голосов
/ 11 января 2012

У меня есть следующий код, благодаря еще одному SO / вопросу:

page = agent.page.search("table tbody tr").each do |row|
  time        = row.css("td:nth-child(1)").text.strip
  source      = row.css("td:nth-child(2)").text.strip
  destination = row.css("td:nth-child(3)").text.strip
  duration    = row.css("td:nth-child(4)").text.strip
  Call.create!(:time => time, :source => source, :destination => destination, :duration => duration)
end

Он работает хорошо, и когда я запускаю задачу rake, он правильно помещает данные в правильную строку таблицы в моем приложении Railsоднако по какой-то причине после успешного создания записи для строки создается пустая запись.

Не могу понять.Судя по внешнему виду кода, он запускает команду create! в каждой строке.

Вы можете увидеть задачу полного рейка на https://gist.github.com/1574942, а другой вопрос, приводящий к этому коду: "Разбирать html в Rails без новой записи каждый раз?".

1 Ответ

1 голос
/ 12 января 2012

На основании комментария:

Я думаю, вы могли бы быть правы, я посмотрел HTML-код на удаленной веб-странице, и они добавляют обтекание каждой строки таблицы, которой присвоен класс.Интересно, есть ли способ заставить скрипт пропустить пустые строки?

Если вы видите структуру HTML вроде:

<table>
  <tbody>
    <tr>
      <tr>
        <td>time</td>
        <td>source</td>
        <td>destination</td>
        <td>duration</td>
      </tr>
    </tr>
  </tbody>
</table>

Тогда это покажет проблему:

require 'nokogiri'
require 'pp'

html = '<table><tbody><tr><tr><td>time</td><td>source</td><td>destination</td><td>duration</td></tr></tr></tbody></table>'
doc = Nokogiri::HTML(html)
page = doc.search("table tbody tr").each do |row|
  time        = row.css("td:nth-child(1)").text.strip
  source      = row.css("td:nth-child(2)").text.strip
  destination = row.css("td:nth-child(3)").text.strip
  duration    = row.css("td:nth-child(4)").text.strip
  hash = {
    :time        => time,
    :source      => source,
    :destination => destination,
    :duration    => duration 
  }
  pp hash
end

Это выводит:

{:time=>"", :source=>"", :destination=>"", :duration=>""}
{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

Причина, по которой вы получаете пустые строки, в том, что HTML-код искажен.Снаружи <tr> не должно быть там.Исправление легко и будет работать с HTML, который также корректен.

Кроме того, внутренний доступ css не совсем корректен, но почему это так неуловимо.Я вернусь к этому.

Чтобы исправить первое, мы добавим условный тест:

page = doc.search("table tbody tr").each do |row|

становится:

page = doc.search("table tbody tr").each do |row|
  next if (!row.at('td'))

После запуска,вывод теперь такой:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

Это действительно все, что вам нужно, чтобы решить проблему, но в коде есть некоторые вещи, которые делают сложные вещи, которые требуют некоторого «сплайна», но сначала вот кодизменить:

С:

time        = row.css("td:nth-child(1)").text.strip
source      = row.css("td:nth-child(2)").text.strip
destination = row.css("td:nth-child(3)").text.strip
duration    = row.css("td:nth-child(4)").text.strip

Изменить на:

time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

Запуск этого кода выводит то, что вы хотите:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

так чтовсе еще неуклюжие.

Вот проблема с вашим исходным кодом:

css является псевдонимом search.Nokogiri возвращает NodeSet для обоих.text вернет пустую строку из пустого NodeSet, которую вы получите за каждый из вызовов row.css("td:nth-child(...)").text.strip, которые смотрели на внешний <tr>.Итак, Нокогири не смог сделать то, что вы хотели молча, потому что это было синтаксически правильно и логически правильно, учитывая то, что вы сказали ему делать;Он просто не оправдал ваших ожиданий.

Использование at или одного из его псевдонимов, например css_at, ищет первый соответствующий метод доступа.Таким образом, теоретически мы могли бы продолжать использовать row.at("td:nth-child(1)").text.strip с несколькими присваиваниями для каждого метода доступа, и это сразу показало бы, что у вас были проблемы с HTML, потому что text взорвалась бы.Но этого недостаточно для дзен.

Вместо этого мы можем перебрать ячейки, возвращенные в NodeSet, используя map, и позволить ему собрать необходимое содержимое ячейки и удалить его, а затем выполнить параллельное присваивание переменным:

time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

Опять же, запустив это:

require 'nokogiri'
require 'pp'

html = '<table><tbody><tr><tr><td>time</td><td>source</td><td>destination</td><td>duration</td></tr></tr></tbody></table>'
doc = Nokogiri::HTML(html)
page = doc.search("table tbody tr").each do |row|
  next if (!row.at('td'))

  time, source, destination, duration = row.search('td').map{ |td| td.text.strip }

  hash = {
    :time        => time,
    :source      => source,
    :destination => destination,
    :duration    => duration 
  }
  pp hash
end

Дает мне:

{:time=>"time",
 :source=>"source",
 :destination=>"destination",
 :duration=>"duration"}

Модифицируйте это в свой код, и вы получите:

page = agent.page.search("table tbody tr").each do |row|
  next if (!row.at('td'))
  time, source, destination, duration = row.search('td').map{ |td| td.text.strip }
  Call.create!(:time => time, :source => source, :destination => destination, :duration => duration)
end

И вам, вероятно, не нужны page =.

...