Ваш код может быть намного проще и более устойчивым:
Медитируйте на это:
require 'nokogiri'
require 'httparty'
URL = 'https://github.com/public-apis/public-apis'
FIELDS = %w[name description auth https cors category url]
doc = Nokogiri::HTML(HTTParty.get(URL))
category = doc.at('article li a').text
rows = doc.at('article table').search('tr')[1..-1].map { |tr|
values = tr.search('td').map(&:text)
link = tr.at('a')['href']
Hash[
FIELDS.zip(values + [category, link])
]
}
Что приводит к:
puts rows
# >> {"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "category"=>"Animals", "url"=>"https://alexwohlbruck.github.io/cat-facts/"}
# >> {"name"=>"Cats", "description"=>"Pictures of cats from Tumblr", "auth"=>"apiKey", "https"=>"Yes", "cors"=>"Unknown", "category"=>"Animals", "url"=>"https://docs.thecatapi.com/"}
# >> {"name"=>"Dogs", "description"=>"Based on the Stanford Dogs Dataset", "auth"=>"No", "https"=>"Yes", "cors"=>"Yes", "category"=>"Animals", "url"=>"https://dog.ceo/dog-api/"}
# >> {"name"=>"HTTPCat", "description"=>"Cat for every HTTP Status", "auth"=>"No", "https"=>"Yes", "cors"=>"Unknown", "category"=>"Animals", "url"=>"https://http.cat/"}
# >> {"name"=>"IUCN", "description"=>"IUCN Red List of Threatened Species", "auth"=>"apiKey", "https"=>"No", "cors"=>"Unknown", "category"=>"Animals", "url"=>"http://apiv3.iucnredlist.org/api/v3/docs"}
# >> {"name"=>"Movebank", "description"=>"Movement and Migration data of animals", "auth"=>"No", "https"=>"Yes", "cors"=>"Unknown", "category"=>"Animals", "url"=>"https://github.com/movebank/movebank-api-doc"}
# >> {"name"=>"Petfinder", "description"=>"Adoption", "auth"=>"OAuth", "https"=>"Yes", "cors"=>"Yes", "category"=>"Animals", "url"=>"https://www.petfinder.com/developers/v2/docs/"}
# >> {"name"=>"PlaceGOAT", "description"=>"Placeholder goat images", "auth"=>"No", "https"=>"Yes", "cors"=>"Unknown", "category"=>"Animals", "url"=>"https://placegoat.com/"}
# >> {"name"=>"RandomCat", "description"=>"Random pictures of cats", "auth"=>"No", "https"=>"Yes", "cors"=>"Yes", "category"=>"Animals", "url"=>"https://aws.random.cat/meow"}
# >> {"name"=>"RandomDog", "description"=>"Random pictures of dogs", "auth"=>"No", "https"=>"Yes", "cors"=>"Yes", "category"=>"Animals", "url"=>"https://random.dog/woof.json"}
# >> {"name"=>"RandomFox", "description"=>"Random pictures of foxes", "auth"=>"No", "https"=>"Yes", "cors"=>"No", "category"=>"Animals", "url"=>"https://randomfox.ca/floof/"}
# >> {"name"=>"RescueGroups", "description"=>"Adoption", "auth"=>"No", "https"=>"Yes", "cors"=>"Unknown", "category"=>"Animals", "url"=>"https://userguide.rescuegroups.org/display/APIDG/API+Developers+Guide+Home"}
# >> {"name"=>"Shibe.Online", "description"=>"Random pictures of Shibu Inu, cats or birds", "auth"=>"No", "https"=>"Yes", "cors"=>"Yes", "category"=>"Animals", "url"=>"http://shibe.online/"}
Проблемы с вашим кодом являются:
Использование search('some selector')[0]
такое же, как at('some selector')
только вторая чище, что приводит к уменьшению визуального шума.
Существуют и другие, более тонкие различия в том, что возвращает search
по сравнению с at
, который описан в документации. Я настоятельно рекомендую прочитать и поэкспериментировать с их примерами, так как вы знаете, какой из них использовать, когда вы можете избавить вас от головной боли.
Полагаться на абсолютные селекторы XPath: абсолютный селектор очень равен agile. Любое изменение в HTML будет иметь высокую вероятность взлома. Вместо этого найдите полезные узлы для проверки уникальности и позвольте парсеру найти их.
С помощью селектора CSS 'article li a'
перебирает все узлы, пока не найдет узел «article», заглядывает в него для ребенка "ли" и после "а". Вы можете сделать то же самое с XPath, но визуально шумно. Я большой поклонник того, чтобы мой код был как можно более легким для чтения и понимания.
Аналогично, at('article table')
находит первую таблицу в узле "article", затем search('tr')
находит вложенные строки в только этот стол.
Поскольку вы хотите пропустить заголовок таблицы, [1..-1]
нарезает NodeSet и пропускает первую строку.
map
облегчает задачу чтобы построить структуру:
rows = doc.at('article table').search('tr')[1..-1].map { |tr|
назначает поля для rows
за один проход через эти l oop строк.
values
назначается с текстом NodeSet каждого текста узла "td".
Вы можете легко построить га sh, используя Ha sh '[]
конструктор и передача массива пар ключ / значение.
FIELDS.zip(values + [category, link])
берет значения из ячеек и добавляет второй массив, содержащий категорию и ссылка из строки.
Мой пример кода в основном тот же шаблон каждый раз, когда я очищаю страницу с таблицей. Различия будут незначительными, но это всего лишь 1076 * по таблице, извлекающей ячейки и преобразующей их в га sh. Можно даже на чисто написанной таблице автоматически извлечь ключи ha sh из текста ячейки в первой строке таблицы.