
简介本资源是一份面向计算机专业本科生的毕业设计文档聚焦Python文本相似度计算系统的设计与实现适用于自然语言处理入门学习、课程设计及毕设参考。文档完整覆盖系统需求分析、技术选型PythonDjangoMySQL、核心模块实现文本清洗、NLTK/spaCy分词、TF-IDF关键词提取、余弦相似度算法原理与代码逻辑、可视化界面说明及实验评估结果内容兼具理论深度与工程落地性。资源为单个749KB的Word文档.docx结构清晰含中英文摘要、目录、六章正文含绪论、可行性分析、系统设计、实现细节、测试结果与展望及规范参考文献可直接用于开题汇报、论文撰写与答辩材料整理。目前已有138人学习下载读者可快速掌握NLP基础任务的全流程实现思路并复用其中的预处理策略、向量构建方法与相似度计算模板。1. 为什么一个“只算两段话像不像”的系统要搭 Django MySQL 词向量表三件套这不是小题大做而是我带过三个文本类毕设后踩出的血泪经验学生交的“能跑通余弦相似度.py”答辩时被问一句“如果用户明天要查1000篇新闻和他输入的标题匹配度你这单文件脚本怎么接”当场哑火。真正能落地、能答辩、能写进“系统设计”章节的文本相似度方案必须回答三个硬问题第一文本来了怎么存——不能每次运行都重分词、重算TF-IDF第二算法选了不止一种余弦/Jaccard/编辑距离怎么让用户点一下就切而不是改代码再重启第三结果不是打印在控制台而是要让老师在浏览器里输两段话、点按钮、看进度条、见柱状图、导出Excel。所以这份基于 Python 的文本相似度计算系统本质是一个「可存、可切、可展」的最小可行 Web 系统用 Django 撑起用户入口和路由调度用 MySQL 固化预处理结果与历史记录用 jiebasklearn 构建可复用的向量化流水线——它不追求 SOTA 模型但保证从“同学A交作业”到“导师B部署试用”之间没有断层。适合计算机/软件工程专业做毕设或课设尤其当你被要求写“数据库设计”“模块划分”“测试用例”时这套结构能直接填满第四章、第五章、第六章的全部骨架。2. 总体架构设计B/S 三层结构与四个核心模块的职责边界这个系统不是“Python 脚本 Flask 临时凑个网页”而是按工业级 Web 系统逻辑拆解的四层责任链用户触达层 → 业务调度层 → 算法执行层 → 数据持久层。每一层只干一件事改算法不影响前端换数据库不碰分词逻辑。下面这张表不是画给老师看的示意图而是你写“4.1 系统模块总体设计”时可以直接抄的职责说明书模块名称所属层级核心职责交付物示例答辩可展示Web 交互模块用户触达层渲染 HTML 页面、校验输入长度、发起 Ajax 请求、接收 JSON 响应、绘制 ECharts 柱状图templates/similarity_input.html中的双文本框下拉选择器任务调度模块业务调度层解析 HTTP 请求参数、判断使用哪种相似度算法、调用对应算法类、组装返回字典views.py中calculate_similarity()视图函数算法引擎模块算法执行层封装预处理、向量化、相似度计算三步逻辑每个算法余弦/Jaccard/编辑距离独立成类algorithms/cosine_calculator.py中的CosineCalculator类数据服务模块数据持久层管理 MySQL 表结构、缓存向量化结果、记录用户查询日志、支持按日期导出历史记录models.py中SimilarityRecord模型定义及迁移文件提示很多同学把“算法模块”写成一坨if-elif-else堆在视图里这是答辩翻车高发区。正确做法是——算法必须封装为独立类且每个类只依赖numpy和sklearn不 importdjango.db或requests。这样未来换成 FastAPI 或迁移到微服务只需替换调度层算法层代码一行不用动。2.1 Web 交互模块用 Bootstrap Django Forms 实现零 JS 编码的表单别被“可视化界面”吓住。本系统所有前端交互靠 Django 自带的 Form 机制 Bootstrap CSS 就能闭环无需手写一行 JavaScript。关键在于理解 Django Forms 如何把“用户输入”变成“可控对象”# forms.py from django import forms class TextCompareForm(forms.Form): text_a forms.CharField( label文本A, widgetforms.Textarea(attrs{ class: form-control, rows: 4, placeholder: 请输入第一段待比较文本支持中文 }) ) text_b forms.CharField( label文本B, widgetforms.Textarea(attrs{ class: form-control, rows: 4, placeholder: 请输入第二段待比较文本 }) ) algorithm forms.ChoiceField( label选择算法, choices[ (cosine, 余弦相似度), (jaccard, Jaccard 相似度), (edit, 编辑距离归一化) ], widgetforms.Select(attrs{class: form-control}) )这段代码生成的 HTML自带 Bootstrap 样式、响应式栅格、必填校验Django 默认校验空值、CSRF 保护{% csrf_token %}。你只需要在模板里写!-- templates/similarity_input.html -- form methodpost {% csrf_token %} {{ form.as_p }} !-- 自动渲染所有字段为 p 包裹的 labelinput -- button typesubmit classbtn btn-primary开始计算/button /form设计说明as_p是最省事的渲染方式它把每个字段转成plabel...input.../pBootstrap 的.form-control类会自动适配宽度和圆角placeholder不是装饰而是降低用户输入门槛的关键——毕设答辩时老师随手输入“人工智能发展很快”和“AI技术进步迅猛”你得确保系统能接住这种非标准输入algorithm字段用ChoiceField而非CharField是为了后续在视图中直接form.cleaned_data[algorithm]拿到字符串cosine避免手动解析下拉框 value。2.2 任务调度模块视图函数如何成为算法与 Web 的“翻译官”Django 视图不是“写业务逻辑的地方”而是“协议转换器”它把 HTTP 请求GET/POST 参数翻译成算法能懂的 Python 对象再把算法返回的数字结果翻译成 JSON 或渲染模板。以下是views.py中最核心的调度函数也是你写“5.1.2 文本相似度计算模块”时的范本# views.py from django.shortcuts import render from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .forms import TextCompareForm from .algorithms.cosine_calculator import CosineCalculator from .algorithms.jaccard_calculator import JaccardCalculator from .algorithms.edit_distance_calculator import EditDistanceCalculator from .models import SimilarityRecord def calculate_similarity(request): if request.method POST: form TextCompareForm(request.POST) if form.is_valid(): # 1. 提取清洗后的原始文本 text_a form.cleaned_data[text_a].strip() text_b form.cleaned_data[text_b].strip() algo_name form.cleaned_data[algorithm] # 2. 根据算法名实例化对应计算器 calculator_map { cosine: CosineCalculator(), jaccard: JaccardCalculator(), edit: EditDistanceCalculator() } calculator calculator_map[algo_name] # 3. 执行计算内部已包含预处理 try: similarity_score calculator.calculate(text_a, text_b) except Exception as e: return render(request, error.html, {message: f计算失败{str(e)}}) # 4. 保存记录到 MySQL可选但毕设加分项 SimilarityRecord.objects.create( text_atext_a[:200], # 防止超长文本撑爆字段 text_btext_b[:200], algorithmalgo_name, scoreround(similarity_score, 4), created_attimezone.now() ) # 5. 返回渲染结果非 AJAX 场景 return render(request, result.html, { text_a: text_a, text_b: text_b, algorithm: dict(form.fields[algorithm].choices)[algo_name], score: similarity_score, interpretation: get_score_interpretation(similarity_score) }) else: form TextCompareForm() return render(request, similarity_input.html, {form: form})参数说明与设计逻辑form.is_valid()是安全底线它自动校验文本非空、长度不超过max_length你可在 Form 中设置比手写if not text_a:可靠十倍calculator_map字典是解耦关键新增算法比如明年加 BM25只需写新类、加字典项不碰调度逻辑SimilarityRecord.objects.create()这行代码就是你写“数据库设计”章节的实物证据——它证明你真用了 MySQL不是口头说说get_score_interpretation()是答辩亮点函数见后文把 0.82 这种数字翻译成“高度相似”让老师一眼看懂结果价值。2.3 算法引擎模块为什么预处理必须和相似度计算捆在一起很多毕设代码把“分词”“去停用词”“向量化”写在视图里导致同一个文本每次请求都重复计算。而本系统强制规定每个算法类的calculate()方法必须内部完成“原始文本→清洗→分词→向量化→相似度计算”全链路。以CosineCalculator为例# algorithms/cosine_calculator.py import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity class CosineCalculator: def __init__(self): # 初始化停用词表实际项目应从文件读取 self.stopwords {的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个} def _preprocess(self, text): 内部预处理清洗 分词 去停用词 # 清洗只保留中文、英文字母、数字、空格 cleaned .join([c for c in text if c.isalnum() or c.isspace() or \u4e00 c \u9fff]) # 分词 words jieba.lcut(cleaned) # 去停用词 过滤单字提升效果 filtered [w for w in words if w not in self.stopwords and len(w) 1] return .join(filtered) def calculate(self, text_a, text_b): 主计算方法端到端输出相似度 # 1. 预处理两段文本 processed_a self._preprocess(text_a) processed_b self._preprocess(text_b) # 2. TF-IDF 向量化注意必须用同一 vectorizer 对两文本向量化 vectorizer TfidfVectorizer() tfidf_matrix vectorizer.fit_transform([processed_a, processed_b]) # 3. 计算余弦相似度 similarity_matrix cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2]) return float(similarity_matrix[0][0])为什么这么设计_preprocess()私有方法封装所有脏活calculate()方法签名干净只接受原始文本返回浮点数方便单元测试TfidfVectorizer在calculate()内部创建而非__init__中——因为毕设场景文本量小无需全局缓存若未来扩展为百万文档库则需升级为vectorizer单例 pickle序列化float(similarity_matrix[0][0])强制转为 Python 原生 float避免numpy.float64导致 JSON 序列化报错这是答辩现场调试 30 分钟才发现的玄学坑。2.4 数据服务模块MySQL 表结构如何支撑“可追溯、可统计”的毕设需求毕设论文里写“采用 MySQL 数据库存储”不能只停留在这句话。你必须给出真实可用的表结构且该结构要支撑两类刚需一是记录每一次计算行为供老师抽查验证二是支持按月导出使用报告体现系统价值。models.py定义如下# models.py from django.db import models from django.utils import timezone class SimilarityRecord(models.Model): 文本相似度计算历史记录表 text_a models.CharField(max_length500, verbose_name文本A前200字) text_b models.CharField(max_length500, verbose_name文本B前200字) algorithm models.CharField( max_length20, choices[ (cosine, 余弦相似度), (jaccard, Jaccard 相似度), (edit, 编辑距离) ], verbose_name使用算法 ) score models.DecimalField( max_digits5, decimal_places4, verbose_name相似度得分 ) created_at models.DateTimeField( defaulttimezone.now, verbose_name创建时间 ) class Meta: db_table similarity_record verbose_name 相似度记录 verbose_name_plural 相似度记录 ordering [-created_at] # 默认按时间倒序 def __str__(self): return f{self.text_a[:10]}... vs {self.text_b[:10]}... {self.score}生成迁移文件并同步数据库的命令写进你的“部署说明.md”python manage.py makemigrations python manage.py migrate这张表解决的实际问题text_a/text_b字段限制 500 字是因为 MySQLVARCHAR(500)足够存摘要又避免TEXT类型影响查询性能score用DecimalField而非FloatField确保 0.9250 这种值不变成 0.9249999999999999答辩时老师放大截图看小数位这是细节可信度ordering [-created_at]让 Django Admin 后台默认显示最新记录你演示时不用手动排序。3. 关键实现细节从分词策略到向量降维的 5 个落地决策点教科书讲“jieba 分词”但没告诉你中文分词不是开箱即用而是要根据你的文本类型调参。比如你用新闻语料测试jieba.cut(苹果公司发布了新款iPhone)默认切成[苹果, 公司, 发布, 了, 新款, iPhone]但“苹果公司”作为实体被拆开相似度计算必然失真。本系统在实现中做了 5 个关键决策每一条都对应毕设报告里的“设计依据”章节。3.1 分词引擎选型jieba 精确模式 自定义词典的必要性jieba有三种模式精确模式默认、全模式、搜索引擎模式。毕设必须用精确模式 自定义词典原因如下场景精确模式无词典精确模式加词典为什么选后者输入“深度学习模型训练很耗时”[深度, 学习, 模型, 训练, 很, 耗时][深度学习, 模型, 训练, 很, 耗时]“深度学习”是固定术语拆开后 TF-IDF 权重分散输入“COVID-19 疫情”[CO, VID, -, 19, 疫情][COVID-19, 疫情]英文缩写必须整体识别否则向量空间错乱落地代码algorithms/base_calculator.pyimport jieba # 加载自定义词典实际项目放于 data/dict.txt每行一个词 custom_words [ 深度学习, 机器学习, 自然语言处理, ChatGPT, Transformer, COVID-19, SARS-CoV-2, 人工智能, 大数据, 云计算 ] for word in custom_words: jieba.add_word(word) class BaseCalculator: def _cut_with_jieba(self, text): return jieba.lcut(text) # lcut 返回列表比 cut() 更稳定注意jieba.add_word()必须在项目启动时执行一次如放于apps.py的ready()方法不能在每次calculate()中重复调用否则性能暴跌。3.2 停用词表构建为什么不能直接用 NLTK 的英文停用词中文停用词表必须定制。NLTK 的stopwords.words(chinese)只有 100 多个词且含大量古汉语词如“之乎者也”对现代新闻/论文文本无效。本系统采用三级停用词过滤基础停用词189 个来自哈工大停用词表精简版覆盖“的、了、在、是、我、有、和、就、不、人”等高频虚词标点符号正则re.sub(r[^\w\s\u4e00-\u9fff], , text)全局清理业务停用词动态添加如你处理的是医疗文本可额外加入“患者”“医生”“治疗”等泛化词避免它们主导向量。停用词过滤代码接续CosineCalculator._preprocessdef _remove_stopwords(self, words): # 基础停用词硬编码确保离线可用 base_stops {的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这} # 业务停用词按需扩展 biz_stops {详见, 如下, 因此, 综上所述, 总而言之} # 新闻/论文常见冗余短语 all_stops base_stops | biz_stops return [w for w in words if w not in all_stops and len(w) 1]3.3 向量化方案TF-IDF 为何比 Word2Vec 更适合毕设场景搜索“文本相似度算法”你会看到 Word2Vec、BERT、Sentence-BERT 等炫酷名词。但毕设必须选TF-IDF 余弦相似度理由铁硬维度TF-IDF 余弦Word2Vec / BERT毕设适配性环境依赖scikit-learn单包pip install5 秒搞定Word2Vec 需gensim 预训练模型300MBBERT 需transformers GPU你答辩用的笔记本可能没 GPU也没外网下模型可解释性每个维度对应一个词vectorizer.get_feature_names_out()可打印全部词汇向量是黑匣子无法告诉老师“为什么‘人工智能’和‘AI’相似度高”答辩时老师问“这个向量怎么来的”你得指着屏幕说清楚计算速度1000 篇新闻向量化 2 秒CPUWord2Vec 训练需分钟级BERT 单句编码 1 秒/句毕设演示要求“秒出结果”不能让老师等进度条TF-IDF 参数调优实战CosineCalculator.calculate内部vectorizer TfidfVectorizer( max_features5000, # 限制最多 5000 个特征词防内存爆炸 ngram_range(1, 2), # 启用 1-gram 和 2-gram捕获“深度学习”这种双词 min_df2, # 词频2 的词直接丢弃过滤拼写错误/噪声 max_df0.95 # 出现在 95% 文档的词丢弃如“的”“了” )3.4 相似度算法对比为什么编辑距离要“归一化”才能和余弦值横向比较系统提供三种算法但它们的数值范围天差地别余弦相似度[-1.0, 1.0]通常[0.0, 1.0]Jaccard 相似度[0.0, 1.0]编辑距离原始值是整数如 5表示最少修改次数必须归一化为[0.0, 1.0]才能放入同一张结果页。归一化公式与代码edit_distance_calculator.pyimport Levenshtein class EditDistanceCalculator: def calculate(self, text_a, text_b): # 原始编辑距离 distance Levenshtein.distance(text_a, text_b) # 归一化1 - distance / max_len确保值域 [0,1] max_len max(len(text_a), len(text_b)) if max_len 0: return 1.0 # 两空串完全相同 normalized 1.0 - (distance / max_len) return max(0.0, min(1.0, normalized)) # 强制截断到 [0,1]设计说明Levenshtein.distance比difflib.SequenceMatcher更准且python-Levenshtein包是 C 实现速度比纯 Python 快 10 倍。安装命令pip install python-Levenshtein。3.5 结果可视化用 ECharts 画柱状图但只传 JSON 不传 HTML很多同学把 ECharts 代码全写在 HTML 里导致图表逻辑和业务逻辑混杂。正确做法是后端只返回 JSON 数据前端用原生 JS 渲染。这样你写“系统测试”章节时可以单独测views.py的 JSON 输出不依赖前端。后端返回 JSON修改calculate_similarity视图# 替换原来的 render(...) 为 return JsonResponse({ success: True, data: { text_a: text_a[:50] ... if len(text_a) 50 else text_a, text_b: text_b[:50] ... if len(text_b) 50 else text_b, algorithm: dict(form.fields[algorithm].choices)[algo_name], score: round(similarity_score, 4), interpretation: get_score_interpretation(similarity_score) } })前端渲染代码templates/result.htmldiv idchart stylewidth: 600px; height: 400px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script script fetch(/calculate/, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({/* 表单数据 */}) }) .then(r r.json()) .then(data { const chartDom document.getElementById(chart); const myChart echarts.init(chartDom); const option { title: { text: 相似度分析结果${data.data.algorithm} }, tooltip: {}, xAxis: { data: [相似度] }, yAxis: {}, series: [{ name: 得分, type: bar, data: [data.data.score], itemStyle: { color: data.data.score 0.7 ? #52c418 : data.data.score 0.4 ? #faad14 : #f5222d } }] }; myChart.setOption(option); }); /script为什么这么做fetchAPI 是现代浏览器标配无需 jQuery减少依赖itemStyle.color根据分数变色0.7 绿0.4~0.7 黄0.4 红让老师一眼看出质量图表宽高写死600x400避免响应式导致答辩时布局错乱。4. 常见问题排查毕设开发中 5 个最高频翻车点与解法别等答辩前一天才 debug。这 5 个问题是我帮 A 同学、B 同学、C 同学救火时总结的“必现坑”按出现频率排序每一条都附真实报错和修复命令。4.1 现象页面提交后报MultiValueDictKeyError at /calculate/原因Django 表单字段名和视图中request.POST.get()的 key 不一致。例如表单定义text_a但视图写了request.POST.get(text1)。解决检查forms.py中字段名如text_a检查views.py中form.cleaned_data[text_a]的 key 是否完全一致大小写、下划线终极验证在视图开头加print(request.POST)看实际提交的 key 是什么。4.2 现象计算结果全是0.0或nan原因TF-IDF 向量化时两段文本预处理后变成空字符串如全被停用词过滤掉TfidfVectorizer对空输入返回全零向量余弦相似度为0/0 nan。解决在_preprocess()后加防御性检查processed_a self._preprocess(text_a) processed_b self._preprocess(text_b) if not processed_a or not processed_b: raise ValueError(预处理后文本为空请检查输入内容及停用词表)或在calculate()开头加兜底if processed_a processed_b : return 1.0 # 两空串视为完全相同4.3 现象MySQL 报错1071 Specified key was too long原因Django 默认为CharField创建VARCHAR(255)索引但 MySQL utf8mb4 字符集下255 字符 * 4 字节 1020 字节 InnoDB 索引最大 767 字节。解决修改models.py中字段定义显式指定db_indexFalse不需要索引的字段text_a models.CharField(max_length500, db_indexFalse) text_b models.CharField(max_length500, db_indexFalse)或在数据库层面调大innodb_large_prefix不推荐毕设环境难操作。4.4 现象中文在网页显示为?????或乱码原因MySQL 数据库、数据表、连接三者字符集不统一。Django 默认用utf8但 MySQL 8.0 推荐utf8mb4。解决创建数据库时指定字符集CREATE DATABASE text_similarity DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Djangosettings.py中配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: text_similarity, OPTIONS: { charset: utf8mb4, }, } }执行python manage.py dbshell进入 MySQL运行ALTER DATABASE text_similarity CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.5 现象pip install jieba成功但运行时报ModuleNotFoundError: No module named jieba原因PyCharm/VSCODE 使用了错误的 Python 解释器如系统 Python 而非项目虚拟环境。解决终端进入项目根目录确认激活虚拟环境source venv/bin/activate # Linux/Mac # 或 venv\Scripts\activate # Windows运行which pythonLinux/Mac或where pythonWindows确认路径含venv在 IDE 设置中将 Python Interpreter 指向venv/bin/pythonLinux/Mac或venv\Scripts\python.exeWindows。5. 答辩验收指南从“能跑”到“能讲”的 4 个必做动作答辩不是代码审查而是价值传递。老师不会逐行看你jieba.lcut()写得对不对但会盯着你演示时的每一个操作判断“这学生真懂设计还是只会 CtrlC/V”。以下 4 个动作是我带过的毕设中100% 被老师追问、且 90% 学生答不全的核心验收点。做完它们你的答辩通过率从 70% 拉到 95%。5.1 动作一准备三组“有故事”的测试用例覆盖算法差异别用“今天天气很好”和“明天阳光明媚”这种弱对比。准备三组能讲出技术逻辑的输入每组配一句解释测试组文本A文本B预期现象与解释答辩话术同义词组“人工智能正在改变世界”“AI技术正在重塑全球格局”余弦相似度 0.6Jaccard 较低因“人工智能”vs“AI”被当不同词“这里体现了 TF-IDF 对同义词的捕捉能力而 Jaccard 严格匹配字面说明我们提供多算法是必要的”长短文本“深度学习”“深度学习是机器学习的一个分支它通过多层神经网络模拟人脑处理信息的方式……200字”余弦相似度 ≈ 0.85因共享关键词编辑距离归一化值 ≈ 0.1因长度悬殊“编辑距离擅长短文本TF-IDF 更适应长短不一的文档这正是我们设计多算法切换的意义”对抗样本“苹果手机很好用”“苹果是一种水果富含维生素C”所有算法得分 0.3尤其余弦相似度接近 0因“苹果”在两文中语义完全不同TF-IDF 无法消歧“这暴露了传统方法的局限性也引出了未来可结合 BERT 等上下文感知模型的方向——这正是我在‘展望’章节提到的”实操建议把这三组数据存在test_cases.txt文件里答辩时直接复制粘贴避免手打出错。老师让你现场演示你就说“我准备了典型场景现在为您展示第三组对抗样本……”5.2 动作二导出一份“本月使用报告”证明系统不是玩具毕设系统最大的信任危机是“这东西真有人用吗”。解决方案用 Django Admin 或写个简易脚本导出一张 Excel 表标题就叫《2024年X月文本相似度系统使用报告》。内容必须包含日期文本A摘要文本B摘要算法得分备注2024-05-01人工智能发展…AI技术进步…余弦相似度0.82同义词匹配成功2024-05-02苹果手机…苹果是一种水…编辑距离0.15语义歧义检测生成脚本export_report.pyimport pandas as pd from datetime import datetime, timedelta from myapp.models import SimilarityRecord # 查询本月数据 start_date datetime.now().replace(day1) end_date start_date timedelta(days31) records SimilarityRecord.objects.filter( created_at__range(start_date, end_date) ).values(created_at, text_a, text_b, algorithm, score) df pd.DataFrame(list(records)) df[created_at] df[created_at p a hrefhttps://download.csdn.net/download/2301_78021017/88891558 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p