Skip to main content

文本标记解析工具 - 支持多状态合并与多格式导出

Project description

textag - 文本标记解析工具

Python Version PyPI version License

基于 SOLID 原则设计的文本标记/标注处理工具

支持多状态合并 · 多格式导出 · 可扩展架构


📖 目录


✨ 功能特性

核心功能

  • 文本标记解析:解析带标签的文本文件,自动识别状态行和 URL 行
  • 多状态合并:同一 URL 在不同状态下自动合并,保留所有状态信息
  • 多格式导出:支持 Excel (.xlsx)、CSV (.json)、JSON (.json) 格式
  • 智能去重:自动检测重复 URL 并生成统计报告

技术特性

  • SOLID 原则:严格遵循面向对象设计原则
  • 模块化架构:清晰的分层设计,易于理解和维护
  • 依赖倒置:高层模块不依赖低层模块的具体实现
  • 开闭原则:新增功能无需修改现有代码
  • 类型安全:完整的类型注解,支持 IDE 智能提示
  • 优雅降级:Rich 美化日志不可用时自动降级到标准日志

📦 安装说明

基础安装

# 从 PyPI 安装(推荐)
pip install textag

# 从源码安装
git clone https://github.com/textag/textag.git
cd textag
pip install -e .

可选依赖

# 完整功能版(包含 Rich 美化日志)
pip install textag[rich]

# 开发版(包含测试和开发工具)
pip install textag[dev]

环境要求

  • Python 3.8+
  • pandas >= 1.5.0
  • openpyxl >= 3.0.0
  • rich >= 12.0.0 (可选,用于美化输出)

🚀 快速开始

1. 准备输入文件

创建 contacts.txt 文件:

#contacted
https://linkedin.com/in/user1
https://linkedin.com/in/user2

#replied
https://linkedin.com/in/user1

#followup urgent
https://linkedin.com/in/user3

2. 命令行使用

# 导出为 Excel(默认)
textag -i contacts.txt -o output.xlsx

# 导出为 CSV
textag -i contacts.txt -o output.csv

# 导出为 JSON
textag -i contacts.txt -o output.json

# 使用标准日志(无 Rich 依赖)
textag -i contacts.txt -o output.xlsx --no-rich

# 查看帮助
textag --help

3. 编程式使用

from pathlib import Path
from textag import ContactService, RichLogger

# 创建服务
service = ContactService(logger=RichLogger())

# 执行处理
result = service.process(
    input_file=Path("contacts.txt"),
    output_file=Path("output.xlsx")
)

# 查看结果
print(f"唯一联系人:{result.unique_count}")
print(f"重复 URL: {result.duplicate_count}")
print(f"警告信息:{len(result.warnings)}")

📚 使用指南

输入文件格式规范

基本结构

#状态标签
URL 地址
URL 地址

#另一个状态
URL 地址

详细说明

  1. 状态行:以 # 开头,可包含多个标签

    #contacted
    #replied
    #followup urgent  # 多个标签用空格分隔
    
  2. URL 行:必须以 http://https:// 开头

    https://linkedin.com/in/user1
    http://example.com/contact
    
  3. 注释处理:URL 后的 # 注释会被自动移除

    https://linkedin.com/in/user1  # 这是注释,会被移除
    
  4. 空行:会被自动忽略

输出格式示例

Excel 输出 (.xlsx)

URL Status Status_Count
https://linkedin.com/in/user1 contacted, replied 2
https://linkedin.com/in/user2 contacted 1
https://linkedin.com/in/user3 followup urgent 1

CSV 输出 (.csv)

URL,Status,Status_Count
https://linkedin.com/in/user1,"contacted, replied",2
https://linkedin.com/in/user2,contacted,1
https://linkedin.com/in/user3,"followup urgent",1

JSON 输出 (.json)

{
  "contacts": [
    {
      "URL": "https://linkedin.com/in/user1",
      "Status": "contacted, replied",
      "Status_Count": "2"
    }
  ],
  "meta": {
    "count": 3,
    "version": "1.0"
  }
}

🔧 API 文档

核心类

ContactService

服务外观类,协调解析和导出流程。

from textag import ContactService, RichLogger
from pathlib import Path

service = ContactService(logger=RichLogger())
result = service.process(
    input_file=Path("input.txt"),
    output_file=Path("output.xlsx")
)

方法:

  • process(input_file, output_file)ParseResult: 执行完整处理流程
  • register_exporter(extension, exporter_class): 注册新的导出器
  • get_supported_extensions()List[str]: 获取支持的扩展名列表

ContactParser

解析器类,负责解析文本文件。

from textag import ContactParser, StandardLogger
from pathlib import Path

parser = ContactParser(logger=StandardLogger())
result = parser.parse(Path("contacts.txt"))

for contact in result.contacts:
    print(f"{contact.url}: {contact.merged_status}")

方法:

  • parse(input_path)ParseResult: 解析输入文件

Contact

不可变联系人数据对象。

from textag import Contact

contact = Contact(url="https://example.com", statuses=frozenset({"tag1", "tag2"}))
print(contact.merged_status)  # "tag1, tag2"
print(contact.status_count)   # 2

属性:

  • urlstr: 联系人 URL
  • statusesFrozenSet[str]: 状态集合
  • merged_statusstr: 合并后的状态字符串
  • status_countint: 状态数量

方法:

  • to_dict()Dict[str, str]: 转换为字典

ParseResult

解析结果容器。

result = parser.parse(Path("input.txt"))
print(f"唯一联系人:{result.unique_count}")
print(f"重复 URL: {result.duplicate_count}")
print(f"是否有警告:{result.has_warnings}")

属性:

  • contactsList[Contact]: 联系人列表
  • duplicatesDict[str, int]: 重复 URL 计数
  • warningsList[str]: 警告信息
  • unique_countint: 唯一联系人数量
  • duplicate_countint: 重复 URL 数量
  • has_warningsbool: 是否有警告

导出器

IExporter (抽象基类)

所有导出器必须继承的接口。

from textag import IExporter
from pathlib import Path

class CustomExporter(IExporter):
    @property
    def supported_extension(self) -> str:
        return ".custom"
    
    def export(self, contacts, output_path):
        # 实现导出逻辑
        pass

内置导出器

  • ExcelExporter: Excel 导出器(需要 pandas 和 openpyxl)
  • CSVExporter: CSV 导出器
  • JSONExporter: JSON 导出器

日志系统

ILogger (协议)

日志接口定义。

from textag import ILogger

logger.info("这是一条信息")
logger.warning("这是一条警告")
logger.error("这是一个错误")
logger.success("操作成功")
logger.header("这是标题")

日志实现

  • StandardLogger: 标准日志(使用 Python logging)
  • RichLogger: Rich 美化日志(自动降级)

🏗️ 架构设计

分层架构图

┌─────────────────────────────────────────────────────────────┐
│                    CLI 层 (Presentation)                    │
│  - 参数解析 (argparse)                                      │
│  - 程序入口 (main)                                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   服务层 (Service Layer)                    │
│  - ContactService: 外观模式,协调解析与导出                  │
│  - 导出器注册表 (Registry Pattern)                          │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│   解析层        │ │   导出层        │ │   日志层        │
│ ContactParser   │ │ IExporter       │ │ ILogger         │
│ (SRP: 只解析)   │ │ ├─ ExcelExporter│ │ ├─ RichLogger   │
│                 │ │ ├─ CSVExporter  │ │ └─ StandardLogger│
│                 │ │ └─ JSONExporter │ │                 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   数据层 (Data Layer)                       │
│  - Contact (不可变数据对象)                                  │
│  - ParseResult (结果容器)                                    │
└─────────────────────────────────────────────────────────────┘

SOLID 原则应用

原则 原代码问题 重构后方案
SRP 一个类同时处理解析、导出、CLI、日志 拆分为 ContactParser、IExporter、ContactService、ILogger
OCP 新增格式需要修改原有代码 通过 IExporter 接口扩展,register_exporter 注册新格式
LSP 无继承结构 所有导出器实现同一接口,可互换使用
ISP 原代码强制依赖 Rich 的所有功能 ILogger 只定义必要方法,RichLogger 内部处理降级
DIP 高层模块依赖 pandas、rich 等具体实现 ContactService 依赖 ILogger/IExporter 抽象

🔨 扩展开发

添加新导出格式

from textag import IExporter, ContactService, Contact
from pathlib import Path
import xml.etree.ElementTree as ET

class XMLExporter(IExporter):
    """自定义 XML 导出器"""
    
    @property
    def supported_extension(self) -> str:
        return ".xml"
    
    def export(self, contacts, output_path):
        root = ET.Element("contacts")
        for contact in contacts:
            child = ET.SubElement(root, "contact")
            child.set("url", contact.url)
            child.set("status", contact.merged_status)
        
        tree = ET.ElementTree(root)
        tree.write(output_path, encoding='utf-8', xml_declaration=True)

# 注册新导出器(无需修改原代码)
ContactService.register_exporter('.xml', XMLExporter)

# 使用
service = ContactService()
service.process(Path("input.txt"), Path("output.xml"))

自定义日志系统

from textag import ILogger

class FileLogger(ILogger):
    """文件日志记录器"""
    
    def __init__(self, log_file):
        self.log_file = log_file
    
    def info(self, msg): self._write("INFO", msg)
    def warning(self, msg): self._write("WARN", msg)
    def error(self, msg): self._write("ERROR", msg)
    def success(self, msg): self._write("SUCCESS", msg)
    def header(self, msg): self._write("HEADER", f"\n{'='*40}\n{msg}\n{'='*40}")
    
    def _write(self, level, msg):
        with open(self.log_file, 'a', encoding='utf-8') as f:
            f.write(f"[{level}] {msg}\n")

# 使用
service = ContactService(logger=FileLogger("app.log"))

🧪 测试与开发

运行测试

# 安装开发依赖
pip install textag[dev]

# 运行所有测试
pytest

# 运行测试并生成覆盖率报告
pytest --cov=textag --cov-report=html

# 查看 HTML 覆盖率报告
open htmlcov/index.html

代码质量检查

# 代码格式化检查
black src/ tests/

# 代码风格检查
flake8 src/ tests/

# 类型检查
mypy src/

本地开发安装

# 克隆仓库
git clone https://github.com/textag/textag.git
cd textag

# 开发模式安装
pip install -e ".[dev]"

# 运行测试
pytest

❓ 常见问题

Q: 中文乱码问题?

A: 确保输入文件保存为 UTF-8 编码。Windows 用户可使用记事本打开文件,选择"另存为",在编码中选择"UTF-8"。

Q: 如何禁用 Rich 美化输出?

A: 使用 --no-rich 参数:

textag -i input.txt -o output.xlsx --no-rich

Q: 如何添加自定义导出格式?

A: 继承 IExporter 接口并注册:

ContactService.register_exporter('.xml', XMLExporter)

Q: 返回码的含义?

A:

  • 0: 成功完成,无警告
  • 1: 成功完成,但有警告(如存在重复 URL)
  • 2: 失败(文件不存在、格式错误等)

Q: 支持的最大文件大小?

A: 当前版本将所有数据加载到内存,建议处理不超过 10 万行的文件。如需处理更大文件,建议使用流式处理版本。


📝 变更日志

v1.0.0 (2026-03-21)

  • ✨ 初始版本发布
  • ✅ 实现核心解析功能
  • ✅ 支持 Excel、CSV、JSON 导出
  • ✅ 完整 SOLID 架构重构
  • ✅ Rich 美化日志支持
  • ✅ 命令行工具

🤝 贡献指南

欢迎提交 Issue 和 Pull Request!

  1. Fork 本项目
  2. 创建功能分支 (git checkout -b feature/amazing-feature)
  3. 提交更改 (git commit -m 'Add some amazing feature')
  4. 推送到分支 (git push origin feature/amazing-feature)
  5. 创建 Pull Request

📄 许可证

MIT License - 详见 LICENSE 文件


👥 作者

  • textag team

如果这个项目对你有帮助,请给一个 ⭐️ Star 支持!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

textag-1.0.0.tar.gz (27.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

textag-1.0.0-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

Details for the file textag-1.0.0.tar.gz.

File metadata

  • Download URL: textag-1.0.0.tar.gz
  • Upload date:
  • Size: 27.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.3

File hashes

Hashes for textag-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c228efb1145c3a8cee13e50d2c5a95b2716604fb041d0647cfa3c2af483d2368
MD5 fa74e3903efde4d8f675b9b28f1e26c9
BLAKE2b-256 7156529a8b387accee516e3ab15914861e78d0c51cb079fd7caf81e7d924f79d

See more details on using hashes here.

File details

Details for the file textag-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: textag-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 20.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.3

File hashes

Hashes for textag-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 30b7ac40b2106fc30e689cd439b3c5aa8c279ff478bf11264ea802a971ee8be8
MD5 d28e348ce5a6f1aed45dc3083d5bda10
BLAKE2b-256 62b09c49aba0ba491c9f2326a887ecdc7775b2f2381925175d8194ad3083400b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page