使用Item封装数据
前两篇博客介绍了从页面中提取数据的方法,现在用item封装爬取到的数据
Item和Field
Scrapy提供了Item和Field类,可以用他们自定义数据类,封装爬取到的数据
- Item :自定义数据类(BookItem)的基类
- Field:描述自定义数据类包含那些字段(name price)
自定义一个数据类,只需要继承Item,并创建一系列Field对象的类属性
from scrapy import Item,Field
class BookItem(Item)
name = Field()
price = Field()
Item支持字典接口,因此BookItem在使用上和Python字典类似
book1= BookItem(name = 'Needful Things',price = 45.0)
book1
{'name':'Needful Things','price':45.0}
book2 = BookItem()
book2
{}
book2['name'] = 'Life of Pi'
book2['price'] = 32.5
{'name':'Life of Pi','price':32.5}
访问BookItem对象中的字段与访问字典类似
book.get('price',60)
45.0
list(book.items())
[('price',45.0),('name','Needful Things')]
修改之前的BooskSpider,使用BookItem替代Python字典
class BooksSpider(scrapy.Spider):
def parse(self,response):
for sel in response.css('article.product_pod'):
book = BookItem()
book['name'] = sel.xpath('./h3/a/@title').extract_first()
book['price'] = sel.css('p.price_color::text').extract_first()
yield book
拓展Item子类
根据需求对已有的自定义数据类(Item子类)进行拓展。
例如:
example项目中又添加了一个新的Spider,它负责另一个网站爬取信息
#继承BookItem定义一个ForeigenBookItem类,在其中添加一个翻译字段
class ForeignBookItem(BookItem):
translator = Field()
book = ForeignBookItem()
book['name'] = ' 巴黎圣母院'
book['price'] = 200
book['translator'] = '陈景荣'
Field元数据
入门2中提到,数据由Spider交给Scrapy引擎后,可能会被传递给其他组件
(Item Pipeline Exporter)处理。可以使用Field的元数据传递额外的信息给处理数据的某个组件(例如告诉组件以何种方式处理数据)
class ExampleItem(Item):
x = Field(a = 'hello',b = [1,2,3])
y = Field(a = lambda x:x**2) #y有一个数据,a是个函数
访问一个ExampleItem对象的fields属性,将得到一个包含所有Fideli对象的字典
e = ExampleItem(x = 100,y = 200)
e.fields
{'x':{'a':'hello','b':[1,2,3]}}
{'y':{'a':<function_main_.ExampleItem.<lambda>>}}
type(e.fields['y'])
scrapy.item.Field
实际上,Field是Python字典的子类,可以通过键值对获取Field对象中的元素:
issubclass(Field,dict)
True
field_x['a']
'hello'
应用Field元数据的实例:
假设要把爬取的书籍信息写入csv,每一项数据最终由Scrapy提供的CsvItemExporter写入文件,在爬取过程中,提取到的信息不一定总是一个字符串,有时可能是一个字符串列表
book['authors'] = ['李雷','韩梅梅','吉姆']
写入csv时,需要将列表内所有字符串串行化成一个字符串,
串行化的方式:
- ’李雷|韩梅梅|吉姆‘ #’|‘.join(book[‘authors’])
- ‘李雷;韩梅梅;吉姆’ #’;‘.join(book[‘authors’])
- “[‘李雷’,’韩梅梅’,’吉姆’]”
通过authors字段的元数据告诉CsvItemExporter如何对authors字段串行话:
class BookItem(Item);
authors = Field(serializer = lambda x:'|'.join(x))
使用Item Pipeline处理数据
前面讲了提取数据以及封装数据的方法,现在讲如何对爬取的数据进行处理。
在Scrapy中,Item Pipeline是处理数据的组件,一个Item Pipeline就是一个包含特定的接口的类,通常只负责一种功能的数据处理,在一个项目中可存在多个,指定次序级连起来,形成一条数据处理流水线
Item Pipline的典型应用:
- 清洗数据
- 验证数据有效性
- 过滤掉重复的数据
- 将数据存入数据库
在(1)中,爬取到的书籍价格是以英镑为单位的,可以用Item Pipeline转换为人民币为大威德
实现Item Pipeline
在创建一个Scrapy时,会自动生成一个pipelines.py文件,它用来放置用户自定义的Item Pipeline
class PriceConverterPipeline(object):
#汇率
exchange_rate = 8.5309
def process_item(self,item,spider):
#提取item的price字段
#去掉英镑符号,转换为float
price = float(item['price'][1:])*self.exchange_rate
#保留两位小数,赋值会item的price字段
item['price'] = '¥%.2f%price'
return itemy
- 一个Item Pipeline不需要继承特定的基类,只需要实现某些特定的方法,process_item \ open_spider \ close_spider
- 一个Item Pipeline必须实现一个process_item(item,spider)方法,该方法用来处理每一项由Spider获取到的数据:item(爬取到的一项数据(Item或者字典))。Spider(爬取此项数据的Spider对象)
process_item是核心 - 若process_item在处理某一项item时返回一项数据(item或者字典),返回的数据会传递给下一级Item Pipeline(若存在)继续处理
- 若抛出异常(DropItem),该项的item会被抛弃,不再传递给后面的item处理。通常,在检测无效数据或过滤数据时,会DropItem
除了process_item还有三个常用方法,
- open_spider(self,spider)打开时回调该方法,用于在开始处理数据之前完成某些初始化工作,链接数据库
- close_spider(self,spider)关闭数据库
- from_crawler(cls,crawer)创建对象时回调该方法,通过crawler.settings读取配置,根据配置创建Item Pipeline对象
启用Item Pipeline
Item Pipeline是可选组件,启用前需要在settings.py中进行配置:
ITEM_PIPELINE = {'example.piplines.PriceConverterPipeline':300}
ITEM_PIPELINE是一个字典,把想要启用的Item Pipeline添加到这个字典中,其中每一项的键是每一个Item Pipeline类的导入路径,0~1000表示次序,数值小的在前。
ITEM Pipeline案例
1.去掉重复数据
from scrapy.exceptions import DropItem
class DuplicatesPipline(object):
def__init__(self):
self.book_set = set()
def process_item(self,item,spider):
name = item['name']
if name in self.book_set:
raise DropItem("Duplicate book found:%s"%item)
self.book_set.add(name)
return item
在settings.py中启用DuplicatesPipline:
ITEM_PIPELINES = {
'example.pipeline.DuplicatesPipline':350}
翻阅爬虫的log信息可以找到重复项
[scrapy.core.scraper]WATNING:Dropped:Duplicate book found:
{'name':'The Star-Touched Queen','price':'¥275.5'}
2.将数据存入MongoliaDB
from scrapy.item import Item
import pymongo
class MongoDBPipeline(object):
DB_URL = 'mongodb://locathost:27017/'
DB_NAME = 'scrapy_data'
def open_spider(self,spider):
self.client = pymongodb.MongoClient(self.DB_URL)
self.db = self.client[self.DB_NAME]
def close_spider(self,spider):
self.client.close()
#process_item实现数据库的写入操作,使用self.db和spider.name获取一个集合(collection),然后将数据插入集合中,集合对象insert_one 方法需转入一个字典对象(不能传入Item对象),插入前先判断,不是字典先转换成字典
def process_item(self,item,spider):
collection = self.db[spider.name]
post = dict(item)if isinstance(item,Item)else item
collection.insert_one(post)
return item
使用LinkExtract提取链接
此处链接为页面中包含的链接,两种提取方式
- Selector 把链接当做数据正常提取,少量时足够
- LinkExtract:专门提取链接的类,提取大量链接
(1)中的例子用了第一种方法,先用CSS选择器选中包含下一页链接的a元素并获取href属性,然后调用response.urljoin方法计算出绝对url地址,最后request对象提交
def parse(self,response):
#下一页的链接在ul.page>li.next>a里面
next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
if next_url:
next_url = response.urljoin(next_url)
yield scrapy.Request(next_url,callback = self.parse)
使用LinkExtractor方式
#导入linkextractor它位于LinkExtractor模块
from scrapy.linkextractors import LinkExtractor
class BooksSpider(scrapy.Spider):
def parse(self,response):
#创建一个LinkExtractor对象,传递给restrict_css参数一个css选择表达式,描述下一页链接所在区域
le = LinkExtractor(restrict_css='ul.pager li.next')
#根据描述提取区域,在response对象所包含的页面中提取链接,返回url列表
links = le.extract_links(response)
if links:
next_url = link[0].url
yield
#由于下一页链接只有一个,用link[0]获取link对象,link属性便是链接页面的绝对url(无需再调用response.urljoin方法),构造response对象并提交
scrapy.Request(next_url,callback=self.parseyyy)
描述提起规则
特殊情况:linkExtractor构造器的所有参数都为默认值,构造参数时不传递任何参数(使用默认值),就提取页面中所有链接。
from scrapy.linkextractors import LinkExtractor
le = LinkExtractor()
links = le.extract_links(response1)
[link.url for link in links]
LinkExtractor构造器中的各个参数
- allow
#接受一个正则表达式,提取绝对url与正则表达式匹配的链接,参数为空
#就提取全部链接
le = LinkExtractor(allow = pattern)
- deny
#接受一个正则表达式,与allow相反,排除绝对url与正则表达式匹配的链接
le = LinkExtractor(deny = pattern)
- allow_domains
#接受一个域名或一个域名列表,提取到指定域的链接。
le = LinkExtractor(allow_domains = domains)
- deny_domains
#接受一个域名或一个域名列表,与allow_domains相反,排除到指定的链接
le = LinkExtractor(deny_domains='github.com')
- restrict_xpaths
#接受一个XPath表达式或一个XPath表达式列表,提取XPath表达式选中区域下的链接。
le = LinkExtractor(restrict_xpath = '///div[@id="top"]')
- restruct_css
#接受一个CSS表达式或一个CSS表达式列表,提取CSS选择器选中区域的链接
le = LinkExtractor(restrict_css='div#bottom')
- tags
#接受一个标签,提取指定标签内的链接。默认['a','area'].
- attrs
#接受一个属性,提取属性内的链接 ['href']
le = LinkExtractor(tag = 'script',attrs = 'src')
- process_value
#接受一个形如func(value)的回调函数。若传递了该参数,LinkExtractor将用该函数对每一个链接处理
le = LinkExtractor(process_value = process)
使用Exporter导出数据
Exproter(导出器)支持文件格式
- json
- json Line
- csv
- xml
- Pickle
- Marshal
指定如何导出数据
- 文件路径
- 格式
两种指定方式
1.通过命令行
scrapy crawl books -o books.csv
-o:指定路径(后面跟的文件格式也可以指定格式)
-t :指定格式(可省略 根据-o后面的格式而定)
scrapy crawl books -t csv -o books1.data
以-t参数中的csv为键,在配置字典FEED_EXPORTERS中搜索Exporter,
FEED_EXPORTERS组成:
- 默认配置文件中的FEED_EXPORTERS_BASE(内部支持导出格式)
用户配置文件FEED_EXPORTERS(用户指定导出格式)
添加新的导出格式(实现新的Exporter),可在配置文件settings.py中定义
FEED_EXPORTERS = {'excel':'my_project.my_exporters.ExcelItemExporter'}
指定文件路径时,可以使用%(name)s和%(time)s两个特殊变量:
- %(name)s:会被替换为Spider的名字
- %(time)s:会被替换为文件创建时间
使用配置文件会更加方便
- FEED_URL
#导出文件路径
FEED_URL = 'export_data/%(name)s.data'
- FEED_FORMAT
#导出文件格式
FEED_FORMAT = 'csv'
- FEED_EXPORT_ENCONDING
#导出文件编码(默认json使用数字编码,其他使用utf-8)
FEED_EXPORT_FIELDS = 'gbk'
- FEED_EXPORT_FIELDS
#导出数据包含的字段(默认导出所有字段)并指定次序
FEED_EXPORT_FIELDS = ['name','author','price']
- FEED_EXPORTERS
#用户自定义Exporter字典,添加新的导出数据格式时使用
FEED_EXPORTERS = {'excel':'my_project.my_exporters.ExcelItemExporter'}
添加导出数据格式
某系情况下,需要添加新的导出数据格式,此时需要实现新的Exporter类
每一个Exporter都是BaseItemExporter的子类,如 jsonItemExporter
BaseItemExporter定义了一些借口待子类实现
- export_item(self,item)
负责导出爬取到的每一项数据,参数item为一项爬取到的数据,每个子类必须实现该方法
- start_expoorting(self)
导出开始时被调用,可在该方法中执行某些初始化工作
- finish_exporting(self)
导出完成时被调用,执行某些清理工作
实例:实现excel导出
在项目中创建一个my_exporters.py(与settings.py同级目录),在其中实现ExcelItemExporter
from scrapy.exporters import BaseItemExporter
import xlwt
#使用第三方库xlwt将数据写入excle
class ExcelItemExporter(BaseItemExporter):
def__init__(self,file,**kwarges):
self._configure(kwargs)
self.file = file
#创建Workbook对象和Worksheet对象,并初始化用来记录写入航坐标的self.row
self.wbook = xlwt.Workbook()
self.wsheet = self.wbook.add_sheet('scrapy')
self.row = 0
#finish_exporting在所有数据都被写入Excel表格后被调用,在该方法中用sefl.wbook.save方法将excle写入
def finish_exporting(self):
self.wbook.save(self.file)
def export_item(self,item):
files = self._get_serialized_fields(item)
for col,v in enumerate(x for _,x in files):
self.wsheet.write(self.row,col,v)
self.row += 1
完成ExcelItemExporter后,在配置文件settings.py中添加
FEED_EXPORTERS = {'excel':'example.my_exporters.ExcelItemExporter'}
可以使用excel导出数据了
scrapy crawl books -t excle -o book.xls