欢迎光临千百叶网
详情描述
手机通讯录导出为 Excel 工具

下面是一个完整的 Python 程序,可以从手机通讯录(假设有通讯录文件或通过手机备份获取)中提取联系人信息并导出到 Excel 文件中。

方案概述

由于直接从手机获取通讯录通常需要特殊权限,我将提供两种方案:

从 VCF 文件导入(大多数手机可导出 VCF 格式通讯录) 从 CSV 文件导入(通用格式)

完整代码实现

import pandas as pd
import re
import os
from pathlib import Path
import sys
from datetime import datetime

class ContactsExporter:
    def __init__(self):
        self.contacts = []

    def parse_vcf(self, vcf_path):
        """解析 VCF (vCard) 文件"""
        print(f"正在解析 VCF 文件: {vcf_path}")

        with open(vcf_path, 'r', encoding='utf-8') as file:
            content = file.read()

        # 分割每个 vCard
        vcards = content.split('BEGIN:VCARD')
        contacts = []

        for vcard in vcards:
            if not vcard.strip():
                continue

            contact = {
                '姓名': '',
                '手机': [],
                '电话': [],
                '邮箱': [],
                '公司': '',
                '职位': '',
                '地址': '',
                '备注': ''
            }

            lines = vcard.split('\n')
            for line in lines:
                line = line.strip()

                # 解析姓名
                if line.startswith('FN:'):
                    contact['姓名'] = line[3:].strip()
                elif line.startswith('N:'):
                    # 格式: N:Last;First;Middle;Prefix;Suffix
                    name_parts = line[2:].split(';')
                    if len(name_parts) >= 2 and name_parts[1]:
                        contact['姓名'] = name_parts[1] + ' ' + name_parts[0]
                    elif name_parts[0]:
                        contact['姓名'] = name_parts[0]

                # 解析电话
                elif line.startswith('TEL;') or line.startswith('TEL:'):
                    tel_match = re.search(r'TEL[^:]*:([\d\+\-\s\(\)]+)', line)
                    if tel_match:
                        phone = tel_match.group(1).strip()
                        # 判断是手机还是电话
                        if re.match(r'^1[3-9]\d{9}$', re.sub(r'\D', '', phone)):
                            contact['手机'].append(phone)
                        else:
                            contact['电话'].append(phone)

                # 解析邮箱
                elif line.startswith('EMAIL;') or line.startswith('EMAIL:'):
                    email_match = re.search(r'EMAIL[^:]*:([^\s;]+)', line)
                    if email_match:
                        contact['邮箱'].append(email_match.group(1).strip())

                # 解析公司
                elif line.startswith('ORG:'):
                    contact['公司'] = line[4:].strip()

                # 解析职位
                elif line.startswith('TITLE:'):
                    contact['职位'] = line[6:].strip()

                # 解析地址
                elif line.startswith('ADR;') or line.startswith('ADR:'):
                    addr_match = re.search(r'ADR[^:]*:(.+)', line)
                    if addr_match:
                        contact['地址'] = addr_match.group(1).strip().replace(';', ' ')

                # 解析备注
                elif line.startswith('NOTE:'):
                    contact['备注'] = line[5:].strip()

            # 将列表转换为字符串以便存储
            if contact['手机']:
                contact['手机'] = '; '.join(contact['手机'])
            else:
                contact['手机'] = ''

            if contact['电话']:
                contact['电话'] = '; '.join(contact['电话'])
            else:
                contact['电话'] = ''

            if contact['邮箱']:
                contact['邮箱'] = '; '.join(contact['邮箱'])
            else:
                contact['邮箱'] = ''

            # 添加到联系人列表
            contacts.append(contact)

        print(f"成功解析 {len(contacts)} 个联系人")
        return contacts

    def parse_csv(self, csv_path):
        """解析 CSV 文件"""
        print(f"正在解析 CSV 文件: {csv_path}")

        try:
            # 尝试不同的编码
            try:
                df = pd.read_csv(csv_path, encoding='utf-8')
            except:
                df = pd.read_csv(csv_path, encoding='gbk')

            contacts = []
            required_columns = []

            # 尝试识别列名
            column_mapping = {}
            for col in df.columns:
                col_lower = col.lower()
                if 'name' in col_lower or '姓名' in col or '名称' in col:
                    column_mapping['姓名'] = col
                elif 'mobile' in col_lower or '手机' in col or '电话' in col:
                    column_mapping['手机'] = col
                elif 'phone' in col_lower or 'tel' in col_lower:
                    column_mapping['电话'] = col
                elif 'email' in col_lower or '邮箱' in col or '邮件' in col:
                    column_mapping['邮箱'] = col
                elif 'company' in col_lower or '公司' in col:
                    column_mapping['公司'] = col
                elif 'title' in col_lower or '职位' in col or '职务' in col:
                    column_mapping['职位'] = col
                elif 'address' in col_lower or '地址' in col:
                    column_mapping['地址'] = col
                elif 'note' in col_lower or '备注' in col:
                    column_mapping['备注'] = col

            # 如果找不到姓名列,使用第一列作为姓名
            if '姓名' not in column_mapping and len(df.columns) > 0:
                column_mapping['姓名'] = df.columns[0]

            # 转换数据
            for _, row in df.iterrows():
                contact = {
                    '姓名': str(row[column_mapping.get('姓名', df.columns[0])]) if '姓名' in column_mapping else '',
                    '手机': str(row[column_mapping['手机']]) if '手机' in column_mapping else '',
                    '电话': str(row[column_mapping['电话']]) if '电话' in column_mapping else '',
                    '邮箱': str(row[column_mapping['邮箱']]) if '邮箱' in column_mapping else '',
                    '公司': str(row[column_mapping['公司']]) if '公司' in column_mapping else '',
                    '职位': str(row[column_mapping['职位']]) if '职位' in column_mapping else '',
                    '地址': str(row[column_mapping['地址']]) if '地址' in column_mapping else '',
                    '备注': str(row[column_mapping['备注']]) if '备注' in column_mapping else '',
                }

                # 清理 NaN 值
                for key in contact:
                    if str(contact[key]) == 'nan':
                        contact[key] = ''

                contacts.append(contact)

            print(f"成功解析 {len(contacts)} 个联系人")
            return contacts

        except Exception as e:
            print(f"解析 CSV 文件时出错: {e}")
            return []

    def export_to_excel(self, contacts, output_path):
        """导出联系人到 Excel 文件"""
        if not contacts:
            print("没有联系人数据可导出")
            return False

        try:
            # 转换为 DataFrame
            df = pd.DataFrame(contacts)

            # 重新排列列顺序
            column_order = ['姓名', '手机', '电话', '邮箱', '公司', '职位', '地址', '备注']
            existing_columns = [col for col in column_order if col in df.columns]
            df = df[existing_columns]

            # 导出到 Excel
            df.to_excel(output_path, index=False)

            print(f"成功导出 {len(contacts)} 个联系人到: {output_path}")
            return True

        except Exception as e:
            print(f"导出到 Excel 时出错: {e}")
            return False

    def run(self):
        """主运行函数"""
        print("=" * 50)
        print("手机通讯录导出工具")
        print("=" * 50)

        # 获取文件路径
        while True:
            file_path = input("请输入通讯录文件路径 (VCF 或 CSV 格式): ").strip('"').strip()

            if not os.path.exists(file_path):
                print("文件不存在,请重新输入")
                continue

            file_ext = os.path.splitext(file_path)[1].lower()

            if file_ext == '.vcf':
                contacts = self.parse_vcf(file_path)
                break
            elif file_ext == '.csv':
                contacts = self.parse_csv(file_path)
                break
            else:
                print("不支持的文件格式,请提供 .vcf 或 .csv 文件")
                continue

        if not contacts:
            print("未找到联系人数据")
            return

        # 生成输出文件名
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        default_output = f"contacts_export_{timestamp}.xlsx"

        # 获取输出路径
        output_path = input(f"请输入输出 Excel 文件路径 (默认: {default_output}): ").strip('"').strip()
        if not output_path:
            output_path = default_output

        # 确保文件扩展名为 .xlsx
        if not output_path.lower().endswith('.xlsx'):
            output_path += '.xlsx'

        # 导出到 Excel
        self.export_to_excel(contacts, output_path)

        # 显示前几个联系人预览
        print("\n前5个联系人预览:")
        print("-" * 80)
        for i, contact in enumerate(contacts[:5]):
            print(f"{i+1}. 姓名: {contact.get('姓名', '无')}, 手机: {contact.get('手机', '无')}, 邮箱: {contact.get('邮箱', '无')}")

        print(f"\n导出完成!文件保存在: {os.path.abspath(output_path)}")

def create_sample_vcf():
    """创建示例 VCF 文件用于测试"""
    sample_vcf = """BEGIN:VCARD
VERSION:2.1
N:张;三;;;
FN:张三
TEL;CELL:13800138000
TEL;WORK:010-12345678
EMAIL;WORK:zhangsan@example.com
ORG:ABC公司
TITLE:经理
ADR;WORK:;;北京市朝阳区;北京市;;100000;中国
NOTE:示例联系人1
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:李;四;;;
FN:李四
TEL;CELL:13900139000
TEL;HOME:021-87654321
EMAIL;PERSONAL:lisi@example.com
ORG:XYZ公司
TITLE:工程师
ADR;HOME:;;上海市浦东新区;上海市;;200000;中国
NOTE:示例联系人2
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:王;五;;;
FN:王五
TEL;CELL:13700137000
EMAIL;WORK:wangwu@company.com
ORG:技术部
TITLE:总监
NOTE:重要客户
END:VCARD"""

    with open("sample_contacts.vcf", "w", encoding="utf-8") as f:
        f.write(sample_vcf)

    print("已创建示例文件: sample_contacts.vcf")
    return "sample_contacts.vcf"

def create_sample_csv():
    """创建示例 CSV 文件用于测试"""
    import pandas as pd

    sample_data = {
        '姓名': ['张三', '李四', '王五', '赵六'],
        '手机': ['13800138000', '13900139000', '13700137000', '13600136000'],
        '电话': ['010-12345678', '021-87654321', '', '0755-66668888'],
        '邮箱': ['zhangsan@example.com', 'lisi@example.com', 'wangwu@company.com', 'zhaoliu@test.com'],
        '公司': ['ABC公司', 'XYZ公司', '技术部', '市场部'],
        '职位': ['经理', '工程师', '总监', '主管'],
        '地址': ['北京市朝阳区', '上海市浦东新区', '广州市天河区', '深圳市南山区'],
        '备注': ['示例联系人1', '示例联系人2', '重要客户', '新同事']
    }

    df = pd.DataFrame(sample_data)
    df.to_csv("sample_contacts.csv", index=False, encoding="utf-8-sig")

    print("已创建示例文件: sample_contacts.csv")
    return "sample_contacts.csv"

if __name__ == "__main__":
    exporter = ContactsExporter()

    # 检查命令行参数
    if len(sys.argv) > 1:
        file_path = sys.argv[1]
        if file_path.lower() == '--sample-vcf':
            file_path = create_sample_vcf()
            exporter.parse_vcf(file_path)
        elif file_path.lower() == '--sample-csv':
            file_path = create_sample_csv()
            exporter.parse_csv(file_path)
        elif os.path.exists(file_path):
            file_ext = os.path.splitext(file_path)[1].lower()
            if file_ext == '.vcf':
                contacts = exporter.parse_vcf(file_path)
                if contacts:
                    output_path = f"contacts_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
                    exporter.export_to_excel(contacts, output_path)
            elif file_ext == '.csv':
                contacts = exporter.parse_csv(file_path)
                if contacts:
                    output_path = f"contacts_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
                    exporter.export_to_excel(contacts, output_path)
            else:
                print("不支持的文件格式,请使用 .vcf 或 .csv 文件")
    else:
        # 交互式运行
        print("提示: 您可以使用以下命令快速测试:")
        print("  python contacts_exporter.py --sample-vcf  (创建并处理示例 VCF 文件)")
        print("  python contacts_exporter.py --sample-csv  (创建并处理示例 CSV 文件)")
        print()
        exporter.run()

使用方法

1. 准备工作

首先,需要安装必要的依赖库:

pip install pandas openpyxl

2. 获取手机通讯录文件

Android 手机: 打开通讯录应用 进入设置 → 导入/导出 → 导出到存储设备 选择导出为 VCF 或 CSV 格式 iPhone: 打开通讯录应用 进入设置 → 通讯录 → 导出 vCard

3. 运行程序

方式1:直接运行(交互式)
python contacts_exporter.py
方式2:通过命令行参数指定文件
# 处理 VCF 文件
python contacts_exporter.py contacts.vcf

# 处理 CSV 文件  
python contacts_exporter.py contacts.csv

# 生成示例文件并测试
python contacts_exporter.py --sample-vcf
python contacts_exporter.py --sample-csv

程序功能说明

支持多种格式:支持 VCF (vCard) 和 CSV 格式的通讯录文件 自动识别字段:能识别常见的联系人字段(姓名、电话、邮箱等) 智能解析
  • 自动区分手机号和固定电话
  • 支持多个电话号码和邮箱
  • 处理不同编码格式
数据预览:导出前显示部分联系人预览 错误处理:包含完整的异常处理机制

注意事项

由于手机系统的安全限制,Python 无法直接访问手机通讯录,需要先导出为文件 不同手机导出的 VCF/CSV 格式可能略有差异,程序已包含常见格式的解析逻辑 导出的 Excel 文件支持中文,确保使用 UTF-8 编码

扩展建议

如果需要直接从手机获取通讯录,可以考虑以下方案:

Android:使用 ADB 命令获取通讯录数据库 iPhone:使用 iTunes 备份后解析备份文件 云服务:如果使用 Google 通讯录或 iCloud,可通过 API 获取

这个工具提供了一个基础框架,您可以根据实际需求进行调整和扩展。