兔子vCard editor v1.8.8(PyInstaller编译)

通讯录里的有几千个电话号码,找手机不好整理,在电脑整理完毕后可以导入手机。作为备份。在论坛搜了一下没找到好用的,自己写了一个。

1、很感谢大家喜爱我的软件,我是一个新手程序员,许多功能不能实现。

2、大家的提议我都看了 不能一一回帖,能实现的我都会劲量实现。

兔子vCard editor v1.8.8(PyInstaller编译)

下载地址:(所有版本)
https://axiu.lanzoue.com/b0cijvnfe 密码:f7mk

1.0版本

  • 新增了导出为csv格式,

  • 新增了删除空号码。

兔子vCard editor v1.8.8(PyInstaller编译)

1.1版本

  • *新增了读取三个手机号码

  • *新增了判断多种格式

  • *新增判断编码

  • *新增删除姓名前面的空格

  • *新增姓名标题左对齐

兔子vCard editor v1.8.8(PyInstaller编译)

1.3版本

  • 重新布局软件界面

  • 新增重置数据功能

  • 新增删除空号码功能

  • 新增查询功能

  • 新增右键复制功能

  • 新增右键删除功能

兔子vCard editor v1.8.8(PyInstaller编译)

1.4版本

  • 重新打包降低为27.5mb左右。

  • 优化细节。

1.5版本

  • 优化GUI界面
  • 新增合并一个姓名的多个号码
  • 移动拖拽打开(撤销了,tkinterDnD库的问题,调试不报错,编译就报错)

兔子vCard editor v1.8.8(PyInstaller编译)

1.6版本

  • 新增保存为VCF格式
  • 命名软件名字为兔子vCard editor方便别人搜索和下载。
  • 修改软件图标
  • 后来应该不和思考增加打开表格的功能,由于这个超出了这个软件的目的。这个软件主要还是以编辑vcf格式为主。

兔子vCard editor v1.8.8(PyInstaller编译)

1.7版本

  • 新增解析华为手机VCF格式

1.8版本

  • 修复右键删除功能

1.8.5版本

兔子vCard editor v1.8.8(PyInstaller编译)

1、重新编译,优化文件大小
2、新增打开excel格式
3、新增自动判断编码

2.0全新版本预告

兔子vCard editor v1.8.8(PyInstaller编译)

共享源码:

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import Menu
import vobject
import pandas as pd
import chardet

data = []
company_to_name = {}  # 用于存储公司名称到姓名的映射

def browse_file(file_path=None):
    if file_path is None:
        file_path = filedialog.askopenfilename(filetypes=[("vCard文件", "*.vcf")])
    if file_path:
        encoding = detect_encoding(file_path)
        if encoding:
            parse_vcf(file_path, encoding)

def detect_encoding(file_path):
    rawdata = open(file_path, 'rb').read()
    result = chardet.detect(rawdata)
    encoding = result['encoding']
    confidence = result['confidence']
    if confidence > 0.8:
        return encoding
    return 'utf-8'

def parse_vcf(file_path, encoding):
    data.clear()
    company_to_name.clear()
    try:
        with open(file_path, 'r', encoding=encoding, errors='ignore') as vcf_file:
            cards = vobject.readComponents(vcf_file)
            num_entries = 0
            for vcard in cards:
                name = ""
                company = ""
                phone_numbers = [""] * 3
                if hasattr(vcard, 'fn'):
                    name = vcard.fn.value.strip()
                if hasattr(vcard, 'org'):
                    company = vcard.org.value[0]
                    company_to_name[company] = name
                if hasattr(vcard, 'tel_list'):
                    tel_list = [tel.value.replace(" ", "").strip() for tel in vcard.tel_list if hasattr(tel, 'value')]
                    for i, tel in enumerate(tel_list):
                        if i < 3:
                            phone_numbers[i] = tel

                if name or company or any(phone_numbers):
                    data.append([name, company] + phone_numbers)
                    num_entries += 1

            if data:
                update_table()
                output_text.set(f"总条目数:{num_entries}")
            else:
                output_text.set("未找到有效的vCard条目")
    except Exception as e:
        output_text.set(f"错误:{str(e)}")

def reset_data():
    data.clear()
    update_table()
    output_text.set("数据已重置")

def add_to_table(name, phone_numbers, company):
    data.append([name, company] + phone_numbers)
    update_table()

def update_table():
    for i in table.get_children():
        table.delete(i)
    for i, row in enumerate(data):
        formatted_row = [item.strip() if isinstance(item, str) else item for item in row]
        table.insert("", "end", values=formatted_row)

def delete_empty_entries():
    global data  # 引用全局的 data 变量
    data = [entry for entry in data if any(entry[2:])]
    update_table()
    output_text.set(f"总条目数:{len(data)}")

def export_to_csv():
    file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV文件", "*.csv")])
    if file_path:
        column_names = ["姓名", "公司", "电话号码1", "电话号码2", "电话号码3"]
        df = pd.DataFrame(data, columns=column_names)
        df.to_csv(file_path, index=False)

def copy_selected():
    selected = table.selection()
    if selected:
        item = table.selection()[0]
        column = table.identify_column(item)
        column_index = int(column.split('#')[-1]) - 1  # 获取选定单元格的列索引
        row = table.item(item)
        data_to_copy = row["values"][column_index]
        if data_to_copy is not None:
            clipboard = str(data_to_copy)
            root.clipboard_clear()
            root.clipboard_append(clipboard)
            root.update()

def create_table_menu(event):
    selected = table.selection()
    if selected:
        table_menu.delete(0, "end")  # 清空菜单项
        for i in range(len(table["columns"])):
            table_menu.add_command(label=f"复制列 {i + 1}", command=copy_selected)
        table_menu.post(event.x_root, event.y_root)

def delete_selected():
    selected = table.selection()
    if selected:
        for item in selected:
            table.delete(item)

def merge_duplicate_numbers():
    number_to_names = {}
    new_data = []

    for row in data:
        name = row[0]
        company = row[1]
        numbers = row[2:]
        if any(numbers):
            numbers_key = tuple(numbers)
            if numbers_key in number_to_names:
                number_to_names[numbers_key].append(name)
            else:
                number_to_names[numbers_key] = [name]
            new_data.append([" & ".join(number_to_names[numbers_key]), company] + list(numbers))
        else:
            new_data.append([name, company] + numbers)

    data.clear()
    data.extend(new_data)
    update_table()

def search_data():
    query = search_entry.get().strip().lower()
    if not query:
        update_table()
        return

    filtered_data = [entry for entry in data if any(query in value.lower() for value in entry)]
    update_table_with_filtered_data(filtered_data)

def update_table_with_filtered_data(filtered_data):
    for i in table.get_children():
        table.delete(i)
    for row in filtered_data:
        formatted_row = [item.strip if isinstance(item, str) else item for item in row]
        table.insert("", "end", values=formatted_row)

def save_as_vcf():
    file_path = filedialog.asksaveasfilename(defaultextension=".vcf", filetypes=[("VCF文件", "*.vcf")])
    if file_path:
        vcf_data = []
        for row in data:
            name, company, phone1, phone2, phone3 = row
            v = vobject.vCard()
            if name:
                v.add('fn').value = name
            if company:
                v.add('org').value = [company]
            if phone1:
                v.add('tel').value = phone1
            if phone2:
                v.add('tel').value = phone2
            if phone3:
                v.add('tel').value = phone3
            vcf_data.append(v.serialize())

        with open(file_path, 'w', encoding='utf-8') as vcf_file:
            vcf_file.write("
".join(vcf_data))

root = tk.Tk()
root.title("兔子vCard Editor v1.6 By 阿修(论坛专版)")
root.resizable(False, False)  # 禁用窗口的最大化功能

main_frame = ttk.Frame(root)
main_frame.grid(column=0, row=0, padx=10, pady=10, sticky=(tk.W, tk.E, tk.N, tk.S))

# 左侧功能按钮
left_frame = ttk.Frame(main_frame)
left_frame.grid(column=0, row=0, padx=5, pady=5, sticky=tk.N)

browse_button = ttk.Button(left_frame, text="打开VCF文件", command=browse_file)
browse_button.grid(column=0, row=0, padx=5, pady=5, sticky=tk.W)

reset_button = ttk.Button(left_frame, text="重置数据", command=reset_data)
reset_button.grid(column=0, row=1, padx=5, pady=5, sticky=tk.W)

delete_empty_button = ttk.Button(left_frame, text="删除空号码", command=delete_empty_entries)
delete_empty_button.grid(column=0, row=2, padx=5, pady=5, sticky=tk.W)

merge_button = ttk.Button(left_frame, text="合并号码", command=merge_duplicate_numbers)
merge_button.grid(column=0, row=3, padx=5, pady=5, sticky=tk.W)

output_text = tk.StringVar()
output_label = ttk.Label(left_frame, textvariable=output_text, wraplength=200)
output_label.grid(column=0, row=4, padx=5, pady=5, sticky=tk.W)

# 中间表格
table_frame = ttk.Frame(main_frame)
table_frame.grid(column=1, row=0, padx=5, pady=5, sticky=(tk.W, tk.E))

# 设置表头样式
style = ttk.Style()
style.configure("Treeview.Heading", font=("Arial", 12, "bold"), background="lightgray")

table = ttk.Treeview(table_frame, columns=["姓名", "公司", "电话号码1", "电话号码2", "电话号码3"], show="headings")
table.heading("姓名", text="姓名", anchor="w")
table.heading("公司", text="公司", anchor="w")
table.heading("电话号码1", text="电话号码1", anchor="w")
table.heading("电话号码2", text="电话号码2", anchor="w")
table.heading("电话号码3", text="电话号码3", anchor="w")

# 设置列宽度
table.column("姓名", width=75)  # 将姓名的宽度改为75
table.column("公司", width=200)  # 将公司名称的宽度改为200
table.column("电话号码1", width=100)  # 将电话号码1的宽度改为150
table.column("电话号码2", width=100)  # 将电话号码2的宽度改为150
table.column("电话号码3", width=100)  # 将电话号码3的宽度改为150

table.pack(fill="both", expand=True)

# 添加右键菜单
table.bind('<Button-3>', create_table_menu)

# 创建右键菜单
table_menu = Menu(root, tearoff=0)
table_menu.add_separator()
table_menu.add_command(label="删除选定行", command=delete_selected)
table_menu.add_command(label="合并号码", command=merge_duplicate_numbers)

# 右侧功能按钮
right_frame = ttk.Frame(main_frame)
right_frame.grid(column=2, row=0, padx=5, pady=5, sticky=tk.N)

export_button = ttk.Button(right_frame, text="导出为CSV", command=export_to_csv)
export_button.grid(column=0, row=0, padx=5, pady=5, sticky=tk.W)

search_label = ttk.Label(right_frame, text="查询数据:")
search_label.grid(column=0, row=1, padx=5, pady=5, sticky=tk.W)

search_entry = ttk.Entry(right_frame)
search_entry.grid(column=0, row=2, padx=5, pady=5, sticky=tk.W)

search_button = ttk.Button(right_frame, text="查询", command=search_data)
search_button.grid(column=0, row=3, padx=5, pady=5, sticky=tk.W)

save_as_vcf_button = ttk.Button(right_frame, text="保存为VCF", command=save_as_vcf)
save_as_vcf_button.grid(column=0, row=4, padx=5, pady=5, sticky=tk.W)

main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(0, weight=1)

root.mainloop()

分享源码给大家,共同完善,希望大家把修改好的程序能回复到下面的帖子内。

预备功能:使用爬虫获取通讯录里的手机号码爬天眼查之类的公司全称和法人姓名,

然后获取数据后再写入表格,做到不用手动去修改和调整。一

直搞不定这个天眼的爬虫,望论坛的大佬多多指点我。感激不尽。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共47条

请登录后发表评论