我屏幕抓取http://www.weather.com/weather/hourbyhour/l/INXX0202:1:IN。
我尝试选择使用CSS和XPath来获取网站中表格的降水预测部分。
它们都不能在我的程序中运行,因为它们返回空数组,但是,它们都适用于Chrome开发工具(Inspect element - > console - > $$代表CSS,$ x代表Xpath)。
为什么会这样?它与名称空间有关吗?
require 'open-uri'
require 'nokogiri'
foo = Nokogiri::HTML(open("http://www.weather.com/weather/hourbyhour/l/INXX0202:1:IN"))
foo.remove_namespaces!
p foo.xpath("//section[@data-ng-class]/p[@class='precip weather-cell ng-isolate-scope']/span[@data-ng-if]") # returns []
p foo.css("section[data-ng-class] p[class='precip weather-cell ng-isolate-scope'] span[data-ng-if]") # returns []
以下是a screenshot of the website,我试图从中获取数据。我想要的是标题下的数字" Precip" (例如:图片中为85,100,100,95,80,70,45,40)。
我将页面的HTML复制到本地HTML文件中,并让我的程序访问该文件。程序然后给了我需要的输出,但是当我有相同的程序使用OpenUri访问网站时,它返回一个空数组:
require 'open-uri'
require 'nokogiri'
foo = open("http://www.weather.com/weather/hourbyhour/l/INXX0202:1:IN")
nokogirifoo = Nokogiri::HTML(foo)
p nokogirifoo.xpath("//section[@data-ng-class]/p[@class='precip weather-cell ng-isolate-scope']/span[@data-ng-if]") # => empty array
bar = File.open('weather.html') # weather.html is just the html code of the page copied into a local file
nokogiribar = Nokogiri::HTML(bar)
p nokogiribar.xpath("//section[@data-ng-class]/p[@class='precip weather-cell ng-isolate-scope']/span[@data-ng-if]").text # => "85%100%100%95%80%70%45%40%" (this is what I need)
以下是HTML的一个片段(显示的部分嵌套在网站的多个标签中):
<section class="wxcard-hourly summary-view ng-isolate-scope last" data-ng-class="{'last': $last}" data-wxcard-hourly="hour" data-wxcard-hourly-methods="hourlyScope" data-hours-index="hoursDataIndex" data-show-wx-labels="false" data-details-view="false">
<div class="heading weather-cell" data-ng-switch="dataMethods.checkTime(data.getForecastLocalDate())">
<h2>
<span class="wx-dsxdate ng-binding ng-scope" ng-bind-template=" 9:30 am" data-dsxdate="" data-ng-switch-when="min" data-datetime="data.getForecastLocalDate()" data-timezone="locTz" data-format="'h:mm a'"> 9:30 am</span>
</h2>
<span class="sub-heading wx-hourly-date wx-dsxdate ng-binding ng-scope" ng-bind-template=" Fri, Nov 20" data-dsxdate="" data-datetime="data.getForecastLocalDate()" data-timezone="locTz" data-format="'EEE, MMM d'"> Fri, Nov 20</span>
</div>
<p class="hi-temp temp-1 weather-cell ng-isolate-scope" data-wx-temperature="data.getTemp()" data-show-temp-unit="hoursIndex === 0"> <span data-ng-if="hasValue()" data-ng-bind="temp" class="ng-binding ng-scope">28</span><sup data-ng-if="hasValue()" class="deg ng-scope">°</sup><sup class="temp-unit ng-binding ng-scope" data-ng-if="showTempUnit" data-ng-bind="tempUnit()">C</sup>
</p>
<p class="feels-like temp-2 weather-cell ng-isolate-scope" data-wx-temperature="data.getFeelsLike()" data-temp-prefix="Feels"><span ng-if="tempPrefix" class="temp-prefix ng-binding ng-scope" data-ng-bind="tempPrefix">Feels</span><span data-ng-if="hasValue()" data-ng-bind="temp" class="ng-binding ng-scope">34</span><sup data-ng-if="hasValue()" class="deg ng-scope">°</sup>
</p>
<div class="weather-cell">
<h3 class="weather-phrase">
<div class="weather-icon ng-isolate-scope wx-weather-icon" data-wxicon="" data-sky-code="data.getSkyCode()"><div class="svg-icon"><img src="/sites/all/modules/custom/angularmods/app/shared/wxicon/svgz/thunderstorm.svgz?1" aria-hidden="true" alt="thunderstorm"></div></div>
<span class="phrase ng-binding" data-ng-bind-template="Thunderstorms">Thunderstorms</span>
</h3>
</div>
<!-- The Next Line Is What I Need-->
<p class="precip weather-cell ng-isolate-scope" data-wx-precip="dataMethods.roundedValue(data.getChanceOfPrecipDay())" data-wx-precip-type="data.getPrecipType()" data-wx-precip-sky-code="data.getSkyCode()"><span aria-hidden="true" class="wx-iconfont-global wx-icon-precip-rain-1"></span><span data-ng-if="!wxPrecipIconOnly" class="precip-val ng-binding ng-scope" data-ng-bind="chanceOfPrecip() | safeDisplay">85%</span></p>
<p class="humidity-wrapper weather-cell">
<span data-ng-bind-template="85%" class="humidity ng-binding ng-isolate-scope" data-wx-percentage="data.getHumidity()">85%</span>
</p>
<p class="wind-conditions weather-cell">
<span class="wx-wind ng-binding ng-isolate-scope" data-ng-bind-template="ESE 9 km/h" data-wx-wind-direction="data.getWindDirectionText()" data-wx-wind-speed="data.getWindSpeed()">ESE 9 km/h</span>
</p>
</section>
答案 0 :(得分:1)
问题在于您使用浏览器查看页面,除了实现HTML解析器之外,该页面还具有嵌入式JavaScript解释器。浏览器查找并处理任何JavaScript <script>
标记,在为用户呈现页面之前加载和调整元素。这就是您想要的页面中发生的事情。与Nokogiri一样,解析器是 NOT 浏览器,并不关心嵌入式脚本,因为在HTML中,脚本只是特定标记内的文本,因此,永远不会检索您想要的HTML。
您说您已将HTML保存到文件中,但是,您没有说 如何保存它。我猜测,因为保存的HTML包含您想要的信息,它是使用浏览器保存的。
使用网页时,第一步是确定页面是使用动态HTML和/或JavaScript还是静态HTML。在浏览器中关闭JavaScript,然后加载URL。或者,您可以从命令行使用wget
或curl
来检索页面并使用编辑器查看它。在任何一种情况下,你看到你想要的内容吗?如果是这样的话,那么你可以通过像Nokogiri这样的解析器获得它后获得的好处。如果你不这样做,那么你必须使用能解释JavaScript的东西,处理加载的信息,然后将它传递给解析器。
像PhantomJS和Watir这样的工具可以帮助您,或者相反,找到一个天气服务,允许您使用API来检索数据而不会刮擦,因为抓取总是非常脆弱。
还可以确定JavaScript用于检索数据的URL,然后请求该辅助资源并解析它。 可能是HTML,或者它可能是包含数据的JSON,然后由JavaScript处理,然后动态构建整个表。
Stack Overflow上有很多问题和答案,讨论如何做到以上所有。
所有人都说,一旦你获得了你想要的HTML,你就可以轻松地减少这些值所需的CSS选择器。每个值都包含在一个具有类的<style>
标记中,因此请使用该类来查找该值。
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<section class="wxcard-hourly summary-view ng-isolate-scope last" data-ng-class="{'last': $last}" data-wxcard-hourly="hour" data-wxcard-hourly-methods="hourlyScope" data-hours-index="hoursDataIndex" data-show-wx-labels="false" data-details-view="false">
<div class="heading weather-cell" data-ng-switch="dataMethods.checkTime(data.getForecastLocalDate())">
<h2>
<span class="wx-dsxdate ng-binding ng-scope" ng-bind-template=" 9:30 am" data-dsxdate="" data-ng-switch-when="min" data-datetime="data.getForecastLocalDate()" data-timezone="locTz" data-format="'h:mm a'"> 9:30 am</span>
</h2>
<span class="sub-heading wx-hourly-date wx-dsxdate ng-binding ng-scope" ng-bind-template=" Fri, Nov 20" data-dsxdate="" data-datetime="data.getForecastLocalDate()" data-timezone="locTz" data-format="'EEE, MMM d'"> Fri, Nov 20</span>
</div>
<p class="hi-temp temp-1 weather-cell ng-isolate-scope" data-wx-temperature="data.getTemp()" data-show-temp-unit="hoursIndex === 0"> <span data-ng-if="hasValue()" data-ng-bind="temp" class="ng-binding ng-scope">28</span><sup data-ng-if="hasValue()" class="deg ng-scope">°</sup><sup class="temp-unit ng-binding ng-scope" data-ng-if="showTempUnit" data-ng-bind="tempUnit()">C</sup>
</p>
<p class="feels-like temp-2 weather-cell ng-isolate-scope" data-wx-temperature="data.getFeelsLike()" data-temp-prefix="Feels"><span ng-if="tempPrefix" class="temp-prefix ng-binding ng-scope" data-ng-bind="tempPrefix">Feels</span><span data-ng-if="hasValue()" data-ng-bind="temp" class="ng-binding ng-scope">34</span><sup data-ng-if="hasValue()" class="deg ng-scope">°</sup>
</p>
<div class="weather-cell">
<h3 class="weather-phrase">
<div class="weather-icon ng-isolate-scope wx-weather-icon" data-wxicon="" data-sky-code="data.getSkyCode()"><div class="svg-icon"><img src="/sites/all/modules/custom/angularmods/app/shared/wxicon/svgz/thunderstorm.svgz?1" aria-hidden="true" alt="thunderstorm"></div></div>
<span class="phrase ng-binding" data-ng-bind-template="Thunderstorms">Thunderstorms</span>
</h3>
</div>
<!-- The Next Line Is What I Need-->
<p class="precip weather-cell ng-isolate-scope" data-wx-precip="dataMethods.roundedValue(data.getChanceOfPrecipDay())" data-wx-precip-type="data.getPrecipType()" data-wx-precip-sky-code="data.getSkyCode()"><span aria-hidden="true" class="wx-iconfont-global wx-icon-precip-rain-1"></span><span data-ng-if="!wxPrecipIconOnly" class="precip-val ng-binding ng-scope" data-ng-bind="chanceOfPrecip() | safeDisplay">85%</span></p>
<p class="humidity-wrapper weather-cell">
<span data-ng-bind-template="85%" class="humidity ng-binding ng-isolate-scope" data-wx-percentage="data.getHumidity()">85%</span>
</p>
<p class="wind-conditions weather-cell">
<span class="wx-wind ng-binding ng-isolate-scope" data-ng-bind-template="ESE 9 km/h" data-wx-wind-direction="data.getWindDirectionText()" data-wx-wind-speed="data.getWindSpeed()">ESE 9 km/h</span>
</p>
</section>
EOT
从简单的搜索开始:
doc.at('.precip-val').text # => "85%"
at
找到第一个匹配的Node并返回它。 text
检索其文本节点。
您需要具有该类的多个节点,因此这样的内容应该有所帮助:
doc.search('.precip-val').map(&:text) # => ["85%"]
search
找到所有匹配的节点并返回一个NodeSet,它类似于一个数组,可以使用map
进行迭代。
他们不太可能将.precip-val
用于包含值的非降水标签,但是,如果他们这样做,请尝试:
doc.search('span.precip-val').map(&:text)
看看你得到了什么。