本文为译文,原文见地址: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。可以是以下两种格式。
-
使用字典:
request_with_cookies = Request(url='http://www.example.com', cookies={'currency': 'USD', 'country': 'UY'})
-
使用字典的列表:
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
该响应的编码字符串。通过尝试以下机制可以解决编码问题,尝试顺序如下:
- 构造函数中传递encoding参数作为编码。
- 在HTTP头的Content-Type中声明的编码。如果此编码无效(比如,unknown),它将被忽略,并尝试下一个解决机制。
- 在响应正文中声明的编码。TextResponse类不会为此提供任何特别功能。然而,HtmlResponse和XmlResponse类提供了。
- 通过观察响应正文来推断编码。这是一种比较脆弱的方法,也是最后一种尝试方法。
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。