我从atpworldtour.com抓取排名表,我试图访问玩家名称。
表中一行的示例如下所示:
<tr>
<td class="rank-cell">1</td>
<td class="move-cell">
<div class="move-none"></div>
<div class="move-text">
</div>
</td>
<td class="country-cell">
<div class="country-inner">
<div class="country-item">
<img src="/~/media/images/flags/srb.png" alt="SRB" onerror="this.remove()">
</div>
</div>
</td>
<td class="player-cell">
<a href="/en/players/novak-djokovic/d643/overview" data-ga-label="Novak Djokovic">Novak Djokovic</a>
</td>
<td class="age-cell">28</td>
<td class="points-cell">
<a href="/en/players/novak-djokovic/d643/rankings-breakdown?team=singles" data-ga-label="rankings-breakdown">15,785</a>
</td>
<td class="tourn-cell">
<a href="/en/players/novak-djokovic/d643/player-activity?matchType=singles" data-ga-label="player-activity">17</a>
</td>
<td class="pts-cell">1,500</td>
<td class="next-cell">0</td>
</tr>
我尝试了几种不同的方法来提取这些信息。到目前为止,我迄今取得的最大成功是:
url = "http://www.atpworldtour.com/en/rankings/singles"
doc = Nokogiri::HTML(open(url))
doc.css("tr").each do |row|
puts row.css("td a")
end
问题是,在玩家的名字后面的每一行中还有另外两个链接,所以我把它们全部集中在一起。播放器的名称是表格中的第四个单元格,因此我尝试先拉出第四个单元格,然后访问该链接:
doc.css("tr").each do |row|
cell = row.css("td")[3]
puts cell.css("a").text
end
但返回错误undefined method 'css' for nil:NilClass
。
经过进一步调查,cell
似乎存储了所有具有玩家名称的单元格,而不仅仅是row
当前迭代的单元格,但是当我尝试迭代{{1}时我得到了相同的cell
错误。
我也尝试使用XPath解决这个问题:
undefined method
但是输出是空白区域的一个大区域,应该列出名称。
到目前为止,我发现的所有内容仅涵盖了基础知识,而且我无法找到有关如何执行更复杂操作的信息。
我实际上使用了它:
doc.xpath("//tr").each do |row|
puts row.xpath("/td[3]/a").text
end
但任何帮助找到使用Nokogiri的XPath和CSS选择器的正确文档/教程仍然会很棒。
答案 0 :(得分:1)
也许这将有助于阐明发生的事情:
require 'nokogiri'
doc = Nokogiri::HTML('<table><tr><td>foo</td><td>bar</td></tr></table>')
at
返回第一个匹配的节点。在这种情况下,它是<tr>
。使用text
将连接在一起的所有文本连接在一起:
doc.at('tr').to_html # => "<tr>\n<td>foo</td>\n<td>bar</td>\n</tr>"
doc.at('tr').text # => "foobar"
使用search
返回一个NodeSet,最容易被认为是一个数组。在这种情况下,它会返回两个元素,每个<tr><td>
对一个元素:
doc.search('tr td').size # => 2
text
将返回NodeSet中所有节点的文本,再次连接字符串:
doc.search('tr td').to_html # => "<td>foo</td>\n<td>bar</td>"
doc.search('tr td').text # => "foobar"
但是,通过遍历NodeSet中的每个节点,我们可以查看单个文本:
doc.search('tr td').map(&:text) # => ["foo", "bar"]
另一种但稍微慢一点的方法是首先找到<tr>
节点,然后在其中搜索各个<td>
节点:
doc.at('tr').search('td').size # => 2
doc.at('tr').search('td').to_html # => "<td>foo</td>\n<td>bar</td>"
doc.at('tr').search('td').text # => "foobar"
再一次,使用map
我们可以迭代它们并获得没有连接的文本:
doc.at('tr').search('td').map(&:text) # => ["foo", "bar"]
以下是使用单个与单独选择器下降并选择<td>
节点的速度差异:
require 'fruity'
require 'nokogiri'
doc = Nokogiri::HTML('<table><tr><td>foo</td><td>bar</td></tr></table>')
compare do
single_selector { doc.search('tr td').map(&:text) }
separate_selectors { doc.at('tr').search('td').map(&:text) }
end
# >> Running each test 32 times. Test will take about 1 second.
# >> single_selector is faster than separate_selectors by 2x ± 0.1
差异是由于tr td
对libXML2的单次往返调用与doc.at('tr').search('td')
的两次调用。
不幸的是,如果我们需要使用条件逻辑或按照他们的顺序访问多个不同类型的子节点,有时我们被迫使用更长,更慢的形式在标记中出现。
答案 1 :(得分:0)
包含玩家姓名的表格单元格有一个类player-cell
:
<td class="player-cell">
<a href="/en/players/novak-djokovic/d643/overview" data-ga-label="Novak Djokovic">Novak Djokovic</a>
</td>
您可以使用此类来获取元素:
doc.css('.player-cell a').map(&:text)
#=> ["Novak Djokovic", "Roger Federer", "Andy Murray", ...]
即使没有明确的类,您也可以通过以下方式获取第4列:
doc.css('td:nth-child(4) a').map(&:text)
#=> ["Novak Djokovic", "Roger Federer", "Andy Murray", ...]
或者使用XPath:
doc.xpath('//td[4]/a').map(&:text)
#=> ["Novak Djokovic", "Roger Federer", "Andy Murray", ...]