爬虫-Scrapy 快速入门指南
简介
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据或者通用的网络爬虫。
使用建议,爬取数据的时候为了防止ip地址被封禁,有以下几个建议:
1.修改配置文件伪造session信息。
2.抓取间隔时间尽量要长。
3.通过代理使用ip地址池,循环使用,避免使用一个地址。
4.请求透过Tor Browser,防止自身ip被禁。
安装
由于scrapy依赖的安装包很多,所以推荐使用pip工具实现快速安装。安装命令如下:pip install Scrapy 。其他安装方式可以参考官方文档。
注:先下载pip安装文件,下载地址:https://bootstrap.pypa.io/get-pip.py,然后运行pip安装命令:python get-pip.py。
实例
将抓取quotes.toscrape.com网站数据,这是一个列出著名作家引用的网站。
本教程将引导您完成这些任务:
1.创建一个新的Scrapy项目
2.编写爬虫抓取网站并提取数据
3.使用命令行导出抓取的数据
4.更改爬虫递归跟随链接
5.使用爬虫参数
创建Scrapy项目
选择工程存放目录,创建名为tutorial的工程,然后运行如下命令:scrapy startproject tutorial.
这将创建一个包含以下内容的tutorial目录:
tutorial/
scrapy.cfg # 部署配置文件
tutorial/ #项目的Python模块,从这里导入你的代码
__init__.py
items.py # 项目提取字段定义文件
middlewares.py # 项目中间件文件,用于定义属于自己特殊应用的中间件
pipelines.py # 项目管道文件
settings.py # 项目配置文件
spiders/ #编写自己抓取各种数据方法目录
__init__.py
编写Spider
Spiders是自己定义的类,Scrapy用来从网站(或一组网站)上抓取信息。 他们必须对scrapy.Spider进行子类化并定义初始请求,可选择如何关注页面中的链接,以及如何解析下载的页面内容以提取数据。
在tutorial/spiders目录下,创建名为quotes_spider.py的文件,用于编写具体的抓取方法。
实例代码如下:
import scrapy
class QuotesSpider(scrapy.Spider):
name="quotes" #爬虫的名称,启动爬虫时需要该爬虫名称。
def start_requests(self):
urls=[
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page=response.url.split("/")[-2]
filename='quotes-%s.html'%page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s'%filename)
为Spider子类(QuotesSpider)scrapy.Spider定义了一些属性和方法:
名称:爬虫名字。 它在项目中必须是唯一的,也就是说,不能为不同的爬虫设置相同的名称。
start_requests():必须返回一个迭代请求(你可以返回一个请求列表或者写一个生成器函数),Spider将开始抓取它。 随后的请求将从这些初始请求中连续生成。
parse():将被调用来处理为每个请求下载的响应的方法。 响应参数是TextResponse的一个实例,用于保存页面内容,并有更多有用的方法来处理它。
parse()方法通常解析响应,将提取的数据提取为字符串,并找到新的URL来跟踪并创建新的请求(Request)。
如何启动爬虫
在工程的同级目录运行命令:scrapy crawl quotes。
该命令使用刚刚添加的名称引号运行该爬虫,该爬虫将向quotes.toscrape.com域发送一些请求。 你会得到类似于这样的输出:
...(omittedforbrevity)
2016-12-16 21:24:05[scrapy.core.engine] INFO: Spider opened
2016-12-16 21:24:05[scrapy.extensions.logstats] INFO: Crawled 0 pages(at 0 pages/min), scraped 0 items(at 0 items/min)
2016-12-16 21:24:05[scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
....
现在,检查当前目录中的文件。 您应该注意到已经创建了两个新文件:quotes-1.html和quotes-2.html,以及相应URL的内容,正如分析方法指示的那样。
爬虫运行的原理
Scrapy安排由Spider的start_requests方法返回的scrapy.Request对象。 在收到每个响应后,它会实例化Response对象,并调用与请求相关的回调方法(在本例中为parse方法),将响应作为参数传递。
除了实现从URL生成scrapy.Request对象的start_requests()方法外,您还可以使用URL列表定义start_urls类属性。 这个列表将被默认的start_requests()实现用来为你的爬虫创建初始请求:
import scrapy
class DmozSpider(scrapy.spiders.Spider):
name="dmoz"
allowed_domains=["dmoz.org"]
start_urls= ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]
def parse(self,response):
filename=response.url.split("/")[-2]
with open(filename,'wb')asf:
f.write(response.body)
该实例将调用parse()方法来处理这些URL的每个请求,即使没有明确告诉Scrapy这样做。 发生这种情况是因为parse()是Scrapy的默认回调方法,该方法在未显式分配回调的情况下针对请求调用。
提取数据
学习如何使用Scrapy提取数据的最佳方法是使用Scrapy shell,运行:
scrapy shell 'http://quotes.toscrape.com/page/1/'
输出如下:
[...Scrapyloghere...]2016-09-1912:09:27[scrapy.core.engine]DEBUG:Crawled(200)<GEThttp://quotes.toscrape.com/page/1/>(referer:None)[s]AvailableScrapyobjects:[s]scrapyscrapymodule(containsscrapy.Request,scrapy.Selector,etc)[s]crawler<scrapy.crawler.Crawlerobjectat0x7fa91d888c90>[s]item{}[s]request<GEThttp://quotes.toscrape.com/page/1/>[s]response<200http://quotes.toscrape.com/page/1/>[s]settings<scrapy.settings.Settingsobjectat0x7fa91d888c10>[s]spider<DefaultSpider'default'at0x7fa91c8af990>[s]Usefulshortcuts:[s]shelp()Shellhelp(printthishelp)[s]fetch(req_or_url)Fetchrequest(orURL)andupdatelocalobjects[s]view(response)Viewresponseinabrowser>>>
查看包含 [dmoz] 的输出,可以看到输出的log中包含定义在 start_urls 的初始URL,并且与spider中是一一对应的。在log中可以看到其没有指向其他页面( (referer:None) )。
除此之外,更有趣的事情发生了。就像parse 方法指定的那样,有两个包含url所对应的内容的文件被创建了: Book , Resources 。
刚才发生了什么?
Scrapy为Spider的 start_urls 属性中的每个URL创建了 scrapy.Request 对象,并将 parse 方法作为回调函数(callback)赋值给了Request。
Request对象经过调度,执行生成 scrapy.http.Response 对象并送回给spider parse() 方法。
提取Item
Selectors选择器简介
从网页中提取数据有很多方法。Scrapy使用了一种基于 XPath 和 CSS 表达式机制: Scrapy Selectors。 关于selector和其他提取机制的信息请参考 Selector文档 。
这里给出XPath表达式的例子及对应的含义:
/html/head/title: 选择HTML文档中 <head> 标签内的 <title> 元素
/html/head/title/text(): 选择上面提到的 <title> 元素的文字
//td: 选择所有的 <td> 元素
//div[@class="mine"]: 选择所有具有 class="mine" 属性的 div 元素
上边仅仅是几个简单的XPath例子,XPath实际上要比这远远强大的多。 如果您想了解的更多,推荐官方文档。
为了配合XPath,Scrapy除了提供了 Selector 之外,还提供了方法来避免每次从response中提取数据时生成selector的麻烦。
Selector有四个基本的方法(点击相应的方法可以看到详细的API文档):
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
extract(): 序列化该节点为unicode字符串并返回list。
re(): 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
在Shell中尝试Selector选择器
为了介绍Selector的使用方法,接下来将要使用内置的 Scrapy shell 。Scrapy Shell需要您预装好IPython(一个扩展的Python终端)。
您需要进入项目的根目录,执行下列命令来启动shell:
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
注解
当您在终端运行Scrapy时,请一定记得给url地址加上引号,否则包含参数的url(例如 & 字符)会导致Scrapy运行失败。
shell的输出类似:
[...Scrapyloghere...]2015-01-0722:01:53+0800[domz]DEBUG:Crawled(200)<GEThttp://www.dmoz.org/Computers/Programming/Languages/Python/Books/>(referer:None)[s]AvailableScrapyobjects:[s]crawler<scrapy.crawler.Crawlerobjectat0x02CE2530>[s]item{}[s]request<GEThttp://www.dmoz.org/Computers/Programming/Languages/Python/Books/>[s]response<200http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>[s]sel<Selectorxpath=Nonedata=u'<html lang="en">\r\n<head>\r\n<meta http-equ'>[s]settings<CrawlerSettingsmodule=<module'tutorial.settings'from'tutorial\settings.pyc'>>[s]spider<DomzSpider'domz'at0x302e350>[s]Usefulshortcuts:[s]shelp()Shellhelp(printthishelp)[s]fetch(req_or_url)Fetchrequest(orURL)andupdatelocalobjects[s]view(response)Viewresponseinabrowser>>>
当shell载入后,您将得到一个包含response数据的本地 response 变量。输入 response.body 将输出response的包体, 输出 response.headers 可以看到response的包头。
更为重要的是,当输入 response.selector 时, 您将获取到一个可以用于查询返回数据的selector(选择器), 以及映射到 response.selector.xpath() 、 response.selector.css() 的 快捷方法(shortcut): response.xpath() 和 response.css() 。
同时,shell根据response提前初始化了变量 sel 。该selector根据response的类型自动选择最合适的分析规则(XML vs HTML)。
来试试:
In[1]:sel.xpath('//title')
Out[1]:[<Selectorxpath='//title'data=u'<title>Open Directory - Computers: Progr'>]
In[2]:sel.xpath('//title').extract()
Out[2]:[u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']
In[3]:sel.xpath('//title/text()')
Out[3]:[<Selectorxpath='//title/text()'data=u'Open Directory - Computers: Programming:'>]
In[4]:sel.xpath('//title/text()').extract()
Out[4]:[u'Open Directory - Computers: Programming: Languages: Python: Books']
In[5]:sel.xpath('//title/text()').re('(\w+):')
Out[5]:[u'Computers',u'Programming',u'Languages',u'Python']
提取数据
现在,来尝试从这些页面中提取些有用的数据。
您可以在终端中输入 response.body 来观察HTML源码并确定合适的XPath表达式。不过,这任务非常无聊且不易。您可以考虑使用Firefox的Firebug扩展来使得工作更为轻松。详情请参考 使用Firebug进行爬取 和 借助Firefox来爬取 。
在查看了网页的源码后,您会发现网站的信息是被包含在 第二个 <ul> 元素中。
可以通过这段代码选择该页面中网站列表里所有 <li> 元素:
sel.xpath('//ul/li')
网站的描述:
sel.xpath('//ul/li/text()').extract()
网站的标题:
sel.xpath('//ul/li/a/text()').extract()
以及网站的链接:
sel.xpath('//ul/li/a/@href').extract()
之前提到过,每个 .xpath() 调用返回selector组成的list,因此可以拼接更多的 .xpath() 来进一步获取某个节点。将在下边使用这样的特性:
forselinresponse.xpath('//ul/li'):title=sel.xpath('a/text()').extract()link=sel.xpath('a/@href').extract()desc=sel.xpath('text()').extract()printtitle,link,desc
注解
关于嵌套selctor的更多详细信息,请参考 嵌套选择器(selectors) 以及 选择器(Selectors) 文档中的 使用相对XPaths 部分。
在spider中加入这段代码:
import scrapy
class DmozSpider(scrapy.Spider):
name="dmoz"allowed_domains=["dmoz.org"]
start_urls= ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]
def parse(self,response):
for sel in response.xpath('//ul/li'):
title=sel.xpath('a/text()').extract()
link=sel.xpath('a/@href').extract()
desc=sel.xpath('text()').extract()
printtitle,link,desc
现在尝试再次爬取dmoz.org,您将看到爬取到的网站信息被成功输出:
scrapy crawl dmoz
使用item
Item 对象是自定义的python字典。 您可以使用标准的字典语法来获取到其每个字段的值。(字段即是之前用Field赋值的属性):
>>> item=DmozItem()>>> item['title']='Example title'>>> item['title']'Example title'
一般来说,Spider将会将爬取到的数据以 Item 对象返回。所以为了将爬取的数据返回,最终的代码将是:
import scrapy
from tutorial.items import DmozItem
class DmozSpider(scrapy.Spider):
name="dmoz"allowed_domains=["dmoz.org"]
start_urls= ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]
def parse(self,response):
for sel in response.xpath('//ul/li'):
item=DmozItem()
item['title']=sel.xpath('a/text()').extract()
item['link']=sel.xpath('a/@href').extract()
item['desc']=sel.xpath('text()').extract()
yield item
注解
您可以在 dirbot 项目中找到一个具有完整功能的spider。该项目可以通过 https://github.com/scrapy/dirbot 找到。
现在对dmoz.org进行爬取将会产生 DmozItem 对象:
[dmoz]DEBUG:Scrapedfrom<200http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>{'desc':[u' - By David Mertz; Addison Wesley. Book in progress, full text, ASCII format. Asks for feedback. [author website, Gnosis Software, Inc.\n],'link':[u'http://gnosis.cx/TPiP/'],'title':[u'Text Processing in Python']}[dmoz]DEBUG:Scrapedfrom<200http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>{'desc':[u' - By Sean McGrath; Prentice Hall PTR, 2000, ISBN 0130211192, has CD-ROM. Methods to build XML applications fast, Python tutorial, DOM and SAX, new Pyxie open source XML processing library. [Prentice Hall PTR]\n'],'link':[u'http://www.informit.com/store/product.aspx?isbn=0130211192'],'title':[u'XML Processing with Python']}
保存爬取到的数据
最简单存储爬取的数据的方式是使用 Feed exports:
scrapy crawl dmoz-oitems.json
该命令将采用 JSON 格式对爬取的数据进行序列化,生成 items.json 文件。
在类似本篇教程里这样小规模的项目中,这种存储方式已经足够。 如果需要对爬取到的item做更多更为复杂的操作,您可以编写 Item Pipeline 。 类似于我们在创建项目时对Item做的,用于您编写自己的 tutorial/pipelines.py 也被创建。 不过如果您仅仅想要保存item,您不需要实现任何的pipeline。