Scrapy从入门到精通(3)

使用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

猜你喜欢

转载自blog.csdn.net/weixin_39381833/article/details/80907847