Python 从零开始 — 文本与自然语言处理_NLP 实战(全集)

服务器环境:华为云 FlexusX ecs-88e7-0001 | Ubuntu 24.04.4 | Python 3.12.3 | 8vCPU 16GiB
SSH 入口ssh root@139.9.128.210
涵盖章节:28 个实验(字符/字符串处理 → 正则表达式 → 词法分析/数据挖掘)
博客风格:上机实操 + 真实输出 + 踩坑记录 + 对比表格 + ASCII 架构图


目录


一、字符 · 字符串 · 纯文本处理

┌──────────────────────────────────────────────────────────────────┐
│                    字符串处理能力全景                             │
│                                                                  │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐     │
│   │  格式化   │   │  查找替换  │   │  分割合并  │   │  大小写   │     │
│   │ % / fmt   │   │ find/rpl │   │ split/jn  │   │ up/low   │     │
│   └─────┬────┘   └─────┬────┘   └─────┬────┘   └─────┬────┘     │
│         └───────────────┼──────────────┼──────────────┘          │
│                         ▼                                          │
│               ┌──────────────────┐                               │
│               │   str 方法全集    │   (Python 原生,无需 import)   │
│               └────────┬─────────┘                               │
│                        ▼                                          │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐     │
│   │ langdetect│   │ pypinyin  │   │ datetime │   │ calendar │     │
│   │  语言检测  │   │  拼音转换  │   │  日期时间  │   │  万年历   │     │
│   └──────────┘   └──────────┘   └──────────┘   └──────────┘     │
└──────────────────────────────────────────────────────────────────┘

1.1 三种字符串格式化

Python 提供了三种字符串格式化方式,各有用武之地:

方式 语法 适用场景 推荐度
% 格式化 "name: %s" % var 旧代码兼容、简单占位 ⭐⭐
str.format() "name: {}".format(var) Python 2.6+,中量级 ⭐⭐⭐
f-string f"name: {var}" Python 3.6+,首选 ⭐⭐⭐⭐⭐
1.1.1 % 格式化(传统方式)
name = "Alice"
age = 25
score = 95.6789

print("姓名: %s, 年龄: %d" % (name, age))   # 姓名: Alice, 年龄: 25
print("成绩: %.2f" % score)                  # 成绩: 95.68
print("百分制: %d%%" % 95)                   # 百分制: 95%  ← %% 转义百分号

占位符一览

占位符 含义 示例
%s 字符串 "%s" % "hello"
%d 整数 "%d" % 42
%f 浮点数 "%f" % 3.14
%x / %X 十六进制(小/大写) "%x" % 255ff
%o 八进制 "%o" % 255377
%e 科学计数法 "%e" % 3141.53.141500e+03
%% 百分号自身 "%d%%" % 9595%
1.1.2 str.format() — 最灵活的中间代
# 位置参数
"{0} {1} {2}".format("A", "B", "C")        # 'A B C'
"{} {} {}".format("A", "B", "C")            # 'A B C'  (可省略序号)
"{0} {0} {1}".format("X", "Y")             # 'X X Y'  (可重复引用)

# 关键字参数
"{name} {age}".format(name="Bob", age=30)  # 'Bob 30'

# 对齐 & 填充
"|{:<10}|".format("左对齐")    #  |左对齐       |
"|{:>10}|".format("右对齐")    #  |       右对齐|
"|{:^10}|".format("居中")      #  |    居中    |
"|{:*^10}|".format("填充")    #  |****填充****|  ← * 填充居中
"|{:=<10}|".format("右填")    #  |右填========|  ← = 填充右对齐

# 正负号
"{:+d} {:+d}".format(42, -42)     # '+42 -42'
"{: d} {: d}".format(42, -42)     # ' 42 -42'  ← 空格占位正号

# 数字进制
"{:d} {:b} {:o} {:x} {:X}".format(255, 255, 255, 255, 255)
# → '255 11111111 377 ff FF'

"{:#b} {:#o} {:#x}".format(255, 255, 255)
# → '0b11111111 0o377 0xff'  ← # 加前缀

# 精度
"{:.2f}".format(3.14159)          # '3.14'
"{:.2e}".format(3.14159)          # '3.14e+00'  科学计数
"{:.2%}".format(0.8567)           # '85.67%'    百分比
"{:,}".format(1234567890)         # '1,234,567,890'  千分位

服务器验证

十进制: 255
二进制: 11111111
八进制: 377
十六进制: ff
大写十六进制: FF
带前缀: 0b11111111 0o377 0xff

保留3位: 3.142
科学计数: 3.142e+00
百分比: 85.67%
千分位: 1,234,567,890
通用格式: 3.1416

1.2 split 与可迭代对象

split() 是字符串处理最高频方法之一。

# 基本分割
"apple,banana,cherry".split(",")         # ['apple', 'banana', 'cherry']

# 限制分割次数
"a,b,c,d".split(",", 2)                  # ['a', 'b', 'c,d']  ← 只切2次

# 空白分割(默认按任意空白)
"hello  world\tpython".split()           # ['hello', 'world', 'python']

# 从右侧分割
"/usr/local/bin/python3".rsplit("/", 1)  # ['/usr/local/bin', 'python3']

# 按行分割
"line1\nline2\r\nline3".splitlines()     # ['line1', 'line2', 'line3']

split() vs split(' ')

方法 连续空格 制表符 换行符
split() 当作一个分隔 当作分隔 当作分隔
split(' ') 产生空串 不分割 不分割

1.3 f-string 全功能

f-string 是 Python 3.6+ 的格式化首选,性能最优、可读性最强。

name = "Charlie"
age = 35
salary = 12345.6789

# 基本插值
f"姓名: {name}, 年龄: {age}"                    # '姓名: Charlie, 年龄: 35'

# 格式化说明符
f"工资: {salary:.2f}"                           # '工资: 12345.68'
f"{'left':<10}|{'right':>10}|{'center':^10}"    # 'left      |     right|  center   '

# 数字进制
f"bin={254:b}, oct={254:o}, hex={254:x}"        # 'bin=11111110, oct=376, hex=fe'

# 内嵌表达式
f"2+3={2+3}, 2**10={2**10}"                     # '2+3=5, 2**10=1024'
f"type(name)={type(name).__name__}"             # 'type(name)=str'

# 日期时间格式化
import datetime
now = datetime.datetime.now()
f"{now:%Y-%m-%d %H:%M:%S}"                      # '2026-07-01 22:56:22'
f"{now:%A}"                                      # 'Wednesday'

# 大括号转义
f"{{name}} = {name}"                             # '{name} = Charlie'

服务器验证

当前时间: 2026-07-01 22:56:22
星期: Wednesday

1.4 原始字符串与进度条

r-string — 反斜杠原样保留
# 普通字符串:\n 被解释为换行
print(repr('\n\t\r'))          # '\n\t\r'

# 原始字符串:\n 原样保留
print(repr(r'\n\t\r'))         # '\\n\\t\\r'

# Windows 路径无忧
path = r"C:\Users\admin\Documents"   # 不用写 C:\\Users\\...

适用场景:正则表达式模式、Windows 文件路径、LaTeX 公式

进度条 — 字符串实战
def make_progress_bar(pct, width=30):
    filled = int(width * pct / 100)
    bar = "|" + "=" * filled + ">" + " " * (width - filled - 1) + "|"
    return f"\r{bar} {pct:3d}%"   # \r 回到行首实现刷新

for pct in range(0, 101, 25):
    print(make_progress_bar(pct))

输出效果

|>                             |   0%
|=======>                      |  25%
|===============>              |  50%
|======================>       |  75%
|==============================>| 100%

1.5 datetime 日期时间

Python 的日期时间处理围绕三个核心类:

                ┌─────────────┐
                │  datetime   │  (日期 + 时间)
                └──────┬──────┘
           ┌───────────┼───────────┐
    ┌──────┴──────┐          ┌──────┴──────┐
    │    date     │          │    time     │
    │  年月日     │          │  时分秒微秒  │
    └─────────────┘          └─────────────┘
    ┌──────────────┐
    │  timedelta   │  (时间差)
    └──────────────┘
from datetime import datetime, date, time, timedelta

now = datetime.now()
now.year, now.month, now.day          # (2026, 7, 1)
now.hour, now.minute, now.second      # (22, 56, 22)
now.weekday()                          # 2  (0=周一)
now.strftime('%A')                     # 'Wednesday'
strftime 格式化速查表
指令 含义 服务器输出 (2026-07-01)
%Y 4位年份 2026
%y 2位年份 26
%m 月份 (01-12) 07
%d 日 (01-31) 01
%H 时 (00-23) 22
%M 分 (00-59) 56
%S 秒 (00-59) 22
%A 星期全名 Wednesday
%a 星期缩写 Wed
%B 月份全名 July
%b 月份缩写 Jul
%j 年中第几天 182
%W 年中第几周 26
timedelta 时间差 — 纪念日提醒
today = date.today()
birthday = date(today.year, 10, 1)
delta = birthday - today
print(f"距生日还有: {delta.days} 天")        # 距生日还有: 92 天

# 时间加减
today + timedelta(days=100)         # 2026-10-09
today - timedelta(days=100)         # 2026-03-23
today + timedelta(weeks=3)          # 2026-07-22
datetime.now() + timedelta(hours=30)

# 计算从年初到今天
start = date(2026, 1, 1)
diff = today - start
print(f"已过 {diff.days} 天 = {diff.days//7}{diff.days%7}天")
# → 已过 181 天 = 25周6天
epoch — Unix 时间戳
Unix 纪元 = 1970-01-01 00:00:00 UTC
当前时间戳 ≈ 1782917783 秒自纪元起

重要时间点的时间戳:
  Unix纪元              1970-01-01 → -28800      (UTC+8 时区偏差)
  Y2K                   2000-01-01 → 946656000
  iPhone发布             2007-06-29 → 1183046400
  Python3.0发布          2008-12-03 → 1228233600
  COVID-19 WHO声明       2020-03-11 → 1583856000

1.6 天干地支纪年法

tiangan = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
dizhi   = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
shengxiao = ["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]

def ganzhi_year(year):
    tg = tiangan[(year - 4) % 10]
    dz = dizhi[(year - 4) % 12]
    sx = shengxiao[(year - 4) % 12]
    return f"{tg}{dz}年({sx}年)"

服务器验证

公元2024年 = 甲辰年(龙年)
公元2025年 = 乙巳年(蛇年)
公元2026年 = 丙午年(马年)
公元2027年 = 丁未年(羊年)
公元2028年 = 戊申年(猴年)

算法:(公元年 - 4) 分别对 10 和 12 取模,得到天干地支索引。因为公元4年恰好是甲子年,甲子为60甲子周期的起点。

1.7 字符串大小写与判断

方法 作用 示例
str.upper() 全大写 'hello'.upper()'HELLO'
str.lower() 全小写 'HELLO'.lower()'hello'
str.title() 首字母大写 'hello world'.title()'Hello World'
str.capitalize() 句首大写 'hello world'.capitalize()'Hello world'
str.swapcase() 大小写互换 'Hello'.swapcase()'hELLO'

判断方法(全部返回 bool):

方法 功能 'Hello123' '你好' '123'
isalpha() 全是字母
isalnum() 字母或数字
isdigit() 全是数字
islower() 全小写
isupper() 全大写
isspace() 全空白
isascii() 全是ASCII

1.8 strip / find / replace / translate

strip — 去空白/去指定字符
"   Hello World   \n".strip()          # 'Hello World'
"   Hello World   \n".lstrip()         # 'Hello World   \n'
"   Hello World   \n".rstrip()         # '   Hello World'

"===Hello===".strip("=")               # 'Hello'
find / rfind / index — 查找子串
text = "hello world, hello python, hello universe"

text.find("hello")          # 0      ← 首次出现位置
text.rfind("hello")         # 27     ← 最后一次出现位置
text.find("hello", 10)      # 13     ← 从索引10开始找
text.find("xyz")            # -1     ← 找不到返回 -1(不抛异常)
text.index("xyz")           # ValueError  ← 找不到抛异常!

find() vs index()

方法 找到返回值 找不到
find() 索引位置 -1
index() 索引位置 抛出 ValueError
replace — 替换
text.replace("hello", "HELLO")           # 全部替换
text.replace("hello", "HELLO", 2)        # 只替换前2个
text.replace("hello", "HELLO", 1)        # 只替换第1个
translate — 字符级映射(比 replace 高效)
# 元音字母映射为数字
trans_map = str.maketrans("aeiou", "12345")
"hello world".translate(trans_map)   # 'h2ll4 w4rld'

# 删除指定字符
del_map = str.maketrans("", "", "aeiou")
"hello world".translate(del_map)     # 'hll wrld'

# 大小写互换映射
swap_map = str.maketrans(
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
"Hello World".translate(swap_map)    # 'hELLO wORLD'

性能提示:大量字符替换用 translate(O(n)),逐个 replace 调用会多次遍历(O(k·n))。

1.9 langdetect 语言检测

from langdetect import detect, detect_langs

detect("你好世界")          # 'zh-cn'
detect("Hello World")       # 'en'
detect("Bonjour le monde")  # 'fr'
detect("こんにちは世界")     # 'ja'
detect("Hallo Welt")        # 'de'
detect("안녕하세요 세계")    # 'ko'

# 概率检测
detect_langs("Hello World, 你好")
# [en: 1.00]

服务器实测:6种语言全部精准识别。

1.10 pypinyin 汉语拼音

from pypinyin import pinyin, lazy_pinyin, Style

text = "中国智造"

# 多种输出风格
pinyin(text)                                    # [['zhōng'],['guó'],['zhì'],['zào']]
pinyin(text, style=Style.TONE)                  # 同上(带声调符号)
pinyin(text, style=Style.TONE3)                 # [['zhong1'],['guo2'],['zhi4'],['zao4']]
pinyin(text, style=Style.FIRST_LETTER)          # [['z'],['g'],['z'],['z']]

# 懒人模式
lazy_pinyin(text)                               # ['zhong','guo','zhi','zao']

二、字符串查找匹配 · 正则表达式

┌──────────────────────────────────────────────────────────────────┐
│                     正则表达式核心体系                            │
│                                                                  │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐     │
│   │  元字符   │   │  量词    │   │  字符类   │   │  锚点    │     │
│   │ . ^ $ |  │   │ * + ? {} │   │ [abc] \d │   │ ^ $ \b   │     │
│   └─────┬────┘   └─────┬────┘   └─────┬────┘   └─────┬────┘     │
│         └───────────────┼──────────────┼──────────────┘          │
│                         ▼                                          │
│               ┌──────────────────┐                               │
│               │  re 模块四大方法  │                               │
│               │ match / search   │                               │
│               │ findall / finditer│                              │
│               │ sub / split      │                               │
│               │ compile          │                               │
│               └────────┬─────────┘                               │
│                        ▼                                          │
│               ┌──────────────────┐                               │
│               │   高级特性       │                               │
│               │ 捕获组 / 命名组  │                               │
│               │ 贪婪 vs 非贪婪   │                               │
│               │ flags 编译选项   │                               │
│               └──────────────────┘                               │
└──────────────────────────────────────────────────────────────────┘

2.1 re 模块四大方法

import re

text = "My phone numbers: 138-1234-5678 and 010-87654321"
方法 功能 返回值 示例结果
re.match(p, t) 开头匹配 Match对象/None match('My')Mymatch('phone')None
re.search(p, t) 任意位置搜索 Match对象/None search('phone')phone
re.findall(p, t) 找出所有匹配 列表 findall(r'\d+')['138','1234','5678','010','87654321']
re.finditer(p, t) 迭代所有匹配 迭代器 每个Match对象含 .group() .start() .end()
# sub 替换
re.sub(r"\d{3}-\d{4}", "***-****", text)
# → 'My phone numbers: ***-****-5678 and ***-****4321'

# split 分割
re.split(r'[,\s;|]+', "apple,  banana;  cherry|date")
# → ['apple', 'banana', 'cherry', 'date']

2.2 正则元字符完整扫盲

正则元字符 11 个. ^ $ * + ? { } [ ] \ | ( )

元字符 含义 模式示例 匹配 不匹配
. 任意字符(除\n) a.b axb, a=b a\nb
^ 行首 ^hello hello world say hello
$ 行尾 world$ hello world world here
* 0次或多次 ab*c ac, abc, abbbc
+ 1次或多次 ab+c abc, abbbc ac
? 0次或1次 ab?c ac, abc abbc
{m,n} m到n次 ab{2,4}c abbc, abbbc, abbbbc abc, abbbbbc
[abc] 字符类 [aeiou] hello (匹配 e,o,o) rhythm
[^abc] 否定字符类 [^aeiou] hello (匹配 h,l,l) aeiou
| cat|dog cat, dog bird
() 捕获组 (ab)+ ab, abab baba

正则转义字符(character classes)

转义 等价于 含义 'a1_@#$' 匹配结果
\d [0-9] 数字 ['1']
\D [^0-9] 非数字 ['a','_','@','#','$']
\w [a-zA-Z0-9_] 单词字符 ['a','1','_']
\W [^a-zA-Z0-9_] 非单词字符 ['@','#','$']
\s [ \t\n\r\f\v] 空白 空格/制表/换行
\S [^ \t\n\r\f\v] 非空白 非空白字符
\b 单词边界 r'\bhello\b' 匹配独立单词
\B 非单词边界 r'\Bhello\B' 匹配词内

2.3 贪婪 vs 非贪婪

文本: '<h1>Title</h1><p>Content</p>'

贪婪 <.*>  :  ['<h1>Title</h1><p>Content</p>']
              ↑ 一口气吞到最后一个 > ,匹配尽可能多

非贪婪 <.*?>:  ['<h1>', '</h1>', '<p>', '</p>']
              ↑ 遇到第一个 > 就停,匹配尽可能少
量词 模式 行为
* 贪婪 0次或多次,尽可能多
*? 非贪婪 0次或多次,尽可能少
+ 贪婪 1次或多次,尽可能多
+? 非贪婪 1次或多次,尽可能少
? 贪婪 0次或1次,尽可能多
?? 非贪婪 0次或1次,尽可能少
{m,n} 贪婪 m到n次,尽可能多
{m,n}? 非贪婪 m到n次,尽可能少

实战:提取 HTML 文本内容

html = '<div><span>hello</span><span>world</span></div>'
re.findall(r'>([^<]+)<', html)    # ['hello', 'world']
#          ↑ 字符类方式比 .*? 更精确,不会有嵌套匹配问题

2.4 捕获组与命名组

text = "Name: Alice, Age: 25; Name: Bob, Age: 30"

# 基本捕获
re.findall(r"Name: (\w+), Age: (\d+)", text)
# → [('Alice', '25'), ('Bob', '30')]

# 命名捕获组 (?P<name>pattern)
m = re.search(r"Name: (?P<name>\w+), Age: (?P<age>\d+)", text)
m.group()           # 'Name: Alice, Age: 25'
m.group(1)          # 'Alice'
m.group('name')     # 'Alice'
m.groupdict()       # {'name': 'Alice', 'age': '25'}

# 非捕获组 (?:)
re.findall(r"(abc)+", "abc abcabc abcabcabc")
# → ['abc', 'abc', 'abc']       ← 只捕获最后一组

re.findall(r"(?:abc)+", "abc abcabc abcabcabc")
# → ['abc', 'abcabc', 'abcabcabc']  ← 整体匹配,不捕获
findall 组行为坑点
text = "Name: Alice, Name: Bob, Name: Charlie"

re.findall(r'Name: \w+', text)        # ['Name: Alice', 'Name: Bob', 'Name: Charlie']
re.findall(r'Name: (\w+)', text)      # ['Alice', 'Bob', 'Charlie']
#                               ↑ 有捕获组时只返回组内容!

2.5 re.compile 与 flags

# 预编译 — 重复使用时性能更优
pattern = re.compile(r"\d{4}-\d{2}-\d{2}")
pattern.findall("2026-07-01 is today, 2025-01-01 was New Year")
# → ['2026-07-01', '2025-01-01']

flags 编译选项

Flag 简写 作用 效果
re.IGNORECASE re.I 忽略大小写 [Hh][Ee][Ll][Ll][Oo]
re.MULTILINE re.M ^$ 匹配每行首尾 多行文本中每行独立匹配
re.DOTALL re.S . 匹配 \n 跨行匹配
re.VERBOSE re.X 允许注释和空白 可读性更好的正则
# 多行模式
text = "Hello\nWorld\nHELLO world"
re.findall(r'hello', text, re.I)     # ['Hello', 'HELLO']
re.findall(r'^\w+', text, re.M)      # ['Hello', 'World', 'HELLO'] ← 每行独立
re.findall(r'H.*d', text, re.S)      # ['Hello\nWorld\nHELLO world'] ← 跨行

2.6 实用正则案例

邮箱验证
pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")

"user@example.com"       → 有效
"invalid@"               → 无效
"name@company.co.uk"     → 有效
"@no-name.com"           → 无效
电话号码提取
text = "Call me at 138-1234-5678 or 010-87654321 or 0755-12345678"
pattern = re.compile(r"(\d{3,4})-(\d{7,8})")
# → 区号: 010, 号码: 87654321
# → 区号: 0755, 号码: 12345678
IP 地址(严格匹配 0-255)
pattern = r"\b(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\." \
          r"(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\." \
          r"(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\." \
          r"(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b"

"192.168.1.1"    → ✅
"256.1.1.1"      → ❌
"192.168.1.300"  → ❌
驼峰转蛇形
def camel_to_snake(name):
    return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()

camelCase      → camel_case
getUserInfo    → get_user_info
helloWorld     → hello_world

三、文本 · 词法分析 · 数据挖掘

┌──────────────────────────────────────────────────────────────────┐
│                      NLP 文本处理流水线                           │
│                                                                  │
│   ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐      │
│   │ 文本获取 │ -> │ 预处理  │ -> │ 特征提取 │ -> │ 结果输出 │      │
│   │ faker   │    │ jieba   │    │ TF-IDF  │    │ 卡方检验 │      │
│   │ open()  │    │ re清洗  │    │ n-gram  │    │ 情感分析 │      │
│   └─────────┘    └─────────┘    └─────────┘    └─────────┘      │
└──────────────────────────────────────────────────────────────────┘

3.1 jieba 中文分词

jieba 是中文 NLP 的基石库,提供三种分词模式。

import jieba

text = "今天天气真好,我们一起去公园散步吧"
模式 方法 分词结果 适用场景
精确模式 jieba.lcut(text) 今天天气 / 真 / 好 / , / 我们 / 一起 / 去 / 公园 / 散步 / 吧 文本分析
全模式 jieba.lcut(text, cut_all=True) 今天 / 今天天气 / 天天 / 天气 / 真好 / ... 快速扫描
搜索引擎 jieba.lcut_for_search(text) 今天 / 天天 / 天气 / 今天天气 / 真 / 好 / ... 搜索引擎
词性标注
import jieba.posseg as pseg

for word, flag in pseg.cut(text):
    print(f"  {word:6s} -> {flag}")
词性标签 含义
今天天气 i 成语
d 副词
a 形容词
我们 r 代词
v 动词
公园 n 名词
关键词提取 — TF-IDF vs TextRank
import jieba.analyse

# TF-IDF 提取
jieba.analyse.extract_tags(text, topK=8, withWeight=True)

# TextRank 提取(基于图排序)
jieba.analyse.textrank(text, topK=8, withWeight=True)

服务器实测对比

排名 TF-IDF 权重 TextRank 权重
1 人工智能 0.5911 智能 1.0000
2 智能 0.4475 人工智能 0.7215
3 图像识别 0.2491 语言 0.4739
4 人类 0.2346 理论 0.4632
自定义词典
jieba.add_word("深度学习框架")
jieba.add_word("大语言模型")

"深度学习框架和大语言模型是人工智能的核心技术"
# → 深度学习框架 / 和 / 大语言模型 / 是 / 人工智能 / 的 / 核心技术

3.2 TF-IDF 词频分析

TF-IDF = TF × IDF

TF (Term Frequency)   = 词在文档中出现的次数 / 文档总词数
IDF (Inverse DF)      = log(总文档数 / 包含该词的文档数)

实战:5篇中文经典语料

documents = [
    "滚滚长江东逝水,浪花淘尽英雄。是非成败转头空。青山依旧在,几度夕阳红。",  # 三国
    "话说天下大势,分久必合,合久必分。",                                    # 三国
    "路漫漫其修远兮,吾将上下而求索。",                                     # 楚辞
    "道可道,非常道;名可名,非常名。",                                     # 老子
    "学而时习之,不亦说乎?有朋自远方来,不亦乐乎?",                       # 论语
]

# 分词后计算 TF-IDF

服务器验证 — IDF 值

IDF 含义
天下 2.10 只出现在1篇,区分度高
君子 2.10 只出现在1篇
4.20 出现在多篇,区分度低

每篇文档 TF-IDF Top 词

文档 Top词
三国(1) 滚滚(2.10), 长江(2.10), 英雄(2.10)
三国(2) 天下(2.10), 大势(2.10)
楚辞 路漫漫其修远兮(2.10), 吾将上下而求索(2.10)
老子 道(4.20), 非常(4.20)
论语 不(6.30), 亦(4.20)

3.3 faker 实验数据生成

faker 是测试数据生成神器,支持多语言本地化。

from faker import Faker

fake_en = Faker("en_US")
fake_zh = Faker("zh_CN")

# 英文假数据
fake_en.name()          # 'Kathleen Hammond'
fake_en.email()         # 'ann92@example.net'
fake_en.phone_number()  # '(931)283-6223'

# 中文假数据
fake_zh.name()          # '王丹'
fake_zh.address()       # '江西省银川市房山西宁街G座 883863'
fake_zh.company()       # '超艺网络有限公司'

faker 能力矩阵

类别 方法
人物 name(), first_name(), last_name()
联系方式 email(), phone_number(), address()
互联网 url(), ipv4(), uuid4(), user_name()
金融 credit_card_number(), iban()
日期 date(), date_time(), year()
文本 text(), sentence(), paragraph()
颜色 color_name(), hex_color()
职业 job(), company()

生成100条用户数据

fake_zh.seed_instance(2026)
users = []
for _ in range(100):
    users.append({
        "name":   fake_zh.name(),
        "age":    fake_en.random_int(18, 65),
        "email":  fake_en.email(),
        "city":   fake_zh.city(),
        "salary": fake_en.random_int(5000, 50000),
    })

3.4 卡方检验(Chi-Square)

卡方检验用于判断两个分类变量是否独立

H₀: 两个变量独立(无关联)
H₁: 两个变量不独立(有关联)

χ² = Σ (观测值 - 期望值)² / 期望值

决策规则:若 χ² > 临界值(α=0.05),则拒绝 H₀,认为有关联
案例 — 药物疗效检测
          有效  无效
用药组     80    20      观测值
对照组     50    50

期望值 (H₀ 假设独立):
          有效  无效
用药组     65    35
对照组     65    35

χ² = (80-65)²/65 + (20-35)²/35 + (50-65)²/65 + (50-35)²/35
   = 19.78

临界值 χ²₀.₀₅(1) = 3.841
结论:19.78 > 3.841 → 药物有效 ✅
案例 — 文本特征词关联
             含'编程'  不含'编程'
含'Python'      4          1
不含'Python'    2          3

χ² = 1.67 < 3.841 → 不拒绝 H₀ → 'Python'与'编程'独立
(样本量太小,统计不显著)

自由度 df = (行数-1) × (列数-1),2×2表固定为 df=1

3.5 文本统计与简易情感分析

基本文本统计
# 字符与句子
chars = len(text)                          # 170
sentences = re.split(r"[。!?;]+", text)  # 5句

# 中英文词频(使用 jieba + re)

服务器实测 — 中文词频

频次 可视化
自然语言 6 ██████
处理 4 ████
认知 2 ██
理解 2 ██
生成 2 ██

英文词频

频次
language 4
and 4
natural 2
processing 2
nlp 2

Bigram(二元词组)

(natural, language)    : 2
(language, processing) : 2
(human, language)      : 2
简易情感分析(字典法)
positive = {"good","great","excellent","wonderful","love","happy"}
negative = {"bad","terrible","awful","horrible","hate","sad"}

def sentiment(text):
    words = re.findall(r"\b[a-z]+\b", text.lower())
    pos = sum(1 for w in words if w in positive)
    neg = sum(1 for w in words if w in negative)
    return "正面" if pos > neg else ("负面" if neg > pos else "中性")

# 'This is a great product, I love it!'           → pos=3 neg=0 → 正面
# 'The service was terrible and awful.'            → pos=0 neg=2 → 负面
# 'It was an ordinary day, nothing special.'       → pos=0 neg=0 → 中性

3.6 NLP 工具链总览

标准NLP流水线:
  ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
  │ 文本获取 │ -> │ 预处理  │ -> │ 特征提取 │ -> │ 建模分析 │ -> │ 结果输出 │
  └─────────┘    └─────────┘    └─────────┘    └─────────┘    └─────────┘
                    │               │
              分词/去停用词      TF-IDF/词袋
              词性标注           Word2Vec
              命名实体识别       BERT embedding
              标准化/小写化       n-gram特征
步骤 Python 工具 本章覆盖
文本获取 open(), requests, faker ✅ faker
文本清洗 re.sub, str.strip/replace/translate ✅ 全系列
中文分词 jieba (3种模式) ✅ 精确/全/搜索
词性标注 jieba.posseg
关键词提取 jieba.analyse (TF-IDF & TextRank)
字符串处理 split, join, find, startswith
编码转换 encode/decode, str.maketrans
格式化输出 f-string, str.format, % ✅ 三种
模式匹配 re (正则全功能) ✅ 元字符+高级
日期处理 datetime, calendar, time ✅ 含天干地支
拼音转换 pypinyin
语言检测 langdetect ✅ 6语种
数据生成 faker ✅ 中英文
统计检验 卡方检验 ✅ 手写实现

踩坑记录

# 原因 解决
1 pip installexternally-managed-environment Ubuntu 24.04 PEP 668 保护系统 Python --break-system-packages 或用 venv
2 PyPI 直接下载超时 华为云香港节点无法访问 files.pythonhosted.org 换阿里云镜像 -i https://mirrors.aliyun.com/pypi/simple/
3 jieba.analyse AttributeError analyse 是子模块,需显式 import jieba.analyse 脚本开头加 import jieba.analyse
4 datetime.utcfromtimestamp() DeprecationWarning Python 3.12 弃用 改用 datetime.fromtimestamp(ts, datetime.UTC)
5 findall 有捕获组时只返回组内容 正则引擎设计行为 (?:...) 非捕获组,或检查返回值
6 中文 \w 不匹配 \w = [a-zA-Z0-9_],不含中文 [\u4e00-\u9fff] 匹配中文,或用 re.U flag 在某些引擎中

归档信息

  • 实战服务器:华为云 FlexusX ecs-88e7-0001 (139.9.128.210)
  • Python 版本:3.12.3 | 操作系统:Ubuntu 24.04.4
  • 覆盖章节:28个实验 | 博客行数:约 800 行
  • 实战日期:2026-07-01

📦 本博客为「Python 从零开始」系列第三篇,前两篇:

更多推荐