如何在Python中构建一个高度可扩展的数据流管道(附教程)

305 阅读5分钟

在Python中建立一个高度可扩展的数据流管道

在Python中构建高度可扩展的数据流管道的步骤指南

Python已经将自己塑造成数据密集型工作的语言。我们到处都能看到它,只是因为用Python做原型真的很快,而且由于其简单的语法,人们都很喜欢它,这一浪潮也在数据行业登陆了。数据工程师和数据科学家也开始在他们的数据密集型工作中使用它。在这个故事中,我们将使用Python建立一个非常简单和高度可扩展的数据流管道。

数据流是传输连续的数据流的过程。

现在我们知道,在管道的一侧,我们将有一些或至少有一个数据生产者不断地生产数据,在另一侧,我们将有一些或至少有一个数据消费者不断地消费这些数据。

架构

第一件事是设计一个可扩展的、灵活的架构,以证明这一主张。我们将使用Redis作为数据管道,为了这个故事,我们将使用一个非常简单的数据搜刮微服务,使用Scrapy独立作为数据生产者,并使用一个单独的微服务作为数据消费者。

构建数据生产者

首先,我们需要建立一个简单的Python项目,并激活虚拟环境。对于这个具体的故事,我们将使用Scrapy的官方教程。我们需要运行下面给出的命令来创建一个空的Scrapy项目。

scrapy startproject producer

现在我们需要创建一个能够真正从某处刮取数据的蜘蛛。让我们在spiders目录下创建一个新的文件quotes_spider.py ,并在其中添加下面的代码。

import scrapyclass QuotesSpider(scrapy.Spider):    name = "quotes"    def start_requests(self):        urls = [            'https://quotes.toscrape.com/page/1/',            'https://quotes.toscrape.com/page/2/',        ]        for url in urls:            yield scrapy.Request(url=url, callback=self.parse)    def parse(self, response, **kwargs):        for quote in response.css('.quote .text::text').getall():            yield {                'quote': quote            }

这段特定的代码将访问网站上的第1页和第2页,并提取报价的标题。要运行这个蜘蛛程序,我们需要输入cd producer ,进入scrapy项目目录,然后用scrapy crawl quotes -o ouput.json 来运行蜘蛛程序。-o 将把所有产生的数据指向output.json 文件。

我们的数据生产者一方现在已经准备好了,但是我们需要把这些数据放到数据管道里,而不是放到文件里。在将数据放入数据管道之前,我们需要先建立一个数据管道。

建立一个数据管道

首先,我们需要在我们的系统上安装Redis,要做到这一点,我们需要遵循Redis的官方安装指南。在安装完Redis并运行后,它应该显示如下的内容。

现在我们需要在Redis函数周围创建包装器,使其更加人性化。让我们先在root ,创建一个目录,名称为pipeline ,并在这个目录下创建一个新的文件reid_client.py

现在我们需要在我们的redis-client.py 文件中添加下面给出的代码。代码是不言自明的,我们创建了一个数据获取器和数据设置器。这两个都是处理JSON数据的,因为Redis只能存储字符串数据,存储字符串数据我们需要对其进行JSONIFY。

import jsonimport redisclass RedisClient:    """    Custom Redis client with all the wrapper funtions. This client implements FIFO for pipeline.    """    connection = redis.Redis(host='localhost', port=6379, db=0)    key = 'DATA-PIPELINE-KEY'    def _convert_data_to_json(self, data):        try:            return json.dumps(data)        except Exception as e:            print(f'Failed to convert data into json with error: {e}')            raise e    def _convert_data_from_json(self, data):        try:            return json.loads(data)        except Exception as e:            print(f'Failed to convert data from json to dict with error: {e}')            raise e    def send_data_to_pipeline(self, data):        data = self._convert_data_to_json(data)        self.connection.lpush(self.key, data)    def get_data_from_pipeline(self):        try:            data = self.connection.rpop(self.key)            return self._convert_data_from_json(data)        except Exception as e:            print(f'Failed to get more data from pipeline with error: {e}')

将数据从生产者放入管道中

现在我们已经创建了一个管道,我们可以开始从数据生产者那边把我们的数据放进去。为此,我们需要在scrapy中创建一个管道,将每一个刮来的项目添加到Redis中,然后我们再消费它。只需将下面的代码添加到你的pipelines.py scrapy项目的文件中。

from pipeline.redis_client import RedisClientclass ProducerPipeline:    redis_client = RedisClient()    def process_item(self, item, spider):        self.redis_client.send_data_to_pipeline(item)        return item

我们还需要在我们的Scrapy项目中启用这个管道,为此我们需要在Scrapy项目的settings.py 中取消对下面几行的注释。

ITEM_PIPELINES = {   'producer.pipelines.ProducerPipeline': 300,}

这将开始向Redis发送数据,为了验证,我们可以用redis-cli 来检查我们的管道,并输入LLEN 'DATA-PIPELINE-KEY’ 来查看数据管道中的引号数量。

构建消费者和消费数据

由于我们已经建立了一个管道和一个生产者,它可以不断地把数据放到管道中,不受数据消费的影响,我们已经完成了一半以上,我们需要从数据管道中获取数据,并根据我们的需要消费它,把它称为一个项目。

让我们在root ,创建一个新的目录,命名为consumer ,并在其中创建一个新的文件,命名为quotes_consumer.py

quotes_consumer.py 文件中添加下面给出的代码。代码检查新的数据,如果它在管道中找不到任何新的数据,那么它就会睡觉,否则它就会摄取这些数据,在我们的例子中,我们要把报价保存到一个文本文件中。

import timefrom pipeline.redis_client import RedisClientclass QuotesConsumer:    redis_client = RedisClient()    sleep_seconds = 1    def run(self):        while True:            if self.redis_client.get_items_in_pipeline() == 0:                print(f'No new data in pipeline, sleeping for {self.sleep_seconds} seconds...')                time.sleep(self.sleep_seconds)                self.sleep_seconds += 1                continue            self.sleep_seconds = 1            data = self.redis_client.get_data_from_pipeline()            print(f'Obtained data from pipeline, saving to file...')            with open('quotes.txt', 'a+') as file:                file.write(data.get('quote'))if __name__ == "__main__":    consumer = QuotesConsumer()    consumer.run()

在这一步之后,我们可以独立运行scrapy spider和consumer,这有助于我们以非常高的速度进行数据流,因为数据生产和消费是相互独立的。

你可以在以下Github仓库中找到完整的代码。

如果你喜欢这篇文章,请在Twitter上关注我 @haseeb_tweets 如果你不喜欢这篇文章,请在Twitter上给我发短信 @haseeb_tweets 以帮助我进行自我反思。