欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

爬虫(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

程序员文章站 2022-06-05 17:35:31
1. Scrapy框架 1.1 Selector的用法 我们之前介绍了利用Beautiful Soup、正则表达式来提取网页数据,这确实非常方便。而Scrapy还提供了自己的数据提取方法,即Selector(选择器)。Selector 是基于lxml来构建的,支持XPath选择器、CSS选择器以及正 ......

1. scrapy框架

1.1 selector的用法

我们之前介绍了利用beautiful soup、正则表达式来提取网页数据,这确实非常方便。而scrapy还提供了自己的数据提取方法,即selector(选择器)。selector 是基于lxml来构建的,支持xpath选择器、css选择器以及正则表达式,功能全面,解析速度和准确度非常高。

selector是一个可以独立使用的模块。我们可以直接利用selector这个类来构建一个选择器对象,然后调用它的相关方法如xpath()、css()等来提取数据。

案例:

from scrapy import selector
body= '<html><head><title>hello world</title></head><body></body></ html>'
selector = selector(text=body)
title = selector.xpath('//title/text()').extract_first()
print(title) 

结果:

爬虫(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

我们没有在scrapy框架中运行,而是把scrapy中的selector单独拿出来使用了,构建的时候传入text参数,就生成了selector选择器对象,然后就可以像前面我们所用的scrapy中的解析方式一样,调用xpath()、css()等方法来提取。

在这里我们查找的是源代码中的title中的文本,在path选择器最后加 text()方法就可以实现文本的提取了。

以上内容就是selector的直接使用方式beautiful soup等库类似,selector其实也是强大的网页解析库。如果方便的话,我们也可以在其他项目中直接使用selector来提取数据。

selector选择器的使用可以分为三步:

导入选择器from scrapy.selector import selector

创建选择器实例selector = selector(response=response)

使用选择器selector.xpath()或者selector.css()

不过scrapy项目里我们可以直接response.css()或response.xpath(),怎么方便怎么用。

1.2 spider的用法

在scrapy中,要抓取网站的链接配置、抓取逻辑、解析逻辑里其实都是在spider中配置的在上一章的实例中,我们发现抓取逻辑也是在spider中完成的。

1.2.1 spider运行流程

在实现scrapy爬虫项目时,最核心的类便是spider类了,它定义了如何爬取某个网站的流程和解析方式。简单来说,spider要做的事就是两件:定义爬取网站的动作和分析爬取下来的网页。

spider循环爬取过程:

以初始的url初始化request,并设置回调函数。当该request成功请求并返回时,response生成并作为参数传给该回调函数。

在回调函数内分析返回的网页内容。返回结果有两种形式。一种是解析到的有效结果返回字典或item对象,它们可以经过处理后(或直接)保存。另一种是解析得到下一个(如下页)链接,可以利用此链接构造reque并设置新的回调函数,返回request等待后续调度。

如果返回的是字典或item对象,我们可通过feed exports等组件将返回结果存入到文件。如果设置了pipeline的话,我们可以使用pipeline处理(如过滤、修正等)并保存。

如果返回的是reqeust,那么request执行成功得到response之后,response会被传递给request中定义的回调函数,在回调函数中我们可以再次使用选择器来分析新得到的网页内 容,并根据分析的数据生成item。

通过以上几步循环往复进行,我们完成了站点的爬取。

1.2.2 spider类分析

在上一章的例子中,我们定义的spider是继承自scrapy.spiders.spider。scrapy.spiders.spider这个类是最简单最基本的spider类,其他spider必须继承这个类。还有后面一些特殊spider类也都继承自它。

scrapy.spiders.spider这个类提供了start_requests()方法的默认实现,读取并请求start_urls属性,并根据返回的结果调用 parse()方法解析结果。

基础属性:

name:爬虫名称,是定义spider名字的字符串。spider的名字定义了scrapy如何定位并初始化spider,它必须是唯一的。不过我们可以生成多个相同的spider实例,数量没有限制。name是spider最重要的属性。如果spider爬取单个网站,一个常见的做法是以该网站的域名名称来命名spider。例如,spider爬取mywebsite.com,该spider通常会被命名为mywebsite。

allowed_domains:允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。

start_urls:它是起始url列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。

custom_settings:它是一个字典,是专属于本spider的配置,此设置会覆盖项目全局的设置。此设置必须在初始化前被更新,必须定义成类变量。

crawler:它是由from_crawler()方法设置的,代表的是本spider类对应的crawler对象。crawler对象包含了很多项目组件,利用它我们可以获取项目的一些配置信息,如最常见的获取项目的设置信息,即settings。

settings:它是一个settings对象,利用它我们可以直接获取项目的全局设置变量。

除了基础属性,spider还有一些常用的方法:

start_requests():此方法用于生成初始请求,它必须返回一个可迭代对象。此方法会默认使用start_urls里面的url来构造request,而且request是get请求方式。如果我们想在启动时以post方式访问某个站点,可以直接重写这个方法,发送 post请求时使用formrequest即可。

parse():当response没有指定回调函数时,该方法会默认被调用。它负责处理response处理返回结果,并从巾提取处想要的数据和下一步的请求,然后返回。该方法需要返回一个包含request或ltem的可迭代对象。

closed():当spider关闭时,该方法会被调用,在这里一般会定义释放资源的一些操作或其他收尾操作。

1.3 downloader middleware的用法

downloader middleware即下载中间件,它是处于scrapy的request和response之间的处理模块。

我们上一章已经看过scrapy框架的架构了。

scheduler从队列中拿出一个request发送给downloader执行下载,这个过程会经过downloader middleware的处理。另外,当downloader将request下载完成得到response返回给spider时会再次经过downloader middleware处理。

也就是说,downloader middleware在整个架构中起作用的位置有两个,分别是:

在scheduler调度出队列的request发送给doanloader下载之前,也就是我们可以在request执行下载之前对其进行修改。

在下载后生成的response发送给spider之前,也就是我们可以在生成resposne被spider解析之前对其进行修改。

downloader middleware的功能非常强大,修改user-agent处理重定向、设置代理、失败重试、设置 cookies等功能都需要借助它来实现。

1.3.1 使用说明

scrapy其实已经提供了许多downloader middleware,比如负责失败重试、自动重定向等功能的middleware,它们被downloader_middlewares_base变量所定义。

官网:https://scrapy-chs.readthedocs.io/zh_cn/0.24/topics/settings.html#std:setting-downloader_middlewares_base

downloader_middlewares_base变量的内容如下所示:

爬虫(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

 

 

这是一个字典格式,字典的键名是scrapy内置的downloader middleware的名称,键值代表了调用的优先级,优先级是一个数字,数字越小代表越靠近scrapy引擎,数字越大代表越靠近downloader,数字小的downloader middleware会被优先调用。

如果向己定义的downloader middleware要添加到项目里,downloader_middlewares_base变量不能直接修改。scrapy提供了另外一个设置变量downloader_middlewares,我们直接修改这个变量就可以添加自己定义的downloadermiddleware,以及禁用downloader_middlewares_base里面定义的downloader middleware。

1.3.2 核心方法

scrapy内置的downloader middleware为scrapy提供了基础的功能,但在项目实战中我们往往需要单独定义downloader middleware。不用担心,这个过程非常简单,我们只需要实现某几个方法即可。

每个downloader middleware都定义了一个或多个方法的类,核心的方法有如下三个:

process_request(request,spider)

process_response(request,response,spider)

pro cess_exception(request,exception,spider)

我们只需要实现至少一个方法,就可以定义一个downloader middleware下面我们来看看这三个方法的详细用法。

(1) process_request(request,spider)

request被scrapy引擎调度给downloader之前,process_request()方法就会被调用,也就是在request从队列里调度出来到downloader下载执行之前,我们都可以用process_request()方法对 request进行处理。方法的返回值必须为none、response对象、request对象之一,或者抛出ignorerequest异常。

process_request()方法的参数有如下两个:

request,是request对象,即被处理的request。

spider,是spdier对象,即此request对应的spider。

返回类型不同,产生的效果也不同。下面归纳一下不同的返回情况。

  • 当返回是none时,scrapy将继续处理该request,接着执行其他downloader middleware的process_request()方法,一直到downloader把request执行后得到response才结束。这个过程其实就是修改request的过程,不同的downloader middleware按照设置的优先级顺序依次对request进行修改,最后送至downloader执行。
  • 当返回为response对象时,更低优先级的downloader middleware的process_request()和process_exception()方法就不会被继续调用,每个downloader middleware的process_response()方法转而被依次调用。调用完毕之后,直接将response对象发送给spider来处理。
  • 当返回为request对象时,更低优先级的downloader middleware的process_request()方法会停止执行。这个request会重新放到调度队列里,其实它就是一个全新的request,等待被调度。如果被scheduler调度了,那么所有的downloader middleware的process_request()方法会被重新按照顺序执行。
  • 如果ignorerequest异常抛出,则所有的downloader middleware的process_exception()方法会依次执行。如果没有一个方法处理这个异常,那么request的errorback()方法就会回调。如果该异常还没有被处理,那么它便会被忽略。

(2) process_response (request, response,spider)

downloader执行request下载之后,会得到对应的response。scrapy引擎便会将response发送给 spider进行解析。在发送之前,我们都可以用process_response()方法来对response进行处理。方法的返回值必须为request对象、response对象之一,或者抛出ignorerequest异常。

process_response()方法的参数有如下三个:

request,是request对象,即此response对应的request。

response,是response对象,即此被处理的response。

spider,是spider对象,即此response对应的spider。

下面归纳下不同的返回情况。

  • 当返回为request对象时,更低优先级的downloader middleware的process_response()方法不会继续调用。该request对象会重新放到调度队列里等待被调度,它相当于一个全新的request。然后,该request会被process_request()方法依次处理。
  • 当返回为response对象时,更低优先级的downloader middleware的process_response()方法会继续调用,继续对该response对象进行处理。
  • 如果ignorerequest异常抛出,则request的errorback()方法会回调。如果该异常还没有被处理,那么它便会被忽略。

(3) process_exception(request,exception,spider)

当downloader或process_request()方法抛出异常时,例如抛出ignorerequest异常,process_exception()方法就会被调用。方法的返回值必须为none、response对象、request对象之一。

process_exception()方法的参数有如下:

request,是request对象,即产生异常的request。

exception,是exception对象,即抛出的异常。

spdier,是spider对象,即request对应的spider。

下面归纳一下不同的返回情况。

  • 当返回为none时,更低优先级的downloader middleware的process_exception()会被继续依次调用,直到所有的方法都被调度完毕。
  • 当返回为response对象时,更低优先级的downloader middleware的process_exception()方法不再被继续调用,每个downloader middleware的process_response()方法转而被依次调用。
  • 当返回为request对象时,更低优先级的downloader middleware的process_exception()也不再被继续调用,该request对象会重新放到调度队列里面等待被调度,它相当于一个全新的request。然后,该request又会被process_request()方法依次处理。

以上内容便是这三个方法的详细使用逻辑。在使用它们之前,请先对这三个方法的返回值的处理情况有一个清晰的认识。在自定义downloader middleware的时候,也一定要注意每个方法的返回类型。

1.3.3 项目实战

新建一个项目。

scrapy startproject scrapydownloadertest

新建了一个scrapy项目,名为scrapydownloadertest。进入项目,新建一个spider。

scrapy genspider httpbin httpbin.org

新建了一个spider,名为httpbin。

# -*- coding: utf-8 -*-
import scrapy

class httpbinspider(scrapy.spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/']

    def parse(self, response):
        pass

接下来我们修改start_urls,将parse()方法添加一行日志输出,将response变量的text属性输出出来,这样我们便可以看到scrapy发送的request信息了。

修改spider内容如下:

# -*- coding: utf-8 -*-
import scrapy


class httpbinspider(scrapy.spider):
    name = 'httpbin'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/get']

    def parse(self, response):
        self.logger.debug(response.text)

接下来运行此spider。

scrapy crawl httpbin

scrapy运行结果包含scrapy发送的request信息,内容如下:

爬虫(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

 

我们观察一下headers,scrapy发送的request使用的user-agent是scrapy/1.8.0(+http: //scrapy.org),这其实是由scrapy内置的useragentmiddleware设置的,useragentmiddleware的源码如下:

爬虫(十五):Scrapy框架(二) Selector、Spider、Downloader Middleware

 

在from_crawler()方法中,首先尝试获取settings里面user_agent,然后把user_agent传递给__init__()方法进行初始化,其参数就是user_agent。如果没有传递user_agent参数就是默认设置为scrapy字符串。我们新建的项目没有设置user_agent,所以这里的user_agent变量就是scrapy。接下来,在process_request()方法中,将user-agent变量设置为headers变量的一个属性,这样就成功设置了user-agent。因此,user-agent就是通过此downloader middleware的process_request()方法设置的。

修改请求时的user-agent可以有两种方式:一是修改settings里面的user_agent变量;二是通过downloader middleware的process_request()方法来修改。

第一种方法非常简单,我们只需要在setting.py里面加一行user_agent的定义即可:

user_agent = 'mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/78.0.3904.108 safari/537.36'

一般推荐使用这种方法来设置。但是如果想设置得更加灵活,比如设置随机的user-agent的设置。

第二种方法,在middlewares.py里添加一个randomuseragentmiddleware的类。

import random

class randomuseragentmiddlerware():
    def __init__(self):
        self.user_agents = [
            #放几个user-agent在里面
            'mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/73.0.3683.103 safari/537.36',
            'mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/78.0.3904.108 safari/537.36',
            'mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/60.0.3100.0 safari/537.36'
        ]
    def process_request(self,request,spider):
        request.headers['user-agent'] = random.choice(self.user_agents)

我们首先在类的__init__()方法中定义几个不同的user-agent,并用一个列表来表示。接下来实现了process_request()方法,它有一个参数request,我们直接修改request的属性即可。在这里我们直接设置了request变量的headers属性的user-agent,设置内容是随机选择的user-agent,这样一个downloader middleware就写好了。

不过,要使之生效我们还需要再去调用这个downloader middleware。在settings.py中,将downloader_middlewares取消注释,并设置成如下内容:

downloader_middlewares = {
    'scrapydownloadertest.middlewares.randomuseragentmiddleware':543
}

然后重新运行spider,就可以看到user-agent被成功修改为列表中所定义的随机的一个user-agent了。

我们就通过实现downloader middleware并利用process_request()方法成功设置了随机的user-agent。

另外,downloader middleware还有process_response()方法。downloader对request执行下载之后会得到response,随后scrapy引擎会将response发送回spider进行处理。但是在response被发送给spider之前,我们同样可以使用process_response()方法对response进行处理。比如这里修改一下response的状态码,在randomuseragentmiddleware添加如下代码:

def process_response(self,request,response,spider):
    response.status = 201
    return response

我们将response变量的status属性修改为201,随后将response返回,这个被修改后的response就会被发送到spider。

我们再在spider里面输出修改后的状态码,在parse()方法中添加如下的输出语句:

self.logger.debug('status code :' + str(response.status)) 

重新运行之后,控制台输出了如下内容:

[httpbin] debug: status code: 201

可以发现,response的状态码成功修改了。

因此要想对response进行后处理,就可以借助于process_response()方法。

另外还有一个process_exception()方法,它是用来处理异常的方法。如果需要异常处理的话,我们可以调用此方法。不过这个方法的使用频率相对低一些,这里就不谈了。