1 spiders

spiders是一个类,定义了如何去爬取一个网站(或一组网站),包括如何执行(跟踪链接)以及如何从他们的页面中提取结构化数据(例如抓取项目),换句话说,spiders是定义为特定站点爬取和解析页面的定制行为(或者,在某些情况下,是一组站点的定制行为)。
对于spiders来说,爬取的流程是这样的:
1.首先生成初始请求,以抓取第一个url,然后指定一个回调函数,并使用从这些请求下载的response作为参数调用;要执行的第一个请求是通过调用startr_request()方法获得的,该方法(默认情况下)为start_url中指定的url生成request,然后通过parse方法回调;
2.在回调函数中,parse方法解析response(web页面),并使用提取的数据、Item对象、request对象或这些对象的迭代对象。这些请求也将包含一个回调(可能是相同的),然后由Scrapy下载,然后由指定的回调处理;
3.在回调函数中,你可以使用 Selectors(也可以使用BeautifulSoup, lxml或其它你熟悉的方式)解析数据,并生成解析后的结果Items;
4.最后,从spiders返回的条目通常会持久化到数据库中(使用 Item Pipeline),或者使用Feed exports将其写入到文件中。
尽管这个流程适合于所有的spider,但是Scrapy里面为不同的使用目的实现了一些常见的Spider。下面我们把它们列出来。

1.2 基础spider

scrapy.Spider
class scrapy.spiders.Spider
这是最简单的爬虫,也是所有其他爬虫必须继承的(包括与scrapy捆绑在一起的爬虫,以及你自己编写的爬虫)。它没有提供任何特殊的功能。它只提供了一个默认的start_request()方法,它从start_urls的爬虫属性中发送请求,并为每个结果的响应调用parse解析。
常用属性和方法如下表:

名称 含义
name spider必须要写明的属性,而且在整个项目中要唯一。
allowed_domains 一个可选的字符串列表,其中包含该爬行器允许爬行的域名,如果启用了offsiteMidware,不属于这个列表中指定的域名(或它们的子域)的请求将不会被跟踪。
start_urls 当没有指定特定的url时,将开始爬取的url列表。
start_requests() 这个方法必须返回一个可遍历的第一个请求,以便为这个spider爬取。
parse(response) 当它们的请求没有指定回调时的默认回调,用于处理下载的response

1.3 通用spider

scrapy附带了一些有用的通用spider,你可以用它来子类化你的蜘蛛spider。它们的目的是为一些常见的抓取案例提供方便的功能,比如根据某个规则跟踪站点上的所有链接,从站点地图爬行,或者解析xml/csv。
CrawlSpider
class scrapy.spiders.CrawlSpider
这是爬行有规则网站最常用的spider,因为它通过定义一组规则提供了一种方便的机制来跟踪链接,除了继承了spider的属性外,CrawlSpider提供了一个特殊的rule属性,rule是一个(或多个)规则对象的列表,每个规则都定义了爬行站点的特定行为,如果多个规则匹配相同的链接,那么第一个规则将根据该属性中定义的顺序使用。
示例如下:

import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # 提取链接匹配'category.php' (但是不匹配 'subsection.php')
        # 如果没有指定默认回调函数则按默认方式递归执行.
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # 提取链接匹配 'item.php' 并且通过parse_item解析
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

XMLFeedSpider
class scrapy.spiders.XMLFeedSpider
XMLFeedSpider是为解析XML而设计的,方法是通过一个特定的节点名对其进行遍历。迭代器可以从iternodes, xml, and html中选择。出于性能的考虑,建议使用iternodes迭代器,因为xml和html迭代器一次生成整个DOM,以便解析它。但是,使用html作为迭代器在解析XML时可能很有用。
代码示例如下:


from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem

class MySpider(XMLFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.xml']
    # 这实际上是不必要的,因为它是默认值
    iterator = 'iternodes'  
    itertag = 'item'

    def parse_node(self, response, node):
        self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

基本上,我们所做的就是创建一个从给定的start_url中下载提要的spider,然后遍历每个条目标记,将它们打印出来,并在一个条目中存储一些随机的数据。

CSVFeedSpider
class scrapy.spiders.CSVFeedSpider
这个爬行器与xmlfeed非常相似,只是它遍历了行,而不是节点。每次迭代中调用的方法都是parse_row()。
示例代码:

from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem

class MySpider(CSVFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.csv']
    delimiter = ';'
    quotechar = "'"
    headers = ['id', 'name', 'description']

    def parse_row(self, response, row):
        self.logger.info('Hi, this is a row!: %r', row)

        item = TestItem()
        item['id'] = row['id']
        item['name'] = row['name']
        item['description'] = row['description']
        return item

2 Items

爬虫爬取的主要目标是从非结构化数据源中提取结构化数据,通常是web页面。作为Python语言,Scrapy spiders可以返回提取的数据。虽然方便而又熟悉,但Python却缺乏结构,特别是在一个包含许多spider的大型项目中在字段名中输入错误或返回不一致的数据。
为了定义常见的输出数据格式,scrapy提供了item类,Item对象是用来收集提取数据的简单容器。它们提供了一个类似于字典的API,提供了一种方便的语法,用于声明可用字段。
各种各样的scrapy组件使用由item提供的附加信息,查看已声明的字段,以找出导出的列,可以使用item字段元数据定制序列化,trackref跟踪项目实例以帮助发现内存泄漏。

1.2 定义Item

Item使用简单的类定义语法和字段对象声明。见下面例子:

import scrapy

class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str)

字段类型就是简单的scrapy.Field.

1.3 Item Fields

Field对象用于为每个字段指定元数据,上面列子中的serializer函数就说明了这一点。
您可以为每个字段指定任何类型的元数据,对Field对象的值没有限制。需要注意的是,用于声明该项的字段对象不会被分配为类属性。相反,可以通过Item.fields访问它们。

1.4 使用Item

下面是一些与项目一起执行的常见任务的例子,使用上面声明的产品项目。您会注意到这个API非常类似于API。

1.4.1 创建items

# 注意要先创建上面例子中的Product类
>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)

1.4.2 获取Field值

>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC

>>> product['price']
1000

>>> product['last_updated']
Traceback (most recent call last):
    ...
KeyError: 'last_updated'

>>> product.get('last_updated', 'not set')
not set

>>> product['lala'] # getting unknown field
Traceback (most recent call last):
    ...
KeyError: 'lala'

>>> product.get('lala', 'unknown field')
'unknown field'

>>> 'name' in product  
True

>>> 'last_updated' in product  
False

>>> 'last_updated' in product.fields 
True

>>> 'lala' in product.fields 
False

1.4.3 设置Field值

>>> product['last_updated'] = 'today'
>>> product['last_updated']
today

>>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
    ...
KeyError: 'Product does not support field: lala'

1.4.4 获取所有内容

要获取所有内容,跟字典操作相同,如下:

>>> product.keys()
['price', 'name']

>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]

1.4.5 其它常见命令

复制items

>>> product2 = Product(product)
>>> print product2
Product(name='Desktop PC', price=1000)

>>> product3 = product2.copy()
>>> print product3
Product(name='Desktop PC', price=1000)

从items创建字典

>>> dict(product) # create a dict from all populated values
{'price': 1000, 'name': 'Desktop PC'}

从字典创建items

>>> Product({'name': 'Laptop PC', 'price': 1500})
Product(price=1500, name='Laptop PC')

>>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
Traceback (most recent call last):
    ...
KeyError: 'Product does not support field: lala'

这篇主要让大家先了解scrapy的各部分概念及基本用法,后续实战项目时我们再来回头看,会更容易懂。