Skip to content

Redis、爬虫、交易日库

20.1 Redis入门、安装和配置

Redis是一个开源的内存内(In-Memory)数据库,它可以用于存储和管理数据,通常用作缓存、消息队列、会话存储等用途。Redis支持多种数据结构,包括字符串、列表、集合、有序集合和哈希表。它以其高性能、低延迟和持久性存储特性而闻名,适用于许多应用场景。

大多数主流的Linux发行版都提供了Redis的软件包。

在Ubuntu/Debian上安装

你可以从官方的packages.redis.io APT存储库安装最新的稳定版本的Redis。

先决条件

如果你正在运行一个非常精简的发行版(比如Docker容器),你可能需要首先安装lsb-releasecurlgpg

Terminal window
sudo apt install lsb-release curl gpg

将该存储库添加到apt索引中,然后更新索引,最后进行安装:

Terminal window
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis

在 Manjaro/Archlinux 上安装

Terminal window
sudo pacman -S redis

用户界面

除了传统的CLI以外,Redis还提供了图形化前端 RedisInsight 方便直观查看:

img.png

下面在20.3小节我们会演示如何为通过Rust和Redis的Rust客户端来插入图示的这对键值对。

20.2 常见Redis数据结构类型

为了将Redis的不同数据结构类型与相应的命令详细叙述并创建一个示例表格,我将按照以下格式为你展示:

数据结构类型:描述该数据结构类型的特点和用途。

常用命令示例:列出该数据结构类型的一些常用命令示例,包括命令和用途。

示例表格:创建一个示例表格,包含数据结构类型、命令示例以及示例值。

现在让我们开始:

字符串(Strings)

数据结构类型: 字符串是Redis中最简单的数据结构,可以存储文本、二进制数据等。

常用命令示例

  • 设置字符串值:SET key value
  • 获取字符串值:GET key

示例表格

数据结构类型命令示例示例值
字符串SET username "Alice"Key: username, Value: “Alice”
字符串GET username返回值: “Alice”

哈希表(Hashes)

数据结构类型: 哈希表是一个键值对的集合,适用于存储多个字段和对应的值。

常用命令示例

  • 设置哈希表字段:HSET key field value
  • 获取哈希表字段值:HGET key field

示例表格

数据结构类型命令示例示例值
哈希表HSET user:id name "Bob"Key: user
, Field: name, Value: “Bob”
哈希表HGET user:id name返回值: “Bob”

列表(Lists)

数据结构类型: 列表是一个有序的字符串元素集合,可用于实现队列或栈。

常用命令示例

  • 从列表左侧插入元素:LPUSH key value1 value2 ...
  • 获取列表范围内的元素:LRANGE key start stop

示例表格

数据结构类型命令示例示例值
列表LPUSH queue "item1"Key: queue, Values: “item1”
列表LRANGE queue 0 -1返回值: [“item1”]

集合(Sets)

数据结构类型: 集合是一个无序的字符串元素集合,可用于存储唯一值。

常用命令示例

  • 添加元素到集合:SADD key member1 member2 ...
  • 获取集合中的所有元素:SMEMBERS key

示例表格

数据结构类型命令示例示例值
集合SADD employees "Alice" "Bob"Key: employees, Members: [“Alice”, “Bob”]
集合SMEMBERS employees返回值: [“Alice”, “Bob”]

有序集合(Sorted Sets)

数据结构类型: 有序集合类似于集合,但每个元素都关联一个分数,用于排序元素。

常用命令示例

  • 添加元素到有序集合:ZADD key score1 member1 score2 member2 ...
  • 获取有序集合范围内的元素:ZRANGE key start stop

示例表格

数据结构类型命令示例示例值
有序集合ZADD leaderboard 100 "Alice"Key: leaderboard, Score: 100, Member: “Alice”
有序集合ZRANGE leaderboard 0 -1返回值: [“Alice”]

这些示例展示了不同类型的Redis数据结构以及常用的命令示例,你可以根据你的具体需求和应用场景使用适当的数据结构和命令来构建你的Redis数据库。在20.3的例子中,我们会用一个最简单的字符串例子来做示范。

20.3 在Rust中使用Redis客户端

将Redis与Rust结合使用可以提供高性能和安全的数据存储和处理能力。下面详细说明如何将Redis与Rust配合使用:

  1. 安装Redis客户端库: 首先,你需要在Rust项目中引入Redis客户端库,最常用的库是redis-rs,可以在Cargo.toml文件中添加以下依赖项:

    [dependencies]
    redis = "0.23"
    tokio = { version = "1.29.1", features = ["full"] }

    然后运行cargo build以安装库。

  2. 创建Redis连接 使用Redis客户端库连接到Redis服务器。以下是一个示例:

    use redis::Commands;
    #[tokio::main]
    async fn main() -> redis::RedisResult<()> {
    let redis_url = "redis://:@127.0.0.1:6379/0";
    let client = redis::Client::open(redis_url)?;
    let mut con = client.get_connection()?;
    // 执行Redis命令
    let _: () = con.set("my_key", "my_value")?;
    let result: String = con.get("my_key")?;
    println!("Got value: {}", result);
    Ok(())
    }

    这个示例首先创建了一个Redis客户端,然后与服务器建立连接,并执行了一些基本的操作。

    详细解释一下Redis链接的构成:

    1. redis://:这部分指示了使用的协议,通常是 redis://rediss://(如果你使用了加密连接)。
    2. :@:这部分表示用户名和密码,但在你的示例中是空白的,因此没有提供用户名和密码。如果需要密码验证,你可以在 : 后面提供密码,例如:redis://password@127.0.0.1:6379/0
    3. 127.0.0.1:这部分是 Redis 服务器的主机地址,指定了 Redis 服务器所在的机器的 IP 地址或主机名。在示例中,这是本地主机的 IP 地址,也就是 127.0.0.1,表示连接到本地的 Redis 服务器。
    4. 6379:这部分是 Redis 服务器的端口号,指定了连接到 Redis 服务器的端口。默认情况下,Redis 使用 6379 端口。
    5. /0:这部分是 Redis 数据库的索引,Redis 支持多个数据库,默认情况下有 16 个数据库,索引从 015。在示例中,索引为 0,表示连接到数据库索引为 0 的数据库。

    综合起来,你的示例 Redis 连接字符串表示连接到本地 Redis 服务器(127.0.0.1)的默认端口(6379),并选择索引为 0 的数据库,没有提供用户名和密码进行认证。如果你的 Redis 服务器有密码保护,你需要提供相应的密码来进行连接。

  3. 处理错误: 在Rust中,处理错误非常重要,因此需要考虑如何处理Redis操作可能出现的错误。在上面的示例中,我们使用了RedisResult来包裹返回结果,然后用?来处理Redis操作可能引发的错误。你可以根据你的应用程序需求来处理这些错误,例如,记录日志或采取其他适当的措施。

  4. 使用异步编程: 如果你需要处理大量的并发操作或需要高性能,可以考虑使用Rust的异步编程库,如Tokio,与异步Redis客户端库配合使用。这将允许你以非阻塞的方式执行Redis操作,以提高性能。

  5. 定期清理过期数据: Redis支持过期时间设置,你可以在将数据存储到Redis中时为其设置过期时间。在Rust中,你可以编写定期任务来清理过期数据,以确保Redis中的数据不会无限增长。

总之,将Redis与Rust配合使用可以为你提供高性能、安全的数据存储和处理解决方案。通过使用Rust的强类型和内存安全性,以及Redis的速度和功能,你可以构建可靠的应用程序。当然,在实际应用中,还需要考虑更多复杂的细节,如连接池管理、性能优化和错误处理策略,以确保应用程序的稳定性和性能。

20.4 爬虫

Rust 是一种图灵完备的系统级编程语言,当然也可以用于编写网络爬虫。Rust 具有出色的性能、内存安全性和并发性,这些特性使其成为编写高效且可靠的爬虫的理想选择。以下是 Rust 爬虫的简要介绍:

20.4.1 爬虫的基本原理

爬虫是一个自动化程序,用于从互联网上的网页中提取数据。爬虫的基本工作流程通常包括以下步骤:

  1. 发送 HTTP 请求:爬虫会向目标网站发送 HTTP 请求,以获取网页的内容。

  2. 解析 HTML:爬虫会解析 HTML 文档,从中提取有用的信息,如链接、文本内容等。

  3. 存储数据:爬虫将提取的数据存储在本地数据库、文件或内存中,以供后续分析和使用。

  4. 遍历链接:爬虫可能会从当前页面中提取链接,并递归地访问这些链接,以获取更多的数据。

20.4.2. Rust 用于爬虫的优势

Rust 在编写爬虫时具有以下优势:

  • 内存安全性:Rust 的借用检查器和所有权系统可以防止常见的内存错误,如空指针和数据竞争。这有助于减少爬虫程序中的错误和漏洞。

  • 并发性:Rust 内置了并发性支持,可以轻松地创建多线程和异步任务,从而提高爬虫的效率。

  • 性能:Rust 的性能非常出色,可以快速地下载和处理大量数据。

  • 生态系统:Rust 生态系统中有丰富的库和工具,可用于处理 HTTP 请求、HTML 解析、数据库访问等任务。

  • 跨平台:Rust 可以编写跨平台的爬虫,运行在不同的操作系统上。

20.4.3. Rust 中用于爬虫的库和工具

在 Rust 中,有一些库和工具可用于编写爬虫,其中一些包括:

  • reqwest:用于发送 HTTP 请求和处理响应的库。

  • scraper:用于解析和提取 HTML 数据的库。

  • tokio:用于异步编程的库,适用于高性能爬虫。

  • serde:用于序列化和反序列化数据的库,有助于处理从网页中提取的结构化数据。

  • rusqlitediesel:用于数据库存储的库,可用于存储爬取的数据。

  • regex:用于正则表达式匹配,有时可用于从文本中提取数据。

20.4.4. 爬虫的伦理和法律考虑

在编写爬虫时,务必遵守网站的 robots.txt 文件和相关法律法规。爬虫应该尊重网站的隐私政策和使用条款,并避免对网站造成不必要的负担。爬虫不应滥用网站资源或进行未经授权的数据收集。

总之,Rust 是一种强大的编程语言,可用于编写高性能、可靠和安全的网络爬虫。在编写爬虫程序时,始终要遵循最佳实践和伦理准则,以确保合法性和道德性。

补充学习:序列化和反序列化

在Rust中,JSON(JavaScript Object Notation)是一种常见的数据序列化和反序列化格式,通常用于在不同的应用程序和服务之间交换数据。Rust提供了一些库来处理JSON数据的序列化和反序列化操作,其中最常用的是serde库。

以下是如何在Rust中进行JSON序列化和反序列化的简要介绍:

  1. 添加serde库依赖: 首先,你需要在项目的Cargo.toml文件中添加serdeserde_json依赖,因为serde_json是serde的JSON支持库。在Cargo.toml中添加如下依赖:
[dependencies]
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0"

然后,在你的Rust代码中导入serdeserde_json

use serde::{Serialize, Deserialize};
  1. 定义结构体: 如果你要将自定义类型序列化为JSON,你需要在结构体上实现SerializeDeserialize trait。例如:
#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u32,
}
  1. 序列化为JSON: 使用serde_json::to_string将Rust数据结构序列化为JSON字符串:
fn main() {
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let json_string = serde_json::to_string(&person).unwrap();
println!("{}", json_string);
}
  1. 反序列化: 使用serde_json::from_str将JSON字符串反序列化为Rust数据结构:
fn main() {
let json_string = r#"{"name":"Bob","age":25}"#;
let person: Person = serde_json::from_str(json_string).unwrap();
println!("Name: {}, Age: {}", person.name, person.age);
}

这只是一个简单的介绍,你可以根据具体的需求进一步探索serdeserde_json库的功能,以及如何处理更复杂的JSON数据结构和场景。这些库提供了强大的工具,使得在Rust中进行JSON序列化和反序列化变得非常方便。

案例:在Redis中构建中国大陆交易日库

这个案例演示了如何使用 Rust 编写一个简单的爬虫,从指定的网址获取中国大陆的节假日数据,然后将数据存储到 Redis 数据库中。这个案例涵盖了许多 Rust 的核心概念,包括异步编程、HTTP 请求、JSON 解析、错误处理以及与 Redis 交互等。

use anyhow::{anyhow, Error as AnyError}; // 导入`anyhow`库中的`anyhow`和`Error`别名为`AnyError`
use redis::{Commands}; // 导入`redis`库中的`Commands`
use reqwest::Client as ReqwestClient; // 导入`reqwest`库中的`Client`别名为`ReqwestClient`
use serde::{Deserialize, Serialize}; // 导入`serde`库中的`Deserialize`和`Serialize`
use std::error::Error; // 导入标准库中的`Error`
#[derive(Debug, Serialize, Deserialize)]
struct DayType {
date: i32, // 定义一个结构体`DayType`,用于表示日期
}
#[derive(Debug, Serialize, Deserialize)]
struct HolidaysType {
cn: Vec<DayType>, // 定义一个结构体`HolidaysType`,包含一个日期列表
}
#[derive(Debug, Serialize, Deserialize)]
struct CalendarBody {
holidays: Option<HolidaysType>, // 定义一个结构体`CalendarBody`,包含一个可选的`HolidaysType`字段
}
// 异步函数,用于获取API数据并存储到Redis
async fn store_calendar_to_redis() -> Result<(), AnyError> {
let url = "http://pc.suishenyun.net/peacock/api/h5/festival"; // API的URL
let client = ReqwestClient::new(); // 创建一个Reqwest HTTP客户端
let response = client.get(url).send().await?; // 发送HTTP GET请求并等待响应
let body_s = response.text().await?; // 读取响应体的文本数据
// 将API响应的JSON字符串解析为CalendarBody结构体
let cb: CalendarBody = match serde_json::from_str(&body_s) {
Ok(cb) => cb, // 解析成功,得到CalendarBody结构体
Err(e) => return Err(anyhow!("Failed to parse JSON string: {}", e)), // 解析失败,返回错误
};
if let Some(holidays) = cb.holidays { // 如果存在节假日数据
let days = holidays.cn; // 获取日期列表
let mut dates = Vec::new(); // 创建一个空的日期向量
for day in days {
dates.push(day.date as u32); // 将日期添加到向量中,转换为u32类型
}
let redis_url = "redis://:@127.0.0.1:6379/0"; // Redis服务器的连接URL
let client = redis::Client::open(redis_url)?; // 打开Redis连接
let mut con = client.get_connection()?; // 获取Redis连接
// 将每个日期添加到Redis集合中
for date in &dates {
let _: usize = con.sadd("holidays_set", date.to_string()).unwrap(); // 添加日期到Redis集合
}
Ok(()) // 操作成功,返回Ok(())
} else {
Err(anyhow!("No holiday data found.")) // 没有节假日数据,返回错误
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// 调用存储数据到Redis的函数
if let Err(err) = store_calendar_to_redis().await {
eprintln!("Error: {}", err); // 打印错误信息
} else {
println!("Holiday data stored in Redis successfully."); // 打印成功消息
}
Ok(()) // 返回Ok(())
}

案例要点:

  1. 依赖库引入: 为了实现这个案例,首先引入了一系列 Rust 的外部依赖库,包括 reqwest 用于发送 HTTP 请求、serde 用于 JSON 序列化和反序列化、redis 用于与 Redis 交互等等。这些库提供了必要的工具和功能,以便从网站获取数据并将其存储到 Redis 中。
  2. 数据结构定义: 在案例中定义了三个结构体,DayTypeHolidaysTypeCalendarBody,用于将 JSON 数据解析为 Rust 数据结构。这些结构体的字段对应于 JSON 数据中的字段,用于存储从网站获取的数据。
  3. 异步函数和错误处理: 使用 async 关键字定义了一个异步函数 store_calendar_to_redis,该函数负责执行以下操作:
    • 发送 HTTP 请求以获取节假日数据。
    • 解析 JSON 数据。
    • 将数据存储到 Redis 数据库中。 这个函数还演示了 Rust 中的错误处理机制,使用 Result 返回可能的错误,以及如何使用 anyhow 库来创建自定义错误信息。
  4. Redis 数据存储: 使用 redis 库连接到 Redis 数据库,并使用 sadd 命令将节假日数据存储到名为 holidays_set 的 Redis 集合中。
  5. main函数: main 函数是程序的入口点。它使用 tokio 框架的 #[tokio::main] 属性宏来支持异步操作。在 main 函数中,我们调用了 store_calendar_to_redis 函数来执行节假日数据的存储操作。如果存储过程中出现错误,错误信息将被打印到标准错误流中;否则,将打印成功消息。