经过了上一章的学习,我们已经在开发环境上安装好了scrapy的包,接下来我们就将初始化一个项目。
初始化项目
scrapy startproject tutorial
在开发环境中,使用命令行切换到项目将来想要存放的目录下执行上述命令,命令中的最后一个参数表示项目名称。运行结果如下所示:
项目目录介绍
经过上面的命令,将会在指定目录(命令行执行的目录)下创建一个项目,项目内的文件目录结构如下。
tutorial/
scrapy.cfg # 部署配置文件
tutorial/ # 项目的 Python 模块,你将从这里导入你的代码
__init__.py
items.py # 项目项定义文件
middlewares.py # 项目中间件文件
pipelines.py # 项目管道文件
settings.py # 项目设置文件
spiders/ # 稍后您将放置spiders的目录
__init__.py
目录结构的每部分功能都对应上一张基础知识中的流程图。
自定义爬虫类
spiders
文件夹中有一个scrapy框架自动创建的文件__init__.py
,我们不去理会。我们需要自己定义一个爬虫类,Scrapy
将使用这个类从一个网站(或一组网站)中抓取信息。自定义的爬虫类必须继承 Spider
,并定义要发出的初始请求(即最开始请求的url),在类方法中可选择如何跟踪页面中的链接,以及如何解析下载的页面内容以提取数据。
自定义第一个爬虫
这是我们第一个自定义的 Spider
的类文件。将其保存在项目中的 tutorial/spider 目录下,一个名为quotes_spider.py 的文件中:
自定义第一个爬虫有两种方法:
-
通过命令行进行创建
在项目的根目录下执行下面的命令,通过命令创建爬虫,命令行将自动地在tutorial/spider
目录下创建一个.py
文件。scrapy genspider quotes_spider quotes.toscrape.com
-
人工创建文件并初始化代码
人工创建的方式,即人工在tutorial/spider
目录下创建一个.py
文件。例如:quotes_spider.py
录入代码
创建好文件后,将下面地这些代码录入到文件中
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page = response.url.split("/")[-2]
filename = f'quotes-{page}.html'
with open(filename, 'wb') as f:
f.write(response.body)
self.log(f'Saved file {filename}')
代码解释:
如您所见,我们的 自定义爬虫类继承了 scrapy.Spider
,并定义了一些属性和方法:
-
name
:标识spiders。它在一个项目中必须是唯一的,即不能为不同的 Spider 设置相同的名称。 -
start_requests()
:必须返回一个可迭代的请求(您可以返回一个请求列表或编写一个函数生成器),spiders
将从中开始爬行。后续请求将从这些初始请求中依次生成。 -
parse()
:该方法将被回调,用来处理每个请求下载的响应。response
参数是TextResponse
的一个实例,它保存页面内容并有更多有用的方法来处理它。
parse()
方法通常会解析响应,将抓取的数据提取为 dicts
,并找到要遵循的新 URL 并从中创建新请求 (Request)
。
运行爬虫
想要运行刚才创建的spider
,请将命令行切换到项目的根目录并运行以下命令:
scrapy crawl quotes
运行命令后会返回很多的运行信息,比如下面这样的:
运行结果
上述命令行执行后,在项目的根目录下会发现多了两个文件,quotes-1.html、quotes-2.html
,而这两个文件正是我们爬虫类中所写的,发送了两个请求后的结果。
代码逐条分析
- 导入scrapy包
- 定义类并继承自
scrapy.Spider
- 定义当前的爬虫名称为
quotes
- 定义方法
start_requests
,该方法名有scrapy
框架定死,并不能随意更改 - 定义一个list,并填充两个要访问的起始
url
- 遍历列表并调用
scrapy.Request
去访问列表中的每一个url,并将结果yield
(后续会详解) - 定义方法
parse
,该方法由scrapy提供不可更改名称,并接受一个response
作为参数 - 获取
response
的url地址用'/'
进行截取获取最后倒数第二位,也就是url中的1、2
表示页码。 - 声明一个文件名,文件名的后半部分就是刚才获取的页码
- 以**
wb
**的凡是打开文件(不存在则创建) - 将获取到的内容的body部分(url请求结果的html源码,后面会讲到),填充到文件中
- 打印一条日志。
由于起始发送的url为列表,两条。因此方法parse
会被回调两遍。
刚才的代码执行过程都做了什么事情呢?
Scrapy 调度 Spider 的 start_requests 方法返回的 scrapy.Request 对象。收到每个响应后,它会实例化 Response 对象并调用与请求关联的回调方法(在本例中为 parse 方法),将响应作为参数传递。
简易版本
上述的代码有没有简易版本呢,当然是有的。因为scrapy其实已经内置了发送请求的部分,因此我们并不需要自己定义 start_requests
方法,并调用 scrapy.Request(url=url, callback=self.parse)
去发送请求,scrapy会帮我们去发送请求,我们只需要告诉scrapy该去请求什么地址即可,完整代码如下:
import scrapy
class QuotesSpidersSpider(scrapy.Spider):
name = 'quotes_spiders'
allowed_domains = ['quotes.toscrape.com']
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
def parse(self, response):
page = response.url.split("/")[-2]
filename = f'quotes-{page}.html'
with open(filename, 'wb') as f:
f.write(response.body)
self.log(f'Saved file {filename}')
如上代码所示,只需要告知爬虫要爬取的地址集即可,地址集放在一个名为start_urls
的变量中。
有没有发现这次的代码和上次的略有不同, 变量中多了一个allowed_domains
,这个变量是在告诉爬虫我们定义了爬取地址的范围,只在这个域名下爬取,当获取到的地址不是该域名下则不会爬取。
注:Parse() 方法将被调用来处理对这些 URL 的每个请求,即使我们没有明确告诉 Scrapy 这样做。发生这种情况是因为 parse() 是 Scrapy 的默认回调方法,它在没有明确分配回调的请求时被调用。
至此我们的项目初始化和第一个爬虫小例子就结束了,我们学习该如何初始化项目和写了第一个爬虫小例。接下来我们将要学习如何配置scrapy
,通过合理的配置,达到我们想要爬取任意不同目标的需求,并为下面的工作做准备。