菜鸟都能看懂的爬虫Scrapy入门指南

640?wx_fmt=gif

爬虫,就是抓取网络数据的小脚本,把访问资源,获取数据,入库保存这一过程自动化的工具。 “挂机神器”按键精灵,“抢课神器”Selenium都可以是很好的爬虫工具,想必大家对这两者已经是比较熟悉了。

但是在生产工作中,大部分情况下,使用前述两种工具虽然能获取数据,但是对于服务器资源的开销比较大,且效率通常达不到要求。究其原因,这两个工具在获得相应数据之后还需要渲染展示,这个环节会造成额外的资源开销。因此,提高数据抓取的效率还需要返璞归真,回到最初的原点,把原始的请求响应拿来研究一下。


在浏览器中输入链接并回车,就能看到展示的网页了,那我们先来瞅一眼输入的URL:

640?wx_fmt=png

网页数据的请求和访问在应用层通常是基于HTTP(Hypertext Transfer Protocol,超文本传输协议),那HTTP协议是怎样封装传输的数据呢?

640?wx_fmt=png

一个HTTP请求分为起始行,头部字段和请求体三个部分。其中,起始行包含了该请求的基本信息:请求方法,请求资源路径和请求协议/版本,是构建一个请求最基本和重要的;其次是若干行的头部字段(HEADERS),包含了该请求的描述信息,例如Cookies,Encoding和User-Agent等;最后是请求体部分,其中有具体的请求参数等信息。

一般来说,常用的请求方法有GET和POST两种,其中GET方法的请求参数信息通过键值对的形式被存放在URL中,在资源路径之后通过“?”开始以key=value的方式,键值对之间用“&”隔开。因此GET方法是没有请求体内容的。 相对而言,POST请求的请求参数是按照访问接口要求以一定的组织方式放在请求体中,所以POST请求通常是有请求体内容的。

640?wx_fmt=png

看完请求我们来看看请求的响应。

一个HTTP请求的响应也分为三个部分: 状态行,头部字段和响应体。 和HTTP请求一样,状态行也包含了该响应的描述信息,包括传输协议/版本,响应状态码和状态短语,一般成功的请求会得到“200 OK”的状态;响应头中往往包含解读响应体内容的信息,例如Encoding,Content-Type和Set-Cookie等的信息,有些信息对于数据抓取的整个流程是非常关键的;响应的内容全部都在响应体中以一定的结构呈现,常见的数据格式有HTML和JSON。


了解完了HTTP协议,下面就来尝试着用Python发送一个HTTP请求。一般我们会使用requests模块(GitHub:https://github.com/kennethreitz/requests.git)。

因为它是“唯一的一个非转基因的 PythonHTTP 库,人类可以安全享用”,并且“非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子症、啃文档症、抑郁、头疼、甚至死亡”(以上理由来自requests文档:http://cn.python-requests.org/zh_CN/latest/)。赶紧发个HTTP请求压压惊:

640?wx_fmt=png

请求三连可以这样愉快的完成。 “Requests 允许你发送纯天然,植物饲养的 HTTP/1.1 请求,无需手工劳动。 你不需要手动为 URL 添加查询字串,也不需要对 POST 数据进行表单编码。 Keep-alive 和 HTTP 连接池的功能是 100% 自动化的,一切动力都来自于根植在 Requests 内部的 urllib3”正如文档介绍的,requests基于urllib3,封装整合了一系列功能,给出了十分优雅简洁的API,称得上是玩转HTTP的必备良药。

然而我们需要的数据很可能是数以万计,十万计甚至更多,每获取一条数据通常需要进行两次以上的请求。这导致我们依次进行请求所消耗的时间将不可接受。并发就成为了一个必选项。 这里我们可以尝试一下Python3.4+的新特性:asyncio(Asynchronous I/O)。 Asynchronous I/O即异步IO,它基于事件循环实现Python协程的并发运行,并控制其流程。对于爬虫这样频繁进行IO操作的场景有非常好的适应性。在新特性的加持下,我们可以通过async和await关键字来定义一个协程(coroutine)和等待返回,然后在一个事件循环(eventloop)中运行它。用同步代码的风格写一个异步过程:

640?wx_fmt=png

尝试一下就会发现效率有了非常大的提升,几乎就是发起一次请求的耗时。并发一时爽,一直并发一直爽,吗?实际应用中,并发数的设置应视情况而定,过高的并发会影响目标网站的正常运行,还会使数据来源不可用,要避免这种状况。

然而问题又来了,编写过几个爬虫之后我们发现,很多代码无法重用导致开发效率不高,爬虫框架Scrapy中就封装了一些常用的模块和工具。


Scrapy框架是完全用Python实现的网络爬虫框架(GitHub: https://github.com/scrapy/scrapy/)。其中封装了大量常用的脚本和流程代码,可以大大提高开发的效率。Scrapy还是个非常灵活的框架,我们可以自主编写框架组件以控制框架的行为,从而达到自己想要的效果。为了了解Scrapy,先上一张经典图:


640?wx_fmt=png


Scrapy的组件主要分为:


一个完整工作流程如下:

640?wx_fmt=png

其中,Scrapy会实例化数个下载器以并发的方式进行请求的发送,除此之外其他部分组件均为同步顺序执行,因此在组件中尽可能的避免会导致频繁阻塞的代码可以避免影响框架的运行效率。


Scrapy组件中最为常用的为Spider,Download Middleware,Pipeline,我们分别看下:

Spider

640?wx_fmt=png


Spider是处理响应的类对象,是爬虫中最为多样的部分,一般情况下也是码代码最多的部分。

我们编写的spider类一般为scrapy.spider的子类,spider类中有类属性“name”,scrapy就是根据这个类属性来区分同一工程项目下不同的爬虫。

一次爬取开始于一个或若干起始URL,一般会默认存放于类属性start_urls中,默认由start_request方法遍历其中的URL并以GET方法请求,该方法默认将发出的请求会绑定parse方法作为回调函数(callback)。

请求和回调函数:request对象的callback属性的值为spider类中的一个方法(非执行结果),引擎将request交给下载器处理之后等待下载器获得响应,当下载器返回响应信息,引擎将调用上述request的callback属性指明的函数处理响应。

Spider类中的方法最主要功能就是作为请求的回调函数来处理请求的响应信息。接收一个参数:response对象,该对象中包含了响应的所有信息包括响应状态码(status)、响应头(headers)、响应体(body)和响应文本(text)等。另外,response对象还封装了解析html的选择器工具Xpath(css选择器路径将首先被转换为xpath路径)。


值得注意的是spider中的函数均为生成器函数,不是用return,而是用yield关键字返回数据,其原因简单说来就是这些方法在引擎调用时是作为回调函数(twisted.inlineCallback)绑定与请求的回调链中,在处理完一个response之后需要用yield挂起等待下一个调用。所以这些方法不能以普通函数的形式调用,直接调用返回的只是一个生成器对象,另外,由于异步并发的原因,方法内部也非一次就能执行一遍,在yield挂起之后将在下一个事件循环中才继续往下执行。


Downloader Middleware:

中间件的设置与开启在Settings模块中,以字典的形式表现,key为中间件模块的导入路径(同import路径),value为一个整数,表示该中间件的权重。对于下载中间件来说,权重约小,离引擎就越近,权重越大越靠近下载器。在处理Request和Response的时候都是按经过顺序依次进行。

640?wx_fmt=png


下载中间件是Scrapy框架中非常灵活的部分,可以高度自由的定制框架处理请求和响应的过程,但是使用起来稍有些复杂,我们来看一下中间件中的方法:process_request、process_response和process_exception三个方法。

process_request 在请求经过时被调用,它接受两个参数:request和spider,在引擎调用的时候回默认传入,分别为待处理的request对象和产生该请求的spider对象,在这个方法中可以对request对象进行加工,增加其属性或在META字典中增加字段来传递信息。这个方法允许返回request对象或返回一个IgnoreRequest异常或者无返回(即返回None)。在返回request对象时,这个对象不会再传递给接下来的中间件,而是重新进入调度器队列中等待引擎调度。返回异常时该异常会作为中间件process_exception方法的参数传入,若无process_exception方法,则会交给该请求err_back绑定的方法处理。若无返回则将request对象交给下一中间件处理。

process_response 在响应被返回经过时调用,接受3个参数,分别为request对象,response对象和spider对象。spider对象同上,response对象为下载器返回的响应,而request对象则表示产生该响应的请求,与spider中的response有所不同,此处的response对象中没有request属性,而是同作为参数传入。在这个方法中可以对下载器返回的响应进行处理,包括响应的过滤,校验,以及与process_request中耦合的操作,如Cookie池和代理池的操作等。该方法允许3中返回:request对象,response对象和IgnoreRequest异常。当返回request对象时同上一方法,当返回response对象时,该response会交由下一中间价继续处理,而返回异常则直接交给该请求err_back绑定的方法处理。

process_exception 方法在下载器或prcoess_request返回异常时被调用,接受3个参数:request对象,exception对象和spider对象,其中request对象为产生该异常的请求。它有3种返回分别为request对象,response对象和无返回。当无返回时,该异常将被后续的中间件处理;当返回request对象和response对象时,均不会再调用后续的process_exception方法,前者将会交给调度器加入队列,后者将会交由process_response处理。

可以看出,中间件就是一个钩子,可以拦截并加工甚至替换请求和响应,可以按照我们的意愿来控制下载器接受和返回的对象。而由于爬虫工作的过程主要是处理各种请求和响应,所以下载中间件可以极大地控制爬虫的运行。

Pipelines

数据管道是Scrapy中处理数据的组件。当spider中的方法yield一个字典或scrapy.Item类及其子类时,引擎会将其交给pipelines处理。Pipelines和中间件十分类似,数据条目将以此通过pipeline进行处理,pipeline的权重约小,则越早被调用。

640?wx_fmt=png


一个pipeline对象需要实现process_item方法,接受item,spider两个参数,其中item即为所处理的数据。该方法可返回item(字典或scrapy.Item类及其子类)或DropItem异常或Twisted Deferred对象。返回的item会交由下一个pipeline继续处理,而返回的是DropItem异常时,则不会向再后传递item,此外,一些费时的操作(例如插入数据库)可通过返回Deferred对象实现异步操作。

上述组件中均可实现open_spider,close_spider和from_crawler等方法,用于在流程中的开始和结束时,以及初始化时进行自定义设置或资源初始化和回收操作等,使整个流程更加“gracefully”。


Scrapy是个优秀的框架,有着友善的文档和活跃的社区。其实抛开文档,scrapy源码就是最好的文档,从阅读代码中可以收获不少scrapy使用和框架设计的技巧。爬虫是一件有意思的事情,从异步到并发,从cookie登录到JS加密,总能发现惊喜。希望大家在获取数据的同时也能收获一份乐趣。


640?wx_fmt=png

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值