使用多个 XPath 选择器定义单个链接的页面的解决方法



以下代码有效,但不会迭代到下一页。我已经发现有问题的网站使用两个不同的 XPath 选择器来定义下一页链接,我不确定如何在代码中实现它。

作为对评论的回应,以下是第一页相关选择器的来源:

<table class="pager" cellspacing="0">
    <tr>
        <td>
                    Items 1 to 72 of 1146 total                </td>
                <td class="pages">
            <strong>Page:</strong>
            <ol>
                                                            <li><span class="on">1</span></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=2">2</a></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=3">3</a></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=4">4</a></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=5">5</a></li>
                                                        <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=2"><img src="http://www.example.com/skin/frontend/default-mongo/a033/images/pager_arrow_right.gif" alt="Next Page"/></a></li>
                        </ol>
        </td>
        <td class="a-right">
            Show <select onchange="setLocation(this.value)">
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=12&amp;order=position">
                    12                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=24&amp;order=position">
                    24                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=48&amp;order=position">
                    48                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position" selected="selected">
                    72                </option>
                        </select> per page        </td>
    </tr>
</table>

以及所有后续页面上完全相同的选择器:

<table class="pager" cellspacing="0">
    <tr>
        <td>
                    Items 73 to 144 of 1146 total                </td>
                <td class="pages">
            <strong>Page:</strong>
            <ol>
                            <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=1"><img src="http://www.example.com/skin/frontend/default-mongo/a033/images/pager_arrow_left.gif" alt="Previous Page" /></a></li>
                                                            <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=1">1</a></li>
                                                                <li><span class="on">2</span></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=3">3</a></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=4">4</a></li>
                                                                <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=5">5</a></li>
                                                        <li><a href="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=3"><img src="http://www.example.com/skin/frontend/default-mongo/a033/images/pager_arrow_right.gif" alt="Next Page"/></a></li>
                        </ol>
        </td>
        <td class="a-right">
            Show <select onchange="setLocation(this.value)">
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=12&amp;order=position&amp;p=2">
                    12                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=24&amp;order=position&amp;p=2">
                    24                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=48&amp;order=position&amp;p=2">
                    48                </option>
                            <option value="http://www.example.com/clothing-accessories?dir=asc&amp;limit=72&amp;order=position&amp;p=2" selected="selected">
                    72                </option>
                        </select> per page        </td>
    </tr>
</table>

在结果的第一页上,下一页链接由 XPath 选择器定义:

//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[6]/‌​a

在所有后续页面上,下一页链接由以下定义:

//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[7]/‌​a

我将更改代码的哪一部分以及如何确保程序迭代到下一页结果,而不管该next_page_link是如何定义的?

require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'fileutils'
DATA_DIR = "data-hold/clothing-accessories"
Dir.mkdir(DATA_DIR) unless File.exists?(DATA_DIR)
BASE_TOM_URL = "http://www.example.com"
list_url = "#{ BASE_TOM_URL }/clothing-accessories?dir=asc&limit=72&order=position"
loop do
  page = Nokogiri::HTML(open(list_url))
  rows = page.xpath('//*[@id="product-list-table"]/li')
  unless rows.empty?
    rows[1..-2].each do |row|
      hrefs = row.xpath('//*[@id="product-list-table"]/li/div/a').map{ |a| a['href'] }.uniq
      hrefs.each do |href|
        remote_url = href
        local_fname = "#{ DATA_DIR }/#{ File.basename(href) }"
        unless File.exists?(local_fname)
          puts "Fetching #{ remote_url }..."
          begin
            tom_content = open(remote_url).read
            File.write(local_fname, tom_content)
            puts "t...Success, saved to #{ local_fname }"
            sleep 1.0 + rand
          rescue Exception => e
            puts "Error: #{ e }"
            sleep 5
          end  
        end 
      end 
    end
  end

  next_results_link = page.at('//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[7]/a')
  if next_results_link
    list_url = next_results_link['href']
    puts "t...Getting next page of results: #{list_url}"
  else
    break
  end
end

在此链接中,有一个图像包含替代文本"下一页"。利用这一点:

//td[contains(@class, 'pages')]/ol/li/a[img/@alt='Next Page']

如果您更喜欢完整路径,则可以轻松地将此 XPath 表达式的选择器应用于上面获取的路径的开头。我什至会更进一步,使用 //td[contains(@class, 'pages')]//a[img/@alt='Next Page'] 进一步将您的代码与 XML 结构分离。

对于匹配类属性,您还应该考虑使用更正确的版本,但这会使表达式稍微复杂一些。看看这个关于匹配 XML 类的问题。

你为什么不做这样的事情:

rows[1..-2].each_with_index do |row, i|
  ...
  xpath_index = if i == 1
    '6'
  else
    '7'
  end
  next_results_link = page.at(%Q!//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[#{ xpath_index }]/a!)
  ...
end

这将使您了解它在做什么:

xpath_index = 6
%Q!//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[#{ xpath_index }]/a!
# => "//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[6]/a"
xpath_index = 7
%Q!//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[#{ xpath_index }]/a!
# => "//*[@id="bodyblock"]/div/div[2]/div[2]/div[3]/table[3]/tbody/tr/td[2]/ol/li[7]/a"

另外,正如您所知,您正在处理XPath中的非ASCII字符。我不知道它是如何到达那里的,但尾随/a无效。目前是:

'/‌​a'.codepoints.to_a # => [47, 8204, 8203, 97]

并且应该是:

'/a'.codepoints.to_a # => [47, 97]

"page.at(%Q!!(选择器语法对我来说是新的,我还没有在我的任何阅读中看到它被引用

at是Nokogiri相当于search(some_node_selector, some_name_space).first。这一切都记录在Nokogiri::XML::Node.at.换句话说,它只找到第一个节点并返回它,而search查找匹配的所有节点并将它们作为 NodeSet 返回。

at同样接受 CSS 或 XPath 选择器。CSS专用版本为at_css,XPath专用版本为at_xpath。我倾向于使用at除非我使用模棱两可的选择器,这会愚弄Nokogiri做错事。

类似地,search 同时接受 CSS 和 XPath,cssxpath 分别是 CSS 和 XPath 变体。

%Q!...! 是定义解释/双引号字符串的另一种方法。除了%Q之外,还有%q%,以及正则表达式的%r,在子shell中执行命令行应用程序的%x,以及Ruby v.2.0中的%i

这里有一堆例子:

foo = 'bar'
%Q[a b]        # => "a b"
%Q^a #{ foo }^ # => "a bar"
%[a b]        # => "a b"
%/a #{ foo }/ # => "a bar"
%q#a b#        # => "a b"
%q[a #{ foo }] # => "a #{ foo }"
%w$a b$ # => ["a", "b"]
%W~a b~ # => ["a", "b"]
%W[a foo]      # => ["a", "foo"]
%W[a #{ foo }] # => ["a", "bar"]
%r.^foo. # => /^foo/
%r!^foo! # => /^foo/
%r/^foo/ # => /^foo/
%x(date) # => "Mon Dec  2 21:13:37 MST 2013n"
%s[a]   # => :a
%s[a b] # => :"a b"
%i[a b] # => [:a, :b]

请注意,分隔符可以是书尾,如()[],也可以是相同的字符,如#!。这在处理包含单引号和双引号的字符串时提供了很大的灵活性,并且可以清理"倾斜牙签综合症"行:

"He's quoting Shakesphere's "The Taming of the Shrew"" # => "He's quoting Shakesphere's "The Taming of the Shrew""
'He's quoting Shakesphere's "The Taming of the Shrew"' # => "He's quoting Shakesphere's "The Taming of the Shrew""
%Q[He's quoting Shakesphere's "The Taming of the Shrew"] # => "He's quoting Shakesphere's "The Taming of the Shrew""

请注意,最后一个在视觉上更干净,并且更容易输入。这些只是嵌入单引号和双引号的简单示例。通读维基百科关于"倾斜牙签综合症"的文章,以获取更多示例和信息。

相关内容

  • 没有找到相关文章

最新更新