欢迎光临
我们一直在努力

【NLP系列第一篇】从零玩转 NLP:分词、文本表示与 Word2Vec 实战

从零玩转 NLP:分词、文本表示与 Word2Vec 实战

一、开场:让计算机"看见"文字

先做一个思想实验。

你把一句中文"我爱自然语言处理"丢给计算机,它会"看到"什么?

答案是一串毫无意义的字符——我 爱 我 自 然 语 言 处 理 这种 Unicode 编码的二进制组合。计算机本身既不"懂"汉字的语义,也"不懂"词的边界。

要让计算机真正"读懂"一句话,我们必须走完一段旅程:

  • 先把句子"切碎"成最小语义单元(这一步叫分词,Tokenization);
  • 再把每个单元"翻译"成数字——因为计算机只会算数字,不会算文字(这一步叫文本张量表示)。
  • 这就是本文要讲的两件最基础也最关键的事:分词 与 文本张量表示。搞懂这两步,你就能读懂后续 80% 的 NLP 代码(Word2Vec、Embedding、RNN、Transformer 全部建立在这两步之上)。

    📌 本文脉络: 我们会先聊 NLP 是什么、它能干什么、它怎么从"规则系统"一路进化到"深度学习";然后聚焦"文本处理的完整 pipeline",重点实战 jieba 分词 和 One-Hot / Word2Vec / nn.Embedding 三种文本表示方法。代码全部可运行,注释拉满,建议边读边敲。


    二、NLP 是什么?能干什么?

    2.1 定义

    自然语言处理(Natural Language Processing, NLP)是人工智能领域的一个重要分支。所谓"自然语言",指人类日常使用的语言(中文、英文、法文……)。NLP 的目标,是让计算机"理解"或"使用"这些语言——说人话就是:让机器听得懂人话、说得出人话。

    2.2 五大常见任务

    NLP 的应用虽然千变万化,但拆开来看都离不开这五类核心任务:

    任务干什么典型应用
    文本分类 对整段文本打标签 / 归类 情感分析(正面 / 负面)、垃圾邮件识别、新闻主题分类
    序列标注 对句子中的每个词打标签 命名实体识别(人名 / 地名 / 手机号)、词性标注
    文本生成 根据已有内容生成新文本 自动写作、摘要生成、智能客服、对话系统
    信息抽取 从文本中抽出结构化信息 关系抽取、阅读理解(给定文本 + 问题 → 抽答案)
    文本转换 把一种文本变成另一种 机器翻译、文本改写、文本纠错

    💡 快速判定任务类型: 看输出。如果输出是"一个标签"(好 / 坏 / 体育 / 娱乐)→ 分类;如果输出是"一串等长的标签"(每个词一个标签)→ 序列标注;如果输出是"一段新的文字"→ 生成或转换。

    2.3 技术演进史:四个阶段

    理解 NLP 的历史,能帮你一眼看穿今天大火的 ChatGPT 为啥能这么"猛"——它是几十年的技术堆叠的产物。

    阶段时间核心做法典型代表局限
    规则系统 1950s ~ 1980s 语言学家手写语法规则 1954 年 Georgetown-IBM 机器翻译实验;1966 年 ELIZA 聊天机器人 通用性差,扩展性差
    统计方法 1990s 用概率建模代替规则 N-gram 语言模型、隐马尔可夫模型(HMM) 依赖手工设计特征,泛化弱
    机器学习 2000s SVM / LR / CRF + 特征工程 词袋模型 + LR 做文本分类 特征工程费时费力
    深度学习 2010s 至今 神经网络自动学表示 RNN / LSTM / GRU → Transformer → BERT / GPT 数据 / 算力需求大

    举个具体的例子感受下"统计方法的局限"。下面两条完全相反的评论:

    • 评论 A:“服务很好但味道差劲”
    • 评论 B:“味道很好但服务差劲”

    早期"词袋模型"(Bag-of-Words)只会统计词频,完全忽略词序。这两句话分词后:

    • A:[服务, 很, 好, 但, 味道, 差劲]
    • B:[味道, 很, 好, 但, 服务, 差劲]

    在词袋模型眼里,两条评论的向量完全一样。这显然不对,但当时只能靠"古人"手动加 n-gram(把相邻 N 个词作为整体)来救场——这正是后来深度学习要彻底解决的问题:让模型自己学出包含语序和语义的表示。


    三、文本处理的完整 Pipeline

    在正式讲分词和文本表示之前,先上一张全景图。所有 NLP 任务,从经典的情感分类到最火的 ChatGPT,文本处理都遵循下面这个流程:

    整个流程可以拆成 5 步:

  • 分词(Tokenization):把句子切成 token(最小语义单元);
  • 命名实体识别 NER / 词性标注 POS:给每个 token 打标签(可选,但多数工业级任务会做);
  • 文本张量表示:把 token 转成数字向量(本文重点);
  • 文本特征处理:归一化、长度填充、停用词过滤等;
  • 文本数据增强:在数据量小时扩充样本。
  • 下面重点讲 分词 和 文本张量表示 这两步。


    四、分词:把句子"切碎"

    分词(Tokenization)是把原始文本切分为若干具有独立语义的最小单元(token)的过程,是所有 NLP 任务的起点。没有这一步,后面所有的"向量化"都是空中楼阁。

    4.1 英文分词的三种粒度

    英文的分词有词级(Word-Level)、字符级(Character-Level)、子词级(Subword-Level)三种,特点如下:

    方式词表规模OOV 问题语义保留序列长度典型代表
    词级分词 很大(10w+) 严重 传统 NLP 工具(NLTK、spaCy)
    字符级分词 极小(~256) 几乎无 字符级 CNN / RNN
    子词级分词 适中(3w~5w) 极少 中等 BERT、GPT 等现代大模型

    📌 什么是 OOV? OOV(Out-Of-Vocabulary)指"词表里没有的词"。举个例子:词表里没有"栓 Q",那"栓 Q"就是 OOV。词级分词最容易遇到 OOV;子词级(BPE)会把"栓 Q"拆成"栓"和"Q"两个子词,从而几乎不会 OOV——这也是现代大模型几乎都用子词分词的核心理由。

    💡 想深入了解 BPE? 推荐 HuggingFace 官方课程:Byte-Pair Encoding tokenization。它一步步演示了 BPE 是怎么从字符开始、一步步合并出子词词表的。

    4.2 中文分词与 jieba

    中文没有天然的"空格"作为词边界,所以"分词"这个动作在中文里比英文更关键。中文分词的代表工具:

    • 词级分词:jieba、HanLP —— 传统 NLP 任务标配;
    • 子词级分词:HuggingFace Tokenizers、SentencePiece、OpenAI tiktoken —— 大模型时代主流。

    下面重点演示 jieba。它提供三种分词模式,适用场景各不相同:

    模式说明API
    精确模式 试图将句子最精确地切开,适合文本分析 jieba.cut(text, cut_all=False)
    全模式 把句子中所有可能成词的词都扫出来,速度快但有歧义 jieba.cut(text, cut_all=True)
    搜索引擎模式 在精确模式基础上对长词再次切分,提高召回率 jieba.cut_for_search(text)

    📌 精确模式(默认,最常用)

    import jieba

    text = '我爱学习,我爱敲代码'

    # 精确模式:cut_all=False(默认)
    words = jieba.cut(text, cut_all=False)
    print('/ '.join(words))

    输出:

    我/ 爱/ 学习/ ,/ 我/ 爱/ 敲/ 代码

    📌 全模式

    import jieba

    text = '我爱学习,我爱敲代码'

    # 全模式:cut_all=True
    words = jieba.cut(text, cut_all=True)
    print('/ '.join(words))

    输出(注意"我爱学习"这种较长词组也会被切出来):

    我/ 爱/ 学习/ 我爱/ 学习/ ,/ 我/ 爱/ 敲/ 代码

    📌 搜索引擎模式

    import jieba

    text = '我爱学习自然语言处理'

    # 搜索引擎模式:基于精确模式,对长词再次切分
    words = jieba.cut_for_search(text)
    print('/ '.join(words))

    输出(既保留"自然语言处理"这种长词,又拆出"自然""语言"这种短词):

    我/ 爱/ 学习/ 自然/ 语言/ 处理/ 自然语言/ 自然语言处理

    💡 选哪个?

    • 做情感分析、文本分类等"重语义"任务 → 精确模式(歧义少);
    • 自己做搜索索引、要尽可能多召回 → 搜索引擎模式;
    • 全模式在生产环境几乎不用(噪音太多)。

    4.3 自定义词典:解决分词歧义

    jieba 自带一个 30w+ 的通用词库,但对专业领域(医疗、法律、金融)和新兴网络用语(“栓 Q”“city 不 city”)是无能为力的。直接后果就是分词歧义。举经典例子:

    “下雨天留客天留我不留” 不同断句可以解释出完全相反的意思。jieba 纯靠通用词库分不准。

    解决办法:加载自定义词典。

    import jieba

    # 加载自定义词典(每行一个词:词语 词频 词性)
    # user_dict.txt 内容示例:
    # 栓 Q 5 n
    # 云计算 3 n
    jieba.load_userdict('user_dict.txt')

    text = '学 Python 离不开云计算'
    print('/ '.join(jieba.cut(text)))
    # 输出:学/ Python/ 离不开/ 云计算 ← 云计算被正确识别为一个词

    4.4 命名实体识别(NER)与词性标注(POS)

    分完词之后,下一步常常是给每个 token 打标签——这是后续做关系抽取、问答系统的基石。jieba 自带词性标注功能:

    from jieba import posseg

    # res 是一个生成器,遍历得到 (word, flag) 对
    res = posseg.cut("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")
    for item in res:
    print(item.word, item.flag, sep='\\t')

    # 输出:
    # 小明 nr # nr: 人名
    # 硕士 n # n: 普通名词
    # 毕业 n
    # 于 p # p: 介词
    # 中国科学院 nt # nt: 机构名
    # 计算所 n
    # , x # x: 标点
    # 后在 t
    # 日本京都大学 nt
    # 深造 v # v: 动词

    📷 jieba 词性对照表 完整标签集参见 jieba 官方词性对照表。生产环境做 NER 通常会用更专业的 HanLP 或 LAC。


    五、文本张量表示:让文字变成"算得动"的数字

    把一段文本用张量(Tensor)的形式表示,这个过程就叫文本张量表示。词被表示为词向量,一句话就成了一个词向量矩阵。

    为什么必须做这一步?因为计算机不懂字、也不懂词,它只懂张量上的加减乘除。

    常见的文本表示方法有三类,我们由浅入深一一展开。

    5.1 One-Hot:最朴素的离散表示

    One-Hot 编码是最基础的表示方法,思路简单到极致:

    • 词表有多大,向量就有多长;
    • 每个词对应一个向量,只有该词索引位置是 1,其余全是 0。

    举例:词表为 [周杰伦, 陈奕迅, 王力宏, 吴亦凡, 刘德华],“周杰伦” 的 One-Hot 向量就是 [1, 0, 0, 0, 0]。

    One-Hot 优点:

    • 简单直观,实现容易;
    • 配合 torch.nn.functional.one_hot 一行代码搞定。

    One-Hot 致命缺点:

    • 维度灾难:中文词表动辄几十万,向量长度也跟着几十万,模型根本跑不动;
    • 完全丢弃语义:任意两个不同词的 One-Hot 向量都是正交的(点积 = 0),无法表达"猫"和"狗"都是动物、"好"和"棒"是近义词。

    ⚠️ 重要提示 One-Hot 在生产中几乎不用,但它是所有 Embedding 方法的"地基"——理解 One-Hot 的"维度灾难"和"语义缺失"问题,才能体会 Word2Vec 为啥是 NLP 史上里程碑式的工作。

    手写 One-Hot

    """
    演示 One-Hot 编码的手写实现
    理解原理即可:先把词映射成 id,再把 id 映射成"独热向量"
    """

    # 1. 构建词表
    words = ['周杰伦', '陈奕迅', '王力宏', '吴亦凡', '刘德华']
    token_id = {'周杰伦': 0, '陈奕迅': 1, '王力宏': 2, '吴亦凡': 3, '刘德华': 4}

    def one_hot(word: str) > list:
    """根据词表生成该词的 One-Hot 向量"""
    if word not in token_id:
    raise ValueError(f'词表里没有 "{word}"')
    vector = [0] * len(token_id) # 初始化全 0 向量
    vector[token_id[word]] = 1 # 把自己对应的位置置 1
    return vector

    # 测试
    print(one_hot('周杰伦')) # [1, 0, 0, 0, 0]
    print(one_hot('陈奕迅')) # [0, 1, 0, 0, 0]

    PyTorch 官方 One-Hot

    import torch
    import torch.nn.functional as F

    # 假设有 3 个 token,词表大小 5
    token_ids = torch.tensor([0, 3, 2]) # shape: (3,)
    vocab_size = 5

    # F.one_hot 会把 id 转成 0/1 向量
    one_hot_vectors = F.one_hot(token_ids, num_classes=vocab_size)
    # shape: (3, 5)
    # 结果:
    # [[1, 0, 0, 0, 0],
    # [0, 0, 0, 1, 0],
    # [0, 0, 1, 0, 0]]
    print(one_hot_vectors)
    print(one_hot_vectors.shape) # torch.Size([3, 5])

    # 注意:F.one_hot 返回的是 int64 类型
    # 如果要喂给深度学习模型(float 输入),记得 .float()
    one_hot_vectors = one_hot_vectors.float()

    5.2 Word2Vec:让向量"有语义"

    Word2Vec 是 Google 在 2013 年提出的词嵌入模型,核心思想只有一句话:

    通过一个浅层神经网络在大规模语料上训练,把每个词映射成低维稠密向量,使得语义相近的词在向量空间里距离更近。

    它直接解决了 One-Hot 的两个核心痛点:维度从"几十万"压到"几百",并且"语义"被编码进了向量的方向里。

    Word2Vec 包含两种训练模式:

    • CBOW(Continuous Bag-of-Words):用上下文预测中心词。训练快,适合小语料;

    • Skip-gram:用中心词预测上下文。对低频词更友好,适合大语料。

    Word2Vec 最神奇的地方是支持语义代数:

    • vec(国王) – vec(男性) + vec(女性) ≈ vec(女王)
    • vec(巴黎) – vec(法国) + vec(日本) ≈ vec(东京)

    这就是"词的语义被编码到向量空间里"的实锤。

    方式一:用 FastText 训练

    FastText 是 Facebook 出的库,自带 Word2Vec 的 CBOW 和 Skip-gram 实现,安装简单,开箱即用。

    📌 安装

    # 推荐 uv 安装预编译 wheel
    uv add fasttext-wheel
    # 或 pip
    pip install fasttext-wheel

    ⚠️ 版本坑 FastText 在 Python 3.14 上会有兼容性问题,Python 3.8 ~ 3.12 是最稳的。如果用 3.14 报 distutils 相关错误,建议降到 3.12 或换用 Gensim。

    📌 准备语料

    # corpus.txt:每行一句,词语已经用空格分开
    我 爱 学习 自然 语言 处理
    今天 天气 真 好
    机器 学习 是 人工智能 的 重要 分支
    深度 学习 推动 了 自然语言处理 的 进步

    📌 训练 Skip-gram 模型

    import fasttext

    # 训练无监督 Word2Vec
    model = fasttext.train_unsupervised(
    input='corpus.txt',
    model='skipgram', # 也可填 'cbow'
    dim=100, # 词向量维度,越大表示能力越强但越慢
    ws=5, # 上下文窗口大小
    minCount=2, # 词频低于此值的词会被过滤
    epoch=5 # 训练轮数
    )

    # 保存模型
    model.save_model("word2vec.bin")

    📌 加载 & 最近邻查询

    import fasttext

    # 加载训练好的模型
    model = fasttext.load_model("word2vec.bin")

    # 1. 取单个词的词向量
    vec = model.get_word_vector("自然语言")
    print(vec.shape) # (100,)

    # 2. 找最相近的 5 个词
    neighbors = model.get_nearest_neighbors("人工智能", k=5)
    for similarity, word in neighbors:
    print(f"{word}: {similarity:.4f}")

    # 输出示例:
    # 机器学习: 0.8765
    # 深度学习: 0.8234
    # 大数据: 0.7891

    💡 FastText 关键参数速查

    参数含义默认值
    model 训练模式 cbow 或 skipgram skipgram
    dim 词向量维度 100
    ws 上下文窗口 5
    minCount 最低词频 5
    epoch 训练轮数 5
    lr 学习率 0.05
    thread 训练线程数 CPU 核心数
    方式二:用 Gensim 加载 / 训练

    Gensim 是 NLP 圈最常用的主题模型 / 词向量库,提供了加载预训练词向量和自行训练两种姿势。

    📌 加载第三方预训练词向量

    from gensim.models import KeyedVectors

    # 加载腾讯 / 微博 / Google 公开词向量(这里以微博 300 维为例)
    # 文件很大(几 GB),首次下载需耐心
    model = KeyedVectors.load_word2vec_format('sgns.weibo.word.bz2')

    print(model.vector_size) # 300
    print(model['地铁'][:10]) # 取"地铁"词向量的前 10 维

    📷 推荐词向量资源

    • Tencent AI Lab Embedding Corpus:覆盖中英 800w+ 词;
    • Chinese-Word-Vectors:北大开源,多种训练语料;
    • Google News Word2Vec:英文 300 维,300w 词。

    📌 余弦相似度与语义代数

    # 1. 计算两个词的余弦相似度
    sim = model.similarity('地铁', '公交')
    print(f"地铁 vs 公交 相似度: {sim:.4f}")

    # 2. 找最相似的词
    print(model.similar_by_word('男孩', topn=3))

    # 3. 经典语义代数:爸爸 – 男性 + 女性 ≈ 妈妈
    result = model.most_similar(
    positive=['爸爸', '女性'],
    negative=['男性'],
    topn=3
    )
    print(result)

    📌 余弦相似度公式

    similarity

    (

    w

    1

    ,

    w

    2

    )

    =

    cos

    (

    θ

    )

    =

    w

    1

    w

    2

    w

    1

    w

    2

    \\text{similarity}(w_1, w_2) = \\cos(\\theta) = \\frac{w_1 \\cdot w_2}{|w_1| \\cdot |w_2|}

    similarity(w1,w2)=cos(θ)=w1w2w1w2 取值范围 [-1, 1]:接近 1 → 语义相近,接近 0 → 不相关,接近 -1 → 方向相反。

    📌 用 Gensim 自行训练

    from gensim.models import Word2Vec

    # 语料:每个元素是一个已分词的句子(list of tokens)
    sentences = [
    ['我', '每天', '乘坐', '地铁', '上班'],
    ['我', '每天', '乘坐', '公交', '上班'],
    ['地铁', '比', '公交', '快', '很多'],
    ['北京', '的', '地铁', '非常', '拥挤'],
    ]

    # 训练 Skip-gram(sg=1);sg=0 表示 CBOW
    model = Word2Vec(
    sentences,
    vector_size=100, # 词向量维度
    window=5, # 上下文窗口
    min_count=1, # 最低词频(语料少时设小一点)
    sg=1, # 1: Skip-Gram, 0: CBOW
    workers=4 # 并行线程数
    )

    # 找最相似的词
    print(model.wv.most_similar('地铁', topn=3))

    # 保存词向量
    model.wv.save_word2vec_format('my_vectors.vec')

    5.3 Word Embedding:深度学习里的"可训练词向量"

    Word Embedding 是个比 Word2Vec 更宽泛的概念:把离散的 token 映射为低维稠密向量的过程都叫词嵌入。

    在现代深度学习中,它通常特指神经网络里的 Embedding 层——一个可训练的参数矩阵。这是和 Word2Vec 最大的区别:

    Word2Vecnn.Embedding
    训练方式 先在语料上预训练,词向量冻住用 跟着下游任务端到端训练
    适用场景 词向量当特征 / 检索 配合 LSTM / Transformer 微调
    典型 API gensim.Word2Vec torch.nn.Embedding
    PyTorch nn.Embedding 实战

    import torch
    import torch.nn as nn

    # 1. 创建 Embedding 层
    # 词表 1000 词,每个词映射成 128 维向量
    embedding = nn.Embedding(num_embeddings=1000, embedding_dim=128)

    # 2. 喂入 token id
    # shape: (batch=2, seq_len=4)
    token_ids = torch.tensor([
    [10, 200, 5, 333],
    [88, 2, 17, 999]
    ])

    vectors = embedding(token_ids)
    # 输出 shape: (2, 4, 128)
    # 对应 batch=2 条句子,每条 4 个 token,每个 token 128 维向量
    print(vectors.shape) # torch.Size([2, 4, 128])

    # 3. 查看可训练参数
    print(embedding.weight.shape) # torch.Size([1000, 128])

    ⚠️ 踩坑提示

    • 输入必须是 LongTensor(int64),不是 FloatTensor,否则会报 RuntimeError: expected scalar type Long;
    • nn.Embedding 内部就是一个 (num_embeddings, embedding_dim) 的可学习矩阵,初始值随机;
    • 如果要加载预训练词向量(Word2Vec / GloVe),用 nn.Embedding.from_pretrained(…),并指定 freeze=False 决定是否微调。

    import torch
    import torch.nn as nn
    import numpy as np

    # 假设已经从 gensim 拿到词向量矩阵 (vocab_size, dim)
    pretrained_weights = np.random.randn(1000, 128).astype(np.float32) # 替换成真实词向量

    embedding_from_pt = nn.Embedding.from_pretrained(
    torch.from_numpy(pretrained_weights),
    freeze=False # True: 不微调;False: 跟着任务继续训练
    )


    六、避坑指南 & 最佳实践

    ⚠️ 5 个最常见的"翻车"现场

  • One-Hot 维度爆炸:词表 50w,向量就是 50w 维,模型直接 OOM。生产环境几乎不用 One-Hot,永远优先用 Embedding / Word2Vec。
  • OOV 直接崩:用 nn.Embedding 时如果遇到 id ≥ num_embeddings,会报 IndexError。一定要预留 <UNK> 词,把 OOV 都映射到 <UNK> 这个特殊 id。
  • 中文分词歧义:默认 jieba 在垂直领域(医疗 / 法律)分不准。必须加载自定义词典,并用 jieba.add_word() 给关键术语兜底。
  • FastText / Word2Vec 的 Python 版本坑:3.14 容易报 ModuleNotFoundError: distutils 或 numpy ABI 错误。优先用 3.10 ~ 3.12。
  • 预训练词向量维度不匹配:自己训的 dim=100,想加载腾讯 300 维,必须用线性层 nn.Linear(300, 100) 重新映射维度,不能直接用。
  • 💡 工业级最佳实践

    • 小数据 + 简单任务 → jieba + 预训练 Word2Vec + LR / SVM,传统但稳定;
    • 中等数据 + 文本分类 → jieba + 自训 Embedding + LSTM / BiLSTM;
    • 大数据 + 通用任务 → HuggingFace Tokenizers + BERT / RoBERTa 微调,永远不会错;
    • 超大规模 + 生成任务 → 直接调用 ChatGPT / Qwen API,自己训 LLM 性价比极低。

    七、总结:一张表打通"选型"

    维度One-HotWord2Vec (FastText / Gensim)nn.Embedding
    出现时间 1980s ~ 2013 (Google) 深度学习时代
    向量类型 高维稀疏(0/1) 低维稠密(float) 低维稠密(可训练)
    词表规模 极小(<1w) 适中(10w 级) 任意
    语义信息 ❌ 无 ✅ 强 ✅ 强(依赖数据量)
    OOV 处理 ❌ 不支持 ⚠️ FastText 子词兜底 ⚠️ 需 <UNK> 兜底
    训练成本 0 中(CPU 几十分钟) 跟着下游任务
    适用场景 教学 / 入门 demo 传统 NLP 任务 / 文本检索 配合 LSTM / Transformer

    📌 下一步学什么? 有了"分词 + 文本表示"这块地基,就可以继续往 NLP 深处走了:

  • RNN / LSTM / GRU:处理变长序列,理解"上下文";
  • 注意力机制 (Attention):解决 RNN 长距离依赖的痛点;
  • Transformer / BERT / GPT:今天大模型的"基石架构",必读;
  • HuggingFace 实战:把上面的理论全部用代码串起来。
  • 笔者后续会按顺序更新这些笔记,欢迎关注~


    参考资料

    • jieba 中文分词
    • FastText 官方文档
    • Gensim Word2Vec 官方文档
    • HuggingFace NLP Course – BPE 分词
    • PyTorch nn.Embedding 官方文档
    • Tencent AI Lab Embedding Corpus
    • Chinese-Word-Vectors(北大开源词向量)
    • HuggingFace Tokenizers
    • SentencePiece
    • OpenAI tiktoken
    赞(0)
    未经允许不得转载:171主机测评 » 【NLP系列第一篇】从零玩转 NLP:分词、文本表示与 Word2Vec 实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址