大家好,感谢邀请,今天来为大家分享一下《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入门教程》第三章:爬虫基础知识】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
学习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位网友表示赞同!