21、Redis与数据解析实战指南

Redis与数据解析实战指南

1. Redis数据存储

Redis是一个高性能的键值对存储数据库,在数据处理和存储方面有着广泛的应用。下面将详细介绍Redis在不同数据结构存储上的应用。

1.1 RedisHashMap

RedisHashMap可作为持久化的键值字典单独使用。虽然它能支持大量的键和任意字符串值,但对于整数值和较少数量的键,其存储结构更为优化。尽管Redis作为网络数据库速度很快,但相较于内存中的FreqDist,它的速度会明显变慢。不过,使用Redis可以获得数据的持久性和并发处理能力。

1.2 存储条件频率分布

nltk.probability.ConditionalFreqDist 类是 FreqDist 实例的容器,每个条件对应一个 FreqDist ,用于统计依赖于其他条件的频率。我们可以基于Redis创建一个与之API兼容的类 RedisConditionalHashFreqDist

准备工作 :需要安装Redis和redis - py,并启动redis - server实例。

操作步骤
redisprob.py 中定义 RedisConditionalHashFreqDist 类,该类继承自 nltk.probability.ConditionalFreqDist 并覆盖 __getitem__() 方法:

from nltk.probability import ConditionalFreqDist
from rediscollections import encode_key

class RedisConditionalHashFreqDist(ConditionalFreqDist):
    def __init__(self, r, name, cond_samples=None):
        self._r = r
        self._name = name
        ConditionalFreqDist.__init__(self, cond_samples)

        for key in self._r.keys(encode_key('%s:*' % name)):
            condition = key.split(':')[1]
            self[condition]  # calls self.__getitem__(condition)

    def __getitem__(self, condition):
        if condition not in self._fdists:
            key = '%s:%s' % (self._name, condition)
            val = RedisHashFreqDist(self._r, key)
            super(RedisConditionalHashFreqDist, self).__setitem__(condition, val)

        return super(RedisConditionalHashFreqDist, self).__getitem__(condition)

    def clear(self):
        for fdist in self.values():
            fdist.clear()

创建该类的实例:

from redis import Redis
from redisprob import RedisConditionalHashFreqDist
r = Redis()
rchfd = RedisConditionalHashFreqDist(r, 'condhash')
print(rchfd.N())  # 输出: 0
print(rchfd.conditions())  # 输出: []
rchfd['cond1']['foo'] += 1
print(rchfd.N())  # 输出: 1
print(rchfd['cond1']['foo'])  # 输出: 1
print(rchfd.conditions())  # 输出: ['cond1']
rchfd.clear()

工作原理 RedisConditionalHashFreqDist 使用名称前缀来引用 RedisHashFreqDist 实例。传入的名称作为基名,与每个条件组合,为每个 RedisHashFreqDist 创建唯一名称。例如,基名为 condhash ,条件为 cond1 ,则 RedisHashFreqDist 的最终名称为 condhash:cond1

1.3 存储有序字典

Redis支持有序字典,其键为字符串,值为浮点分数。这种结构在计算信息增益和存储单词及其分数时非常有用。

准备工作 :安装Redis和redis - py,并启动redis - server实例。

操作步骤
rediscollections.py 中定义 RedisOrderedDict 类,该类继承自 collections.MutableMapping

import collections
from rediscollections import encode_key

class RedisOrderedDict(collections.MutableMapping):
    def __init__(self, r, name):
        self._r = r
        self._name = encode_key(name)

    def __iter__(self):
        return iter(self.items())

    def __len__(self):
        return self._r.zcard(self._name)

    def __getitem__(self, key):
        return self._r.zscore(self._name, encode_key(key))

    def __setitem__(self, key, score):
        self._r.zadd(self._name, encode_key(key), score)

    def __delitem__(self, key):
        self._r.zrem(self._name, encode_key(key))

    def keys(self, start=0, end=-1):
        return self._r.zrevrange(self._name, start, end)

    def values(self, start=0, end=-1):
        return [v for (k, v) in self.items(start=start, end=end)]

    def items(self, start=0, end=-1):
        return self._r.zrevrange(self._name, start, end, withscores=True)

    def get(self, key, default=0):
        return self[key] or default

    def iteritems(self):
        return iter(self)

    def clear(self):
        self._r.delete(self._name)

创建该类的实例:

from redis import Redis
from rediscollections import RedisOrderedDict
r = Redis()
rod = RedisOrderedDict(r, 'test')
print(rod.get('bar'))  # 输出: None
print(len(rod))  # 输出: 0
rod['bar'] = 5.2
print(rod['bar'])  # 输出: 5.2000000000000002
print(len(rod))  # 输出: 1
print(rod.items())  # 输出: [(b'bar', 5.2)]
rod.clear()

工作原理
| 方法 | 功能 | Redis命令 |
| ---- | ---- | ---- |
| __len__() | 获取有序集合中的元素数量 | zcard |
| __getitem__() | 获取键的分数,键不存在时返回0 | zscore |
| __setitem__() | 向有序集合中添加键及其分数,键已存在则更新分数 | zadd |
| __delitem__() | 从有序集合中删除键 | zrem |
| keys() | 获取有序集合中按最高分数排序的所有键 | zrevrange |
| values() | 从 items() 方法中提取所有分数 | - |
| items() | 获取有序集合中按最高分数排序的键值对列表 | zrevrange |
| clear() | 从Redis中删除整个有序集合 | delete |

默认情况下,Redis有序集合中的项按从低到高排序,而我们通常希望按从高到低排序,因此 keys() items() 方法使用 zrevrange 而不是 zrange

1.4 分布式单词评分

我们可以结合Redis和execnet进行分布式单词评分。

准备工作 :安装Redis、redis - py和execnet,并在本地主机上启动redis - server实例。

操作步骤

from dist_featx import score_words
from nltk.corpus import movie_reviews

labels = movie_reviews.categories()
labelled_words = [(l, movie_reviews.words(categories=[l])) for l in labels]
word_scores = score_words(labelled_words)
print(len(word_scores))  # 输出: 39767
topn_words = word_scores.keys(end=1000)
print(topn_words[0:5])  # 输出: [b'bad', b',', b'and', b'?', b'movie']

from redis import Redis
r = Redis()
[r.delete(key) for key in ['word_fd', 'label_word_fd:neg', 'label_word_fd:pos', 'word_scores']]

工作原理
dist_featx.py 模块中的 score_words() 函数完成以下操作:
1. 打开网关和通道,向每个通道发送初始化数据。
2. 通过通道发送每个 (label, words) 元组进行计数。
3. 向每个通道发送完成消息,等待完成回复,然后关闭通道和网关。
4. 根据计数计算每个单词的分数,并存储在 RedisOrderedDict 中。

import itertools, execnet, remote_word_count
from nltk.metrics import BigramAssocMeasures
from redis import Redis
from redisprob import RedisHashFreqDist, RedisConditionalHashFreqDist
from rediscollections import RedisOrderedDict

def score_words(labelled_words, score_fn=BigramAssocMeasures.chi_sq, host='localhost', specs=[('popen', 2)]):
    gateways = []
    channels = []

    for spec, count in specs:
        for i in range(count):
            gw = execnet.makegateway(spec)
            gateways.append(gw)
            channel = gw.remote_exec(remote_word_count)
            channel.send((host, 'word_fd', 'label_word_fd'))
            channels.append(channel)

    cyc = itertools.cycle(channels)

    for label, words in labelled_words:
        channel = next(cyc)
        channel.send((label, list(words)))

    for channel in channels:
        channel.send('done')
        assert 'done' == channel.receive()
        channel.waitclose(5)

    for gateway in gateways:
        gateway.exit()

    r = Redis(host)
    fd = RedisHashFreqDist(r, 'word_fd')
    cfd = RedisConditionalHashFreqDist(r, 'label_word_fd')
    word_scores = RedisOrderedDict(r, 'word_scores')
    n_xx = cfd.N()

    for label in cfd.conditions():
        n_xi = cfd[label].N()
        for word, n_ii in cfd[label].iteritems():
            word = word.decode()
            n_ix = fd[word]
            if n_ii and n_ix and n_xi and n_xx:
                score = score_fn(n_ii, (n_ix, n_xi), n_xx)
                word_scores[word] = score

    return word_scores

remote_word_count.py 模块:

from redis import Redis
from redisprob import RedisHashFreqDist, RedisConditionalHashFreqDist

if __name__ == '__channelexec__':
    host, fd_name, cfd_name = channel.receive()
    r = Redis(host)
    fd = RedisHashFreqDist(r, fd_name)
    cfd = RedisConditionalHashFreqDist(r, cfd_name)

    for data in channel:
        if data == 'done':
            channel.send('done')
            break
        label, words = data
        for word in words:
            fd[word] += 1
            cfd[label][word] += 1
2. 特定数据类型解析

在实际的数据处理中,我们经常需要解析特定类型的数据,如日期、时间和HTML等。下面将介绍使用不同的库来完成这些任务。

2.1 使用dateutil解析日期和时间

dateutil是Python中解析日期和时间的优秀库,其 parser 模块可以解析多种格式的日期时间字符串, tz 模块可用于时区查找和转换。

准备工作 :使用pip或easy_install安装dateutil 2.0版本,以确保Python 3兼容性。安装命令如下:

sudo pip install dateutil==2.0

sudo easy_install dateutil==2.0

完整文档可参考:http://labix.org/python - dateutil

操作步骤

from dateutil import parser

# 解析不同格式的日期时间字符串
print(parser.parse('Thu Sep 25 10:36:28 2010'))
print(parser.parse('Thursday, 25. September 2010 10:36AM'))
print(parser.parse('9/25/2010 10:36:28'))
print(parser.parse('9/25/2010'))
print(parser.parse('2010-09-25T10:36:28Z'))

如果无法解析字符串, parse() 函数将引发 ValueError 异常。

工作原理
dateutil的解析器不使用正则表达式,而是通过识别可识别的标记并猜测其含义来解析日期时间字符串。日期格式的顺序很重要,不同文化可能使用不同的日期格式,如Month/Day/Year或Day/Month/Year。 parse() 函数提供了 dayfirst yearfirst 两个可选关键字参数来处理这些问题。

# 处理Day/Month/Year格式
print(parser.parse('25/9/2010', dayfirst=True))

# 处理两位数年份的歧义
print(parser.parse('10-9-25'))
print(parser.parse('10-9-25', yearfirst=True))

此外,dateutil的解析器还支持模糊解析,允许忽略日期时间字符串中的无关字符。

try:
    print(parser.parse('9/25/2010 at about 10:36AM'))
except ValueError:
    print('cannot parse')

print(parser.parse('9/25/2010 at about 10:36AM', fuzzy=True))

通过以上介绍,我们可以看到Redis在数据存储和分布式处理方面的强大功能,以及dateutil在日期时间解析上的灵活性。这些工具和技术可以帮助我们更高效地处理和分析各种类型的数据。

Redis与数据解析实战指南

2.2 时区查找和转换

在使用 dateutil 完成日期和时间解析后,我们可能还需要进行时区的查找和转换,这就需要用到 dateutil tz 模块。

准备工作 :确保已经安装了 dateutil 2.0 版本,安装方式如前文所述。

操作步骤

from dateutil import parser
from dateutil import tz

# 解析一个带有时区信息的日期时间字符串
dt = parser.parse('2024-01-01T12:00:00+08:00')

# 定义目标时区
target_tz = tz.gettz('America/New_York')

# 进行时区转换
dt_converted = dt.astimezone(target_tz)

print(f"原始时间: {dt}")
print(f"转换后的时间: {dt_converted}")

工作原理
dateutil tz 模块提供了 gettz 函数,用于获取不同的时区对象。 astimezone 方法则可以将一个 datetime 对象从当前时区转换到指定的时区。在进行时区转换时, astimezone 会根据两个时区之间的时差自动调整时间。

2.3 从HTML中提取URL

在处理网页数据时,我们常常需要从HTML中提取出其中包含的URL。 lxml 是一个强大的Python库,可以帮助我们完成这个任务。

准备工作 :安装 lxml 库,可以使用以下命令:

pip install lxml

操作步骤

from lxml import html
import requests

# 发送请求获取网页内容
url = 'https://example.com'
response = requests.get(url)
tree = html.fromstring(response.content)

# 提取所有的URL
urls = tree.xpath('//a/@href')

for url in urls:
    print(url)

工作原理
lxml html 模块提供了 fromstring 函数,用于将HTML内容解析为一个可操作的树结构。 xpath 方法则可以根据XPath表达式来选择树中的元素。在上述代码中, //a/@href 表示选择所有 <a> 标签的 href 属性,从而提取出所有的URL。

2.4 清理和剥离HTML

有时候,我们只需要HTML中的文本内容,而不需要其中的HTML标签。 lxml 同样可以帮助我们完成HTML的清理和剥离工作。

准备工作 :确保已经安装了 lxml 库。

操作步骤

from lxml import html

html_content = '<p>这是一段 <b>HTML</b> 内容。</p>'
tree = html.fromstring(html_content)

# 剥离HTML标签,获取纯文本
text = tree.text_content()

print(text)

工作原理
lxml text_content 方法可以递归地遍历树结构,提取出其中的所有文本内容,并去除HTML标签。

2.5 使用BeautifulSoup转换HTML实体

HTML实体是一种用于表示特殊字符的编码方式,如 &lt; 表示 < BeautifulSoup 是一个用于解析HTML和XML文档的Python库,可以帮助我们将HTML实体转换为对应的字符。

准备工作 :安装 BeautifulSoup 库,可以使用以下命令:

pip install beautifulsoup4

操作步骤

from bs4 import BeautifulSoup

html_content = '这是一个 &lt; 符号。'
soup = BeautifulSoup(html_content, 'html.parser')

# 转换HTML实体
text = soup.get_text()

print(text)

工作原理
BeautifulSoup 在解析HTML内容时,会自动将HTML实体转换为对应的字符。 get_text 方法则用于提取解析后的文本内容。

2.6 检测和转换字符编码

在处理不同来源的文本数据时,我们可能会遇到字符编码不一致的问题。 charade UnicodeDammit 可以帮助我们检测和转换字符编码。

准备工作 :安装 chardet 库( charade 的替代品),可以使用以下命令:

pip install chardet

操作步骤

import chardet

# 模拟一个未知编码的文本
unknown_encoded_text = b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

# 检测编码
result = chardet.detect(unknown_encoded_text)
encoding = result['encoding']

# 解码文本
decoded_text = unknown_encoded_text.decode(encoding)

print(f"检测到的编码: {encoding}")
print(f"解码后的文本: {decoded_text}")

工作原理
chardet 库通过分析文本的字节模式来猜测其编码。 detect 方法返回一个包含编码信息和置信度的字典。我们可以根据这个字典中的编码信息来对文本进行解码。

总结

通过本文的介绍,我们详细了解了Redis在不同数据结构存储上的应用,包括 RedisHashMap RedisConditionalHashFreqDist RedisOrderedDict ,以及如何结合 execnet 进行分布式单词评分。同时,我们还学习了使用 dateutil 解析日期和时间、进行时区转换,使用 lxml 从HTML中提取URL、清理和剥离HTML,使用 BeautifulSoup 转换HTML实体,以及使用 chardet 检测和转换字符编码。这些工具和技术可以帮助我们更高效地处理和分析各种类型的数据,为实际的数据处理工作提供了有力的支持。

下面是一个简单的mermaid流程图,展示了从HTML数据处理到最终文本提取的流程:

graph LR
    A[获取HTML内容] --> B[使用lxml解析HTML]
    B --> C{选择操作}
    C -->|提取URL| D[使用xpath提取URL]
    C -->|清理HTML| E[使用text_content清理标签]
    E --> F[使用BeautifulSoup转换HTML实体]
    F --> G[使用chardet检测和转换编码]
    D --> H[输出URL列表]
    G --> I[输出纯文本]

通过这个流程图,我们可以更清晰地看到整个数据处理的流程,以及各个步骤之间的关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值