Scrapy1.5基本概念(九)——请求和响应(Requests and Responses)

本文为译文,原文见地址:https://docs.scrapy.org/en/latest/topics/request-response.html

请求和响应(Requests and Responses)

Scrapy使用Request和Response对象来爬行web站点。

通常来说,Request对象在爬虫中生成,并且系统中传递,直到它们到达下载器(Downloader),下载器执行请求并返回Response对象,Response对象返回到发出请求的爬虫。

Request和Response类都可以有子类,这些子类添加了基类中不是必须的功能。后续会有介绍。

Request对象

class scrapy.http.Request(url[, callback, method=‘GET’, headers, body, cookie, meta, encoding=‘utf-8’, priority=0, dont_filter=False, errback, flags])

一个Request对象代表一个HTTP请求,Request对象通常在爬虫(Spider)中生成,并且由下载器(Downloader)执行,最终的结果生成一个Response对象。

参数:

  • url(string)- 该请求的url

  • callback(callable)- 回调函数,调用这个函数时的第一个参数,是该请求的响应(下载后)。想要获取更多信息,请见后面的传递附加数据到回调函数。如果一个Request没有指定callback,那么将默认使用爬虫的parse()函数。注意,如果在处理过程中出现了异常,那么将会调用errback而不是callback。

  • method(string)- 该请求的HTTP函数。默认为’GET’。

  • meta(dict)- Request.meta属性的初始值。如果给定了该值,该参数传递的字典将被浅拷贝。

  • body(str或者unicode)- 请求的body。如果传递了一个unicode,那么将使用传递的encoding参数(默认为utf-8)来将其转码为str。如果body没有给定,将存储一个空字符串。不论这个参数的类似那个是什么,最终存储的都将是一个str(不管unicode或是None)。

  • header(dict)- 该请求的头。这个字典的值可以是字符串(单独值的头)或者列表(多个值的头)。如果传递了None作为值,HTTP头将根本不会被发送。

  • cookies(dict或者list)- 请求的cookies。可以是以下两种格式。

    1. 使用字典:

      request_with_cookies = Request(url='http://www.example.com', cookies={'currency': 'USD', 'country': 'UY'})
      
    2. 使用字典的列表:

      request_with_cookies = Request(url='http://www.example.com', cookies=[{'name': 'currency', 'value': 'USD', 'domain': 'example.com', 'path': '/currency'}])
      

后一种形式允许自定义cookie的domain和path属性。这只有在cookie被保存,以备后续的请求可以使用。

当一些站点返回了cookies(在响应中),这些cookie将被存储在domain的cookie中,并且后续的请求中都需要带有这些cookie。这是任何常用的web浏览器都带有的典型行为。然而,如果为了某些原因,你希望避免合并已经存在的cookie,你可以在Request.meta中设置dont_merge_cookies关键字为True来指示Scrapy这样做。

请求不带合并cookie,示例如下:

request_with_cookie = Request(url='http://www.example.com', cookies={'currency': 'USD', 'country': 'UY'}, meta={'dont_merge_cookies': True})

更多信息请见CookiesMiddleware

  • encoding(string)- 该请求的编码(默认为’utf-8’)。这个编码将被用于URL的百分比编码,并且将正文转为str(如果给定的是unicode)。
  • priority(int)- 该请求的优先级(默认为0)。这个优先级被用于调度器,调度器根据这个优先级定义需要处理的请求的先后顺序。更高优先级值的请求将会越早执行。为了表示相对较低的优先级,允许这个值为负数。
  • dont_filter(boolean)- 指示该请求不应该被调度器所筛选。这通常用于当你希望多次执行同一个指定的请求,为了忽略重复筛选器。需要小心使用这个参数,否则你可能调用爬行循环。默认值为False。
  • errback(callable)- 回调函数,在处理请求的过程中如果有任何异常被抛出,则调用此函数。比如访问页面失败并返回404HTTP错误,等等。这个函数接受一个Twisted Failure实例作为第一个参数。有关更多信息,参见下面的使用errback来捕获请求处理过程中的异常
  • flags(list)- 发送给请求的flags,可以被用于记录日志或者类似目的。

url

一个包含了该请求的URL字符串。请记住,这个属性包含的是转义后的URL,因此它可能会与传递给构造函数中的URL不同。

这个属性是一个只读属性。若要改变请求的URL,请使用replace()。

method

在请求中表示HTTP函数的字符串。它保证是大写的。比如:“GET”,“POST”,“PUT”,等。

headers

一个类似字典的对象,包含了请求的头。

body

包含了请求正文的字符串。

这个属性是只读的。若要改变请求正文,请使用replace()。

meta

一个包含了该请求任意元数据的字典。对于新请求来说这个字典是空的,并且这个字典通常被不同的Scrapy组件所填充(扩展,如中间件,等)。因此这个字典所包含的内容,是根据你已经启用的扩展来决定的。

Scrapy可识别的特定meta键,请参见Request.meta的特定键

当请求通过copy()或者replace()函数来克隆的时候,这个字典是被浅拷贝的,并且依然能够被访问——在你的爬虫中,访问response.meta属性即可。

copy()

返回一个新请求,这个请求是该请求的复制品。请参见:传递附加数据到回调函数

replace([url, method, headers, body, cookies, meta, encoding, dont_filter, callback, errback])

返回一个请求对象,这个请求对象有用相同的成员,除非指定关键字参数给定了新的值。属性Reqeust.meta默认是拷贝的(除非meta参数给定了新的值)。请参见:传递附加数据到回调函数

传递附加数据到回调函数

请求的callback是一个函数,当该请求的响应被下载的时候调用此回调函数。该回调函数的第一个参数即为下载的Response对象。

示例:

def parse_page1(self, response):
	return scrapy.Request('http://www.example.com/some_page.html', callback=self.parse_page2)

def parse_page2(self, response):
	# 将记录http://www.example.com/some_page.html
	self.logger.info('Visited %s', response.url)

在某些情况下,你可能会对传递给这些回调函数的参数感兴趣,因此你可以晚一点接收这些参数,比如在第二个回调函数中。你可以使用Request.meta属性来实现。

这里有一个示例演示了如何通过传递一个item来使用这个机制,主要是为了在不同页面填充不同的字段:

der parse_page1(self, response):
	item = MyItem()
	item['main_url'] = response.url
	request = scrapy.Request('http://www.example.com/some_page.html', callback=self.parse_page2)
	request.meta['item'] = item
	yield request

def parse_page2(self, response):
	item = response.meta['item']
	item['other_url'] = response.url
	yield item

使用errback来捕获请求处理过程中的异常

请求的errback是一个函数,在处理请求的过程中如果抛出了一个异常则会调回这个errback函数。

errback将接收Twisted Failure实例作为其第一个参数,并且这个参数可以追踪连接建立超时,DNS错误等异常。

这里有一个示例,爬虫记录了所有错误并且捕获一些需要的特定错误:

import scrapy

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError


class ErrbackSpider(scrapy.Spider):
	name = 'errback_example'
	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 sucessful response from {}'.format(response.url))
		# 剩余代码...

	def errback_httpbin(self, failure):
		# 记录所有错误
		self.logger.error(repr(failure))

		# 如果你想要处理某些特定的错误,你需要以failure的type为依据:
		if failure.check(HttpError):
			# 这些异常来自爬虫中间件的HttpError
			# 你可以获取到所有非200的响应
			response = failure.value.response
			self.logger.error('HttpError on %s', response.url)
		elif failure.check(DNSLookupError):
			# 这是原始请求
			request = failure.request
			self.logger.error('DNSLookupError on %s', request.url)
		elif failure.check(TimeoutError, TCPTimedOutError):
			request = failure.request
			self.logger.error('TimeoutError on %s', request.url)

Request.meta的特定键

Request.meta属性可以包含任意数据,但是这里有一些Scrapy以及其内置扩展能够识别的特定键:

  • dont_redirect
  • dont_retry
  • handle_httpstatus_list
  • handle_httpstatus_all
  • dont_merge_cookies
  • cookiejar
  • dont_cache
  • redirect_urls
  • bindaddress
  • dont_obey_robotstxt
  • download_timeout
  • download_maxsize
  • download_latency
  • download_fail_on_dataloss
  • proxy
  • ftp_user(详情请见FTP_USER
  • ftp_password(详情请见FTP_PASSWORD
  • referrer_policy
  • max_retry_times

bindaddress

外网IP地址,用来执行请求。

download_timeout

下载器等待的最大时间,单位秒。也可见配置项的DOWNLOAD_TIMEOUT

download_latency

自请求开始以来,用于获取响应花费的时间总量(单位秒),比如通过网络发送HTTP消息。只有当响应被下载后,这个元数据键才可用。虽然大多数元数据键用于控制Scrapy的行为,但是这个键只能是只读的。

download_fail_on_dataloss

是否在中断的响应上失败。也可见配置项的DOWNLOAD_FAIL_ON_DATALOSS

max_retry_times

这个元数据键用来设置每个请求的重试次数。当初始化后,元数据键max_retry_times的优先级将高于配置项RETRY_TIMES

Request的子类

这里有一组内置的Request子类。你也可以继承Request类来实现你所需的功能。

FormRequest对象

FormRequest类扩展了基础类Request的处理HTML表单的功能。FormRequest类使用了lxml.html forms,将Response对象的表单数据中预填充到表单字段中。

class scrapy.http.FormRequest(url[, formdata, …])

FormRequest类在构造函数中添加了新的参数。剩余的参数与Request类保持一致,这里就不再赘述。

参数:

  • formdata(dict或者元组类型的迭代器)- 是一个包含了HTML表单数据的字典(或者(key, value)元组类型的迭代器),这些HTML表单数据将被URL编码,并且赋值给请求的body。

FormRequest对象在Request基础上,附加了一些类函数:

classmethod from_response(response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, … ])

返回一个FormRequest对象,该对象的表单字段预先填充了响应中包含的HTML <form>元素中的值。示例请见使用FormRequest.from_response()来模拟用户登录

默认情况下的策略是自动模拟单击任何看起来可点击的表单控件,比如<input type=“submit”>。虽然这确实很方便,并且常常能够得到预期的行为,但是有时候这样做也会引出一些问题,这些问题还很难调试。举个栗子,当处理使用javascript填充和/或提交的表单时,默认的from_response()行为可能不是最合适的。为了禁止这个行为,你可以设置dont_click参数为True。此外,如果你希望改变单击的控件(替代禁止),你也可以使用clickdata参数。

警告:由于lxml中的错误(应该在lxml 3.8或更高版本中已修复),将此方法与选项值中具有前导或后导空格的select元素一起使用会无法工作。

参数:

  • response(Response对象)- 包含HTML表单的响应对象,这个对象将用来预填充表单字段。
  • formname(string)- 如果给定,将使用name属性为此值的表单。
  • formid(string)- 如果给定,将使用id属性为此值的表单。
  • formxpath(string)- 如果给定,将使用第一个匹配xpath的表单。
  • formcss(string)- 如果给定,将使用第一个匹配css选择器的表单。
  • formnumber(integer)- 当响应包含多个表单时,将使用该number指定的表单。第一个表单(默认值)的number为0。
  • formdata(dict)- 要重写表单数据中的字段。如果字段在响应的<form>元素中存在,那么该字段的值将被这个参数传递进来的值所覆盖。如果在这个参数中传递的值为None,那么该字段不会包含在请求中,即使它出现在响应的<form>元素中。
  • clickdata(dict)- 属性用来查找单击的控件。如果没有给出,表单数据将通过模拟单击第一个可单击的元素来进行提交。除了html属性之外,控件还可以凭借nr属性,通过其相对于表单内其他可提交输入的基于零的索引进行标识。
  • dont_click(boolean)- 如果为True,则表单数据在没有点击任何元素时就可被提交。

这个类函数的其他参数通过FormRequest构造函数直接传递。

formname参数新增于版本0.10.3。
formxpath参数新增于版本0.17。
formcss参数新增于版本1.1.0。
formid参数新增于版本1.1.0。

使用Request的示例

通过HTTP POST来使用FormRequest发送数据

如果你希望在你的爬虫中模拟一个HTML表单的POST,并且发送几个键值对的字段,你可以像下面一样返回一个FormRequest对象(从你的爬虫):

return [FormRequest(url='http://www.example.com/post/action', formdata={'name': 'Regan', 'age': '27'}, callback=self.after_post)]

使用FormRequest.from_response()来模拟用户登录

web站点通常通过<input type=“hidden”>元素提供预填充的表单字典,比如会话相关数据,或者身份验证令牌(用于登录页面)。在抓取时,需要自动预填充这些字段,并且只覆盖其中的几个字段,例如用户名和密码。你可以为此使用FormRequest.from_response()函数。下面是一个使用它的爬虫示例:

import scrapy


class LoginSpider(scrapy.Spider):
	name = 'example.com'
	start_urls = ['http://www.example.com/users/login.php']

	def parse(self, response):
		return scrapy.FormRequest.from_response(
			response,
			formdata={'username': 'regan', 'password': 'secret'},
			callback=self.after_login
		)

	def after_login(self, response):
		# 在继续之前检查是否登录成功
		if 'authentication failed' in response.body:
			self.logger.error('Login failed')
			return
		
		# 继续爬取,并携带通过验证的会话...

Response对象

class scrapy.http.Response(url[, status=200, headers=None, body=b’’, flags=None, request=None])

Response对象代表了一个HTTP响应,这个对象通常被下载(通过下载器)并且提供给爬虫(Spider)进行处理。

参数:

  • url(string)- 这个响应的URL。
  • status(integer)- 该响应的HTTP状态码。默认为200。
  • headers(dict)- 该响应的头。这个字典值可以是字符串(对于单个值的头),也可以是列表(对于多个值的头)。
  • body(bytes)- 响应的正文。为了像访问字符串(Python 2中是unicode)一样访问这个编码后的文本,你可以使用可识别编码的Response子类中response.text,比如TextResponse类。
  • flags(list)- 一个包含了Response.flags属性的初始值列表。如果给出,这个列表将被浅拷贝。
  • request(Request对象)- 一个包含了Response.Request属性的初始值。这个值代表了生成这个响应的Request对象。

url

包含了该响应的URL的字符串。

这个属性是只读的。为了更改响应的URL,请使用replace()。

status

该响应的HTTP状态码。比如:200,404。

headers

一个类似字典的对象,包含了该响应的头。想要访问该对象的值,你可以使用get()来返回指定名称的第一个header值,或者使用getlist()来返回指定名称的所有header值。例如,下面的调用将返回header中的所有cookie:

response.headers.getlist('Set-Cookie')

body

该响应的正文。请记住,Response.body总是一个bytes对象。如果你希望得到unicode版本的字符串,你可以使用TextResponse.text(仅在TextResponse或者其子类上有效)。

这个属性是只读的。为了更改响应的正文,请使用replace()。

request

生成该响应的Request对象。这个属性在响应和请求通过所有下载中间件之后,在Scrapy引擎中分配了值。尤其是,这意味着:

  • HTTP重定向将引起原始请求(在重定向之前的URL)被分配给重定向后的响应(在重定向后的最终URL)。
  • Response.request.url不一定总是与Response.url相等。
  • 这个属性仅在爬虫代码中和爬虫中间件中可用,在下载中间件(尽管你可以通过其他方式获得可用的Request)和response_downloaded信号的处理器中不可用。

Response的子类

这里有一组可用的内置Response子类。你也可继承Response类来实现你自己的功能。

TextResponse对象

class scrapy.http.TextResponse(url[, encoding[, …]])

TextResponse对象在Response类的基础上新增了编码功能,这意味着这只能用于二进制数据,比如图片、声音或者一些流媒体文件。

TextResponse对象在Response对象的基础上支持一个新的构造参数,而剩下的功能与Response类一致,这里就不赘述了。

参数:

  • encoding(string)- 一个包含了用于该响应的编码字符串。如果你创建了一个带有unicode正文的TextResponse对象,它将通过这个encoding来编码(记住,body属性总是一个字符串)。如果encoding是None(默认值),那么encoding将在响应头和正文中来查找,并替换这个值。

TextResponse对象在Response类的基础上支持下面的属性:

text

响应正文,编码为unicode。

与response.body.decode(response.encoding)一致,但是该结果在第一个调用后将被缓存,因此你可以多次访问response.text,而不会有额外的开销。

注意:unicode(response.body)不是一个将响应正文转换为unicode的正确方式:你应该使用系统默认的编码(一般是ascii)替换该响应的编码。

encoding

该响应的编码字符串。通过尝试以下机制可以解决编码问题,尝试顺序如下:

  1. 构造函数中传递encoding参数作为编码。
  2. 在HTTP头的Content-Type中声明的编码。如果此编码无效(比如,unknown),它将被忽略,并尝试下一个解决机制。
  3. 在响应正文中声明的编码。TextResponse类不会为此提供任何特别功能。然而,HtmlResponse和XmlResponse类提供了。
  4. 通过观察响应正文来推断编码。这是一种比较脆弱的方法,也是最后一种尝试方法。

selector

使用response作为目标的Selector示例。该选择器是在第一次访问时延迟加载的。

TextResponse对象在Response类的基础上还支持下面的函数:

xpath(query)

TextResponse.selector.xpath(query)的快捷方式:

response.xpath('//p')

css(query)

TextResponse.selector.css(query)的快捷方式:

response.css('p')

follow(url, callback=None, method=‘GET’, headers=None, body=None, cookies=None, meta=None, encoding=None, priority=0, dont_filter=False, errback=None)

返回一个Request实例用来追踪链接url。它接收与Request.__init__函数相同的参数,但是url不仅仅是一个绝对URL,同时也可能是:

  • 一个相对URL;
  • 一个scrapy.link.Link对象(比如,一个链接提取器的结果);
  • 一个属性选择器(不是SelectorList,而是Selector),比如response.css(‘a::attr(href)’)[0]或者response.xpath(’//img/@src’)[0]。
  • 一个<a>或者<link>元素的选择器,比如response.css(‘a.my_link’)[0]。

使用示例请参见创建请求的快捷方式

HtmlResponse对象

class scrapy.http.HtmlResponse(url[, … ])

HtmlResponse类是TextResponse的子类,它通过查找HTML的meta中的http-equiv属性来实现自动添加编码。详情请见TextResponse.encoding

XmlResponse对象

class scrapy.http.XmlResponse(url[, … ])

XmlResponse类是TextResponse的子类,它通过查找XML的定义行来实现自动添加编码。详情请见TextResponse.encoding

猜你喜欢

转载自blog.csdn.net/ReganDu/article/details/85778634