运行
进入Scrapy创建的目录,运行如下命令:
scrapy crawl quotes
首先,Scrapy输出了当前的版本号以及正在启动的项目名称。接着输出了当前settings.py中一些重写后的配置。然后输出了当前所应用的Middlewares和Pipelines。Middlewares默认是启动的,可以在settings.py中修改。Pipelines默认是空,同样页可以在settings.py中配置。
接下来就是输出各个页面的抓取结果了,可以看到爬虫一边解析,一边翻页,直至将所有内容抓取完毕,然后终止。
最后,Scrapy输出了整个抓取过程的统计信息,如请求的字节数、请求次数、响应次数、完成原因等。
保存到文件
运行完Scrapy后,我们只在控制台看到了输出结果。要完成保存结果,不需要额外的代码,Scrapy提供的Feed Exports可以轻松将抓取结果输出。例如,我们想将上面的结果保存成JSON文件,可以执行如下命令:
scrapy crawl quotes -o quotes.json
命令运行后,项目内多了一个quotes.json文件,文件包含了刚才抓取的所有内容,内容是JSON格式。
另外我们还可以每一个Item输出一行JSON,输出后缀为jl,为jsonline的缩写,命令如下所示:
scrapy crawl quotes -o quotes.jl
或
scrapy crawl quotes -o quotes.jsonlines
输出格式还支持很多种,例如csv、xml、pickle、marshal格式以及ftp远程输出:
scrapy crawl quotes -o quotes.csv
scrapy crawl quotes -o quotes.xml
scrapy crawl quotes -o quotes.pickle
scrapy crawl quotes -o quotes.marshal
scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv
其中,ftp输出需要正确配置用户名、密码、地址、输出路径,否则会报错。
通过Scrapy提供的Feed Exports,我们可以轻松地输出抓取结果到文件。对于一些小型项目来说,这应该足够了。不过如果想要更复杂的输出,如输出到数据库等,我们可以使用Item Pileline来完成。
使用Item Pipeline
如果想进行更复杂的操作,如将结果保存到MongoDB数据库,护着筛选某些有用的Item,则我们可以定义Item Pipeline来实现。
Item Pipeline为项目管道。当Item生成后,它会自动被送到Item Pipeline进行处理,我们常用Item Pipeline来做如下操作。
- 清理HTML数据。
- 验证爬取数据,检查爬取字段。
- 查重并丢弃重复内容。
- 将爬取结果保存到数据库。
要实现Item Pipeline很简单,只需要定义一个类并实现process_item()方法即可。启用Item Pipeline后,Item Pipeline会自动调用这个方法。process_item()方法必须返回包含数据的字典或Item对象,或者抛出DropItem异常。
porcess_item()方法有两个参数。一个参数是item,每次Spider生成的Item都会作为参数传递过来。另一个参数是spider,就是Spider的实例。
接线来,我们实现一个Item Pipeline,筛掉text长度大于50的Item,并将结果保存到MongoDB。
修改项目里的pipelines.py文件,之前用命令行自动生成的文件内容可以删掉,增加一个TextPipeline类,内容如下:
from scrapy.exceptions import DropItem
class TextPipeline(object):
def __init__(self):
self.limit = 50
def process_item(self, item, spider):
if item['text']:
if len(item['text']) > self.limit:
item['text'] = item['text'][0:self.limit].rstrip() + '...'
return item
else:
return DropItem('Missing Text')
这段代码在构造方法里定义了限制长度为50,实现了process_item()方法,其参数是item和spider。首先该方法判断item的text属性是否存在,如果不存在,则抛出DropItem异常;如果存在,再判断长度是否大于50,如果大于,那就截断然后拼接省略号,再将item返回即可。
接下来,我们将处理后的item存入MongoDB,定义另一个Pipeline。同样在pipelines.py中,我们实现另一个类MongoPipeline,内容如下:
import pymongo
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri = crawler.settings.get('MONGO_URI'),
mongo_db = crawler.settings.get('MONGO_DB')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
name = item.__class__.__name__
self.db[name].insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
MongoPipeline类实现了API定义的另外几个方法:
- from_crawler。它是一个类方法,用@classmethod标识,是一种依赖注入的方式。它的参数就是crawler,通过crawler我们可以拿到全局配置的每个配置信息。在全局配置settings.py中,我们可以定义MONGO_URI和MONGO_DB来指定MongoDB连接需要的地址和数据库名称,拿到配置信息之后返回类对象即可。所以这个方法的定义主要是用来获取settings.py中的配置的。
- open_spider。当Spider开启时,这个方法被调用。上文程序中主要进行了一些初始化操作。
- close_spider。当Spider关闭时,这个方法会调用。上文程序中将数据库连接关闭。
最主要的process_item()方法则执行了数据插入操作。
定义好TextPipeline和MongoPipeline这两个类后,我们需要再settings.py中使用它们。MongoDB的连接信息还需要定义。
我们在settings.py中加入如下内容:
ITEM_PIPELINES = {
'tutorial.pipelines.TextPipeline':300,
'tutorial.pipelines.MongoPipeline':400,
}
MONGO_URI = 'localhost'
MONGO_DB = 'tutorial'
赋值ITEM_PIPELINES字典,键名是Pipeline的类名称,键值是调用优先级,是一个数字,数字越小赌赢的Pipeline越先被调用。
再重新执行爬取,命令如下所示:
scrapy crawl quotes
爬取结束后,MongoDB中穿件了一个tutorial的数据库、QuoteItem的表,如图所示:
长的text已经被处理并追加省略号,短的text保持不变,author和tags也都相应保存。