news 2026/5/8 8:05:39

基于正则与上下文分析的土耳其语票价信息智能提取技术实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于正则与上下文分析的土耳其语票价信息智能提取技术实践

1. 项目概述:一个为土耳其语设计的智能票价计算器

最近在做一个跟公共交通相关的项目,需要处理土耳其语的票价信息,偶然间发现了这个名为“Fare-imleci-vurgulayici”的仓库。这个名字直译过来就是“票价计算器-强调器”,听起来有点抽象,但深入探究后,我发现它实际上是一个专门针对土耳其语文本的、用于智能识别和突出显示票价相关信息的工具库或脚本。

在土耳其,尤其是伊斯坦布尔这样的超大城市,公共交通系统非常复杂,地铁、公交、渡轮、有轨电车(Tramvay)和缆车(Füniküler)构成了庞大的网络。票价体系也随之复杂化:有单次票、多次票、储值卡(İstanbulkart)、学生票、老年人票等多种类型,并且票价会根据乘坐的交通工具类型、换乘次数、乘坐距离(在某些线路上)以及支付方式(现金或电子卡)而动态变化。这就导致在新闻、公告、社交媒体甚至官方App的说明文本中,关于票价的描述往往是一段夹杂着数字、货币符号(₺)、票种名称和复杂条件的土耳其语长句。

对于普通用户,尤其是外国游客或不熟悉当地规则的人来说,快速从一段文字中提取出核心的票价数字和适用条件,是一件挺头疼的事。这个“Fare-imleci-vurgulayici”项目,就是为了解决这个问题而生。它不是一个完整的票价计算App,而更像是一个“文本挖掘引擎”或“信息提取增强模块”。它的核心任务是:输入一段土耳其语文本,自动找出其中所有与“票价”相关的数字和关键描述词(如“ücreti”、“bilet”、“İstanbulkart ile”等),并以高亮、加粗或添加特殊标记的方式将它们突出显示,从而帮助用户或下游程序快速定位和理解票价信息。

2. 核心需求与设计思路拆解

2.1 为什么需要专门的土耳其语票价提取工具?

你可能会问,用正则表达式匹配数字和“TL”(土耳其里拉)不就行了吗?在实际场景中,这远远不够。土耳其语票价文本的复杂性,催生了对这个专用工具的需求。

首先,语言特异性。土耳其语是一种黏着语,通过添加丰富的后缀来表达语法关系。例如,“票价”是“ücret”,但“它的票价”是“ücreti”,“单程票价”可能是“tek yön ücreti”。一个简单的关键词匹配,很容易漏掉这些变体。此外,数字的书写方式也可能包含小数点(逗号)和千位分隔符(点),例如“5,50 TL”或“15.00 TL”,这在正则表达式中需要精细处理。

其次,上下文关联性。一个孤立的数字“3.50”可能是票价,也可能是时间、站台号或其他信息。判断一个数字是否为票价,极度依赖其周围的上下文词汇。例如,“Öğrenci bileti 3,50 TL'dir.”(学生票是3.50 TL)和“3,50 dakika sonra kalkar.”(3.5分钟后发车)。前者中的“3,50”是票价,后者则是时间。工具必须能理解“bileti”(票)、“TL”(货币)这类强关联词。

最后,条件信息的提取。票价往往附带条件,如“İstanbulkart ile aktarmada ücretsiz”(使用伊stanbul卡换乘免费)、“65 yaş üstü ücretsiz”(65岁以上免费)。一个优秀的提取器不仅要标出价格,最好还能关联并标出这些条件,形成结构化的信息片段。这正是“vurgulayici”(强调器)想达到的更高目标——不仅仅是高亮,更是信息的结构化提取和呈现。

2.2 项目的整体架构设想

基于以上需求,一个合理的“Fare-imleci-vurgulayici”应该包含以下核心模块:

  1. 文本预处理模块:负责接收原始土耳其语文本,进行基础清洗(如去除多余空格、统一字符编码)、句子分割和分词。考虑到土耳其语的形态,一个简单的空格分词可能不够,需要集成或调用土耳其语的分词库(如Zemberek,一个著名的土耳其语NLP库)。

  2. 关键词与模式匹配引擎:这是核心之一。需要维护一个动态的“票价相关词典”,包括:

    • 货币与单位TL,,lira,kuruş(库鲁,分)。
    • 票务核心名词ücret(费用),bilet(票),fiyat(价格),geçiş(通行),yolculuk(旅程)。
    • 票种类型öğrenci(学生),tam(全价),indirimli(折扣),aile(家庭),günlük(日票),haftalık(周票)。
    • 支付与条件İstanbulkart ile(用İstanbul卡),nakit(现金),aktarma(换乘),ücretsiz(免费),ilk(首次),sonraki(后续)。 引擎需要支持这些关键词的变体形式(如带后缀的ücreti,bileti)的模糊匹配。同时,编写一系列正则表达式模式来捕获常见的票价表述模式,如[\d.,]+\s*(TL|₺|lira)
  3. 上下文分析与消歧模块:利用简单的规则或轻量级机器学习模型(如基于词性的规则或预训练词向量的相似度计算),判断匹配到的数字是否真的属于票价上下文。例如,如果数字附近出现dakika(分钟)、saat(小时)、durak(站台)等词,则可能不是票价,应降低其权重或排除。

  4. 信息关联与结构化模块:将匹配到的价格数字与附近的关键词进行关联。例如,识别出“Öğrenci bileti 3,50 TL”这个片段,并尝试将其结构化为{“ticket_type”: “öğrenci”, “price”: 3.50, “currency”: “TL”}。对于更复杂的条件句,如“İstanbulkart ile ilk geçiş 7.67 TL, sonraki aktarmalar ücretsiz.”,应能识别出两个信息块:一个带价格的首次刷卡,一个免费的换乘条件。

  5. 渲染与输出模块:根据结构化信息,生成带有高亮标记的文本。输出格式可以是HTML(用<mark><span style>标签)、ANSI终端颜色代码,或者纯文本附加位置索引,供其他应用程序调用。同时,也可以选择输出结构化的JSON数据,包含提取出的所有票价实体及其元数据(位置、类型、关联条件)。

3. 关键技术点与实现细节

3.1 土耳其语文本处理的特殊性

处理土耳其语,有几个坑是必须提前知道的。首先就是大小写转换。土耳其语中有一个字母“i”,它的大写是“İ”(上面带点),而小写“i”的大写是“I”(上面无点)。反之,大写“I”的小写是“ı”(下面无点)。如果你用Python默认的str.lower()str.upper(),会得到错误的结果,因为它们是针对ASCII设计的。必须使用区域设置或专门的库。

# 错误的做法 text = "İSTANBUL İstanbulkart İLE" print(text.lower()) # 输出:'i̇stanbul i̇stanbulkart i̇le' (可能显示乱码,且'i'上有点不符合土耳其语规则) # 正确的做法:使用 locale 或 unicodedata 进行规范化处理 import locale locale.setlocale(locale.LC_ALL, 'tr_TR.UTF-8') # 设置土耳其语区域 # 或者使用 str.casefold() 结合自定义映射,或使用土耳其语NLP库如 Zemberek 或 `tr` 库

其次,分词。虽然空格是主要分隔符,但土耳其语中复合词很多,且后缀变化复杂。例如,“İstanbulkart'ımla”(用我的İstanbul卡)是一个词,但包含了所有格后缀-ım和工具格后缀-la。对于高精度场景,使用Zemberek这样的库进行词干化和形态分析是更可靠的选择,它能将“ücretlerinden”分解为词根“ücret”和后缀“-ler(复数)-in(从属)-den(从格)”,这对于精确匹配关键词至关重要。

3.2 模式匹配引擎的构建策略

构建匹配引擎,我倾向于采用“正则表达式为主,词典为辅,规则后处理”的混合策略。

第一步,构建正则表达式模式库。不要试图写一个万能的正则,而是针对不同场景编写多个模式,然后组合使用。

import re patterns = [ # 匹配基础价格格式:数字+货币符号 r'(\d{1,3}(?:\.\d{3})*(?:,\d{1,2})?)\s*(TL|₺|lira|Lira)', # 匹配“X TL Y Kuruş”格式 r'(\d+)\s*(TL|₺)\s*(\d+)\s*kuruş', # 匹配“ücreti X TL”格式 r'(ücreti|fiyatı|bedeli)\s*(\d[\d.,]*)\s*(TL|₺)?', # 匹配“bilet X TL”格式 r'(bilet|bileti)\s*(\d[\d.,]*)\s*(TL|₺)?', ]

第二步,构建并维护关键词词典。将关键词按类别分组,并考虑其常见变体。可以通过读取文件或配置来管理,便于更新。

fare_keywords = { 'ticket_type': ['öğrenci', 'tam', 'indirimli', 'sosyal', 'aile', 'günlük', 'haftalık', 'aylık'], 'payment_method': ['istanbulkart', 'istanbulkart ile', 'nakit', 'kredi kartı', 'bankkart'], 'condition': ['aktarma', 'ücretsiz', 'ilk geçiş', 'sonraki geçiş', '90 dakika içinde', 'metro', 'otobüs', 'vapur'], }

第三步,实施匹配与关联。遍历文本,应用所有正则模式,找到所有候选数字和关键词。然后,在一个滑动窗口(例如,匹配数字前后10个词)内搜索关联关键词。如果一个价格数字附近找到了öğrencibileti,就可以给它打上student_ticket的标签。

注意:正则表达式中的数字匹配要小心处理土耳其语的数字分隔符。\d{1,3}(?:\.\d{3})*用于匹配千位分隔符为点的情况(如15.00),而(?:,\d{1,2})?用于匹配小数部分(逗号分隔)。在Python中,匹配到字符串后,需要将点(千位分隔符)移除,将逗号(小数点)替换为点,再转换为浮点数:float(price_str.replace('.', '').replace(',', '.'))

3.3 上下文消歧的实用技巧

如何减少误报?这里分享几个基于规则的实用技巧:

  1. 距离加权法:给每个匹配到的关键词一个基础分,价格数字本身也有基础分。然后,计算价格数字与每个关键词在文本中的距离(词数或字符数),距离越近,该关键词对价格数字的贡献分数越高。最后,价格数字的总得分是所有关联关键词贡献分的加权和。设定一个阈值,高于阈值的才被认为是“票价数字”。

  2. 否定词检测:如果价格数字附近出现否定词,如değil(不是)、hariç(除外)、ücretsiz değil(不免费),则需要特别小心。例如,“İlk 10 dakika ücretsiz değil, 5 TL.”(前10分钟不免费,5 TL)。这里的“5 TL”是价格,但“10”和“dakika”在一起就不是价格。规则引擎需要能处理这种否定范围。

  3. 词性过滤:如果集成了分词和词性标注工具,可以增加一层过滤:只考虑那些出现在名词(票价相关名词)或动词(如ücretlendirilir-被收费)附近的数字。出现在动词(如kalkar-出发)或时间名词附近的数字,则更可能是时间或其他信息。

4. 从零搭建一个简易版“票价强调器”

下面,我将演示如何用Python构建一个简化但功能完整的核心模块。这个示例不依赖重型NLP库,侧重于展示思路和可运行的代码。

4.1 环境准备与依赖

我们主要使用标准库re(正则表达式)和json。为了更好的土耳其语大小写处理,可以安装轻量级的tr库(pip install tr),它提供了简单的土耳其语大小写转换。

pip install tr

4.2 核心类设计与实现

我们创建一个名为FareHighlighter的类。

import re from typing import List, Dict, Any, Tuple import json try: from tr import tr_lower HAS_TR = True except ImportError: HAS_TR = False class FareHighlighter: def __init__(self): self._compile_patterns() self._load_keywords() def _compile_patterns(self): """编译所有预定义的正则表达式模式""" # 价格模式 (数字 + 货币) self.price_patterns = [ re.compile(r'(\d{1,3}(?:\.\d{3})*(?:,\d{1,2})?)\s*(TL|₺|lira|Lira)', re.IGNORECASE), re.compile(r'(\d+)\s*(TL|₺)\s*(\d+)\s*kuruş', re.IGNORECASE), ] # 上下文关键词模式 (用于辅助定位) self.context_patterns = { 'ticket': re.compile(r'\b(bilet|ücret|fiyat|geçiş|yolculuk)\w*\b', re.IGNORECASE), 'type': re.compile(r'\b(öğrenci|tam|indirimli|sosyal|aile|günlük|haftalık)\b', re.IGNORECASE), 'payment': re.compile(r'\b(istanbulkart|nakit|kart|kredi)\w*\b', re.IGNORECASE), 'condition': re.compile(r'\b(aktarma|ücretsiz|ilk|sonraki|dakika|saat)\w*\b', re.IGNORECASE), } def _load_keywords(self): """加载关键词词典 (这里硬编码,实际可从文件读取)""" self.keywords = { 'ticket': ['bilet', 'ücret', 'fiyat', 'geçiş', 'yolculuk'], 'type': ['öğrenci', 'tam', 'indirimli', 'sosyal', 'aile', 'günlük', 'haftalık'], 'payment': ['istanbulkart', 'nakit', 'kart', 'kredi kartı'], 'condition': ['aktarma', 'ücretsiz', 'ilk geçiş', 'sonraki geçiş', 'dakika içinde', 'metro', 'otobüs'], } def _normalize_text(self, text: str) -> str: """简单的文本规范化:统一为小写以便匹配,处理土耳其语i/I问题""" if HAS_TR: # 使用 tr 库进行更准确的土耳其语小写转换 normalized = tr_lower(text) else: # 回退方案:标准小写,但警告用户 normalized = text.lower() # 手动替换一些常见错误(不完整,仅示例) normalized = normalized.replace('i̇', 'i') # 处理可能出现的错误编码 return normalized def find_fare_candidates(self, text: str) -> List[Dict[str, Any]]: """在文本中查找所有可能的票价候选""" normalized_text = self._normalize_text(text) candidates = [] # 1. 查找所有价格数字 for pattern in self.price_patterns: for match in pattern.finditer(normalized_text): price_str = match.group(1) currency = match.group(2) if match.group(2) else 'TL' # 默认货币 # 清理价格字符串并转换为浮点数 try: # 移除千位分隔符点,将小数逗号替换为点 clean_price_str = price_str.replace('.', '').replace(',', '.') price_value = float(clean_price_str) except ValueError: continue # 转换失败,跳过 start, end = match.span() candidates.append({ 'type': 'price', 'value': price_value, 'currency': currency.upper(), 'original_text': text[start:end], # 保留原始大小写 'start': start, 'end': end, 'score': 10.0, # 基础分 }) # 2. 查找所有上下文关键词 for key, pattern in self.context_patterns.items(): for match in pattern.finditer(normalized_text): start, end = match.span() candidates.append({ 'type': f'context_{key}', 'value': match.group(), 'original_text': text[start:end], 'start': start, 'end': end, 'score': 5.0, # 关键词基础分 }) # 按起始位置排序 candidates.sort(key=lambda x: x['start']) return candidates def _calculate_context_score(self, price_candidate: Dict, all_candidates: List[Dict], window_size=100) -> float: """计算一个价格候选对象的上下文得分""" score = price_candidate['score'] price_start, price_end = price_candidate['start'], price_candidate['end'] price_center = (price_start + price_end) / 2 for ctx in all_candidates: if ctx['type'].startswith('context_'): ctx_center = (ctx['start'] + ctx['end']) / 2 distance = abs(ctx_center - price_center) # 距离越近,贡献分数越高(使用线性衰减,可改用指数衰减) if distance <= window_size: proximity_factor = 1.0 - (distance / window_size) score += ctx['score'] * proximity_factor * 0.5 # 权重系数 return score def highlight_fares(self, text: str, threshold=15.0) -> Tuple[str, List[Dict]]: """ 主函数:高亮文本中的票价信息。 返回高亮后的文本和提取出的结构化票价信息列表。 """ all_candidates = self.find_fare_candidates(text) price_candidates = [c for c in all_candidates if c['type'] == 'price'] structured_fares = [] # 为每个价格计算上下文得分 for price in price_candidates: price['final_score'] = self._calculate_context_score(price, all_candidates) # 筛选出得分高于阈值的价格,并尝试结构化 high_scored_prices = [p for p in price_candidates if p['final_score'] >= threshold] # 简单去重:如果两个价格位置重叠,取分高的 high_scored_prices.sort(key=lambda x: x['final_score'], reverse=True) selected_prices = [] used_positions = set() for p in high_scored_prices: overlap = False for pos in range(p['start'], p['end']): if pos in used_positions: overlap = True break if not overlap: selected_prices.append(p) used_positions.update(range(p['start'], p['end'])) # 生成高亮文本 (使用HTML标记) highlighted_text_parts = [] last_end = 0 for p in sorted(selected_prices, key=lambda x: x['start']): highlighted_text_parts.append(text[last_end:p['start']]) highlighted_text_parts.append(f'<mark class="fare-highlight">{text[p["start"]:p["end"]]}</mark>') last_end = p['end'] # 构建结构化信息 fare_info = { 'price': p['value'], 'currency': p['currency'], 'text_snippet': text[max(0, p['start']-30): min(len(text), p['end']+30)], } structured_fares.append(fare_info) highlighted_text_parts.append(text[last_end:]) highlighted_html = ''.join(highlighted_text_parts) return highlighted_html, structured_fares # 使用示例 if __name__ == "__main__": highlighter = FareHighlighter() sample_texts = [ "İstanbul'da öğrenci bileti 3,50 TL, tam bilet ise 7,67 TL'dir. İstanbulkart ile aktarmalar ücretsiz.", "Vapur ücretleri tam 15.00 TL, indirimli 7.50 TL. Nakit ödemede 20 TL alınmaktadır.", "Otobüs ile 5 durak sonra 10 dakika içinde metroya aktarma yapabilirsiniz. Ücret 5 TL.", ] for txt in sample_texts: print(f"\n原文: {txt}") highlighted, fares = highlighter.highlight_fares(txt) print(f"高亮后: {highlighted}") print(f"提取的票价: {json.dumps(fares, indent=2, ensure_ascii=False)}")

4.3 代码解读与核心逻辑

这个简易实现包含了几个关键部分:

  1. 模式初始化 (__init__): 加载了匹配价格和上下文关键词的正则表达式。
  2. 文本规范化 (_normalize_text): 尝试使用tr库进行正确的土耳其语小写转换,这是提高匹配准确率的第一步。
  3. 候选查找 (find_fare_candidates): 扫描文本,找出所有可能是价格(数字+货币)和上下文关键词的片段,并记录其位置和原始文本。
  4. 上下文评分 (_calculate_context_score): 这是消歧的核心。对于一个价格候选,检查其周围一定窗口(默认100字符)内所有的上下文关键词。距离越近的关键词,对价格的“票价属性”贡献越大。通过加权求和,计算出一个“最终得分”。
  5. 高亮与结构化 (highlight_fares): 设定一个阈值(如15分),只有最终得分高于阈值的价格才被认定为“票价”。然后,用HTML的<mark>标签包裹这些价格,生成高亮文本。同时,提取价格、货币和周围文本片段,形成结构化数据。

实操心得:阈值threshold的选择需要根据实际语料进行调优。可以先收集一批标注好的文本(哪些数字是票价,哪些不是),然后运行脚本,观察不同阈值下的精确率(Precision)和召回率(Recall),选择一个平衡点。一开始可以设得保守一些(高阈值),确保高亮出来的基本都是对的,避免用户被错误信息干扰。

5. 性能优化与高级功能拓展

基础版本跑通后,我们可以从以下几个方面增强其实用性和鲁棒性。

5.1 处理更复杂的票价表述

现实文本中,票价表述可能更复杂:

  • 区间票价:“5-15 TL arası”(5到15 TL之间)。我们的正则需要扩展以捕获连字符和“arası”这样的词。
    # 新增模式 interval_pattern = re.compile(r'(\d[\d.,]*)\s*-\s*(\d[\d.,]*)\s*(TL|₺|lira)\s*(arası|arasında)?', re.IGNORECASE)
  • 条件票价:“İlk 10 km 5 TL, sonraki her km 0.50 TL”(前10公里5 TL,之后每公里0.50 TL)。这需要解析两个价格及其关联的条件短语。我们可以通过寻找“ilk”(首先)、“sonraki”(后续)、“her”(每)等序列词,并尝试将后面的价格数字与前面的条件绑定。
  • 免费描述:“65 yaş üstü ve 0-6 yaş ücretsiz”(65岁以上和0-6岁免费)。这里没有价格数字,但有关键的票价信息“ücretsiz”(免费)。我们的系统应该也能识别并高亮“ücretsiz”,或者将其作为一种特殊的“票价实体”(价格为0)输出。

5.2 集成机器学习进行消歧

当规则变得过于复杂时,可以考虑引入轻量级机器学习模型。我们并不需要训练一个庞大的NER(命名实体识别)模型,一个简单的文本分类模型可能就足够了。

思路:将任务转化为二分类问题。对于文本中每一个匹配到的数字(及其周围固定窗口的上下文,例如前后各5个词),判断它是否是“票价”。

  1. 数据准备:手动标注一批数据,每个样本是(context_window, label)label为1(是票价)或0(不是票价)。
  2. 特征工程:可以提取如下特征:
    • 数字本身是否匹配货币模式(布尔)。
    • 上下文窗口中特定关键词(来自我们的词典)的出现次数(词袋模型)。
    • 数字在句子中的位置。
    • 数字前后特定词性的词(如果做了词性标注)。
  3. 模型训练:使用如逻辑回归、随机森林或简单的神经网络(如FastText)进行训练。Scikit-learn足以完成这个任务。
  4. 集成到流程:在highlight_fares函数中,对于每个通过基础正则匹配到的价格候选,用训练好的模型预测其概率,将概率值作为final_score的一部分,或者直接使用模型预测结果代替阈值判断。

这种方法比纯规则更灵活,能学习到更复杂的上下文模式,但需要标注数据。

5.3 提升处理速度与部署考量

如果处理海量文本(如爬取新闻网站),性能很重要。

  • 正则表达式优化:将多个正则表达式用|合并编译成一个,减少多次扫描。使用re.finditer而不是re.findall来获取位置信息。
  • 缓存机制:对于频繁出现的固定文本片段(如“Öğrenci bileti”),可以缓存其分析结果。
  • 异步处理:如果作为Web服务部署(例如提供一个API端点),使用异步框架(如FastAPI +async/await)来处理并发请求。
  • 输出格式多样化:除了HTML,可以提供纯文本位置索引([start, end]列表)、JSON-LD(一种结构化数据格式,利于搜索引擎理解)或自定义的标记语言输出,方便不同下游系统集成。

6. 常见问题与实战排坑记录

在实际使用和开发类似工具的过程中,我遇到过不少典型问题,这里记录一下。

6.1 匹配不准确或漏匹配

  • 问题:有些票价没有被高亮,或者不是票价的内容被错误高亮。
  • 排查
    1. 检查正则表达式:是否覆盖了所有数字格式?例如,文本中是“5.5 TL”(小数点用点)而你的正则只匹配了“5,5 TL”?使用在线的正则表达式测试器(如regex101.com)针对你的样例文本进行调试。
    2. 检查文本规范化:大小写转换是否正确?特别是“İ”和“i”。打印出规范化后的文本,看看关键词是否被正确转换为小写形式。
    3. 检查上下文窗口window_size参数是否设置合理?对于长句子,可能需要增大窗口。或者,更好的办法是以句子为单位进行处理,而不是在整个段落上滑动窗口。
    4. 检查阈值threshold是否太高(导致漏报)或太低(导致误报)?需要根据验证集调整。
  • 解决:建立一个小的测试用例集,包含各种正例和反例。每次修改代码后都跑一遍测试集,确保准确率和召回率在可接受范围内。

6.2 处理包含换行符和HTML标签的文本

  • 问题:从网页爬取的文本可能包含<br><p>标签或大量的&nbsp;,这会影响分词和匹配。
  • 解决:在预处理阶段,使用BeautifulSouplxml库提取纯文本,或者用简单的正则re.sub(r'<[^>]+>', ' ', text)移除HTML标签。将多个连续空格和换行符替换为单个空格。

6.3 数字与单位的错误解析

  • 问题:价格“1.500,50 TL”被解析为1.500(一千五)和50 TL。这是因为正则或清洗逻辑错误地将千位分隔符和小数点混淆了。
  • 解决:土耳其的数字格式是:千位分隔符是点.,小数点是逗号,。在清洗字符串时,必须先移除千位分隔符(点)再将小数逗号替换为点。顺序不能错。更稳健的方法是先判断格式:如果数字部分包含逗号,且逗号后只有1-2位数字,则逗号是小数点;否则,点可能是千位分隔符。可以编写一个专用的parse_turkish_number函数。
def parse_turkish_number(num_str: str) -> float: """解析土耳其语格式的数字字符串,如 '1.500,50' 或 '7,67'""" num_str = num_str.strip() if ',' in num_str: # 可能有小数部分 parts = num_str.split(',') if len(parts) == 2 and len(parts[1]) <= 2: # 标准格式:逗号是小数点 integer_part = parts[0].replace('.', '') return float(integer_part + '.' + parts[1]) else: # 异常情况,可能逗号是千位分隔符?在土耳其语中不太可能,谨慎处理 # 这里简单尝试移除所有点,将第一个逗号替换为点 cleaned = num_str.replace('.', '') if ',' in cleaned: cleaned = cleaned.replace(',', '.', 1) return float(cleaned) else: # 无逗号,直接移除千位分隔符 return float(num_str.replace('.', ''))

6.4 部署为服务时的注意事项

如果你打算将这个东西封装成一个微服务:

  • API设计:设计简洁的REST API,例如POST /highlight,接受{“text”: “...”},返回{“highlighted_html”: “...”, “fares”: [...]}
  • 错误处理:做好输入验证和异常捕获,返回清晰的错误信息。
  • 性能监控:记录处理时长、请求量,监控在高并发下是否出现内存泄漏或性能下降。
  • 版本管理:关键词词典和规则可能会更新,API最好有版本号(如/v1/highlight)。

这个“Fare-imleci-vurgulayici”项目,从一个独特的细分需求出发,展示了如何将领域知识(土耳其语、公共交通票价规则)与基础的文本处理技术结合,解决一个实际痛点。它的价值不在于用了多高深的技术,而在于对问题域的深刻理解和细致实现。对于开发者而言,复现或借鉴这个项目的思路,完全可以扩展到其他语言或其他垂直领域的信息提取场景,比如从中文文本中提取快递价格、从英文合同中提取关键条款金额等。核心逻辑是相通的:定义模式、理解上下文、消除歧义、结构化输出。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 7:57:47

Arm GICv3/v4架构与LPI中断机制详解

1. GICv3/v4架构概述在现代计算机系统中&#xff0c;中断控制器扮演着至关重要的角色。作为Arm架构的核心组件&#xff0c;通用中断控制器(Generic Interrupt Controller, GIC)经历了多个版本的演进&#xff0c;其中GICv3和v4版本引入了革命性的基于消息的中断机制(Locality-sp…

作者头像 李华
网站建设 2026/5/8 7:56:40

3分钟快速上手:免费ncmdump工具完整解密网易云NCM音乐终极指南

3分钟快速上手&#xff1a;免费ncmdump工具完整解密网易云NCM音乐终极指南 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾经在网易云音乐下载了心爱的歌曲&#xff0c;却发现只能在网易云音乐App中播放&#xff1f;当你想要…

作者头像 李华
网站建设 2026/5/8 7:55:45

为AI编码助手集成PDF处理技能:Nutrient Agent Skill实战指南

1. 项目概述&#xff1a;为你的AI编码助手装上PDF处理引擎如果你和我一样&#xff0c;日常开发中经常需要和PDF文档打交道——无论是从扫描件里提取表格数据、批量给合同加水印签名&#xff0c;还是把一堆报告合并归档——那你肯定体会过那种在代码编辑器和一堆在线转换工具之间…

作者头像 李华
网站建设 2026/5/8 7:48:31

深度解析Android虚拟相机:实现摄像头内容替换的终极方案

深度解析Android虚拟相机&#xff1a;实现摄像头内容替换的终极方案 【免费下载链接】com.example.vcam 虚拟摄像头 virtual camera 项目地址: https://gitcode.com/gh_mirrors/co/com.example.vcam VCAM虚拟相机是一款基于Xposed框架的Android虚拟摄像头模块&#xff0…

作者头像 李华
网站建设 2026/5/8 7:29:10

c语言绿皮书第三版第八章习题

1.习题8.1#include<stdio.h>void main() {int a, b, t;int max, min;int Maxgys(int, int);int Mingbs(int, int);scanf("%d%d", &a, &b);if (a < b) {t a;a b;b t;}max Maxgys(a, b);min Mingbs(a, b);printf("max%d\nmin%d\n", ma…

作者头像 李华