做网站搜索、做文章标签、做词云统计,这几件事的第一步几乎都是同一件事:把一段连续的中文文本切成有意义的词语。中文不像英文有天然空格,分词这一步不做,后面所有文本处理都跑不起来。这个领域里,SCWS是一个很特别的方案——它不走花哨的路子,就是老老实实基于词典加规则做切分,但因为实现是C语言,效率高、部署轻,还附带了PHP扩展,所以很多PHP技术栈的项目都拿它当站内搜索和热词抽取的底层工具。
这篇内容就是围绕SCWS的安装和使用方法展开,我会把从编译安装、命令行调用、自定义词典维护,到PHP项目集成这一整条链路完整讲一遍,并把我实际踩过的坑、调试方式、调优经验也放进来。适合正在做PHP站内搜索、自动标签、文本关键词提取的开发者,也适合想快速给中文文本加一个分词能力、又不想引入太重依赖的同学。
1. 认识SCWS:它到底是什么样的分词工具
1.1 与主流分词方案的定位差异
现在提到中文分词,很多人第一反应是某些基于Python的分词组件,或者Java生态里集成进搜索引擎的分词框架。它们确实好用,但问题是它们大多默认绑定了一种语言生态和应用场景,想在PHP项目里集成,往往需要额外搭一层HTTP服务或者消息队列,维护成本一下就上来了。
SCWS的定位就完全不一样。它是直接用C实现的分词库,编译完就是一个动态库加几个命令行工具,然后在PHP扩展层做一层薄薄的封装。你在PHP-FPM进程里直接new SimpleCWS()就可以用,不用起任何外部服务。这个“简单”体现在整个依赖链上:下载源码、编译、加载扩展,三步就能让PHP获得中文分词能力。
另外一个核心差别是分词机制。SCWS属于词典驱动的机械分词,它读入词典,按词频和规则决定边界。不会像统计模型或神经网络模型那样,需要几十上百兆的模型参数。对很多站内搜索、标签抽取场景来说,这种“机械但可控”的方案反而更省心——出问题你大概知道是词典缺了词,还是规则没覆盖到,能动手改、能立刻看到效果。
1.2 核心工作原理简述
SCWS的工作过程可以理解为三步:先加载词典,再扫描文本找候选词,最后用词频和规则决定留下哪些切分结果。
词典内部用xdb格式存储,也就是一种索引化的二进制词库,每个词条主要包含词文本、词频、词性标注。词频这个字段很关键,它相当于每个候选词的“竞争筹码”。分词器在同一个位置扫描出多种可能的切分组合时,会优先保留词频更有优势的组合。
除此之外,SCWS还支持一层规则机制,用来处理机器词典本身解决不了的问题。比如中国人名的“姓+名”结构、常见地名的后缀、连续数字的切分方式,这些都可以通过规则文件来干预。所以它不是单纯的最大匹配,而是“词典匹配+词频加权+规则修正”的组合方案。
还有一点值得提的是复合分词模式。默认状态下SCWS输出更偏向细粒度,比如“中文分词”可能切成“中文/分词”两个词条。开启复合模式后,它会把一些相邻词重新组合成更长的词候选,方便搜索引擎做召回。这种多粒度输出方式,对“搜索要召回、标签要精炼”这类矛盾需求很有价值。
1.3 适合什么样的业务场景
从我接触过的项目来看,SCWS最适合的场景有这么几类。
第一类是站内搜索。搜索请求进来后,先用SCWS把查询词切好,再去倒排索引里查词。因为分词结果可控,搜索词被切成什么样子心里有数,召回和排序都好调。
第二类是自动标签和关键词提取。文章发布后跑一段离线脚本,分词、过滤词性、统计词频,最后取TopN作为标签。这个过程不需要复杂的模型,SCWS跑一遍就够。
第三类是词频统计、词云生成、舆情热词聚合。把大量文本分批切词,然后合并统计,出来的就是相对真实的主题词分布。
它也适合快速原型验证。比如你手上有一段中文文本,想知道里面大概讲什么,装好SCWS后一条命令就能把分词结果吐出来。这种“小快灵”的特征,是很多重量级方案不具备的。
当然,它也有明显的边界。如果你要做情感分析、意图识别、句法分析这类高语义需求,SCWS就帮不上什么忙了,那种场景应该交给更底层的NLP框架或预训练模型。SCWS解决的是“切分”这个基础问题,而不是“理解”这个高端问题。
2. 编译安装过程与环境依赖
2.1 安装前的环境准备清单
SCWS虽然用起来轻,但它是源码分发,需要自己编译,所以环境里得先备齐几样东西:GCC和Make这组编译工具,Autoconf用于生成configure脚本,以及PHP开发包,因为PHP扩展要用phpize编译。如果PHP是通过系统包管理器装的,一般还需要对应的php-devel或者php-dev包。
安装之前可以先确认一下现状,建议依次执行这几条命令看看缺什么:
gcc --version make --version autoconf --version php -v phpize --version如果其中某条提示命令不存在,按对应系统的包管理器装就行。CentOS系可以用yum install gcc make autoconf php-devel,Ubuntu/Debian系用apt install build-essential autoconf php-dev。
这里有个细节容易忽略:phpize的版本必须和正在运行的PHP版本一致。如果你机器上有多版本PHP共存,一定要看清楚phpize指向的是不是目标版本,否则编译出来的scws.so很可能加载失败。
2.2 源码包获取与解压
SCWS的源码包是tar.gz格式,从官方发布渠道下载就好。下载后放到一个工作目录,比如/usr/local/src下面,然后解压:
tar -zxvf scws-*.tar.gz cd scws-*/解压之后可以先看看目录结构。一般会看到这几个关键目录:src放的是C语言源码,phpext是PHP扩展源码,etc下面放着默认词典和规则文件,bin目录在编译后会出现scws和scws-gen-dict等工具。
有件事我建议在编译前就做掉:看一眼etc目录里的内容。默认词典文件名通常是dict.utf8.xdb,规则文件是rule.ini。后面使用阶段需要用到这两个文件的绝对路径,现在记住了会省不少事。
2.3 编译安装核心C库
编译安装C库的过程非常标准,三连命令:
./configure --prefix=/usr/local/scws make make install--prefix参数指定安装前缀,我习惯装到/usr/local/scws,这样所有东西都集中在一个目录下,后续查找和管理比较方便。安装完成后,/usr/local/scws下面会生成bin、include、lib、etc这些子目录。
编译过程中如果报错,绝大多数情况是前面说的基础工具没装全。比如configure阶段提示无法找到某种构建工具,那就回退到2.1节,把依赖补齐再重新执行。重新执行前不需要清理,configure和make会自动覆盖。
这里还有一个容易踩的小坑:有些发行版的默认链接路径不包含/usr/local/scws/lib。如果后面运行scws命令时提示找不到libscws.so,需要先执行ldconfig或者设置LD_LIBRARY_PATH。具体怎么处理,我在第6章会详细讲。
2.4 编译安装PHP扩展
核心C库装好之后,进入源码包的phpext目录,开始装PHP扩展:
cd phpext phpize ./configure --with-scws=/usr/local/scws make make install其中phpize是用来生成PHP扩展编译环境的工具,--with-scws参数指向的路径就是上一步configure --prefix指定的安装目录,不能写错。执行完make install后,终端会打印出scws.so被拷贝到什么位置,比如/usr/local/lib/php/extensions/no-debug-non-zts-xxxx/scws.so,记下这个完整路径。
接下来修改php.ini,在扩展配置区加一行:
extension=scws.so然后重启PHP进程。如果你用的是PHP-FPM,常见做法是systemctl restart php-fpm,或者service php-fpm reload。重启后验证:
php -m | grep scws如果能看到scws,说明扩展加载成功。如果没有任何输出,先别急,检查两件事:一是extension配置的路径是否写对,二是CLI模式下的php.ini和FPM的php.ini是不是同一个。很多服务器上CLI和FPM用的配置文件位置不一样,只改了一个就会出现“命令行能加载,网页里调不了”的现象。
2.5 环境变量与词典准备
为了让命令行工具用起来顺手,建议把scws所在的目录加入PATH。编辑~/.bashrc,加上:
export PATH=$PATH:/usr/local/scws/bin然后source ~/.bashrc。
接着检查一下默认词典。如果/usr/local/scws/etc/下面已经有dict.utf8.xdb,那很好,直接可以用。如果发现是空的,也不用慌,把源码包etc目录里的词典和规则文件复制过去:
cp /path/to/scws-*/etc/dict.utf8.xdb /usr/local/scws/etc/ cp /path/to/scws-*/etc/rule.ini /usr/local/scws/etc/词典文件是SCWS的核心依赖,没有它分词器什么都做不了。后面所有命令和代码里的词典路径,都以你实际放置的路径为准。
3. 命令行工具下的日常分词操作
3.1 最快速上手:管道分词
安装完成后,最快的验证方式就是用管道把待分词文本喂给scws命令:
echo "中文分词工具安装完成,测试文本处理效果" | scws -c utf8 -M 8-c utf8表示文本按UTF-8编码处理,-M 8表示开启复合词模式。命令执行后,终端会输出类似下面这样的结果:
中文/n 分词/vn 工具/n 安装/v 完成/v 测试/vn 文本处理/vn 效果/n输出里第一个字段是分词结果,第二个字段是词性标注。n代表名词,v是动词,vn是动名词形式的词。看到这种格式,说明SCWS已经正常工作了。
第一次用的时候可以试着去掉-M 8再跑一次,对比一下输出差异。你会发现默认模式下词条更碎,而开启复合模式后会尽量保留一些组合词。这个差异在做搜索召回时特别有用。
3.2 常用参数速查表
scws命令本身支持的参数不少,很多一看帮助文档就能明白。这里我把实际最常用的几个整理成表格:
| 参数 | 作用 | 典型用法 |
|---|---|---|
-c | 指定字符集 | -c utf8或-c gbk |
-i | 指定输入文件 | -i article.txt |
-o | 指定输出文件 | -o tokens.txt |
-d | 指定词典文件 | -d /usr/local/scws/etc/dict.utf8.xdb |
-r | 指定规则文件 | -r /usr/local/scws/etc/rule.ini |
-M | 复合分词模式 | -M 8开启复合词,-M 3开启人名+地名 |
-x | 输出XML格式 | -x然后从stdout读XML |
需要在命令里查看全部参数时,直接执行:
scws -h输出里会列出所有可用项和简单说明。我最常用的就是上表中的几个组合,日常90%的需求都能覆盖。
3.3 输出格式与结果解读
SCWS命令行默认输出是“词语 空格 词性”的纯文本格式,每行一个词。如果遇到需要程序化解析的场景,建议用-x参数让它输出XML格式:
echo "测试一下XML输出" | scws -c utf8 -xXML格式会把词、偏移量、词性等字段结构化标出来,方便写脚本来读取。
理解输出的时候要特别注意词性标注。SCWS的词性标注基本沿用了常见的词性标记体系:n名词、v动词、a形容词、d副词、ns地名、nr人名等。开发标签系统时,通常只统计名词和动词类词性,过滤掉虚词和停用词,能大大提升关键词质量。
3.4 用命令行完成批量分词与词频统计
命令行还有一个很实用的用途,就是不需要写PHP代码也能对整篇文章做词频统计。假设你有一个UTF-8编码的文本文件article.txt,先分词输出到文件:
scws -c utf8 -i article.txt -o tokens.txt -M 8然后借助管道和文本处理命令,就能统计高频词:
awk '{print $1}' tokens.txt | sort | uniq -c | sort -rn | head -20这条命令先把每行的第一列也就是词语提取出来,然后排序、去重、计数,最后输出出现次数最高的前20个词。速度非常快,几百KB的文本几秒就能出结果。写文章关键词分析或者临时看一批文本的主题分布,用这一招就够了。
需要注意tokens.txt会被直接覆盖,如果文件里已有内容,最好先备份。另外,如果文本里有中英文混排,字母串也会被分词器按规则处理,统计时可以根据自己的需要过滤掉非中文词条。
4. 自定义词典、规则与分词效果调优
4.1 文本词典格式与编写
SCWS自带的词典覆盖了常用词,但每个业务都有自己的领域术语,比如产品名、品牌名、专业名词。这些词在通用词典里大概率没有,分词时就会被拆得七零八落。解决办法就是维护一份自定义词典。
自定义词典是一个纯文本文件,每一行包含三项信息:词语、词频、词性,中间用空白分隔。比如:
小程序 1000 n 自动化测试 800 n 智能客服 800 n 图数据库 600 n这里有个原则:词频越高,分词时这个词越容易被保留。如果你发现某个词总被切碎,给它一个足够高的词频就能稳定输出成一个词条。词性标注根据自己的需要写,名词类标注为n就好。
保存词典文件时必须用UTF-8编码,并且不要带BOM。带BOM会导致第一行词条解析异常,这类问题特别隐蔽。
4.2 将文本词典编译为xdb
SCWS运行时不直接读文本词典,它需要先把文本词典编译成xdb二进制格式。编译工具是scws-gen-dict,命令如下:
/usr/local/scws/bin/scws-gen-dict -c utf8 -i mydict.txt -o mydict.xdb-c指定输入词典的字符集,-i是文本词典路径,-o是输出的xdb文件路径。编译完成后会生成一个mydict.xdb文件,这个文件才是分词器能高效加载的格式。
使用时通过-d指定:
echo "小程序开发过程中需要用到图数据库" | scws -c utf8 -d /usr/local/scws/etc/dict.utf8.xdb -d /path/to/mydict.xdb -M 8同一个分词命令可以多次使用-d参数叠加多个词典,系统词典负责通用词,自定义词典负责领域词,两者配合效果最好。在PHP扩展里,同样可以连续调用set_dict加载多个词典。
有个操作细节要提醒:如果修改了文本词典,一定要重新执行一次scws-gen-dict编译。我自己就犯过这种错误——改完文本词典直接放在原路径,没重新编译,结果新词始终不生效,排查了半天才发现加载的还是旧版xdb。
4.3 词频调整的策略与经验
自定义词典里最核心的工作其实就是调词频。这个词频设置得合不合理,直接决定分词结果符不符合业务预期。
我常用的调优策略是这样的:一个领域词如果经常被从中间切开,说明它的竞争能力不够,这时把词频往上调,直到分词结果稳定输出完整词条。反过来,如果某个词把不该合并的内容也吸进去了,比如出现“小程序员”这种本来该切“小/程序员”的结果,就要把“小程序员”的词频降下来,或者直接删掉这个词条。
词频并不是越大越好。每个候选位置都有多个词条在竞争,某个词频一家独大,可能把相邻词强行吸收成异常长词。调频是一种平衡艺术,要结合实际分词语料反复验证。
我建议维护词典时,先跑一批代表性文本,把分词结果打印出来,人工扫一遍找出切错的地方,再回头改词频。这个循环看起来笨,却最有效。机器模型的黑盒不一定符合业务的口味,但SCWS的词典是透明的,你可以完全控制分词行为。
4.4 规则文件与分词粒度的选择
除了词典,SCWS还有一个rule.ini规则文件。它主要控制人名、地名、数字、时间等特殊文本的识别模式。比如规则里定义了常见姓氏和名字结构,分词时才有可能把“某同学参加了发布会”里的“某同学”切成一个整体词条。
想要调整规则,直接编辑rule.ini文件即可。不过我不建议一上来就动规则文件,因为规则是全局性的,改动一个模式会影响所有文本的分词结果。更稳妥的做法是先用自定义词典解决领域词问题,确属规则层面需要调整时,再小心翼翼地改。
分词粒度的选择也要结合场景。搜索场景建议开启复合词模式,让分词结果尽量多保留长词组合,召回率会更好。自动标签、关键词提取场景,反而建议使用默认的细粒度切分,因为细词更容易反映文本主题,再通过词频统计把核心词筛出来。
5. PHP项目集成及自动化分词实战
5.1 SimpleCWS扩展类方法速览
SCWS的PHP扩展封装了一个叫SimpleCWS的类,常用方法不多,但都是必须的。我整理了一个速查表:
| 方法 | 作用 |
|---|---|
set_charset('utf8') | 设置输入文本字符集 |
set_dict($path) | 加载词典,可多次调用 |
set_rule($path) | 加载规则文件 |
set_multi($mode) | 设置复合分词模式,对应命令行-M |
set_ignore(true) | 忽略标点符号和虚词 |
set_duality(true) | 开启散字二元聚合 |
send_text($text) | 传入待分词文本 |
get_result() | 获取一个分词结果,循环调用直到返回空 |
close() | 释放对象资源 |
核心调用顺序是固定的:先设置字符集和词典,再send_text传入文本,最后循环get_result取结果。这个顺序如果乱了,比如忘了加载词典就send_text,分词结果大概率是一堆单字。
5.2 一个完整的分词PHP函数
下面这个函数可以直接复制到项目里用:
<?php function textSegment(string $text): array { $cws = new SimpleCWS(); $cws->set_charset('utf8'); $cws->set_dict('/usr/local/scws/etc/dict.utf8.xdb'); $cws->set_dict('/data/dict/mydict.xdb'); $cws->set_rule('/usr/local/scws/etc/rule.ini'); $cws->set_ignore(true); $cws->set_multi(8); $cws->send_text($text); $result = []; while ($row = $cws->get_result()) { $result[] = $row; } $cws->close(); return $result; } $rows = textSegment('中文分词是文本处理的基础步骤。'); foreach ($rows as $row) { echo $row['word'] . ' / ' . $row['attr'] . PHP_EOL; }get_result()返回的数组通常包含word(词文本)、attr(词性)、idf(词权重)等字段。实际开发中主要用的是word和attr两个字段。
5.3 长文本分词与性能控制
处理长文本时,很多人的第一反应是一次性把整篇内容丢给send_text。这个操作本身没错,但如果文本特别长,比如几万字,建议分段处理。一段文本丢进去后,循环get_result()取完结果,再送下一段。这样做的好处是内存占用可控,也不会让分词器长期占着资源不释放。
在CLI脚本里处理一批文件时,可以重复使用同一个SimpleCWS对象,每次只需要重新send_text和循环取结果就行,不需要重新new对象和重新加载词典。词典加载是比较重的操作,减少重复加载对性能提升非常明显。
如果是在PHP-FPM进程里跑,每个Worker首次请求会初始化扩展对象,之后相同Worker后续请求会复用进程内的资源。所以频繁调用分词并不会像想象中那么慢,实际抖动主要来自第一次词典加载。
5.4 案例:基于分词实现文章标签自动提取
这里分享一个比较完整的场景:给一篇文章自动生成标签。逻辑是分词、过滤词性、过滤停用词、统计词频、取TopN。
function extractTags(string $text, int $topN = 5): array { $rows = textSegment($text); $counter = []; foreach ($rows as $row) { $word = $row['word']; $attr = $row['attr']; if ($attr !== 'n' && $attr !== 'ns' && $attr !== 'nr' && $attr !== 'vn') { continue; } if (mb_strlen($word, 'utf-8') < 2) { continue; } if (in_array($word, ['可以', '进行', '使用', '通过'], true)) { continue; } $counter[$word] = ($counter[$word] ?? 0) + 1; } arsort($counter); return array_slice(array_keys($counter), 0, $topN); } $tags = extractTags('在这篇文章中,我们讨论基于中文分词工具构建自动标签系统的过程。'); print_r($tags);这里过滤了非名词类词性和单字词,又排除了一批常用虚词,剩下的高频词基本就是文章主题相关的标签。词性标注的准确性会影响过滤效果,所以自定义词典里给领域词标注正确的词性很重要。
实际跑下来,这个方案对技术文章的效果不错。相比某些在线AI标签服务,它的优势是全程在本机执行,速度快、隐私可控,几十万篇文章批量处理也扛得住。
6. 常见故障排查与避坑心得
6.1 问题速查表
把实际中可能遇到的问题整理成一个表格,方便快速对照排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 分词结果全是单字 | 默认词典没加载或路径错误 | 检查-d参数和词典文件是否存在 |
| 输出中文乱码 | 字符集设置与文本编码不一致 | 统一使用-c utf8,或先转码 |
| PHP扩展无法加载 | phpize与PHP版本不一致 | 重新用匹配版本的phpize编译 |
提示找不到libscws.so | 动态库路径未加入系统链接库 | 配置ldconfig或设置LD_LIBRARY_PATH |
| 自定义词不生效 | 文本词典未重新编译为xdb | 修改后重新执行scws-gen-dict |
| 词性标注不准 | 自定义词缺少词性字段 | 词典中补全词性标注 |
| 切分结果不符合预期 | 词频不合理或复合模式配置不当 | 调整词频或按场景修改-M参数 |
这个表基本覆盖了我遇到的大部分问题。剩下一些“幽灵问题”,比如明明配置都对了还是报错,大概率是PHP-FPM没重启,或者加载了错误的php.ini。
6.2 编译安装阶段的坑
编译阶段最典型的坑有三个。
第一个是phpize命令不存在。这通常意味着没有安装对应版本的PHP开发包。装好之后重新执行phpize即可。
第二个是configure阶段提示找不到SCWS库。遇到这个,把./configure --with-scws=/usr/local/scws里的路径改成你实际安装的前缀路径,重点确认这个路径下真的有include和lib目录。
第三个是运行时提示找不到libscws.so。这是因为扩展加载时,动态链接器不知道去哪找SCWS的库文件。解决办法二选一:在/etc/ld.so.conf.d/下新建一个conf文件写入/usr/local/scws/lib,然后执行ldconfig;或者在PHP的启动配置里加上export LD_LIBRARY_PATH=/usr/local/scws/lib。我用的是第一种方案,一劳永逸。
6.3 分词结果不符合预期的调整路径
面对分词结果不符合预期,不要一上来就怀疑是安装有问题,先从输出本身的特征判断。
如果全是单字,第一反应不是去调规则,而是检查词典有没有被正确加载。跑一次命令行,明确指定-c utf8 -d /usr/local/scws/etc/dict.utf8.xdb,如果结果变正常,说明PHP侧或代码侧的词典路径配置有问题。
如果大部分词都对,就个别领域词被切碎,这是词典问题的典型信号。去自定义词典里加上这个词,给一个较高的词频,重新编译后测试。一般来说,分词工具的调优过程就是“发现问题,加词/调频,重跑验证”的循环。
如果分词结果整体偏碎或者偏长,那就去调复合分词模式。想保留更多长词就提高多模式值,想更细粒度就降低或关闭复合模式。模式值不是越大越好,它只是给了分词器更多组词候选,最终效果要靠实际语料验证。
6.4 性能与稳定性优化建议
对外提供分词能力前,性能问题也值得提前考虑。首先建议尽量使用CLI常驻脚本处理批量文本,省去重复的进程创建开销。其次,SCWS的词典加载是耗时的,不要在循环里反复new对象和加载词典,能复用就复用。
在PHP-FPM环境里,每个Worker进程都会加载自己的词典副本,如果你的词典特别大,内存占用会成倍增长。这时候可以评估一下自定义词典大小,把不常用、低频、无意义的词条清掉,词典越小加载越快,内存占用也越低。
另一个容易被忽略的点是权限问题。PHP-FPM运行用户如果没有读取词典文件的权限,扩展加载会静默失败,分词结果呈现为单字。遇到诡异的单字问题,检查一下词典文件的读写权限。
最后再提醒一次:修改任何PHP配置、规则文件、php.ini之后,一定要重启PHP-FPM。很多“怎么改都没反应”的问题,其实只是进程还挂着旧配置。
我个人在实际操作里最深的体会是,SCWS的价值不是它有多“智能”,而是它足够简单透明。它在分词领域可能不是最先进的方案,但在“快速给某个业务系统加上中文分词能力”这件事上,它确实是我用过最省心的选择。遇到切分不对,直接打开词典文本,改一个词频,重新编译,立刻见效,这种掌控感是很多黑盒模型给不了的。