Очистка сайта с Nokogiri - PullRequest
       25

Очистка сайта с Nokogiri

2 голосов
/ 06 декабря 2011

Я использую Nokogiri для очистки веб-сайта и сталкиваюсь с проблемой, когда пытаюсь извлечь поле из таблицы. Я использую селекторный гаджет , чтобы найти CSS-селектор таблицы. Я собираю данные с правительственного веб-сайта , который содержит подробную информацию об автомобильных перевозчиках .

Метод, который я использую, выглядит следующим образом:

def scrape_database
  url = "http://safer.fmcsa.dot.gov/query.asp?searchtype=ANY&query_type=queryCarrierSnapshot&query_param=USDOT&query_string=#{self.dot}#Inspections"
  doc = Nokogiri::HTML(open(url))
  self.name = doc.at_css("tr:nth-child(4) .queryfield").text
  self.address = doc.at_css("tr:nth-child(6) .queryfield").text
end

Я беру все поля в верхней таблице, используя этот синтаксис, и метод работает нормально, однако у меня возникают проблемы с таблицей аварийности / проверок ниже.

Вот что я использую, чтобы получить эту информацию:

self.vehicle_inspections = doc.at_css("center:nth-child(13) tr:nth-child(2) :nth-child(2)").text

undefined method `text' for nil:NilClass

Если я удаляю text в конце этого, метод запускается, но не получает никакой соответствующей информации (очевидно). Я предполагаю, что это связано со сложным селектором, который я использую для захвата поля, но я не совсем уверен.

Кто-нибудь сталкивался с подобной проблемой, и вы можете дать мне несколько советов?

1 Ответ

4 голосов
/ 07 декабря 2011

Да, эта ошибка означает, что ваш селектор 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 могут выглядеть пугающими. Позвольте мне объяснить, как они работают:

  1. //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 находит первую таблицу соответствия.

  2. .//tr[2]/td

    • . Начиная с текущего узла (эта таблица)
    • //tr[2]… найдите второго <tr>, который является потомком на любом уровне
    • /td… а затем найдите <td> детей этого.

    Опять же, поскольку мы используем at_xpath, мы находим первое совпадение <td>.

  3. .//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 также хрупок, полагаясь на порядок строк вместо текста метки для строки.

...