Да, эта ошибка означает, что ваш селектор CSS не находит информацию; at_css
возвращает nil
, а nil.text
недействительно. Вы можете защититься от этого так:
insp = doc.at_css("long example css selector")
self.vehicle_inspections = insp && insp.text
Однако для меня это звучит так, как будто вам «нужны» эти данные. Поскольку вы не предоставили ни HTML-страницу, ни селекторы CSS, я не могу помочь вам создать работающий селектор CSS или XPath.
Что касается будущих вопросов или редактирования этого, обратите внимание, что реальный (урезанный) код настоятельно предпочтительнее, чем размахивание руками, и подробные описания того, как выглядит ваш код. Если вы покажете нам страницу HTML или соответствующий фрагмент и опишите, какой элемент / текст / атрибут вы хотите, мы расскажем вам, как его выбрать.
Обновление : я вижу 6 таблиц на этой странице. Какова таблица «частота аварий / проверок»? Учитывая, что ваш URL включает в себя #Inspections
в конце, я предполагаю, что вы говорите о двух таблицах непосредственно под разделом «Инспекции / Сбои в США». Вот XPath-селекторы, которые соответствуют каждому:
require 'nokogiri'
require 'open-uri'
url = "http://safer.fmcsa.dot.gov/query.asp?searchtype=ANY&query_type=queryCarrierSnapshot&query_param=USDOT&query_string=800585"
doc = Nokogiri::HTML(open(url))
table1 = doc.at_xpath('//table[@summary="Inspections"][preceding::h4[.//a[@name="Inspections"]]]')
table2 = doc.at_xpath('//table[@summary="Crashes"][preceding::h4[.//a[@name="Inspections"]]]')
# Find a row by index (1 is the first row)
vehicle_inspections = table1.at_xpath('.//tr[2]/td').text.to_i
# Find a row by header text
out_of_service_drivers = table1.at_xpath('.//tr[th="Out of Service"]/td[2]').text.to_i
p [ vehicle_inspections, out_of_service_drivers ]
#=> [6, 0]
tow_crashes = table2.at_xpath('.//tr[th="Crashes"]/td[3]').text.to_i
p tow_crashes
#=> 0
Запросы XPath могут выглядеть пугающими. Позвольте мне объяснить, как они работают:
//table[@summary="Inspections"][preceding::h4[.//a[@name="Inspections"]]]
//table
найти <table>
на любом уровне документа
[@summary="Inspections"]
… но только если он имеет атрибут summary
с этим значением
[preceding::h4…]
… и только если вы можете найти элемент <h4>
ранее в документе
[.//a…]
… в частности, <h4>
, который имеет <a>
где-то под ним
[@name="Inspections"]
… и что <a>
должен иметь атрибут name
с этим текстом.
Это на самом деле будет соответствовать двум таблицам (есть еще одна таблица summary="Inspections"
на странице позже), но использование at_xpath
находит первую таблицу соответствия.
.//tr[2]/td
.
Начиная с текущего узла (эта таблица)
//tr[2]
… найдите второго <tr>
, который является потомком на любом уровне
/td
… а затем найдите <td>
детей этого.
Опять же, поскольку мы используем at_xpath
, мы находим первое совпадение <td>
.
.//tr[th="Out of Service"]/td[2]
.
Начиная с текущего узла (эта таблица)
//tr
… найти любого <tr>
, который является потомком на любом уровне
[th="Out of Service]
… но только те <tr>
, у которых есть <th>
ребенок с этим текстом
/td[2]
… а затем найдите второго <td>
детей из них.
В этом случае есть только один <tr>
, который соответствует критериям, и, таким образом, только один <td>
, который соответствует, но мы все еще используем at_xpath
, так что мы получаем этот узел напрямую вместо NodeSet с одним элементом в нем
Целью здесь (и с любым очисткой экрана) является фиксация значимых значений на странице, а не произвольных индексов.
Например, я мог бы написать свой table1
xpath как:
# Find the first table with this summary
table1 = doc.at_xpath('//table[@summary="Inspections"][1]')
... или даже ...
# Find the 20th table on the page
//table[20]
Однако, они хрупкие . Кто-то, добавивший новый раздел на страницу, или код, который добавит или удалит таблицу форматирования, вызовет разрыв этих выражений. Вы хотите искать сильные атрибуты и текст, которые, вероятно, не изменятся, и привязывать свои поиски на основе этого.
XPath vehicle_inspections
также хрупок, полагаясь на порядок строк вместо текста метки для строки.