整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

网络爬虫框架Scrapy详解之Request

网络爬虫框架Scrapy详解之Request

介: Request类是一个http请求的类,对于爬虫而言是一个很重要的类。通常在Spider中创建这样的一个请求,在Downloader中执行这样的一个请求。同时也有一个子类FormRequest继承于它,用于post请求。

介绍

Request类是一个http请求的类,对于爬虫而言是一个很重要的类。通常在Spider中创建这样的一个请求,在Downloader中执行这样的一个请求。同时也有一个子类FormRequest继承于它,用于post请求。

在Spider中通常用法:

yield
 scrapy.
Request
(url='zarten.com'
)

类属性和方法有:

url
method
headers
body
meta
copy()
replace([url, method, headers, body, cookies, meta, encoding, dont_filter, callback, errback])
Request
class
 scrapy.http.

Request

(url[, callback, method='GET'
, headers, body, cookies, meta, encoding='utf-8'
, priority=0
, dont_filter=False
, errback, flags])

参数说明:

url 请求的url

callback 回调函数,用于接收请求后的返回信息,若没指定,则默认为parse()函数

method http请求的方式,默认为GET请求,一般不需要指定。若需要POST请求,用FormRequest即可

headers 请求头信息,一般在settings中设置即可,也可在middlewares中设置

body str类型,为请求体,一般不需要设置(get和post其实都可以通过body来传递参数,不过一般不用)

cookies dict或list类型,请求的cookie dict方式(name和value的键值对):

cookies={
'name1'
 : 
'value1'
 , 
'name2'
 : 
'value2'
}

list方式:

cookies=[
{
'name'
: 
'Zarten'
, 
'value'
: 
'my name is Zarten'
, 
'domain'
: 
'example.com'
, 
'path'
: 
'/currency'
}
]

encoding 请求的编码方式,默认为'utf-8'

priority int类型,指定请求的优先级,数字越大优先级越高,可以为负数,默认为0

dont_filter 默认为False,若设置为True,这次请求将不会过滤(不会加入到去重队列中),可以多次执行相同的请求

errback 抛出错误的回调函数,错误包括404,超时,DNS错误等,第一个参数为Twisted Failure实例

from
 scrapy.spidermiddlewares.httperror 
import
 
HttpError
from
 twisted.internet.error 
import
 
DNSLookupError
from
 twisted.internet.error 
import
 
TimeoutError
, 
TCPTimedOutError
class
 
ToScrapeCSSSpider
(scrapy.
Spider
):
 name="toscrape-css"
 
# start_urls=[
 
# 'http://quotes.toscrape.com/',
 
# ]
 start_urls=[
 
"http://www.httpbin.org/"
, 
# HTTP 200 expected
 
"http://www.httpbin.org/status/404"
, 
# Not found error
 
"http://www.httpbin.org/status/500"
, 
# server issue
 
"http://www.httpbin.org:12345/"
, 
# non-responding host, timeout expected
 
"http://www.httphttpbinbin.org/"
, 
# DNS error expected
 ]
 
def
 start_requests(
self
):
 
for
 u 
in
 
self
.start_urls:
 
yield
 scrapy.
Request
(u, callback=self
.parse_httpbin,
 errback=self
.errback_httpbin,
 dont_filter=True
)
 
def
 parse_httpbin(
self
, response):
 
self
.logger.info(
'Got successful response from {}'
.format(response.url))
 
# do something useful here...
 
def
 errback_httpbin(
self
, failure):
 
# log all failures
 
self
.logger.info(repr(failure))
 
# in case you want to do something special for some errors,
 
# you may need the failure's type:
 
if
 failure.check(
HttpError
):
 
# these exceptions come from HttpError spider middleware
 
# you can get the non-200 response
 response=failure.value.response
 
self
.logger.info(
'HttpError错误 on %s'
, response.url)
 
elif
 failure.check(
DNSLookupError
):
 
# this is the original request
 request=failure.request
 
self
.logger.info(
'DNSLookupError错误 on %s'
, request.url)
 
elif
 failure.check(
TimeoutError
, 
TCPTimedOutError
):
 request=failure.request
 
self
.logger.info(
'TimeoutError错误 on %s'
, request.url)

flags list类型,一般不会用到,发送请求的标志,一般用于日志记录

meta 可用户自定义从Request到Response传递参数,这个参数一般也可在middlewares中处理

yield
 scrapy.
Request
(url='zarten.com'
, meta={
'name'
 : 
'Zarten'
})

在Response中:

my_name=response.meta[
'name'
]

不过也有scrapy内置的特殊key,也非常有用,它们如下:

proxy 设置代理,一般在middlewares中设置

可以设置http或https代理

request.meta[
'proxy'
]='https://'
 + 
'ip:port'

downloadtimeout 设置请求超时等待时间(秒),通常在settings中设置DOWNLOADTIMEOUT,默认是180秒(3分钟)

maxretrytimes 最大重试次数(除去第一次下载),默认为2次,通常在settings中 RETRY_TIMES设置

dont_redirect 设为True后,Request将不会重定向

dont_retry 设为True后,对于http链接错误或超时的请求将不再重试请求

handlehttpstatuslist http返回码200-300之间都是成功的返回,超出这个范围的都是失败返回,scrapy默认是过滤了这些返回,不会接收这些错误的返回进行处理。不过可以自定义处理哪些错误返回:

yield
 scrapy.
Request
(url='https://httpbin.org/get/zarten'
, meta={
'handle_httpstatus_list'
 : [
404
]})

在parse函数中可以看到处理404错误:

def
 parse(
self
, response):
 
print
(
'返回信息为:'
,response.text)

handlehttpstatusall 设为True后,Response将接收处理任意状态码的返回信息

dontmergecookies scrapy会自动保存返回的cookies,用于它的下次请求,当我们指定了自定义cookies时,如果我们不需要合并返回的cookies而使用自己指定的cookies,可以设为True

cookiejar 可以在单个spider中追踪多个cookie,它不是粘性的,需要在每次请求时都带上

def
 start_requests(
self
):
 urls=[
'http://quotes.toscrape.com/page/1'
,
 
'http://quotes.toscrape.com/page/3'
,
 
'http://quotes.toscrape.com/page/5'
,
 ]
 
for
 i ,url 
in
 enumerate(urls):
 
yield
 scrapy.
Request
(url=url, meta={
'cookiejar'
 : i})
 
def
 parse(
self
, response):
 next_page_url=response.css(
"li.next > a::attr(href)"
).extract_first()
 
if
 next_page_url 
is
 
not
 
None
:
 
yield
 scrapy.
Request
(response.urljoin(next_page_url), meta={
'cookiejar'
 : response.meta[
'cookiejar'
]}, callback=self
.parse_next)
 
def
 parse_next(
self
, response):
 
print
(
'cookiejar:'
, response.meta[
'cookiejar'
])

dont_cache 设为True后,不会缓存

redirect_urls 暂时还不清楚具体的作用,知道的小伙伴们欢迎在评论留言

bindaddress 绑定输出IP

dontobeyrobotstxt 设为True,不遵守robots协议,通常在settings中设置

downloadmaxsize 设置下载器最大下载的大小(字节),通常在settings中设置DOWNLOADMAXSIZE,默认为1073741824 (1024MB=1G),若不设置最大的下载限制,设为0

download_latency 只读属性,获取请求的响应时间(秒)

def
 start_requests(
self
):
 headers={
 
'user-agent'
 : 
'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
 }
 
yield
 scrapy.
Request
(url='https://www.amazon.com'
, headers=headers)
 
def
 parse(
self
, response):
 
print
(
'响应时间为:'
, response.meta[
'download_latency'
])

downloadfailon_dataloss 很少用到,详情看这里

referrer_policy 设置Referrer Policy

FormRequest

FormRequest 类为Request的子类,用于POST请求

这个类新增了一个参数 formdata,其他参数与Request一样,详细可参考上面的讲述

一般用法为:

yield
 scrapy.
FormRequest
(url="http://www.example.com/post/action"
,
 formdata={
'name'
: 
'Zarten'
, 
'age'
: 
'27'
},
 callback=self
.after_post)

点击“了解更多”看源代码,建议收藏

得十多年前,自己还是个高中生的时候,所谓的智能手机还完全没有普及,如果想在学校里面大量阅读电子书的话,基本上靠的就是有阅读 功能的MP3或者MP4了。而电子书的来源呢?在无法随时随地接触网络的情况下,有时靠的就是笨办法:将一些小说网站上的内容一页页的粘贴复制下来。而那些动辄几百章节的网络小说,靠这样的手工操作,确实让人非常头疼。那时候是多么希望有一个工具能帮我自动完成这些吃力的手工活啊!!!

好吧,言归正传,最近在研究怎么使用爬虫框架Scrapy。先说说想要学习Scrapy的初衷吧。

  • 学习目的:抓取自己经常浏览的十几个新闻类网站的新闻数据,然后统一在的html页面中浏览。
  • 实现方法:第一步,使用Scrapy抓取数据。第二步,使用Django生成html页面
  • Scrapy介绍

Scrapy是Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试(百度百科上的介绍)。

经过几天的学习,Scrapy的初步使用,首先需要了解的就是如下几个概念了:

  • Spiders:用于解析网页的类,你的大部分工作就是如何写一个继承自Spiders的类了。
  • Selectors:帮助你方便的查询到网页中你所需要的内容。(在Spiders中使用)
  • Items:数据类,将你抓取到的内容保存到其中。
  • Item Pipeline:在这里定义你想要对数据类进行的所有操作。

所以,你需要做得就是写好上面提到的四个类,剩下的只要交给Scrapy框架就可以了。

可以先创建一个scrapy项目:

scrapy startproject getMyFavoritePages

文件spiderForShortPageMsg.py里面就是我们要写的 Spiders子类了。

  • 简单的实例

举例:现在我想要将网站https://lobste.rs/中所有文章的标题和文章的地址抓取下来。

第一步:写一个继承自 Spiders的类

Scrapy框架会自动调用该类的方法parse(),在这里parse()最后调用了自定义的方法parse_lobste_com()来解析具体的html页面,并从中找到我想要的数据,然后保存于一个 Items的数据类对象当中。

不要被下面的这行代码吓到:

response.xpath("//div/div[2]/span[1]/a[@class='u-url']"

它就是之前提到的 Selectors。这就是用来定位你想要查找的html标签的方法。有俩种 Selectors, 分别是 XPath Selectors 和 CSS Selectors,上面都有用到。

这是我的Item数据类(即上面的pageItem)。

第二步:在Item Pipeline中定义你想要对数据类Item进行的所有操作。

现在想要的数据已经在Item对象当中了。考虑到自己的最终目的,最好的选择当然是把所有的数据都保存在数据库当中了。

而说到数据库的操作,就不得不提Django中的models类了,通过几步简单的设置,你可以直接调用Django中的models类,进而省去复杂了数据库操作,简直不要太省心了。谁用谁知道啊!!

、Scrapy 框架

  • Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
  • 框架的力量,用户只需要定制开发几个模块就可以轻松地实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
  • Scrapy 使用了 Twisted['tw?st?d](其主要对手是Tornado)异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy架构图(绿线是数据流向):

  • Scrapy Engine(引擎): 负责SpiderItemPipelineDownloaderScheduler中间的通讯,信号、数据传递等。
  • Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎
  • Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
  • Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)
  • Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
  • Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
  • Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

Scrapy的运作流程

代码写好,程序开始运行...

  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx.com。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx.com。
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  12. Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道``调度器:好的,现在就做!

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)

制作 Scrapy 爬虫 一共需要4步:

  • 新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
  • 明确目标 (编写items.py):明确你想要抓取的目标
  • 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
  • 存储内容 (pipelines.py):设计管道存储爬取内容

二、配置安装

Scrapy的安装介绍

Scrapy框架官方网址:http://doc.scrapy.org/en/latest

Scrapy中文维护站点:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html

Windows 安装方式

  • Python 2 / 3
  • 升级pip版本:pip install --upgrade pip
  • 通过pip 安装 Scrapy 框架pip install Scrapy

Ubuntu 需要9.10或以上版本安装方式

  • Python 2 / 3
  • 安装非Python的依赖 sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
  • 通过pip 安装 Scrapy 框架 sudo pip install scrapy

安装后,只要在命令终端输入 scrapy,提示类似以下结果,代表已经安装成功

具体Scrapy安装流程参考:http://doc.scrapy.org/en/latest/intro/install.html#intro-install-platform-notes 里面有各个平台的安装方法

三、入门案例

1. 新建项目(scrapy startproject)

  • 在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:
scrapy startproject mySpider
  • 其中, mySpider 为项目名称,可以看到将会创建一个 mySpider 文件夹,目录结构大致如下:

下面来简单介绍一下各个主要文件的作用:

scrapy.cfg :项目的配置文件
mySpider/ :项目的Python模块,将会从这里引用代码
mySpider/items.py :项目的目标文件
mySpider/pipelines.py :项目的管道文件
mySpider/settings.py :项目的设置文件
mySpider/spiders/ :存储爬虫代码目录

2. 明确目标(mySpider/items.py)

我们打算抓取:http://www.itcast.cn/channel/teacher.shtml 网站里的所有讲师的姓名、职称和个人信息。

  1. 打开mySpider目录下的items.py
  2. Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
  3. 可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item(可以理解成类似于ORM的映射关系)。
  4. 接下来,创建一个ItcastItem 类,和构建item模型(model)。
import scrapy

class ItcastItem(scrapy.Item):
    name=scrapy.Field()
    level=scrapy.Field()
    info=scrapy.Field()

3. 制作爬虫 (spiders/itcastSpider.py)

爬虫功能要分两步:

(1)爬数据

  • 在当前目录下输入命令,将在mySpider/spider目录下创建一个名为itcast的爬虫,并指定爬取域的范围:
scrapy genspider itcast "itcast.cn"
  • 打开 mySpider/spider目录里的 itcast.py,默认增加了下列代码:
import scrapy

class ItcastSpider(scrapy.Spider):
    name="itcast"
    allowed_domains=["itcast.cn"]
    start_urls=(
        'http://www.itcast.cn/',
    )

    def parse(self, response):
        pass

其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦

要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。

  • name="" :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。
  • allow_domains=[] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。
  • start_urls=() :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
  • parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
  1. 负责解析返回的网页数据(response.body),提取结构化数据(生成item)
  2. 生成需要下一页的URL请求。

将start_urls的值修改为需要爬取的第一个url

start_urls=("http://www.itcast.cn/channel/teacher.shtml",)

修改parse()方法

def parse(self, response):
    with open("teacher.html", "w") as f:
        f.write(response.text)

然后运行一下看看,在mySpider目录下执行:

scrapy crawl itcast

是的,就是 itcast,看上面代码,它是 ItcastSpider 类的 name 属性,也就是使用 scrapy genspider命令的爬虫名。

一个Scrapy爬虫项目里,可以存在多个爬虫。各个爬虫在执行时,就是按照 name 属性来区分。

运行之后,如果打印的日志出现 [scrapy] INFO: Spider closed (finished),代表执行完成。 之后当前文件夹中就出现了一个 teacher.html 文件,里面就是我们刚刚要爬取的网页的全部源代码信息。

# 注意,Python2.x默认编码环境是ASCII,当和取回的数据编码格式不一致时,可能会造成乱码;
# 我们可以指定保存内容的编码格式,一般情况下,我们可以在代码最上方添加:

    import sys
    reload(sys)
    sys.setdefaultencoding("utf-8")

# 这三行代码是Python2.x里解决中文编码的万能钥匙,经过这么多年的吐槽后Python3学乖了,默认编码是Unicode了...(祝大家早日拥抱Python3)

(2)取数据

  • 爬取整个网页完毕,接下来的就是的取过程了,首先观察页面源码:

<div class="li_txt">
    <h3>  xxx  </h3>
    <h4> xxxxx </h4>
    <p> xxxxxxxx </p>

是不是一目了然?直接上XPath开始提取数据吧。

  • 我们之前在mySpider/items.py 里定义了一个ItcastItem类。 这里引入进来
from mySpider.items import ItcastItem
  • 然后将我们得到的数据封装到一个 ItcastItem 对象中,可以保存每个老师的属性:
from mySpider.items import ItcastItem

def parse(self, response):
    #open("teacher.html","wb").write(response.body).close()

    # 存放老师信息的集合
    items=[]

    for each in response.xpath("//div[@class='li_txt']"):
        # 将我们得到的数据封装到一个 `ItcastItem` 对象
        item=ItcastItem()
        #extract()方法返回的都是unicode字符串
        name=each.xpath("h3/text()").extract()
        title=each.xpath("h4/text()").extract()
        info=each.xpath("p/text()").extract()

        #xpath返回的是包含一个元素的列表
        item['name']=name[0]
        item['title']=title[0]
        item['info']=info[0]

        items.append(item)

    # 直接返回最后数据
    return items
  • 我们暂时先不处理管道,后面会详细介绍。

保存数据

scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件,,命令如下:

# json格式,默认为Unicode编码
scrapy crawl itcast -o teachers.json

# json lines格式,默认为Unicode编码
scrapy crawl itcast -o teachers.jsonl

# csv 逗号表达式,可用Excel打开
scrapy crawl itcast -o teachers.csv

# xml格式
scrapy crawl itcast -o teachers.xml

思考

如果将代码改成下面形式,结果完全一样。

请思考 yield 在这里的作用: