当前位置: 首页 > news >正文

实体解析实施的复杂性

                                                实体的艺术表现斯特凡·伯克纳

一、说明

        实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体(通常是个人或公司)的过程。乍一看,实体分辨率可能看起来像一个相对简单的任务:例如,给定一张人物的两张照片,即使是一个小孩子也可以确定它是否以相当高的精度显示同一个人。计算机也是如此:比较包含姓名、地址、电子邮件等属性的两条记录可以很容易地完成。然而,深入探讨该主题,它就越具有挑战性:需要评估各种匹配算法,处理数百万或数十亿条记录意味着二次复杂性,更不用说实时和数据删除用例了。

二、模糊文本匹配

        让我们从比较著名艺术家文森特梵高的两张唱片开始——还是梵高?

        第二条记录中有一些错误(除了一个世纪后出生和电子邮件地址):姓名拼写错误,出生日期混淆,邮政编码丢失,电子邮件地址略有不同。

        那么我们如何比较这些值呢?如果,假设名称相等,那么对这些值进行简单的字符串比较就足够了。由于情况并非如此,我们需要一些更高级的模糊匹配。有许多不同的算法可用于基于文本的模糊匹配,它们可以大致分为三组。语音算法侧重于文本的发音相似程度。最著名的算法是Soundex和Metaphone,它们主要用于英语文本,但其他语言也存在这些算法的变体,例如德语的Kölner Phonetik(科隆语音)。文本距离算法通常定义文本需要更改多少个字符才能到达其他文本。Levenshtein和Hamming距离是该组中两个众所周知的算法。相似性算法,如余弦相似性或杰卡德指数,计算文本的结构相似性,通常以百分比表示相似性。

        出于本文的目的,我们将使用一种非常简单的方法,仅使用名称上的Levenshtein距离和城市的相等性。此示例和以下所有示例将使用 golang 作为编程语言,并尽可能使用现有库。将其转换为python,java或任何其他语言应该是微不足道的。此外,它只会对 name 属性执行匹配。添加更多属性甚至使其可配置不是本文的目的。

package mainimport ("fmt""github.com/hbollon/go-edlib"
)type Record struct {ID intName stringCity string
}func matches(a, b Record) bool {distance := edlib.LevenshteinDistance(a.Name, b.Name)return distance <= 3 && a.City == b.City
}func main() {a := Record{Name: "Vincent Van Gogh",City: "Paris",}b := Record{Name: "Vince Van Gough",City: "Paris",}if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)} else {fmt.Printf("%s and %s are probably not the same person\n", a.Name, b.Name)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        两个名字之间的列文森距离正好是3。这是因为,还有三个附加字符(名字中的“en”和姓氏中的“u”)。请注意,这适用于此特定输入。然而,它离完美还很远。例如,“Joe Smith”和“Amy Smith”这两个名字的Levenshtein距离也是三个,但显然不是同一个人。将距离算法与语音算法相结合可以解决这个问题,但这超出了本文的范围。

        使用基于规则的方法而不是基于 ML 的方法时,选择为您的使用案例产生最佳结果的正确算法是业务成功的最关键方面。这是您应该花费大部分时间的地方。不幸的是,正如我们现在将发现的那样,如果您决定自己开发实体解析引擎,还有很多其他事情会分散您优化这些规则的注意力。

三、朴素实体解析

        现在我们知道了如何比较两个记录,我们需要找到彼此匹配的所有记录。最简单的方法是简单地将每条记录与所有其他记录进行比较。出于此示例的目的,我们使用随机选择的名称和城市。对于名称,我们最多强制三个错误(用 x 替换任何字符)。

var firstNames = [...]string{"Wade", "Dave", "Seth", "Ivan", "Riley", "Gilbert", "Jorge", "Dan", "Brian", "Roberto", "Daisy", "Deborah", "Isabel", "Stella", "Debra", "Berverly", "Vera", "Angela", "Lucy", "Lauren"}
var lastNames = [...]string{"Smith", "Jones", "Williams", "Brown", "Taylor"}func randomName() string {fn := firstNames[rand.Intn(len(firstNames))]ln := lastNames[rand.Intn(len(lastNames))]name := []byte(fmt.Sprintf("%s %s", fn, ln))errors := rand.Intn(4)for i := 0; i < errors; i++ {name[rand.Intn(len(name))] = 'x'}return string(name)
}var cities = [...]string{"Paris", "Berlin", "New York", "Amsterdam", "Shanghai", "San Francisco", "Sydney", "Cape Town", "Brasilia", "Cairo"}func randomCity() string {return cities[rand.Intn(len(cities))]
}func loadRecords(n int) []Record {records := make([]Record, n)for i := 0; i < n; i++ {records[i] = Record{ID:   i,Name: randomName(),City: randomCity(),}}return records
}func compare(records []Record) (comparisons, matchCount int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {fmt.Printf("%s and %s are probably the same person\n", a.Name, b.Name)matchCount++}}}return comparisons, matchCount
}func main() {records := loadRecords(100)comparisons, matchCount := compare(records)fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        您应该看到一些类似的输出(如果您没有得到随机数据的任何匹配项,您可能需要多次运行它):

Daisy Williams and Dave Williams are probably the same person
Deborax Browx and Debra Brown are probably the same person
Riley Brown and RxxeyxBrown are probably the same person
Dan Willxams and Dave Williams are probably the same person
made 9900 comparisons and found 16 matches

        如果幸运的话,您还会得到像“黛西”和“戴夫”这样的不匹配。这是因为我们使用的 Levenshtein 距离为 3,作为短名称的唯一模糊算法,这是高的方式。请随时自行改进。

        性能方面,真正有问题的一点是获得结果所需的 9,900 次比较,因为输入量加倍将大约使所需比较量翻两番。39 条记录需要 800,200 次比较。对于只有 100,000 条记录的少量数据,这意味着需要近 10 亿次比较。无论您的系统有多大,随着数据量的增长,系统都将无法在可接受的时间内完成此操作。

        一个快速但几乎无用的优化是不对每个组合进行两次比较。我们将 A 与 B 进行比较或将 B 与 A 进行比较应该无关紧要。然而,这只会减少因子 2 所需的比较量,由于二次增长,这是可以忽略的。

四、通过阻塞降低复杂性

        如果我们查看我们创建的规则,我们很容易注意到,如果城市不同,我们将永远不会有匹配。所有这些比较都是完全浪费的,应该加以防止。将您怀疑相似的记录放入公共存储桶中,而将其他不相同的记录放入另一个存储桶中,在实体解析中称为阻塞。由于我们想使用城市作为我们的阻塞键,因此实现相当简单。

func block(records []Record) map[string][]Record {blocks := map[string][]Record{}for _, record := range records {blocks[record.City] = append(blocks[record.City], record)}return blocks
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0matchCount := 0for _, blockRecords := range blocks {c, m := compare(blockRecords)comparisons += cmatchCount += m}fmt.Printf("made %d comparisons and found %d matches\n", comparisons, matchCount)
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        现在的结果将是相同的,但我们只有大约十分之一的比较,因为我们有十个不同的城市。在实际应用中,由于城市的差异要大得多,这种影响会大得多。此外,每个块可以独立于其他块进行处理,例如在相同或不同的服务器上并行处理。

        找到正确的阻止密钥本身就是一个挑战。使用像城市这样的属性可能会导致分布不均匀,因此会导致一个巨大的区块(例如大城市)比所有其他区块花费更长的时间。或者城市包含微小的拼写错误,不再被视为有效匹配。使用多个属性和/或使用拼音键或 q-gram 作为阻塞键可以解决这些问题,但会增加软件的复杂性。

五、从匹配项到实体

        到目前为止,关于我们的记录,我们只能说,其中两个是否匹配。对于非常基本的用例,这可能已经足够了。但是,在大多数情况下,您想知道属于同一实体的所有匹配项。这可以从简单的星形模式,其中 A 与 B、C 和 D 匹配,到 A 匹配 B、B 匹配 C 和 C 匹配 D 的链状模式,到非常复杂的图形模式。这种所谓的传递记录链接可以使用连接的组件算法轻松实现,只要所有数据都适合单个服务器上的内存。同样,在实际应用中,这更具挑战性。

func compare(records []Record) (comparisons int, edges [][2]int) {for _, a := range records {for _, b := range records {if a == b {continue // don't compare with itself}comparisons++if matches(a, b) {edges = append(edges, [2]int{a.ID, b.ID})}}}return comparisons, edges
}func connectedComponents(edges [][2]int) [][]int {components := map[int][]int{}nextIdx := 0idx := map[int]int{}for _, edge := range edges {a := edge[0]b := edge[1]aIdx, aOk := idx[a]bIdx, bOk := idx[b]switch {case aOk && bOk && aIdx == bIdx: // in same componentcontinuecase aOk && bOk && aIdx != bIdx: // merge two componentscomponents[nextIdx] = append(components[aIdx], components[bIdx]...)delete(components, aIdx)delete(components, bIdx)for _, x := range components[nextIdx] {idx[x] = nextIdx}nextIdx++case aOk && !bOk: // add b to component of aidx[b] = aIdxcomponents[aIdx] = append(components[aIdx], b)case bOk && !aOk: // add a to component of bidx[a] = bIdxcomponents[bIdx] = append(components[bIdx], a)default: // create new component with a and bidx[a] = nextIdxidx[b] = nextIdxcomponents[nextIdx] = []int{a, b}nextIdx++}}cc := make([][]int, len(components))i := 0for k := range components {cc[i] = components[k]i++}return cc
}func main() {records := loadRecords(100)blocks := block(records)comparisons := 0edges := [][2]int{}for _, blockRecords := range blocks {c, e := compare(blockRecords)comparisons += cedges = append(edges, e...)}cc := connectedComponents(edges)fmt.Printf("made %d comparisons and found %d matches and %d entities\n", comparisons, len(edges), len(cc))for _, component := range cc {names := make([]string, len(component))for i, id := range component {names[i] = records[id].Name}fmt.Printf("found the following entity: %s from %s\n", strings.Join(names, ", "), records[component[0]].City)}
}

        在围棋游乐场尝试:Go Playground - The Go Programming Language

        连接的组件功能遍历所有边,然后创建新组件、将新 id 添加到现有组件或将两个组件合并为一个组件。结果如下所示:

made 1052 comparisons and found 6 matches and 2 entities
found the following entity: Ivan Smxth, Ixan Smith, Ivax Smitx from Cairo
found the following entity: Brxan Williams, Brian Williams from Cape Town

        保持这些边缘给我们带来了一些优势。我们可以使用它们来使生成的实体易于理解和解释,理想情况下,即使有一个漂亮的 UI 来显示实体的记录是如何连接的。或者,在使用实时实体解析系统时,我们可以使用边缘在删除数据时拆分实体。或者,您在构建图神经网络 (GNN) 时使用它们,从而获得更好的 ML 结果,而不仅仅是记录。

实体的可视化表示(作者图片)

        当有很多非常相似的记录时,可能会出现一个来自边缘的问题。例如,如果 A 与 B 匹配,B 与 C 匹配,则 C 也可能与 A 匹配,具体取决于使用的规则。如果 D、E、F 等也与现有记录匹配,那么我们又回到了二次增长问题,很快导致如此多的边变得不再可处理。

        还记得我们是如何构建阻塞桶的吗?惊喜!对于非常相似的数据,这些数据最终都集中在几个巨大的桶中,计算性能再次急剧下降——即使您遵循了之前从多个属性创建桶的建议。

        这种不相同的重复项的典型示例是有人定期在同一家商店订购,但具有来宾访问权限(抱歉,没有很好的客户 ID)。该人可能几乎总是使用相同的送货地址,并且大多能够正确写下自己的名字。因此,应以特殊方式处理这些记录,以确保稳定的系统性能,但这本身就是一个主题。

        在你对所获得的知识感到太舒服并想开始实施自己的解决方案之前,让我快速粉碎你的梦想。我们还没有讨论实时执行任何操作的挑战。即使你认为你不需要一个总是最新的实体(显而易见的好处),实时方法也会产生进一步的价值:你不需要一遍又一遍地做同样的计算,而只需要对新数据。另一方面,实现起来要复杂得多。想要阻止?将新记录与其所属存储桶的所有记录进行比较,但这可能需要一段时间,并且可以被视为增量批处理。同样在最终完成之前,还有大量新记录等待处理。想要使用连接的组件计算实体?当然,将整个图形保留在内存中,只需添加新的边。但是不要忘记跟踪由于新记录而刚刚合并在一起的两个实体。

        因此,您仍然愿意自己实现这一点。你做出了(在这种情况下)明智的决定,不存储边缘,不支持实时。因此,您成功地运行了包含所有数据的第一个实体解析批处理作业。这花了一段时间,但你每个月只会这样做一次,所以这很好。当您看到您的数据保护官跑到拐角处并告诉您由于GDPR投诉而从数据集中删除该人时,可能就在那时。因此,您再次为单个已删除的实体运行整个批处理作业 — 耶。

六、结论

        进行实体解析乍一看可能相当简单,但它包含许多重大的技术挑战。其中一些可以简化和/或忽略,但其他问题需要解决以获得良好的性能。

相关文章:

实体解析实施的复杂性

实体的艺术表现斯特凡伯克纳 一、说明 实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体&#xff08;通常是个人或公司&#xff09;的过程。乍一看&#xff0c;实体分辨率可能看起来像一个相对简单的任务&#xff1a;例如&#xff0c;给定一张人物的两张照片&a…...

MAKEFLAGS += -rR --include-dir=$(CURDIR)的含义

一、目的 在看uboot顶层Makefile文件时遇到这个代码不甚明白&#xff0c;故查找了一下资料以供大家学习 二、介绍 MAKEFLAGS -rR 表示禁止使用内置的隐含规则和变量定义&#xff1b;这个选项用于启用recursive make&#xff0c;使得Makefile目标可以调用其他Makefile目标&…...

maven问题与解决方案、部署

问题一、was cached in the local repository, resolution will not be reattempted until the update interval of idea中 Maven中Lifecycle时&#xff0c;能正常clean 和 install&#xff0c;但在idea的Terminal中mvn install出现&#xff1a; was cached in the local repo…...

【大数据】Hadoop MapReduce与Hadoop YARN(学习笔记)

一、Hadoop MapReduce介绍 1、设计构思 1&#xff09;如何对付大数据处理场景 对相互间不具有计算依赖关系的大数据计算任务&#xff0c;实现并行最自然的办法就是采取MapReduce分而治之的策略。 不可拆分的计算任务或相互间有依赖关系的数据无法进行并行计算&#xff01; …...

接口测试文档

接口测试的总结文档 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1a;主要介绍为什…...

Ubuntu中不能使用ifconfig命令

​ 问题 打开终端使用如下命令不能运行&#xff1a; ifconfig显示如下错误: 解决方法 在VMware中的虚拟机下面打开“编辑虚拟机设置”&#xff0c;或者在已经打开的虚拟机面板上面打开“虚拟机—设置” 选择网络适配器&#xff0c;选择“NAT模式”&#xff0c;没开机的就…...

BAT020:将文本文档中多行文本拼接为;分隔的单行文本

引言&#xff1a;编写批处理程序&#xff0c;实现将文本文档中多行文本拼接为;分隔的单行文本。 一、新建Windows批处理文件 参考博客&#xff1a; CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件&#xff0c;点击【…...

安防初识命令【学习笔记】

1、美杜莎爆破 medusa -M ssh -h 192.168.42.135 -u guest -P top1000.txt -t 8 2、passwd文件与shadow文件 root:x:0:0:root:/root:/usr/bin/zsh 用户名&#xff1a;密码&#xff1a;uid&#xff1a;gid&#xff1a;描述性信息&#xff1a;主目录&#xff1a;默认…...

idea 启动出现 Failed to create JVM JVM Path

错误 idea 启动出现如下图情况 Error launching IDEA If you already a 64-bit JDK installed, define a JAVA_HOME variable in Computer > System Properties> System Settings > Environment Vanables. Failed to create JVM. JVM Path: D:\Program Files\JetB…...

凉鞋的 Unity 笔记 108. 第二个通识:增删改查

在这一篇&#xff0c;我们来学习此教程的第二个通识&#xff0c;即&#xff1a;增删改查。 增删改查我们不只是一次接触到了。 在最先接触的场景层次窗口中&#xff0c;我们是对 GameObject 进行增删改查。 在 Project 文件窗口中&#xff0c;我们是对文件&文件夹进行增删…...

angular项目指定端口,实现局域网内ip访问

直接修改package.json文件 "dev": "ng serve --host 0.0.0.0 --port 8080"终端运行npm run dev启动项目。 这里就指定了使用8080端口运行项目&#xff0c;同时局域网内的其他电脑可以通过访问运行项目主机的ip来访问项目 例如项目运行在ip地址为192.168.2…...

解决uniapp里scroll-view横向滚动的问题

一、前言 本以为是一件很简单的事&#xff0c;结果浪费了整整一个上午&#xff0c;并且问题并没有全部解决....后来没办法&#xff0c;用了touchmove模拟的滑动&#xff0c;如果有好的解决方法麻烦告诉我...非常感谢~ 一、问题 其实我想要实现的功能很简单&#xff0c;就是一…...

LeetCode——动态规划(五)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 目录 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 123. 买卖股票的最佳时机 III …...

与HTTP相关的各种概念

网络世界 网络世界中最重要的一个名词就是互联网&#xff08;Internet&#xff09;,它以TCP/IP协议族为基础&#xff0c;构建成了一望无际的信息传输网络。而我们通常所说的“上网”&#xff0c;主要就是访问互联网的一个子集——万维网&#xff08;World Wide Web&#xff09…...

CentOS 7 编译安装Boost

1、前提条件 linux平台/CentOS 7 下要编译安装Boost除gcc和gcc-c之外&#xff0c;还需要两个开发库&#xff1a;bzip2-devel 和python-devel &#xff0c;因此在安装前应该先保证这两个库已经安装。 安装指令: yum install bzip2 bzip2-devel bzip2-libs python-devel Cent…...

vue图表制作

Vue.js是一个非常流行的JavaScript框架&#xff0c;可以用于开发交互式Web应用程序。Vue.js的优点之一是它的灵活性和可扩展性。因此&#xff0c;可以使用Vue.js与许多其他库和框架集成&#xff0c;包括图表库。 下面是使用Vue.js制作图表的一些步骤&#xff1a; 1.选择一个适…...

使用 GitHub Action 自动更新 Sealos 集群的应用镜像

在 IT 领域&#xff0c;自动化无疑已成为提高工作效率和减少人为错误的关键。Sealos 作为一个强大的云操作系统&#xff0c;已经为许多企业和开发者提供了稳定可靠的服务。与此同时&#xff0c;随着技术不断发展&#xff0c;集成更多的功能和服务变得尤为重要。考虑到这一点&am…...

windows频繁更新问题解决方案

解决方案&#xff1a;将更新策略增加到无穷大 1.windowsr 输入regedit 2.找到&#xff1a;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 3.右键新建DWORD32 4.命名&#xff1a;FlightSettingsMaxPauseDays 5.双击&#xff1a;数值数据改为4321 基数&#…...

day05-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…...

046:mapboxGL加载天地图路网图+标记(wmts方式)

第046个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载天地图路网图+标记(wmts方式)。瓦片中的url地址引用的是天地图的wmts的形式。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共99行)相关AP…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...