【问题标题】:How to get rid of phantom row in array?如何摆脱数组中的幻像行?
【发布时间】:2020-08-08 07:08:29
【问题描述】:

我正在使用 httparty 抓取一堆表格,然后使用 nokogiri 解析响应。一切正常,但我在顶部得到一个幻影行:

require 'nokogiri'
require 'httparty'
require 'byebug'
def scraper
    url = "https://github.com/public-apis/public-apis"
    parsed_page = Nokogiri::HTML(HTTParty.get(url))
    # Get categories from the ul at the top
    categories = parsed_page.xpath('/html/body/div[4]/div/main/div[2]/div/div/div/article/ul/li/a')
    # Get all tables from the page
    tables = parsed_page.xpath('/html/body/div[4]/div/main/div[2]/div/div/div/article/table')
    rows = []
    # Acting on one first for testing before making it dynamic 
    tables[0].search('tr').each do |tr|
        cells = tr.search('td')
        link = ''
        values = []
        row = {
            'name' => '',
            'description' => '',
            'auth' => '',
            'https' => '',
            'cors' => '',
            'category' => '',
            'url' => ''
        }
        cells.css('a').each do |a|
            link += a['href']
        end
        cells.each do |cell|
            values << cell.text
        end
        values << categories[0].text
        values << link
        rows << row.keys.zip(values).to_h
    end
    puts rows
end
scraper

控制台中的结果:

{"name"=>"Animals", "description"=>"", "auth"=>nil, "https"=>nil, "cors"=>nil, "category"=>nil, "url"=>nil}
{"name"=>"Cat Facts", "description"=>"Daily cat facts", "auth"=>"No", "https"=>"Yes", 
...

第一行是从哪里来的?

【问题讨论】:

  • 欢迎来到 SO。请参阅“How to Ask”、“Stack Overflow question checklist”和“MCVE”及其所有链接页面。当就此类问题寻求帮助时,我们需要演示问题的绝对最小代码,以及问题本身中的最小输入数据。要求我们检索 HTML 页面并对其进行扫描以确定您遇到问题的区域会减少提供帮助的人数,因此请帮助我们帮助您。
  • 在 XPath 或 CSS 中使用节点的绝对/完整路径是不好的。相反,找到用于导航到所需节点的航路点。 标记发生变化时,绝对路径会中断,但跳过查找特定节点模式的几率很大。

标签: arrays ruby hash nokogiri


【解决方案1】:

您看到的第一行很可能是标题行。标题行使用&lt;th&gt; 而不是&lt;td&gt;。这意味着cells = tr.search('td') 将是标题行的空集合。

在大多数情况下,标题行放在&lt;thead&gt; 中,数据行放在&lt;tbody&gt; 中。所以你可以不做tables[0].search('tr'),而是做tables[0].search('tbody tr'),它只选择&lt;tbody&gt;标签中的行。

【讨论】:

  • Welp,我确信我已经瞄准了 tbody,这很尴尬。非常感谢!
  • thead, tbody,虽然它们应该存在,但在网络上的大多数文档中并不存在,因为它们是在它们之前创建的被添加或因为文档的创建者不在乎。浏览器会自动添加它们并在我们查看源代码时显示它们,但是如果这些节点实际上不存在于源文件中,则依赖浏览器渲染的源代码通常会导致选择器失败。在命令行中使用wgetcurlnokogiri 来查看真正的标记并忽略浏览器所说的内容总是好的。
【解决方案2】:

您的代码可能会更简单,更有弹性:

思考这个:

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])
    

    正在从单元格中获取值并添加第二个数组,其中包含行中的类别和链接。

我的示例代码基本上是相同的模板每次我用表格抓取页面。会有细微的差别,但它是表格上的一个循环,提取单元格并将它们转换为哈希。甚至可以在一个写得很清楚的表格上,自动从表格第一行的单元格文本中获取哈希的键。

【讨论】:

    猜你喜欢
    • 2013-10-13
    • 2013-12-12
    • 1970-01-01
    • 2022-11-19
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    • 2020-10-29
    • 2015-05-03
    相关资源
    最近更新 更多