欢迎来真孝善网,为您提供真孝善正能量书籍故事!

《Scrapy入门教程》第三章:爬虫基础知识

时间:11-23 民间故事 提交错误

大家好,感谢邀请,今天来为大家分享一下《Scrapy入门教程》第三章:爬虫基础知识的问题,以及和的一些困惑,大家要是还不太明白的话,也没有关系,因为接下来将为大家分享,希望可以帮助到大家,解决大家的问题,下面就开始吧!

本章非常重要,您可能需要阅读几遍或寻找问题的解决方案。我们将从如何安装Scrapy开始,然后通过案例讲解如何编写爬虫。在我们开始之前,有几点需要注意。

由于我们即将进入编程的有趣部分,因此使用本书中的代码片段非常重要。当你看到:

$ 回显你好世界

Hello world是让你在终端输入echo hello world(忽略$),第二行是看结果。

当你看到:

打印“嗨”

hi让你进入Python或Scrapy界面(忽略)。同样,第二行是输出结果。

您还需要编辑该文件。编辑工具取决于您的计算机环境。如果您使用Vagrant(强烈推荐),则可以使用文本编辑器,例如Notepad、Notepad++、Sublime Text、TextMate、Eclipse 或PyCharm。如果你对Linux/Unix比较熟悉,可以使用控制台自带的vim或者emacs。这两个编辑器功能强大,但有一定的学习曲线。如果您是初学者,可以选择适合初学者的nano编辑器。

安装ScrapyScrapy的安装比较简单,但是也取决于读者的电脑环境。为了支持更多的人,本书安装和使用Scrapy的方法是使用Vagrant,它可以让你在一个Linux盒子里使用所有工具,而不管操作系统是什么。下面提供了Vagrant 和一些常见操作系统的说明。

MacOS为了轻松阅读本书,请参阅以下Vagrant 说明。如果你想在MacOS上安装Scrapy,只需在控制台输入:

$ easy_install scrapy 然后,一切就可以交给电脑了。安装过程中可能会询问你的密码或者是否安装Xcode,同意即可。

Windows在Windows 中安装Scrapy 有点棘手。另外,本书的所有软件在Windows上安装起来非常麻烦。我们已经为您想到了可能会出现的问题。 Vagrant with Virtualbox 可在所有64 位计算机上流畅运行。通读相关章节,只需几分钟即可安装完毕。如果你确实想在Windows中安装它,请参考本书网站http://scrapybook.com/上的信息。

Linux您可以在各种Linux服务器上安装Scrapy。步骤如下:

提示:确切的安装依赖关系变化很快。在撰写本文时,Scrapy 版本为1.0.3(翻译时为1.4)。以下只是针对不同服务器的建议方法。

Ubuntu或Debian Linux为了使用apt在Ubuntu(测试机是Ubuntu 14.04 Trusty Tahr - 64位)或其他服务器上安装Scrapy,可以使用以下三个命令:

$ sudo apt-get 更新

$ sudo apt-get install python-pip python-lxml python-crypto python-

cssselect python-openssl python-w3lib python-twisted python-dev libxml2-

dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev

$ sudo pip install scrapy 此方法需要编译,并且可能随时中断,但您可以在PyPI 上安装最新版本的Scrapy。如果想避免编译并且安装的不是最新版本,可以搜索“安装Scrapy Ubuntu packages”并按照官方文档进行安装。

Red Hat或CentOS Linux使用yum 在Linux 上安装Scrapy 也很简单(测试机器是Ubuntu 14.04 Trusty Tahr - 64 位)。只需三个命令:

须藤百胜更新

sudo yum -y 安装libxslt-devel pyOpenSSL python-lxml python-devel gcc

sudo easy_install scrapy从GitHub安装按照前面的说明安装Scrapy 的依赖项。 Scrapy 是用纯Python 编写的。如果您想编辑源代码或测试最新版本,可以从https://github.com/scrapy/scrapy 克隆最新版本。只需在命令行输入:

$ git clonehttps://github.com/scrapy/scrapy.git

$ cd scrapy

$ python setup.py install 我想如果你是这种类型的用户,你不需要我提醒你安装virtualenv。

升级ScrapyScrapy 升级相当频繁。如果需要升级Scrapy,可以使用pip、easy_install或aptitude:

$ sudo pip install --upgrade Scrapy 或

$ sudo easy_install --upgrade scrapy 如果您想降级或安装特定版本的Scrapy,您可以:

$ sudo pip install Scrapy==1.0.0 或

$ sudo easy_install scrapy==1.0.0Vagrant:本书案例的运行方法本书中的一些示例比较复杂,并且一些示例使用了很多东西。无论您的水平如何,请尝试运行所有示例。只需一条命令,即可使用Vagrant搭建运行环境。

在本书使用的Vagrant 系统中,你的计算机被称为“主机”。 Vagrant 在主机上创建一个虚拟机。这使得我们可以忽略主机软件和硬件并运行该案例。

本书的大部分章节都使用了两种服务,即——开发机和网络机。我们登录在开发机上运行Scrapy,在网络机上进行爬虫。后面的章节会用到更多的服务,包括数据库和大数据处理引擎。

按照附录A的安装要求安装Vagrant,直至安装好git和Vagrant。打开命令行输入以下命令即可获取本书的代码:

$ git克隆https://github.com/scalingexcellence/scrapybook.git

$ cd scrapybook 打开Vagrant:

$ vagrant up --no-parallel 首次打开Vagrant 可能需要一些时间,具体取决于您的网络。第二次打开会更快。打开后,通过以下方式登录到您的虚拟机:

$vagrant ssh 代码已从主机复制到开发机器,现在可以在本书的目录中看到:

$ CD 书

$ls

$ ch03 ch04 ch05 ch07 ch08 ch09 ch10 ch11.可以打开几个窗口,输入vagrant ssh,这样就可以打开多个终端了。输入vagrant stop 关闭系统,输入vagrantstatus 检查状态。 vagrant stop 无法关闭虚拟机。如果你在VirtualBox 中遇到问题,可以手动关闭它,或者使用vagrant global-status 查找id,并使用vagranthalt 暂停它。大多数示例都可以离线运行,这是Vagrant 的一大优势。

安装好环境后,就可以开始学习Scrapy了。

UR2IM——基础抓取过程每个网站都是不同的,对每个网站进行额外的研究是不可避免的。如果您遇到特别不常见的问题,您可能还需要查阅Scrapy 邮件列表。要找到答案,在哪里找到答案,如何找到答案,你必须熟悉整个流程和相关术语。 Scrapy的基本流程可以写成缩写UR2IM,如下图所示。

The URL一切都从URL 开始。您需要目标网站的URL。我的例子是https://www.gumtree.com/,一个Gumtree分类网站。

例如,访问伦敦房地产主页http://www.gumtree.com/flats-houses/london,你会发现很多房子的网址。右键单击链接地址以复制URL。其中一个URL 可能如下所示:https://www.gumtree.com/p/studios-bedsits-rent/split-level。但是,Gumtree网站发生变化后,URL的XPath表达式将变得无效。如果不添加用户标头,Gumtree 将不会响应。我稍后再讲,但现在如果你想加载网页,你可以使用Scrapy 终端,如下所示:

scrapy shell -s USER_AGENT="Mozilla/5.0" 进行调试时,可以在Scrapy语句后添加--pdb,例如:

scrapy shell --pdb https://gumtree.com 我们不希望大家如此频繁地点击Gumtree 网站,而且Gumtree 网站上的URL 很快就会过期,所以不适合用作示例。我们也希望大家能够离线练习书中的例子。这就是为什么Vagrant开发环境内置了一个Web服务器,可以生成类似于Gumtree的网页。这些网页可能看起来不太好,但从爬虫开发者的角度来看,它们是完全合格的。如果想在Vagrant上访问Gumtree,可以在Vagrant开发机上访问http://web:9312/,或者在浏览器中访问http://localhost:9312/。

让我们在这个网页上尝试一下Scrapy。在Vagrant 开发机器上,输入:

$ scrapy shell http://web:9312/properties/property_000000.html

.

[s] 可用的Scrapy 对象:

[s]抓取工具[s]项{}

[s]请求[s]响应200 http://web:9312/./property_000000.html[s]设置[s]蜘蛛[s]有用的快捷方式:

[s] help() Shell 帮助(打印此帮助)

[s] fetch(req_or_url) 获取请求(或URL)并更新本地.

[s] view(response) 在浏览器中查看响应

获取一些输出,加载页面,然后输入Python(可以使用Ctrl+D 退出)。

请求和响应在前面的输出日志中,Scrapy自动帮我们做了一些工作。我们输入一个地址,Scrapy 发出GET 请求,并得到成功响应值为200。这意味着网页信息已成功加载并可供使用。如果我们想打印response.body的前50个字母,我们会得到:

响应.body[:50]

"nnn这是此Gumtree 网页的HTML 文档。有时请求和响应可能很复杂。它们将在第5 章中解释,但现在我们只讨论最简单的情况。

抓取对象下一步是从响应文件中提取信息并将其输入到Item 中。由于这是一个HTML 文档,因此我们使用XPath 来完成它。首先看一下这个页面:

页面上有很多信息,但大部分都是关于布局的:标志、搜索框、按钮等。从爬行的角度来看,它们并不重要。例如,我们关注列表的标题、地址、电话号码。它们都对应于HTML 中的元素。我们需要在HTML 中找到它们,并使用我们在上一章中学到的知识来提取它们。我们先从标题开始吧。

右键单击标题并选择检查元素。再次右键单击自动定位的HTML,然后选择复制XPath。 Chrome 提供的XPath 总是很复杂并且容易失败。我们将简化它。我们只取最后一个h1。这是因为从SEO 的角度来看,每页HTML 最好只有一个h1。事实上,大多数网页只有一个h1,所以不必担心重复。

提示:SEO的意思是搜索引擎优化:通过优化网页代码、内容和链接来提高对搜索引擎的支持。

让我们看看h1 标签是否有效:

response.xpath("//h1/text()").extract()

[u"set unique family well"]太棒了,完全有效。我在h1后面添加了text(),这意味着只会提取h1标签中的文本。如果不添加text(),它看起来像这样:

response.xpath("//h1").extract()

[u"

set unique family well

"]我们已经成功获得了称号,但是如果我们仔细看看,我们可以找到更简单的方法。

Gumtree给标签添加了一个属性,即itemprop=name。所以XPath可以简化为//*[@itemprop="name"][1]/text()。在XPath 中,请记住数组从1 开始,因此这里[] 是1。

选择属性itemprop="name" 是因为Gumtree 使用该属性来命名许多其他内容,例如“你可能也喜欢”,并且使用数组序号提取它非常方便。

接下来看价格。价格在HTML 中的位置如下:

334.39pw我们又看到了属性itemprop="name",XPath表达式为//*[@itemprop="price"][1]/text()。验证一下:

response.xpath("//*[@itemprop="price"][1]/text()").extract()

[u"xa3334.39pw"] 请注意Unicode 字符( 符号)和价格350.00pw。这表明数据需要清理。在此示例中,我们使用正则表达式来提取数字和小数点。使用常规方法如下:

response.xpath("//*[@itemprop="price"][1]/text()").re("[.0-9]+")

[u"334.39"]提取房屋描述的文字和房屋地址也很相似,如下:

//*[@itemprop="描述"][1]/text()

与//*[@itemtype="http://schema.org/Place"][1]/text()类似,您可以使用//img[@itemprop="image"][1]/@src来抓取图像。请注意,此处未使用text(),因为我们只需要图像的URL。

如果这就是我们想要提取的所有信息,那么它的组织方式如下:

目标XPath表达式标题//*[@itemprop="name"][1]/text()

示例value: [u"set unique family well"]Price//*[@itemprop="price"][1]/text()

示例值(使用re()):[u"334.39"]description//*[@itemprop="description"][1]/text()

示例值: [u"网站法院仓库rnpool."]地址//*[@itemtype="http://schema.org/Place"][1]/text()

示例值: [u"Angel, London"]Image_URL//*[@itemprop="image"][1]/@src

示例value: [u"./images/i01.jpg"]这个表很重要,因为稍微改变一下表达式也许就可以爬取其他页面。另外,如果要爬取几十个网站,可以用这样的表格来区分。

到目前为止,我只使用过HTML 和XPath。接下来我会用Python来做一个项目。

一个Scrapy项目到目前为止,我们只在Scrapy shell 中工作。学习完前面的知识后,现在启动一个Scrapy项目,Ctrl+D退出Scrapy shell。 Scrapy shell 只是一个操作网页、XPath 表达式和Scrapy 对象的工具。不要在上面浪费太多,因为一旦退出,你写的代码就会消失。我们创建一个名为properties的项目:

$ scrapy startproject 属性

$ cd 属性

$ 树。

属性

__init__.py

items.py

pipelines.py

设置.py

蜘蛛

__init__.py

scrapy.cfg

2个目录,6个文件我们先看一下这个Scrapy项目的文件目录。该文件夹包含一个同名文件夹,其中包含三个文件items.py、pipelines.py 和settings.py。还有一个子文件夹Spiders,目前为空。后面的章节将详细讨论设置、管道和scrapy.cfg 文件。

定义items使用编辑器打开items.py。里面已经有代码了,我们需要修改一下。使用上表的内容重新定义类PropertiesItem。

另外补充一些以后要用到的内容。稍后将对此进行深入解释。这里需要注意的是,声明一个字段并不需要填写它。因此,请随意添加您认为需要的字段,并且稍后可以修改它们。

Python 表达式imagespipeline 字段将根据image_URL 自动填充到此处。稍后会详细介绍。位置地理编码将在此处填充。稍后会详细介绍。我们还会添加一些杂务字段,这些字段可能与当前的项目关系不大,但我个人对它们非常感兴趣,将来也许能够使用它们。您可以选择添加或不添加。观察这些项目,你就会明白这些项目是如何帮助我找到在哪里(服务器、url)、何时(日期)以及如何(爬虫)进行爬行的。它们帮助我取消项目、计划新的重复爬网或忽略爬网程序错误。这里不懂也没关系,稍后我会详细解释。

Chore 字段Python 表达式urlresponse.url

示例value: "http://web./property_000000.html"projectself.settings.get("BOT_NAME")

示例value: "properties"spiderself.name

示例value: "basic"serverserver socket.gethostname()

示例value: "scrapyserver1"datedatetime.datetime.now()

示例value: datetime.datetime(2015, 6, 25.) 使用此表修改PropertiesItem 类。修改文件properties/items.py如下:

从scrapy.item 导入项目、字段

类属性项(项目):

# 主要字段

标题=字段()

价格=字段()

描述=字段()

地址=字段()

图像_URL=字段()

# 计算字段

图像=字段()

位置=字段()

#管家领域

网址=字段()

项目=字段()

蜘蛛=字段()

服务器=字段()

date=Field() 这是我们的第一段代码。请注意,Python 使用空格进行缩进。每个字段名称前面都有四个空格或一个制表符。如果一行有四个空格,另一行有三个空格,则会报语法错误。如果一行有四个空格,另一行有制表符,也会报错。空格字符指定这些项目位于PropertiesItem 下。其他一些语言使用大括号{},有些使用begin-end,而Python则使用空格。

编写爬虫完成一半。现在我们来编写爬虫。一般来说,每个网站或大型网站的一部分只有一个爬虫。爬虫代码成为UR2IM进程。

当然,你可以使用文本编辑器逐句编写爬虫,但更方便的方法是使用scrapy genspider 命令,如下所示:

$ scrapy genspider basic web 使用模块中的模板“basic”创建一个爬虫“basic”:

properties.spiders.basic 爬虫文件basic.py 出现在目录properties/spiders中。刚才的命令是生成一个名为basic的默认文件,该文件仅限于抓取网络上的URL。我们可以取消这个限制。该爬虫使用基本模板。可以使用scrapy genspider -l 查看所有模板,然后使用参数-t 使用模板生成想要的爬虫。稍后会介绍一个例子。

查看properties/spiders/basic.py文件,其代码如下:

导入scrapy

类BasicSpider(scrapy.Spider):

名称="基本"

allowed_domains=["网络"]

开始网址=(

"http://www.web/",

def 解析(自身,响应):

passimport命令允许我们使用Scrapy框架。然后定义一个类BasicSpider,它继承自scrapy.Spider。继承是指虽然我们没有写任何代码,但是这个类继承了Scrapy框架中Spider类的很多特性。这使得我们只需几行代码就可以拥有一个功能齐全的爬虫。然后我们看到了爬虫的一些参数,比如名称和爬取域字段名。最后,我们定义一个空函数parse(),它有两个参数self和response。通过self,你可以使用爬虫的一些有趣的功能。该响应看起来很熟悉,这是我们在Scrapy shell 中看到的响应。

让我们开始编辑这个爬虫。 start_URL 更改为Scrapy 命令行中使用的URL。然后使用爬虫预先准备好的log()方法输出内容。修改后的properties/spiders/basic.py文件为:

导入scrapy

类BasicSpider(scrapy.Spider):

名称="基本"

allowed_domains=["网络"]

起始网址=(

"http://web:9312/properties/property_000000.html",

def 解析(自身,响应):

self.log("title: %s" % response.xpath(

"//*[@itemprop="名称"][1]/text()").extract())

self.log("price: %s" % 响应.xpath(

"//*[@物品

prop="price"][1]/text()").re("[.0-9]+")) self.log("description: %s" % response.xpath( "//*[@itemprop="description"][1]/text()").extract()) self.log("address: %s" % response.xpath( "//*[@itemtype="http://schema.org/" "Place"][1]/text()").extract()) self.log("image_URL: %s" % response.xpath( "//*[@itemprop="image"][1]/@src").extract())总算到了运行爬虫的时间!让爬虫运行的命令是scrapy crawl接上爬虫的名字: $ scrapy crawl basic INFO: Scrapy 1.0.3 started (bot: properties) ... INFO: Spider opened DEBUG: Crawled (200)DEBUG: title: [u"set unique family well"] DEBUG: price: [u"334.39"] DEBUG: description: [u"website..."] DEBUG: address: [u"Angel, London"] DEBUG: image_URL: [u"../images/i01.jpg"] INFO: Closing spider (finished) ...成功了!不要被这么多行的命令吓到,后面我们再仔细说明。现在,我们可以看到使用这个简单的爬虫,所有的数据都用XPath得到了。 来看另一个命令,scrapy parse。它可以让我们选择最合适的爬虫来解析URL。用—spider命令可以设定爬虫: $ scrapy parse --spider=basic http://web:9312/properties/property_000001.html你可以看到输出的结果和前面的很像,但却是关于另一个房产的。填充一个项目接下来稍稍修改一下前面的代码。你会看到,尽管改动很小,却可以解锁许多新的功能。 首先,引入类PropertiesItem。它位于properties目录中的item.py文件,因此在模块properties.items中。它的导入命令是: from properties.items import PropertiesItem然后我们要实例化,并进行返回。这很简单。在parse()方法中,我们加入声明item = PropertiesItem(),它产生了一个新项目,然后为它分配表达式: item["title"] = response.xpath("//*[@itemprop="name"][1]/text()").extract()最后,我们用return item返回项目。更新后的properties/spiders/basic.py文件如下: import scrapy from properties.items import PropertiesItem class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] start_URL = ( "http://web:9312/properties/property_000000.html", ) def parse(self, response): item = PropertiesItem() item["title"] = response.xpath( "//*[@itemprop="name"][1]/text()").extract() item["price"] = response.xpath( "//*[@itemprop="price"][1]/text()").re("[.0-9]+") item["description"] = response.xpath( "//*[@itemprop="description"][1]/text()").extract() item["address"] = response.xpath( "//*[@itemtype="http://schema.org/" "Place"][1]/text()").extract() item["image_URL"] = response.xpath( "//*[@itemprop="image"][1]/@src").extract() return item现在如果再次运行爬虫,你会注意到一个不大但很重要的改动。被抓取的值不再打印出来,没有“DEBUG:被抓取的值”了。你会看到: DEBUG: Scraped from<200 http://...000.html>{"address": [u"Angel, London"], "description": [u"website ... offered"], "image_URL": [u"../images/i01.jpg"], "price": [u"334.39"], "title": [u"set unique family well"]}这是从这个页面抓取的PropertiesItem。这很好,因为Scrapy就是围绕Items的概念构建的,这意味着我们可以用pipelines填充丰富项目,或是用“Feed export”导出保存到不同的格式和位置。保存到文件试运行下面: $ scrapy crawl basic -o items.json $ cat items.json [{"price": ["334.39"], "address": ["Angel, London"], "description": ["website court ... offered"], "image_URL": ["../images/i01.jpg"], "title": ["set unique family well"]}] $ scrapy crawl basic -o items.jl $ cat items.jl {"price": ["334.39"], "address": ["Angel, London"], "description": ["website court ... offered"], "image_URL": ["../images/i01.jpg"], "title": ["set unique family well"]} $ scrapy crawl basic -o items.csv $ cat items.csv description,title,url,price,spider,image_URL... "...offered",set unique family well,,334.39,,../images/i01.jpg $ scrapy crawl basic -o items.xml $ cat items.xml334.39...不用我们写任何代码,我们就可以用这些格式进行存储。Scrapy可以自动识别输出文件的后缀名,并进行输出。这段代码中涵盖了一些常用的格式。CSV和XML文件很流行,因为可以被Excel直接打开。JSON文件很流行是因为它的开放性和与JavaScript的密切关系。JSON和JSON Line格式的区别是.json文件是在一个大数组中存储JSON对象。这意味着如果你有一个1GB的文件,你可能必须现在内存中存储,然后才能传给解析器。相对的,.jl文件每行都有一个JSON对象,所以读取效率更高。 不在文件系统中存储生成的文件也很麻烦。利用下面例子的代码,你可以让Scrapy自动上传文件到FTP或亚马逊的S3 bucket。 $ scrapy crawl basic -o "ftp://user:pass@ftp.scrapybook.com/items.json " $ scrapy crawl basic -o "s3://aws_key:aws_secret@scrapybook/items.json"注意,证书和URL必须按照主机和S3更新,才能顺利运行。 另一个要注意的是,如果你现在使用scrapy parse,它会向你显示被抓取的项目和抓取中新的请求: $ scrapy parse --spider=basic http://web:9312/properties/property_000001.html INFO: Scrapy 1.0.3 started (bot: properties) ... INFO: Spider closed (finished) >>>STATUS DEPTH LEVEL 1<<< # Scraped Items ------------------------------------------------ [{"address": [u"Plaistow, London"], "description": [u"features"], "image_URL": [u"../images/i02.jpg"], "price": [u"388.03"], "title": [u"belsize marylebone...deal"]}] # Requests ------------------------------------------------ []当出现意外结果时,scrapy parse可以帮你进行debug,你会更感叹它的强大。清洗——项目加载器和杂务字段恭喜你,你已经创建成功一个简单爬虫了!让我们让它看起来更专业些。 我们使用一个功能类,ItemLoader,以取代看起来杂乱的extract()和xpath()。我们的parse()进行如下变化:

def parse(self, response): l = ItemLoader(item=PropertiesItem(), response=response) l.add_xpath("title", "//*[@itemprop="name"][1]/text()") l.add_xpath("price", ".//*[@itemprop="price"]" "[1]/text()", re="[,.0-9]+") l.add_xpath("description", "//*[@itemprop="description"]" "[1]/text()") l.add_xpath("address", "//*[@itemtype=" ""http://schema.org/Place"][1]/text()") l.add_xpath("image_URL", "//*[@itemprop="image"][1]/@src") return l.load_item()是不是看起来好多了?事实上,它可不是看起来漂亮那么简单。它指出了我们现在要干什么,并且后面的加载项很清晰。这提高了代码的可维护性和自文档化。(自文档化,self-documenting,是说代码的可读性高,可以像文档文件一样阅读) ItemLoaders提供了许多有趣的方式整合数据、格式化数据、清理数据。它的更新很快,查阅文档可以更好的使用它,http://doc.scrapy.org/en/latest/topics/loaders。通过不同的类处理器,ItemLoaders从XPath/CSS表达式传参。处理器函数快速小巧。举一个Join()的例子。//p表达式会选取所有段落,这个处理函数可以在一个入口中将所有内容整合起来。另一个函数MapCompose(),可以与Python函数或Python函数链结合,实现复杂的功能。例如,MapCompose(float)可以将字符串转化为数字,MapCompose(unicode.strip, unicode.title)可以去除多余的空格,并将单词首字母大写。让我们看几个处理函数的例子: 处理函数功能Join()合并多个结果。MapCompose(unicode.strip)除去空格。MapCompose(unicode.strip, unicode.title)除去空格,单词首字母大写。MapCompose(float)将字符串转化为数字。MapCompose(lambda i: i.replace(",", ""), float)将字符串转化为数字,逗号替换为空格。MapCompose(lambda i: urlparse.urljoin(response.url, i))使用response.url为开头,将相对URL转化为绝对URL。你可以使用Python编写处理函数,或是将它们串联起来。unicode.strip()和unicode.title()分别用单一参数实现了单一功能。其它函数,如replace()和urljoin()需要多个参数,我们可以使用Lambda函数。这是一个匿名函数,可以不声明函数就调用参数: myFunction = lambda i: i.replace(",", "")可以取代下面的函数: def myFunction(i): return i.replace(",", "")使用Lambda函数,打包replace()和urljoin(),生成一个结果,只需一个参数即可。为了更清楚前面的表,来看几个实例。在scrapy命令行打开任何URL,并尝试: >>>from scrapy.loader.processors import MapCompose, Join >>>Join()(["hi","John"]) u"hi John" >>>MapCompose(unicode.strip)([u" I",u" amn"]) [u"I", u"am"] >>>MapCompose(unicode.strip, unicode.title)([u"nIce cODe"]) [u"Nice Code"] >>>MapCompose(float)(["3.14"]) [3.14] >>>MapCompose(lambda i: i.replace(",", ""), float)(["1,400.23"]) [1400.23] >>>import urlparse >>>mc = MapCompose(lambda i: urlparse.urljoin("http://my.com/test/abc", i)) >>>mc(["example.html#check"]) ["http://my.com/test/example.html#check"] >>>mc(["http://absolute/url#help"]) ["http://absolute/url#help"]要记住,处理函数是对XPath/CSS结果进行后处理的的小巧函数。让我们来看几个我们爬虫中的处理函数是如何清洗结果的: def parse(self, response): l.add_xpath("title", "//*[@itemprop="name"][1]/text()", MapCompose(unicode.strip, unicode.title)) l.add_xpath("price", ".//*[@itemprop="price"][1]/text()", MapCompose(lambda i: i.replace(",", ""), float), re="[,.0-9]+") l.add_xpath("description", "//*[@itemprop="description"]" "[1]/text()", MapCompose(unicode.strip), Join()) l.add_xpath("address", "//*[@itemtype="http://schema.org/Place"][1]/text()", MapCompose(unicode.strip)) l.add_xpath("image_URL", "//*[@itemprop="image"][1]/@src", MapCompose( lambda i: urlparse.urljoin(response.url, i)))完整的列表在本章后面给出。如果你用scrapy crawl basic再运行的话,你可以得到干净的结果如下: "price": [334.39], "title": [u"Set Unique Family Well"]最后,我们可以用add_value()方法添加用Python(不用XPath/CSS表达式)计算得到的值。我们用它设置我们的“杂务字段”,例如URL、爬虫名、时间戳等等。我们直接使用前面杂务字段表里总结的表达式,如下: l.add_value("url", response.url) l.add_value("project", self.settings.get("BOT_NAME")) l.add_value("spider", self.name) l.add_value("server", socket.gethostname()) l.add_value("date", datetime.datetime.now())记得import datetime和socket,以使用这些功能。 现在,我们的Items看起来就完美了。我知道你的第一感觉是,这可能太复杂了,值得吗?回答是肯定的,这是因为或多或少,想抓取网页信息并存到items里,这就是你要知道的全部。这段代码如果用其他语言来写,会非常难看,很快就不能维护了。用Scrapy,只要25行简洁的代码,它明确指明了意图,你可以看清每行的意义,可以清晰的进行修改、再利用和维护。 你的另一个感觉可能是处理函数和ItemLoaders太花费精力。如果你是一名经验丰富的Python开发者,你已经会使用字符串操作、lambda表达构造列表,再学习新的知识会觉得不舒服。然而,这只是对ItemLoader和其功能的简单介绍,如果你再深入学习一点,你就不会这么想了。ItemLoaders和处理函数是专为有抓取需求的爬虫编写者、维护者开发的工具集。如果你想深入学习爬虫的话,它们是绝对值得学习的。创建协议协议有点像爬虫的单元测试。它们能让你快速知道错误。例如,假设你几周以前写了一个抓取器,它包含几个爬虫。你想快速检测今天是否还是正确的。协议位于评论中,就在函数名后面,协议的开头是@。看下面这个协议: def parse(self, response): """ This function parses a property page. @url http://web:9312/properties/property_000000.html @returns items 1 @scrapes title price description address image_URL @scrapes url project spider server date """这段代码是说,检查这个URL,你可以在找到一个项目,它在那些字段有值。现在如果你运行scrapy check,它会检查协议是否被满足: $ scrapy check basic ---------------------------------------------------------------- Ran 3 contracts in 1.640s OK 如果url的字段是空的(被注释掉),你会得到一个描述性错误: FAIL: [basic] parse (@scrapes post-hook) ------------------------------------------------------------------ ContractFail: "url" field is missing当爬虫代码有错,或是XPath表达式过期,协议就可能失效。当然,协议不会特别详细,但是可以清楚的指出代码的错误所在。 综上所述,我们的第一个爬虫如下所示: from scrapy.loader.processors import MapCompose, Join from scrapy.loader import ItemLoader from properties.items import PropertiesItem import datetime import urlparse import socket import scrapy class BasicSpider(scrapy.Spider): name = "basic" allowed_domains = ["web"] # Start on a property page start_URL = ( "http://web:9312/properties/property_000000.html", ) def parse(self, response): """ This function parses a property page. @url http://web:9312/properties/property_000000.html @returns items 1 @scrapes title price description address image_URL @scrapes url project spider server date """ # Create the loader using the response l = ItemLoader(item=PropertiesItem(), response=response) # Load fields using XPath expressions l.add_xpath("title", "//*[@itemprop="name"][1]/text()", MapCompose(unicode.strip, unicode.title)) l.add_xpath("price", ".//*[@itemprop="price"][1]/text()", MapCompose(lambda i: i.replace(",", ""), float), re="[,.0-9]+") l.add_xpath("description", "//*[@itemprop="description"]" "[1]/text()", MapCompose(unicode.strip), Join()) l.add_xpath("address", "//*[@itemtype="http://schema.org/Place"]" "[1]/text()", MapCompose(unicode.strip)) l.add_xpath("image_URL", "//*[@itemprop="image"]" "[1]/@src", MapCompose( lambda i: urlparse.urljoin(response.url, i))) # Housekeeping fields l.add_value("url", response.url) l.add_value("project", self.settings.get("BOT_NAME")) l.add_value("spider", self.name) l.add_value("server", socket.gethostname()) l.add_value("date", datetime.datetime.now()) return l.load_item()提取更多的URL到目前为止,在爬虫的start_URL中我们还是只加入了一条URL。因为这是一个元组,我们可以向里面加入多个URL,例如: start_URL = ( "http://web:9312/properties/property_000000.html", "http://web:9312/properties/property_000001.html", "http://web:9312/properties/property_000002.html", )不够好。我们可以用一个文件当做URL源文件: start_URL = [i.strip() for i in open("todo.URL.txt").readlines()]还是不够好,但行得通。更常见的,网站可能既有索引页也有列表页。例如,Gumtree有索引页:http://www.gumtree.com/flats-houses/london: 一个典型的索引页包含许多列表页、一个分页系统,让你可以跳转到其它页面。 因此,一个典型的爬虫在两个方向移动: 水平——从索引页到另一个索引页垂直——从索引页面到列表页面提取项目在本书中,我们称前者为水平抓取,因为它在同一层次(例如索引)上抓取页面;后者为垂直抓取,因为它从更高层次(例如索引)移动到一个较低的层次(例如列表)。 做起来要容易许多。我们只需要两个XPath表达式。第一个,我们右键点击Next page按钮,URL位于li中,li的类名含有next。因此XPath表达式为//*[contains(@class,"next")]//@href。 对于第二个表达式,我们在列表的标题上右键点击,选择检查元素: 这个URL有一个属性是itemprop="url。因此,表达式确定为//*[@itemprop="url"]/@href。打开scrapy命令行进行确认: $ scrapy shell http://web:9312/properties/index_00000.html >>>URL = response.xpath("//*[contains(@class,"next")]//@href").extract() >>>URL [u"index_00001.html"] >>>import urlparse >>>[urlparse.urljoin(response.url, i) for i in URL] [u"http://web:9312/scrapybook/properties/index_00001.html"] >>>URL = response.xpath("//*[@itemprop="url"]/@href").extract() >>>URL [u"property_000000.html", ... u"property_000029.html"] >>>len(URL) 30 >>>[urlparse.urljoin(response.url, i) for i in URL] [u"http://..._000000.html", ... /property_000029.html"]很好,我们看到有了这两个表达式,就可以进行水平和垂直抓取URL了。使用爬虫进行二维抓取将前一个爬虫代码复制到新的爬虫manual.py中: $ ls properties scrapy.cfg $ cp properties/spiders/basic.py properties/spiders/manual.py在properties/spiders/manual.py中,我们通过添加from scrapy.http import Request引入Request,将爬虫的名字改为manual,将start_URL改为索引首页,将parse()重命名为parse_item()。接下来写心得parse()方法进行水平和垂直的抓取: def parse(self, response): # Get the next index URL and yield Requests next_selector = response.xpath("//*[contains(@class," ""next")]//@href") for url in next_selector.extract(): yield Request(urlparse.urljoin(response.url, url)) # Get item URL and yield Requests item_selector = response.xpath("//*[@itemprop="url"]/@href") for url in item_selector.extract(): yield Request(urlparse.urljoin(response.url, url), callback=self.parse_item)提示:你可能注意到了yield声明。它和return很像,不同之处是return会退出循环,而yield不会。从功能上讲,前面的例子与下面很像 next_requests = [] for url in... next_requests.append(Request(...)) for url in... next_requests.append(Request(...)) return next_requestsyield可以大大提高Python编程的效率。 做好爬虫了。但如果让它运行起来的话,它将抓取5万张页面。为了避免时间太长,我们可以通过命令-s CLOSESPIDER_ITEMCOUNT=90(更多的设定见第7章),设定爬虫在一定数量(例如,90)之后停止运行。开始运行: $ scrapy crawl manual -s CLOSESPIDER_ITEMCOUNT=90 INFO: Scrapy 1.0.3 started (bot: properties) ... DEBUG: Crawled (200)<...index_00000.html>(referer: None) DEBUG: Crawled (200)<...property_000029.html>(referer: ...index_00000.html) DEBUG: Scraped from<200 ...property_000029.html>{"address": [u"Clapham, London"], "date": [datetime.datetime(2015, 10, 4, 21, 25, 22, 801098)], "description": [u"situated camden facilities corner"], "image_URL": [u"http://web:9312/images/i10.jpg"], "price": [223.88], "project": ["properties"], "server": ["scrapyserver1"], "spider": ["manual"], "title": [u"Portered Mile"], "url": ["http://.../property_000029.html"]} DEBUG: Crawled (200)<...property_000028.html>(referer: ...index_00000. html) ... DEBUG: Crawled (200)<...index_00001.html>(referer: ...) DEBUG: Crawled (200)<...property_000059.html>(referer: ...) ... INFO: Dumping Scrapy stats: ... "downloader/request_count": 94, ... "item_scraped_count": 90,查看输出,你可以看到我们得到了水平和垂直两个方向的结果。首先读取了index_00000.html, 然后产生了许多请求。执行请求的过程中,debug信息指明了谁用URL发起了请求。例如,我们看到,property_000029.html, property_000028.html ... 和 index_00001.html都有相同的referer(即index_00000.html)。然后,property_000059.html和其它网页的referer是index_00001,过程以此类推。 这个例子中,Scrapy处理请求的机制是后进先出(LIFO),深度优先抓取。最后提交的请求先被执行。这个机制适用于大多数情况。例如,我们想先抓取完列表页再取下一个索引页。不然的话,我们必须消耗内存存储列表页的URL。另外,许多时候你想用一个辅助的Requests执行一个请求,下一章有例子。你需要Requests越早完成越好,以便爬虫继续下面的工作。 我们可以通过设定Request()参数修改默认的顺序,大于0时是高于默认的优先级,小于0时是低于默认的优先级。通常,Scrapy会先执行高优先级的请求,但不会花费太多时间思考到底先执行哪一个具体的请求。在你的大多数爬虫中,你不会有超过一个或两个的请求等级。因为URL会被多重过滤,如果我们想向一个URL多次请求,我们可以设定参数dont_filter Request()为True。用CrawlSpider二维抓取如果你觉得这个二维抓取单调的话,说明你入门了。Scrapy试图简化这些琐事,让编程更容易。完成之前结果的更好方法是使用CrawlSpider,一个简化抓取的类。我们用genspider命令,设定一个-t参数,用爬虫模板创建一个爬虫: $ scrapy genspider -t crawl easy web Created spider "crawl" using template "crawl" in module: properties.spiders.easy现在properties/spiders/easy.py文件包含如下所示: ... class EasySpider(CrawlSpider): name = "easy" allowed_domains = ["web"] start_URL = ["http://www.web/"] rules = ( Rule(LinkExtractor(allow=r"Items/"), callback="parse_item", follow=True), ) def parse_item(self, response): ...这段自动生成的代码和之前的很像,但是在类的定义中,这个爬虫从CrawlSpider定义的,而不是Spider。CrawlSpider提供了一个包含变量rules的parse()方法,以完成之前我们手写的内容。 现在将start_URL设定为索引首页,并将parse_item()方法替换。这次不再使用parse()方法,而是将rules变成两个rules,一个负责水平抓取,一个负责垂直抓取: rules = ( Rule(LinkExtractor(restrict_xpaths="//*[contains(@class,"next")]")), Rule(LinkExtractor(restrict_xpaths="//*[@itemprop="url"]"), callback="parse_item") )两个XPath表达式与之前相同,但没有了a与href的限制。正如它们的名字,LinkExtractor专门抽取链接,默认就是寻找a、href属性。你可以设定tags和attrs自定义LinkExtractor()。对比前面的请求方法Requests(self.parse_item),回调的字符串中含有回调方法的名字(例如,parse_item)。最后,除非设定callback,一个Rule就会沿着抽取的URL扫描外链。设定callback之后,Rule才能返回。如果你想让Rule跟随外链,你应该从callback方法return/yield,或设定Rule()的follow参数为True。当你的列表页既有Items又有其它有用的导航链接时非常有用。 你现在可以运行这个爬虫,它的结果与之前相同,但简洁多了: $ scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=90总结对所有学习Scrapy的人,本章也许是最重要的。你学习了爬虫的基本流程UR2IM、如何自定义Items、使用ItemLoaders,XPath表达式、利用处理函数加载Items、如何yield请求。我们使用Requests水平抓取多个索引页、垂直抓取列表页。最后,我们学习了如何使用CrawlSpider和Rules简化代码。多度几遍本章以加深理解、创建自己的爬虫。 我们刚刚从一个网站提取了信息。它的重要性在哪呢?答案在下一章,我们只用几页就能制作一个移动app,并用Scrapy填充数据。

用户评论

熟悉看不清

学习Scrapy 第3章的内容简直太棒了!感觉对爬虫的基础有了更深的理解。

    有20位网友表示赞同!

∞◆暯小萱◆

这节课讲得深入浅出,很好地介绍了爬虫的基本原理。

    有8位网友表示赞同!

一笑抵千言

终于明白Scrapy框架的强大之处了,可以高效地构建各种类型的爬虫。

    有13位网友表示赞同!

伤离别

之前对爬虫的概念一直比较模糊,看完这章感觉豁然开朗!

    有7位网友表示赞同!

╭摇划花蜜的午后

有好多实用的爬虫实例,能够直接应用到实际项目中。

    有13位网友表示赞同!

烬陌袅

学习爬虫很重要啊,可以从互联网上获取大量的数据分析。

    有19位网友表示赞同!

孤者何惧

以后想尝试自己用Scrapy爬一些感兴趣的网站信息了。

    有8位网友表示赞同!

葵雨

第3章的内容讲解得非常清晰,图片和代码都很容易理解。

    有18位网友表示赞同!

哽咽

这类基础知识还是需要花时间好好学习一下的。

    有7位网友表示赞同!

终究会走-

感觉这个课程学来学去就是基础基础!但扎实掌握的基础才能更上一层楼啊!

    有5位网友表示赞同!

冷落了♂自己·

"Learning Scrapy" 中文版的教程太好了,非常适合入门学习。

    有7位网友表示赞同!

代价是折磨╳

想通过爬虫实现一些自动化任务,这章的内容很有帮助。

    有12位网友表示赞同!

青瓷清茶倾城歌

之前尝试过其他的爬虫框架,Scrapy 真是太强大了!

    有14位网友表示赞同!

軨倾词

感谢作者制作这样的优秀教程,学习起来更加方便了!

    有11位网友表示赞同!

余温散尽ぺ

学习一个编程框架的开始总是需要从基础知识入手的,这章内容很棒!

    有7位网友表示赞同!

情字何解ヘ

对爬虫技术一直充满好奇,这次正好来学习一下Scrapy。

    有10位网友表示赞同!

怪咖

希望后续课程也能一样精彩有趣,期待深入了解Scrapy框架。

    有16位网友表示赞同!

冷青裳

这堂课帮助我建立了爬虫的基础知识体系,打下了一个坚实的基矗!

    有12位网友表示赞同!

【《Scrapy入门教程》第三章:爬虫基础知识】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活