3.2 UR2IM——基本抓取流程
每个网站都是不同的,如果发现某些不常见的情况,则需要一些额外的学习,或是在Scrapy的邮件列表中咨询一些问题。不过,为了知道在哪里和如何搜索,重要的是对其流程有一个整体的了解,并且清楚相关的术语。和Scrapy打交道时,你所遵循的最通用的流程是UR2IM流程,如图3.3所示。
图3.3 UR2IM流程
3.2.1 URL
一切始于URL。你需要从准备抓取的网站中选择几个示例URL。我将使用Gumtree分类广告网站(https://www.gumtree.com)作为示例进行演示。
比如,通过访问Gumtree上的伦敦房产主页(链接为http://www.gumtree.com/flats-houses/london),你能够找到一些房产的示例URL。可以通过右键单击分类列表,选择Copy Link Address(复制链接地址)或你浏览器中同样的功能,来复制这些链接。比如,其中一个可能类似于https://www.gumtree.com/p/studios-bedsits-rent/split-level。虽然可以在真实网站中使用这些URL来操作,但不幸的是,经过一段时间后,真实的Gumtree网站可能会发生变化,造成XPath表达式无法正常工作。此外,除非设置一个用户代理头,否则Gumtree不会回应你的请求。稍后我们会对此进行更进一步的讲解,不过就现在而言,如果想加载它们的某个页面,可以在scrapy shell中使用如下命令。
scrapy shell -s USER_AGENT="Mozilla/5.0" <your url here e.g. http://www. gumtree.com/p/studios-bedsits-rent/...>
如果想要在使用scrapy shell时调试问题,可以使用--pdb参数启用交互式调试,以避免发生异常。例如:
scrapy shell --pdb https://gumtree.com
scrapy shell是一个非常有用的工具,能够帮助我们使用Scrapy开发。
很显然,我们并不鼓励你在学习本书内容时访问Gumtree的网站,我们也不希望本书的示例在不久之后就无法使用。此外,我们还希望即使无法连接互联网,你仍然能够开发和使用我们的示例。这就是为什么你的Vagrant开发环境中包含一个提供了类似于Gumtree网站页面的Web服务器的原因。虽然它们可能不如真实网站那么漂亮,但是从爬虫角度来说,它们其实是一样的。即便如此,我们在本章中的所有截图还是来自真实的Gumtree网站。在你Vagrant的dev机器中,可以通过http://web:9312/访问该Web服务器,而在你的浏览器中,可以通过http://localhost:9312/来访问。
在scrapy shell中打开服务器中的一个网页,并且在dev机器上输入如下内容进行操作。
$ scrapy shell http://web:9312/properties/property_000000.html ... [s] Available Scrapy objects: [s] crawler <scrapy.crawler.Crawler object at 0x2d4fb10> [s] item {} [s] request <GET http:// web:9312/.../property_000000.html> [s] response <200 http://web:9312/.../property_000000.html> [s] settings <scrapy.settings.Settings object at 0x2d4fa90> [s] spider <DefaultSpider 'default' at 0x3ea0bd0> [s] Useful shortcuts: [s] shelp() Shell help (print this help) [s] fetch(req_or_url) Fetch request (or URL) and update local... [s] view(response) View response in a browser >>>
我们得到了一些输出,现在可以在Python提示符下,用它来调试刚才加载的页面(一般情况下,可以使用Ctrl + D退出)。
3.2.2 请求和响应
大家可能注意到在前面的日志中,scrapy shell本身已经为我们做了一些工作。我们给出了一个URL,然后它执行了一个默认的GET请求,并得到了一个状态码为200的响应。这就意味着,页面信息已经加载完毕,可以使用了。如果想要打印response.body的前50个字符,可以按如下命令操作。
>>> response.body[:50] '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8"'
[:50]是什么?这是Python从文本变量(本例为response.body)中抽取最前面50个字符(如果存在)的方式。如果你之前并不了解Python,请保持冷静,继续向前。很快,你就会熟悉并享受所有这些语法技巧了。
这是Gumtree上指定页面的HTML内容。请求和响应部分不会给我们带来太多麻烦。不过,在很多情况下,你需要做一些工作才能保证其正确。第5章中讲到这些内容。就目前来说,我们尽量保持简单,直接进入下一部分——Item。
3.2.3 Item
下一步是尝试从响应中将数据抽取到Item的字段中。因为该页面的格式是HTML,因此可以使用XPath表达式进行操作。首先,让我们看一下这个页面,如图3.4所示。
图3.4 页面、感兴趣的字段及其HTML代码
在图3.4中有大量的信息,但其中大部分都是布局:logo、搜索框、按钮等。虽然这些信息都很有用,但是爬虫并不会对其产生兴趣。我们可能感兴趣的字段,比如说包括房源的标题、位置或代理商的电话号码,它们都具有对应的HTML元素,我们需要定位到这些元素,然后使用前一节中所描述的流程抽取数据。那么,先从标题开始吧(如图3.5所示)。
图3.5 抽取标题
右键单击页面上的标题,并选择Inspect Element。这样就可以看到相应的HTML源代码了。现在,尝试通过右键单击并选择Copy XPath,抽取标题的XPath表达式。你会发现Chrome浏览器给我们的XPath表达式很精确,但又十分复杂,因此该表达式是非常脆弱的。我们将对其进行一些简化,只使用最后的一部分,通过使用表达式//h1,选择在页面中可以看到的任何H1元素。尽管这种方式有些误导,因为我们并不是真的需要页面中的每一个H1,不过实际上这里只有标题使用了H1;而作为优秀的SEO实践,每个页面应当只有一个H1元素,并且大部分网站确实是这样的。
SEO是Search Engine Optimization(搜索引擎优化)的缩写,即通过优化网站代码、内容和出入站链接的流程,实现提供给搜索引擎的最佳方式。
我们来检查下该XPath表达式能否在scrapy shell中良好运行。
>>> response.xpath('//h1/text()').extract() [u'set unique family well']
非常好,完美工作。你应该已经注意到我在//h1表达式的结尾处添加了/text()。如果想要只抽取H1元素所包含的文本内容,而不是H1元素自身的话,就需要使用到它。我们通常都会使用/text()来获得文本字段。如果忽略它,就会得到整个元素的文本,包括并不需要的标记。
>>> response.xpath('//h1').extract() [u'<h1 itemprop="name" class="space-mbs">set unique family well</h1>']
此时,我们就得到了抽取本页中第一个感兴趣的属性(标题)的代码,不过如果你观察得更仔细的话,就会发现还有一种更好更简单的方法也可以做到。
Gumtree通过微数据标记注解它们的HTML。比如,我们可以看到,在其头部有一个itemprop="name"的属性,如图3.6所示。非常好,这样我们就可以使用一个更简单的XPath表达式,而不再包含任何可视化元素了,此时得到的表达式为//*[@itemprop="name"][1]/text()。你可能会奇怪为什么我们选择了包含itemprop="name"的第一个元素。
图3.6 Gumtree拥有微数据标记
稍等!你是说第一个?如果你是一个经验丰富的程序员,可能已经将array[1]作为数组的第二个元素了。令人惊讶的是,XPath是从1开始的,因此array[1]是数组的第一个元素。
我们这么做,不只是因为itemprop="name"在许多不同的上下文中作为微数据来使用,还因为Gumtree在其页面的“你可能还喜欢……”部分为其他属性使用了嵌套的信息,以这种方式阻止我们对其轻易识别。尽管如此,这并不是一个大问题。我们只需要选择第一个,而且我们也将使用同样的方式处理其他字段。
让我们来看一下价格。价格被包含在如下的HTML结构当中。
<strong class="ad-price txt-xlarge txt-emphasis" itemprop="price"> £334.39pw</strong>
我们又一次看到了itemprop="name"这种形式,太棒了。此时,XPath表达式将会是//*[@itemprop="price"][1]/text()。我们来试一下吧。
>>> response.xpath('//*[@itemprop="price"][1]/text()').extract() [u'\xa3334.39pw']
我们注意到,这里包含一些Unicode字符(英镑符号£),然后是334.39pw的价格。这表明数据并不总是像我们希望的那样干净,所以可能还需要对其进行一些清洗的工作。比如,在本例中,我们可能需要使用一个正则表达式,以便只选择数字和点号。可以使用re()方法做到这一要求,并使用一个简单的正则表达式替代extract()。
>>> response.xpath('//*[@itemprop="price"][1]/text()').re('[.0-9]+') [u'334.39']
这里使用了一个response对象,并调用了它的xpath()方法来抽取感兴趣的值。不过,xpath()返回的值是什么呢?如果在一个简单的XPath表达式中,不使用.extract()方法,将会得到如下的显示输出:
>>> response.xpath('.') [<Selector xpath='.' data=u'<html>\n<head>\n<meta charse'>]
xpath()返回了网页内容预加载的Selector对象。我们目前只使用了xpath()方法,不过它还有另一个有用的方法:css()。xpath()和css()都会返回选择器,只有当调用extract()或re()方法的时候,才会得到真实的文本数组。这种方式非常好用,因为这样就可以将xpath()和css()操作串联起来了。比如,可以使用css()快速抽取正确的HTML元素。
>>> response.css('.ad-price') [<Selector xpath=u"descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' ad-price ')]" data=u'<strong class="ad-price txt-xlarge txt-e'>]
请注意,在后台中css()实际上编译了一个xpath()表达式,不过我们输入的内容要比XPath自身更加简单。接下来,串联一个xpath()方法,只抽取其中的文本。
>>> response.css('.ad-price').xpath('text()') [<Selector xpath='text()' data=u'\xa3334.39pw'>]
最后,还可以通过re()方法,串联上正则表达式,以抽取感兴趣的值。
>>> response.css('.ad-price').xpath('text()').re('[.0- 9]+') [u'334.39']
实际上,这个表达式与原始表达式相比,并无好坏之差。请把它当作一个引起思考的说明性示例。在本书中,我们将尽可能保持事物简单,同时也会尽可能多地使用虽然有些老旧但仍然好用的XPath。关键点是记住xpath()和css()返回的Selector对象是可以被串联起来的。为了获取真实值,可以使用extract(),也可以使用re()。在Scrapy的每个新版本当中,都会围绕这些类添加新的令人兴奋且高价值的功能。相关的Scrapy文档部分为http://doc.scrapy.org/en/latest/topics/selectors.html。该文档非常优秀,相信你可以从中找到抽取数据的最有效的方式。
描述文本的抽取也是相似的。有一个itemprop="description"的属性用于标示描述。其XPath表达式为 //*[@itemprop="description"][1]/text()。相似地,住址部分使用itemtype="http://schema.org/ Place"注解;因此,XPath表达式为 //*[@itemtype="http://schema.org/Place"][1]/text()。
同理,图片使用了itemprop="image"。因此使用//img[@itemprop="image"][1]/@src。这里需要注意的是,我们没有使用/text(),这是因为我们并不需要任何文本,而是只需要包含图片URL的src属性。
假设这些是我们想要抽取的全部信息,我们可以将其总结到表3.1中。
表3.1
基本字段 | XPath表达式 |
title | //*[@itemprop="name"][1]/text() |
price | //*[@itemprop="price"][1]/text() |
description | //*[@itemprop="description"][1]/text() |
address | //*[@itemtype="http://schema.org/Place"][1]/text() |
image_urls | //*[@itemprop="image"][1]/@src |
现在,表3.1就变得非常重要了,因为如果我们有许多包含相似信息的网站,则很可能需要创建很多类似的爬虫,此时只需改变前面的这些表达式。此外,如果想要抓取大量网站,也可以使用这样一张表格来拆分工作量。
到目前为止,我们主要在使用HTML和XPath。接下来,我们将开始编写一些真正的Python代码。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论