Как извлечь номера телефонов из Excel: решение проблем формата
Открываете Excel-файл с телефонными номерами. Вместо 79001234567 видите 7.9E+10. Часть номеров с префиксом 8, часть с +7, часть без префикса вообще. В соседних ячейках — имена, адреса, email. Файл на пять листов, нужные данные разбросаны по всей книге. Знакомая картина?
Парсинг номеров из Excel — это не просто чтение ячеек. Excel активно «помогает», превращая номера в научную нотацию, отбрасывая ведущие нули, применяя автоформатирование. В этой статье разберём, почему Excel такой сложный, и как извлечь номера правильно.
Почему парсинг Excel отличается от CSV
CSV — это простой текстовый формат. Вы читаете файл построчно, и то что видите — то и получаете. Excel — это бинарный формат с метаданными, формулами, форматированием и логикой обработки данных.
Типичные проблемы Excel
| Проблема | Что происходит | Пример |
|---|---|---|
| Научная нотация | Длинные числа автоматически преобразуются | 79001234567 → 7.9E+10 |
| Потеря ведущих нулей | Числа с нулём вначале усекаются | 01234567890 → 1234567890 |
| Автоформатирование дат | Числа распознаются как даты | 12-34-56 → 12.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 перед экспортом.
Пошаговая инструкция
- Исправьте научную нотацию: - Выделите столбец с номерами - Щёлкните правой кнопкой → Формат ячеек - Выберите Текстовый формат - Вручную исправьте повреждённые значения
- Удалите форматирование:
- Используйте функцию
=ПОДСТАВИТЬ()для удаления скобок, пробелов, дефисов - Приведите к единому формату: - Добавьте префикс 7, где его нет - Замените 8 на 7 в начале номеров
- Экспортируйте в CSV: - Файл → Сохранить как → CSV (UTF-8)
Формула для очистки номера
=ПОДСТАВИТЬ(ПОДСТАВИТЬ(ПОДСТАВИТЬ(ПОДСТАВИТЬ(ТЕКСТ(A2;"0");" ";"");"(";"");")";"");"-";"")
Эта формула:
ТЕКСТ(A2;"0")— преобразует число в текст без экспоненциальной записиПОДСТАВИТЬ()— удаляет пробелы, скобки, дефисы
Проблемы ручного подхода
- Потеря данных — если Excel уже преобразовал
79001234567в7.9E+10, точные цифры потеряны безвозвратно - Время — на файл с 10 тысячами строк уйдёт час работы
- Ошибки — легко пропустить столбец или лист
- Не работает с несколькими листами — нужно обрабатывать каждый лист отдельно
Вердикт: годится только для маленьких файлов (до 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
Подходит, если:
- Нужна сложная логика обработки (фильтрация, валидация, трансформация)
- Файлы регулярно поступают в одном и том же формате
- Требуется интеграция с другими системами (API, базы данных)
- Вы умеете программировать на Python
Не подходит, если:
- Задача разовая, и писать скрипт дольше, чем сделать вручную
- Файлы очень большие (больше 500 МБ) — Python загружает всё в память
- Нет опыта программирования — порог входа высокий
Базальт — специализированный парсер
Базальт — десктопное приложение, заточенное под работу с телефонными базами. Умеет парсить Excel нативно, обрабатывая все проблемы формата автоматически.
Как извлечь номера в Базальте
- Откройте раздел «Парсинг Excel»
- Выберите .xlsx или .xls файл
- Укажите базовое название для выходных файлов
- Нажмите «Извлечь данные»
- Получите два файла:
*_phones.csvи*_links.csv
Что делает Базальт автоматически
Базальт использует библиотеку node-xlsx и обрабатывает:
- Все листы книги — не только активный, а все вкладки
- Все ячейки — независимо от структуры таблицы
- Числа и текст — корректно читает оба типа данных
- Научную нотацию — преобразует обратно в полный номер
- Разные форматы номеров — парсит +7, 8, без префикса, со скобками, дефисами
- Извлекает ссылки — бонусом создаёт файл с URL, если они есть в Excel
Как это работает (из исходников Базальта)
// Упрощённый код из 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, которая парсит номера из любого текста, применяя нормализацию.
Уникальные возможности Базальта
- Обработка ВСЕХ листов — не нужно экспортировать каждый лист отдельно
- Два выходных файла — телефоны и ссылки разделены
- Автоматическая дедупликация — использует Set, гарантирует уникальность
- Нормализация к 79XXXXXXXXX — все номера в едином формате
- Работает офлайн — данные не загружаются на сервер
- История операций — можно вернуться к предыдущим файлам
Производительность Базальта
Тест на реальных данных — 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: «Активные клиенты»
- Лист 2: «Архив»
- Лист 3: «Холодная база»
- Лист 4: «Отказы»
- Лист 5: «Статистика» (итоговые данные, графики)
Стратегии обработки
Вариант 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: обрабатывает все листы, но в результат попадают только валидные номера.
Практические советы
- Работайте с копией файла — если Excel испортит данные, оригинал останется нетронутым.
- Не открывайте файл в Excel перед парсингом — Excel автоматически применяет форматирование при открытии, что может повредить данные.
- Проверяйте первый лист вручную — посмотрите на структуру данных, чтобы понять, что парсить.
- Считайте количество извлечённых номеров — если в файле визуально 10 тысяч строк, а извлеклось 500 номеров, что-то пошло не так.
- Сохраняйте результат в UTF-8 — избегайте проблем с кодировками при дальнейшей обработке.
- Дедуплицируйте результат — в разных листах могут быть одинаковые номера.
Что выбрать для разных задач
Маленький файл (до 1000 строк), один лист, разовая задача — исправьте в Excel вручную, экспортируйте в CSV. Быстрее, чем устанавливать инструменты.
Регулярная задача с предсказуемым форматом — напишите Python-скрипт. Один раз потратите час, потом будете запускать одной командой.
Сложная структура, несколько листов, разные форматы номеров — используйте Базальт. Обрабатывает все листы, нормализует номера, извлекает дополнительно ссылки, работает офлайн.
Большие файлы (больше 100 МБ) или интеграция с другими системами — Python с потоковой обработкой (chunked reading) или специализированные библиотеки для больших данных.
Главная проблема парсинга Excel — это не чтение файла, а обработка «особенностей» формата: научной нотации, потери ведущих нулей, автоформатирования. Специализированные инструменты типа Базальта решают эти проблемы автоматически, экономя время и снижая риск ошибок. Для разовых задач подойдёт ручная обработка, для регулярных — Python-скрипт, для максимальной скорости и удобства — готовое решение.