Spider 的使用
在 Scrapy 中,网站的链接配置,抓取逻辑、解析逻辑其实都是在 Spider 中配置的。在前一节的实例中,我们发现抓取逻辑也是在 Spider 中完成的。本节我们就来专门了解一下 Spider 的基本用法。
Spider 运行流程
在实现 Scrapy 爬虫项目时,最核心的类便是 Spider 类了,它定义了如何爬取某个网站的流程和解析方式。简单来讲,Spider 就是要做如下两件事:
-
定义爬取网站的动作;
-
分析爬取下来的网页。
对于 Spider 类来说,整个爬取循环如下所述。
-
以初始的 URL 初始化 Request 并设置回调方法。当该 Request 成功请求并返回时,将生成 Response 并将其作为参数传给该回调方法。
-
在回调方法内分析返回的网页内容。返回结果可以有两种形式,一种是将解析到的有效结果返回字典或 Item 对象,下一步可直接保存或者经过处理后保存;另一种是解析的下一个(如下一页)链接,可以利用此链接构造 Request 并设置新的回调方法,返回 Request。
-
如果返回的是字典或 Item 对象,可通过 Feed Exports 等形式存入文件,如果设置了 Pipeline,可以经由 Pipeline 处理(如过滤、修正等)并保存。
-
如果返回的是 Reqeust,那么 Request 执行成功得到 Response 之后会再次传递给 Request 中定义的回调方法,可以再次使用选择器来分析新得到的网页内容,并根据分析的数据生成 Item。
循环进行以上几步,便完成了站点的爬取。
Spider 类分析
在上一节的例子中,我们定义的 Spider 继承自 scrapy.spiders.Spider,即 scrapySpider 类,二者指代的是同一个类,这个类是最简单最基本的 Spider 类,其他的 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 或 Item 的可迭代对象。
-
closed:当 Spider 关闭时,该方法会被调用,这里一般会定义释放资源的一些操作或其他收尾操作。
实例演示
接下来我们以一个实例来演示一下 Spider 的一些基本用法。首先我们创建一个 Scrapy 项目,名字叫作 scrapyspiderdemo,创建项目的命令如下:
scrapy startproject scrapyspiderdemo
运行完毕后,当前运行目录便出现了一个 scrapyspiderdemo 文件夹,即对应的 Scrapy 项目就创建成功了。
接着我们进入 demo 文件夹,来针对 www.httpbin.org 这个网站创建一个 Spider,命令如下:
scrapy genspider httpbin www.httpbin.org
这时候我们可以看到项目目录下生成了一个 HttpbinSpider,内容如下:
import scrapy
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['www.httpbin.org']
start_urls = ['https://www.httpbin.org/']
def parse(self, response):
pass
这时候我们可以在 parse 方法中打印输出一些 response 对象的基础信息,同时修改 start_urls 为 https://www.httpbin.org/get ,这个链接可以返回 GET 请求的一些详情信息,最终我们可以将 Spider 修改如下:
import scrapy
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['www.httpbin.org']
start_urls = ['https://www.httpbin.org/']
def parse(self, response):
print('url', response.url)
print('request', response.request)
print('status', response.status)
print('headers', response.headers)
print('text', response.text)
print('meta', response.meta)
这里我们打印了 response 的多个属性
-
url:请求的页面 URL,即 Request URL。
-
request:response 对应的 request 对象。
-
status:状态码,即 Response Status Code。
-
headers:响应头,即 Response Headers。
-
text:响应体,即 Response Body。
-
meta:一些附加信息,这些参数往往会附在 meta 属性里。
运行该 Spider,命令如下:
scrapy crawl httpbin
运行结果如下:
2024-01-30 19:15:22 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://httpbin.org/get> (referer: None)
url https://httpbin.org/get
request <GET https://httpbin.org/get>
status 200
headers {b'Content-Length': [b'564'], b'Date': [b'Tue, 30 Jan 2024 11:15:21 GMT'], b'Content-Type': [b'application/json'], b'Server': [b'gunicorn/19.9.0'], b'Access-Control-Allow-Origin': [b'*'], b'Access-Control-Allow-Credentials': [b'true']}
text {
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en",
"Cookie": "name=germey; age=26",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-65b8da49-582154551ba2c815108f46af"
},
"origin": "27.38.238.130",
"url": "https://httpbin.org/get?offset=0"
}
以上省略了部分结果,只摘取了关键的 parse 方法的输出内容。
可以看到,这里分别打印输出了 url、request、status、headers、text、meta 信息。我们可以观察一下,text 的内容中包含了我们请求所使用的 User-Agent、请求 IP 等信息,另外 meta 中包含了几个默认设置的参数。
注意,这里并没有显式地声明初始请求,是因为 Spider 默认为我们实现了一个 start_requests 方法,代码如下:
def start_requests(self):
for url in self.start_urls:
yield Request(url, dont_filter=True)
可以看到,逻辑就是读取 start_urls 然后生成 Request,这里并没有为 Request 指定 callback,默认就是 parse 方法。它是一个生成器,返回的所有 Request 都会作为初始 Request 加入调度队列。
因此,如果我们想要自定义初始请求,就可以在 Spider 中重写 start_requests 方法,比如我们想自定义请求页面链接和回调方法,可以把 start_requests 方法修改为下面这样:
import scrapy
from scrapy import Request
class HttpbinSpider(scrapy.Spider):
name = 'httpbin2'
allowed_domains = ['httpbin.org']
start_url = 'https://httpbin.org/get'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
cookies = {'name': 'germey', 'age': '26'}
def start_requests(self):
for offset in range(5):
url = self.start_url + f'?offset={offset}'
yield Request(url, headers=self.headers,
cookies=self.cookies,
callback=self.parse_response,
meta={'offset': offset})
def parse_response(self, response):
print('url', response.url)
print('request', response.request)
print('status', response.status)
print('headers', response.headers)
print('text', response.text)
print('meta', response.meta)
这里我们自定义了如下内容。
-
url:我们不再依赖 start_urls 生成 url,而是声明了一个 start_url,然后利用循环给 URL 加上了 Query 参数,如 offset=0,拼接到 https://www.httpbin.org/get 后面,这样请求的链接就变成了 https://www.httpbin.org/get?offset=0 。
-
headers:这里我们还声明了 headers 变量,为它添加了 User-Agent 属性并将其传递给 Request 的 headers 参数进行赋值。
-
cookies:另外我们还声明了 Cookie,以一个字典的形式声明,然后传给 Request 的 cookies 参数。
-
callback:在 HttpbinSpider 中,我们声明了一个 parse_response 方法,同时我们也将 Request 的 callback 参数设置为 parse_response,这样当该 Request 请求成功时就会回调 parse_response 方法进行处理。
-
meta:meta 可以用来传递额外参数,这里我们将 offset 的值也赋值给 Request,通过 response.meta 就能获取这个内容了,这样就实现了 Request 到 Response 的额外信息传递。
重新运行看看效果,输出内容如下:
url https://httpbin.org/get?offset=1
request <GET https://httpbin.org/get?offset=1>
status 200
headers {b'Content-Length': [b'564'], b'Date': [b'Tue, 30 Jan 2024 11:15:22 GMT'], b'Content-Type': [b'application/json'], b'Server': [b'gunicorn/19.9.0'], b'Access-Control-Allow-Origin': [b'*'], b'Access-Control-Allow-Credentials': [b'true']}
text {
"args": {
"offset": "1"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en",
"Cookie": "name=germey; age=26",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-65b8da4a-5c3504de597cfd5a7fd5ea4b"
},
"origin": "27.38.238.130",
"url": "https://httpbin.org/get?offset=1"
}
meta {'offset': 1, 'download_timeout': 180.0, 'download_slot': 'httpbin.org', 'download_latency': 0.9361684322357178}
这时候我们看到相应的设置就成功了。
-
url:url 上多了我们添加的 Query 参数。
-
text:结果的 headers 可以看到 Cookie 和 User-Agent,说明 Request 的 Cookie 和 User-Agent 都设置成功了。
-
meta:meta 中看到了 offset 这个参数,说明通过 meta 可以成功传递额外的参数。
通过上面的案例,我们就大致知道了 Spider 的基本流程和配置,可以发现其实现还是很灵活的。
当然除了发起 GET 请求,我们还可以发起 POST 请求。POST 请求主要分为两种,一种是以 Form Data 的形式提交表单,一种是发送 JSON 数据,二者分别可以使用 FormRequest 和 JsonRequest 来实现。例如我们可以分别发起两种 POST 请求,对比一下结果:
import scrapy
from scrapy.http import JsonRequest, FormRequest
class HttpbinSpider(scrapy.Spider):
name = 'httpbin'
allowed_domains = ['httpbin.org']
start_url = 'https://httpbin.org/post'
data = {'name': 'germey', 'age': '26'}
def start_requests(self):
yield FormRequest(self.start_url,
callback=self.parse_response,
formdata=self.data)
yield JsonRequest(self.start_url,
callback=self.parse_response,
data=self.data)
def parse_response(self, response):
print('text', response.text)
这里我们利用 start_requests 方法生成了一个 FormRequest 和 JsonRequest,请求的页面链接修改为了 https://www.httpbin.org/post ,它可以把 POST 请求的详情返回,另外 data 保持不变。
运行结果如下:
2024-01-30 19:22:56 [scrapy.core.engine] DEBUG: Crawled (200) <POST https://httpbin.org/post> (referer: None)
text {
"args": {},
"data": "",
"files": {},
"form": {
"age": "26",
"name": "germey"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en",
"Content-Length": "18",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.0 (KHTML, like Gecko) Chrome/28.0.857.0 Safari/536.0",
"X-Amzn-Trace-Id": "Root=1-65b8dc10-0b5b9afd33b4b51b71974d71"
},
"json": null,
"origin": "27.38.238.130",
"url": "https://httpbin.org/post"
}
2024-01-30 19:22:57 [scrapy.core.engine] DEBUG: Crawled (200) <POST https://httpbin.org/post> (referer: None)
text {
"args": {},
"data": "{\"age\": \"26\", \"name\": \"germey\"}",
"files": {},
"form": {},
"headers": {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en",
"Content-Length": "31",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Windows NT 5.1; ur-IN; rv:1.9.2.20) Gecko/3174-05-19 00:19:44 Firefox/3.6.4",
"X-Amzn-Trace-Id": "Root=1-65b8dc10-3b65de4b0f02dc7823685415"
},
"json": {
"age": "26",
"name": "germey"
},
"origin": "27.38.238.130",
"url": "https://httpbin.org/post"
}
这里我们可以看到两种请求的效果。
第一个 FormRequest,我们可以观察到页面返回结果的 form 字段就是我们请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/x-www-form-urlencoded 的 POST 请求,这种对应的就是表单提交。
第二个 JsonRequest,我们可以观察到页面返回结果的 json 字段就是我们所请求时添加的 data 内容,这说明实际上是发送了 Content-Type 为 application/json 的 POST 请求,这种对应的就是发送 JSON 数据。
这两种 POST 请求的发送方式我们需要区分清楚,并根据服务器的实际需要进行选择。
Request 和 Response
在上面的 Spider 例子中,大部分流程实际是在构造 Request 对象和解析 Response 对象,因此对于它们的用法和参数我们需要详细了解一下。
Request
在 Scrapy 中,Request 对象实际上指的就是 scrapy.http.Request 的一个实例,它包含了 HTTP 请求的基本信息,用这个 Request 类我们可以构造 Request 对象发送 HTTP 请求,它会被 Engine 交给 Downloader 进行处理执行,返回一个 Response 对象。
这个 Request 类怎么使用呢?那自然要了解一下它的构造参数都有什么,梳理如下。
-
url:Request 的页面链接,即 Request URL。
-
callback:Request 的回调方法,通常这个方法需要定义在 Spider 类里面,并且需要对应一个 response 参数,代表 Request 执行请求后得到的 Response 对象。如果这个 callback 参数不指定,默认会使用 Spider 类里面的 parse 方法。
-
method:Request 的方法,默认是 GET,还可以设置为 POST、PUT、DELETE 等。
-
meta:Request 请求携带的额外参数,利用 meta,我们可以指定任意处理参数,特定的参数经由 Scrapy 各个组件的处理,可以得到不同的效果。另外,meta 还可以用来向回调方法传递信息。
-
body:Request 的内容,即 Request Body,往往 Request Body 对应的是 POST 请求,我们可以使用 FormRequest 或 JsonRequest 更方便地实现 POST 请求。
-
headers:Request Headers,是字典形式。
-
cookies:Request 携带的 Cookie,可以是字典或列表形式。
-
encoding:Request 的编码,默认是 utf-8。
-
prority:Request 优先级,默认是 0,这个优先级是给 Scheduler 做 Request 调度使用的,数值越大,就越被优先调度并执行。
-
dont_filter:Request 不去重,Scrapy 默认会根据 Request 的信息进行去重,使得在爬取过程中不会出现重复请求,设置为 True 代表这个 Request 会被忽略去重操作,默认是 False。
-
errback:错误处理方法:如果在请求处理过程中出现了错误,这个方法就会被调用。
-
flags:请求的标志,可以用于记录类似的处理。
-
cb_kwargs:回调方法的额外参数,可以作为字典传递。
以上便是 Request 的构造参数,利用这些参数,我们可以灵活地实现 Request 的构造。
值得注意的是,meta 参数是一个十分有用而且易扩展的参数,它可以以字典的形式传递,包含的信息不受限制,所以很多 Scrapy 的插件会基于 meta 参数做一些特殊处理。在默认情况下,Scrapy 就预留了一些特殊的 key 作为特殊处理。
比如 request.meta['proxy']可以用来设置请求时使用的代理,request.meta['max_retry_times'] 可以设置用来设置请求的最大重试次数等。
更多具体内容可以参见: https://docs.scrapy.org/en/latest/topics/request-response.html#request-meta-special-keys。
另外如上文所介绍的,Scrapy 还专门为 POST 请求提供了两个类一FormRequest 和 JsonRequest,它们都是 Request 类的子类,我们可以利用 FormRequest 的 formdata 参数传递表单内容,利用 JsonRequest 的 json 参数传递 JSON 内容,其他的参数和 Request 基本是一致的。二者的详细介绍可以参考官方文档:
Response
Request 由 Downloader 执行之后,得到的就是 Response 结果了,它代表的是 HTTP 请求得到的响应结果,同样地我们可以梳理一下其可用的属性和方法,以便我们做解析处理使用。
-
url:Request URL。
-
status:Response 状态码,如果请求成功就是 200。
-
headers:Response Headers,是一个字典,字段是一一对应的。
-
body:Response Body,这个通常就是访问页面之后得到的源代码结果了,比如里面包含的是 HTML 或者 JSON 字符串,但注意其结果是 bytes 类型。
-
request:Response 对应的 Request 对象。
-
certificate:是 twisted.internet.ssl.Certificate 类型的对象,通常代表一个 SSL 证书对象。
-
ip_address:是一个 ipaddress.IPv4Address 或 ipaddress.IPv6Address 类型的对象,代表服务器的 IP 地址。
-
urljoin:是对 URL 的一个处理方法,可以传人当前页面的相对 URL,该方法处理后返回的就是绝对 URL。
-
follow/follow_all:是一个根据 URL 来生成后续 Request 的方法,和直接构造 Request 不同的是,该方法接收的 url 可以是相对 URL,不必一定是绝对 URL。
另外 Response 还有几个常用的子类,如 TextResponse 和 HtmlResponse,HtmlResponse 又是 TextResponse 的子类,实际上回调方法接收的 response 参数就是一个 HtmlResponse 对象,它还有几个常用的方法或属性。
-
text:同 body 属性,但结果是 str 类型。
-
encoding:Response 的编码,默认是 utf-8。
-
selector:根据 Response 的内容构造而成的 Selector 对象,Selector 在上一节我们已经了解过,利用它我们可以进一步调用 xpath、css 等方法进行结果的提取。
-
xpath:传入 XPath 进行内容提取,等同于调用 selector 的 xpath 方法。
-
css:传入 css 选择器进行内容提取,等同于调用 selector 的 css 方法。
-
json:是 Scrapy 2.2 新增的方法,利用该方法可以直接将 text 属性转为 JSON 对象。
以上便是对 Response 的基本介绍,关于 Response 更详细的解释可以参考官方文档: https://docs.scrapy.org/en/latest/topics/request-responsehtml#response-subclasses 。