Как извлечь номера телефонов из Excel: решение проблем формата

Алексей Петров · · 10 мин чтения

Открываете Excel-файл с телефонными номерами. Вместо 79001234567 видите 7.9E+10. Часть номеров с префиксом 8, часть с +7, часть без префикса вообще. В соседних ячейках — имена, адреса, email. Файл на пять листов, нужные данные разбросаны по всей книге. Знакомая картина?

Парсинг номеров из Excel — это не просто чтение ячеек. Excel активно «помогает», превращая номера в научную нотацию, отбрасывая ведущие нули, применяя автоформатирование. В этой статье разберём, почему Excel такой сложный, и как извлечь номера правильно.

Почему парсинг Excel отличается от CSV

CSV — это простой текстовый формат. Вы читаете файл построчно, и то что видите — то и получаете. Excel — это бинарный формат с метаданными, формулами, форматированием и логикой обработки данных.

Типичные проблемы Excel

Проблема Что происходит Пример
Научная нотация Длинные числа автоматически преобразуются 790012345677.9E+10
Потеря ведущих нулей Числа с нулём вначале усекаются 012345678901234567890
Автоформатирование дат Числа распознаются как даты 12-34-5612.34.2056
Тип данных ячейки Ячейка может быть текстом или числом Одно и то же значение хранится по-разному
Несколько листов Данные распределены по вкладкам Нужно обработать все листы
Объединённые ячейки Значение хранится только в первой ячейке Парсер читает пустоту вместо данных

Реальный пример

Вы получили Excel-файл от клиента. Открываете, видите:

| Имя      | Телефон          | Email               |
|----------|------------------|---------------------|
| Иван     | 7.9E+10          | ivan@example.com    |
| Мария    | +7 (900) 234-56-78| maria@example.com   |
| Пётр     | 9003456789       | petr@example.com    |

Первая строка — научная нотация. Вторая — текст с форматированием. Третья — число без префикса. При экспорте в CSV первая строка превратится в 79000000000 (потеря точности), вторая останется текстом, третья — числом без кода страны.

Ручное исправление в Excel

Самый простой, но трудоёмкий способ — исправить форматирование прямо в Excel перед экспортом.

Пошаговая инструкция

  1. Исправьте научную нотацию: - Выделите столбец с номерами - Щёлкните правой кнопкой → Формат ячеек - Выберите Текстовый формат - Вручную исправьте повреждённые значения
  2. Удалите форматирование: - Используйте функцию =ПОДСТАВИТЬ() для удаления скобок, пробелов, дефисов
  3. Приведите к единому формату: - Добавьте префикс 7, где его нет - Замените 8 на 7 в начале номеров
  4. Экспортируйте в CSV: - Файл → Сохранить как → CSV (UTF-8)

Формула для очистки номера

=ПОДСТАВИТЬ(ПОДСТАВИТЬ(ПОДСТАВИТЬ(ПОДСТАВИТЬ(ТЕКСТ(A2;"0");" ";"");"(";"");")";"");"-";"")

Эта формула:

Проблемы ручного подхода

Вердикт: годится только для маленьких файлов (до 1000 строк) с одним листом. Для остального — слишком медленно и рискованно.

Python с библиотекой openpyxl

Для программного чтения Excel-файлов в Python есть несколько библиотек. openpyxl — самая популярная для файлов .xlsx.

Простое чтение Excel

from openpyxl import load_workbook
import re

def extract_phones_from_excel(file_path):
    wb = load_workbook(file_path, data_only=True)
    phones = set()

    # Обрабатываем все листы
    for sheet in wb.worksheets:
        for row in sheet.iter_rows(values_only=True):
            for cell in row:
                if cell is None:
                    continue

                # Преобразуем в строку
                value = str(cell)

                # Извлекаем номера через regex
                pattern = r'(?:\+7|8|7)?[\s\-\(]?(?:\d{3})[\s\-\)]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}'
                matches = re.findall(pattern, value)

                for match in matches:
                    # Нормализуем номер
                    clean = re.sub(r'[^\d]', '', match)
                    if clean.startswith('89') and len(clean) == 11:
                        clean = '7' + clean[1:]
                    elif clean.startswith('9') and len(clean) == 10:
                        clean = '7' + clean

                    if len(clean) == 11 and clean.startswith('7'):
                        phones.add(clean)

    return phones

# Использование
phones = extract_phones_from_excel('data.xlsx')
with open('phones.csv', 'w', encoding='utf-8') as f:
    f.write('\n'.join(sorted(phones)))

print(f'Извлечено {len(phones)} уникальных номеров')

Обработка научной нотации

Если Excel уже сохранил номер как число, Python получит его в виде float: 79001234567.0. Это можно исправить:

def normalize_phone_from_excel(cell_value):
    """Обрабатывает номер, даже если он в научной нотации"""
    if cell_value is None:
        return None

    # Если это число (float или int)
    if isinstance(cell_value, (int, float)):
        # Преобразуем в строку без экспоненциальной записи
        phone = f'{cell_value:.0f}'
    else:
        phone = str(cell_value)

    # Убираем все кроме цифр
    phone = re.sub(r'[^\d]', '', phone)

    # Нормализуем формат
    if phone.startswith('89') and len(phone) == 11:
        phone = '7' + phone[1:]
    elif phone.startswith('9') and len(phone) == 10:
        phone = '7' + phone
    elif phone.startswith('79') and len(phone) == 11:
        pass
    else:
        return None

    return phone if len(phone) == 11 else None

Альтернатива с pandas

Библиотека pandas тоже умеет читать Excel, и часто удобнее для табличных данных:

import pandas as pd
import re

def extract_with_pandas(file_path):
    # Читаем все листы
    all_sheets = pd.read_excel(file_path, sheet_name=None, dtype=str)

    phones = set()

    for sheet_name, df in all_sheets.items():
        # Проходим по всем ячейкам
        for col in df.columns:
            for value in df[col].dropna():
                # Извлекаем номера
                pattern = r'(?:\+7|8|7)?[\s\-\(]?(?:\d{3})[\s\-\)]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}'
                matches = re.findall(pattern, str(value))

                for match in matches:
                    clean = re.sub(r'[^\d]', '', match)
                    # Нормализация аналогична normalize_phone_from_excel() выше
                    if clean.startswith('89') and len(clean) == 11:
                        clean = '7' + clean[1:]
                    elif clean.startswith('9') and len(clean) == 10:
                        clean = '7' + clean

                    if len(clean) == 11 and clean.startswith('7'):
                        phones.add(clean)

    return phones

Производительность Python

Тест на реальном файле — 5 листов, 50 тысяч ячеек, 12 тысяч номеров:

Библиотека Время загрузки Время парсинга Память
openpyxl 8,2 секунды 3,5 секунды ~250 МБ
pandas 3,1 секунды 2,8 секунды ~180 МБ

Вывод: pandas быстрее для чтения, но openpyxl даёт более низкоуровневый доступ к данным (полезно для сложных форматов).

Когда использовать Python

Подходит, если:

Не подходит, если:

Базальт — специализированный парсер

Базальт — десктопное приложение, заточенное под работу с телефонными базами. Умеет парсить Excel нативно, обрабатывая все проблемы формата автоматически.

Как извлечь номера в Базальте

  1. Откройте раздел «Парсинг Excel»
  2. Выберите .xlsx или .xls файл
  3. Укажите базовое название для выходных файлов
  4. Нажмите «Извлечь данные»
  5. Получите два файла: *_phones.csv и *_links.csv

Что делает Базальт автоматически

Базальт использует библиотеку node-xlsx и обрабатывает:

Как это работает (из исходников Базальта)

// Упрощённый код из parse-excel.ts
const workbook = xlsx.parse(buffer)

const phonesSet = new Set<string>()
const linksSet = new Set<string>()

// Обрабатываем все листы
for (const sheet of workbook) {
  for (const row of sheet.data) {
    for (const cell of row) {
      if (cell == null) continue

      const value = cell.toString()
      if (value.trim() === '') continue

      // Извлекаем телефоны
      const extracted = extractPhones(value)
      for (const item of extracted) {
        for (const phone of item.phone) {
          phonesSet.add(phone)
        }
      }

      // Извлекаем ссылки
      if (/https?:\/\//.test(value) || /\.[a-z]{2,}/.test(value)) {
        linksSet.add(value.trim())
      }
    }
  }
}

Ключевой момент: extractPhones() — это умная функция из @basalt/shared, которая парсит номера из любого текста, применяя нормализацию.

Уникальные возможности Базальта

Производительность Базальта

Тест на реальных данных — Excel-файл 15 МБ, 8 листов, 120 тысяч ячеек, 28 тысяч номеров:

Метод Время Клики Листы обработаны Нормализация
Ручное (Excel) ~2 часа ~200 Только активный Нет
Python openpyxl ~25 секунд 0 Все (код нужен) Да (код нужен)
Python pandas ~18 секунд 0 Все (код нужен) Да (код нужен)
Базальт ~16 секунд 3 Все Да

Подводные камни Excel-формата

Научная нотация необратима

Если Excel уже сохранил 79001234567 как 7.9E+10, точность потеряна. Float в Excel хранит только 15 значащих цифр, а номер состоит из 11. При обратном преобразовании последние цифры будут нулями: 79001230000.

Решение: работайте с исходным файлом до открытия в Excel, либо попросите отправителя экспортировать с форматом «Текст».

Ведущие нули в номерах

Если номер записан как 01234567890, Excel отбросит ноль: 1234567890. Это делает номер невалидным.

Решение: используйте парсеры, которые пытаются восстановить номер (например, добавляют 7 в начало, если длина 10 цифр).

Объединённые ячейки

Когда ячейки объединены, значение хранится только в первой ячейке диапазона. Парсер видит:

Ячейка A1: "79001234567"
Ячейка A2: null
Ячейка A3: null

Хотя визуально номер отображается на три строки.

Решение: обрабатывайте все ячейки, игнорируя null. Либо попросите отправителя разъединить ячейки перед экспортом.

Формулы вместо значений

Ячейка может содержать формулу =CONCATENATE("+7"; B2). При чтении вы можете получить либо формулу, либо вычисленное значение — зависит от настроек парсера.

Решение: используйте режим data_only=True (openpyxl) или аналогичный параметр в других библиотеках, чтобы читать результаты формул, а не сами формулы.

Работа с многолистовыми книгами

Типичная структура файла с клиентской базой:

Стратегии обработки

Вариант 1: Обработать все листы подряд

for sheet in workbook.worksheets:
    extract_phones(sheet)

Плюсы: ничего не пропустите. Минусы: можете захватить служебные листы со статистикой.

Вариант 2: Фильтровать по названию листа

exclude = ['Статистика', 'Графики', 'Итоги']
for sheet in workbook.worksheets:
    if sheet.title not in exclude:
        extract_phones(sheet)

Плюсы: исключаете ненужные данные. Минусы: нужно знать названия листов заранее.

Вариант 3: Обрабатывать всё, фильтровать по содержимому

for sheet in workbook.worksheets:
    phones = extract_phones(sheet)
    # Только валидные номера попадут в результат

Плюсы: универсально работает. Минусы: чуть медленнее из-за обработки лишних данных.

Базальт использует вариант 3: обрабатывает все листы, но в результат попадают только валидные номера.

Практические советы

  1. Работайте с копией файла — если Excel испортит данные, оригинал останется нетронутым.
  2. Не открывайте файл в Excel перед парсингом — Excel автоматически применяет форматирование при открытии, что может повредить данные.
  3. Проверяйте первый лист вручную — посмотрите на структуру данных, чтобы понять, что парсить.
  4. Считайте количество извлечённых номеров — если в файле визуально 10 тысяч строк, а извлеклось 500 номеров, что-то пошло не так.
  5. Сохраняйте результат в UTF-8 — избегайте проблем с кодировками при дальнейшей обработке.
  6. Дедуплицируйте результат — в разных листах могут быть одинаковые номера.

Что выбрать для разных задач

Маленький файл (до 1000 строк), один лист, разовая задача — исправьте в Excel вручную, экспортируйте в CSV. Быстрее, чем устанавливать инструменты.

Регулярная задача с предсказуемым форматом — напишите Python-скрипт. Один раз потратите час, потом будете запускать одной командой.

Сложная структура, несколько листов, разные форматы номеров — используйте Базальт. Обрабатывает все листы, нормализует номера, извлекает дополнительно ссылки, работает офлайн.

Большие файлы (больше 100 МБ) или интеграция с другими системами — Python с потоковой обработкой (chunked reading) или специализированные библиотеки для больших данных.

Главная проблема парсинга Excel — это не чтение файла, а обработка «особенностей» формата: научной нотации, потери ведущих нулей, автоформатирования. Специализированные инструменты типа Базальта решают эти проблемы автоматически, экономя время и снижая риск ошибок. Для разовых задач подойдёт ручная обработка, для регулярных — Python-скрипт, для максимальной скорости и удобства — готовое решение.