您的代码可能会更简单,更有弹性:
思考这个:
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') 相同,只是第二个更清洁,从而减少视觉噪音。
与at 相比,search 返回的内容还有其他更细微的差异,文档中对此进行了介绍。我强烈建议您阅读并尝试他们的示例,因为知道何时使用哪个可以省去您的麻烦。
-
依赖绝对 XPath 选择器:绝对选择器非常脆弱。对 HTML 的任何更改都极有可能被破坏。相反,找到有用的节点来检查它们是唯一的,然后让解析器找到它们。
使用 CSS 选择器'article li a' 跳过所有节点,直到找到“article”节点,在其中查找子节点“li”并跟随“a”。您可以用 XPath 做同样的事情,但它在视觉上很嘈杂。我非常喜欢让我的代码尽可能易于阅读和理解。
同样,at('article table') 在“文章”节点下查找第一个表,然后search('tr') 仅在该表中查找嵌入的行。
因为您想跳过表头 [1..-1] 对 NodeSet 进行切片并跳过第一行。
-
map 使构建结构更容易:
rows = doc.at('article table').search('tr')[1..-1].map { |tr|
将字段分配给rows 一次通过该行循环。
values 分配有每个“td”节点文本的 NodeSet 文本。
-
您可以通过使用Hash's [] 构造函数并传入一个键/值对数组来轻松构建哈希。
FIELDS.zip(values + [category, link])
正在从单元格中获取值并添加第二个数组,其中包含行中的类别和链接。
我的示例代码基本上是相同的模板每次我用表格抓取页面。会有细微的差别,但它是表格上的一个循环,提取单元格并将它们转换为哈希。甚至可以在一个写得很清楚的表格上,自动从表格第一行的单元格文本中获取哈希的键。