Nokogiri и xpath разбирают таблицу HTML - PullRequest
1 голос
/ 15 марта 2011

Я могу настроить синтаксический анализ и подключиться к сайту, но когда я запускаю скрипт, он возвращает пустой NodeSet:

require 'rubygems'
require 'mechanize'
require 'nokogiri'
require 'ap'

time = Time.new

url = <<-EOS
'http://www.events.psu.edu/cgi-bin/cal/webevent.cgi?cmd=listday&y=%d&m=%d&d=%d&cat=&sib=1&sort=m,e,t&ws=0&cf=list&set=1&swe=1&sa=1&de=1&tf=0&sb=1&stz=Default&cal=cal299' % [time.year, time.month, time.day]
EOS

page = Nokogiri::HTML(url)

rows =  page.xpath('/html/body/p/table/tbody/tr/td[3]/p/table/tbody/tr[2]')
details = rows.collect do |row|
detail = {}
[
 [:time, 'td[3]/p/text()'],
 [:name, 'td[4]/div/a/b/font/text()'],
 [:location, 'td[4]/div[2]/text()'],
 [:details, 'td[4]/div[4]/text()'],
].collect do |name, xpath|

detail[name] = row.at_xpath(xpath).to_s.strip
end
detail
end
ap details

Возвращаемое значение "[]".

Это HTML-файл перед таблицей /html/body/p/table/tbody/tr/td[3]/p:

<TABLE BORDER=0 CELLPADDING=3 WIDTH="100%">

<!--Begin Event-->
<TR>
  <TD WIDTH="2%">
    <P></P>
  </TD>
  <TD WIDTH="10%">
    <P></P>
  </TD>
  <TD WIDTH="19%">

    <P></P>
  </TD>
  <TD WIDTH="60%">
    <P></P>
  </TD>
</TR>
<TR>
<!--Icon Section-->
  <TD CLASS="listeventbg" VALIGN=top WIDTH="2%">
    <P CLASS="listeventicon">&nbsp;</P>

  </TD>
<!--Date Section-->
  <TD CLASS="listeventbg" VALIGN=top WIDTH="10%">
    <P CLASS="listeventdate">Mar 14</P>
  </TD>
<!--Time Section-->
  <TD CLASS="listeventbg" VALIGN=top WIDTH="19%">
    <P CLASS="listeventtime">8:30 a.m. - 4:30 p.m.<BR>
</P>

  </TD>
<!--Main Event Section-->
  <TD CLASS="listeventbg" VALIGN=top WIDTH="60%">
<div class=listeventtitlelarge><A HREF="http://www.pennstatehershey.org/web/diabetesresearch/home">
<B><font color="#0000CC">2011 Diabetes and Obesity Research Spring Summit</FONT></B></A>
</div>
<div class=listeventtitle><B>Calendar:</B> HHD Seminars<BR>
<B>Posted by:</B> <A HREF="mailto:luk10%40psu.edu">Lauren Kipp</A><BR><B>Location:</B> The Nittany Lion Inn<BR>

</div>
<DIV CLASS="listeventspacer"> </DIV>
<DIV CLASS="listeventdetails">
<B>Details:</B><BR>Registration and Abstract Deadline: February 15, 2011<BR>        <BR>Registration: Please follow the link for more details and access to on line registration. Space is limited, so please register early to ensure your seat at the conference.<BR><BR>The Keynote Speaker for this year’s event is <b>Dr. Robert Sherwin, from the Yale School of Medicine.</b>  Dr. Sherwin is known for his research in the effect of insulin on brain function and immune mechanisms leading to type 1 diabetes.  The topic of his presentation is <i>Pathophysiological Mechanisms in Diabetes, from Laboratory to Bedside.</i><BR><BR>A welcome to the University Park campus will be offered by <b>Eugene Marsh, MD</b>, Senior Associate Dean for the Penn State College of Medicine Regional Medical Campus and Associate Director of the Penn State Hershey Medical Group in State College<BR><BR>Abstract Submission<BR>Please follow the link for formatting details and to register your intent to submit an abstract using the on-line form.<BR>    <BR>You will receive a confirmation immediately upon submission of your on-line form. Subsequently, the final formatted abstract must be sent directly to Continuing Ed by email attachment (see website instructions). Within 48 hours of sending your abstract in final format, you will receive an email confirmation from ContinuingEd@hmc.psu.edu indicating that both your form & the abstract attachment have been received.<BR><BR><i>All abstracts will be considered for poster presentations. A subset of these abstracts will be selected and invited for brief oral presentations during the “Poster Headlines” plenary sessions. To be considered for an oral presentation, please be sure to meet the submission deadline for submission of your final abstract. Prizes will be awarded for the top three posters from by post-doc/fellow/student presenters.</i>

</div>
</TD>
<!--EndEvent-->
......Followed by more of the same format

Я пытаюсь получить название события, время, место и описание события.

Ответы [ 3 ]

4 голосов
/ 15 марта 2011

Это упрощенная версия того, как я это сделаю.

require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'ap'

time = Time.new

url = 'http://www.events.psu.edu/cgi-bin/cal/webevent.cgi?cmd=listday&y=%d&m=%d&d=%d&cat=&sib=1&sort=m,e,t&ws=0&cf=list&set=1&swe=1&sa=1&de=1&tf=0&sb=1&stz=Default&cal=cal299' % [time.year, time.month, time.day]

page = Nokogiri::HTML(open(url))

details = page.search('//tr/td[@class="listeventbg"]/..').map do |row|
  time     = row.at( 'p.listeventtime'         ).text.strip rescue ''
  name     = row.at( 'div.listeventtitlelarge' ).text.strip rescue ''
  location = row.at( 'div.listeventtitle'      ).text.strip rescue ''
  details  = row.at( 'div.listeventdetails'    ).text.strip rescue ''

  {
    :time     => time,
    :name     => name,
    :location => location,
    :details  => details
  }
end

ap details

Вместо того, чтобы полагаться на длинные средства доступа XPath, часто проще разбить поиск. Это перебирает строки, затем для каждой строки выполняет простой поиск ячеек.

Обычно я бы не использовал rescue '', но для быстрого и грязного это нормально. Для производства я бы настроил реальную обработку исключений.

Ваш пример кода требовал Mechanize, но не использовал его, поэтому я удалил его для этого примера. В нём не было способа получить HTML от Nokogiri, поэтому я добавил Open-URI.

Nokogiri позволяет использовать средства доступа CSS и XPath. Много раз CSS приведет к более простому поиску. XPath обладает большей мощью, но это может быть достигнуто ценой сложности. /tr/td[@class="listeventbg"]/.. ищет строки со встроенными ячейками, а затем возвращается к уровню строки.

0 голосов
/ 15 марта 2011

Похоже, что вы анализируете структуру, а не используете классы, приведенные в документе.Я бы использовал CSS-классы, которые вставил создатель документа, например:

page = Nokogiri::HTML(url)
eventdate = page.at_css("p.listeventdate").content
eventtime = page.at_css("p.listeventtime").content
details =   page.at_css("div.listeventdetails").content

Если вы делаете это с большим документом, в котором будет возвращено несколько результатов, используйте css и выполните итерациюрезультаты вместо at_css.Последний находит только один экземпляр тега и класса.

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

0 голосов
/ 15 марта 2011

Вы можете использовать XPath вместо средства доступа CSS следующим образом:

//div[@class='listeventtitlelarge']

, но помните, что это полнотекстовое совпадение, поэтому foobar также будет перехвачен.В любом случае вы можете изменить его с помощью нескольких простых функций регулярного выражения или просто не использовать слишком похожие имена классов.Или вы также можете пойти с " XPATH CSS CLASS MATCHING " от ребят из pivotall.

...