2025, Sep 18 20:02

Как нормализовать адреса из объявлений при веб-скрапинге на Python

Пошаговая нормализация адресов для веб-скрапинга объявлений о недвижимости на Python: единые разделители, суффиксы улиц, удаление дублей. Пример кода.

Скрапинг объявлений о недвижимости кажется простым до тех пор, пока не встаёт вопрос нормализации адресов. Мелкие несоответствия накапливаются: в одних записях вместо запятых встречаются вертикальные черты, в других — дублируется улица с разными суффиксами, а один и тот же адрес может появляться дважды в слегка отличающихся формах. Типичный пример — строка вроде

"747 Geary Street, 747 Geary St, Oakland, CA 94609"

которую нужно свести к одному аккуратному адресу без повторяющихся частей.

Пример на Python, который очищает примеры входных данных

import re

def unify_address(text):
    suffix_aliases = {
        r'\bStreet\b': 'St',
        r'\bAvenue\b': 'Ave',
        r'\bRoad\b': 'Rd',
        r'\bBoulevard\b': 'Blvd',
        r'\bDrive\b': 'Dr',
        r'\bLane\b': 'Ln',
        r'\bCourt\b': 'Ct',
    }
    draft = text.replace('|', ',')
    for rex, short in suffix_aliases.items():
        draft = re.sub(rex, short, draft, flags=re.IGNORECASE)
    segments = [frag.strip() for frag in draft.split(',')]
    pruned = []
    for idx, frag in enumerate(segments):
        if not any(idx < j and frag in other for j, other in enumerate(segments)):
            pruned.append(frag)
    return ', '.join(pruned)

addresses_to_clean = [
    'The Gantry | 1340 3rd St, San Francisco, CA',
    '845 Sutter, 845 Sutter St APT 509, San Francisco, CA',
    '1350 Washington Street | 1350 Washington St, San Francisco, CA',
    'Parkmerced 3711 19th Ave, San Francisco, CA',
    '747 Geary Street, 747 Geary St, Oakland, CA 94609'
]

normalized = [unify_address(item) for item in addresses_to_clean]
for src, dst in zip(addresses_to_clean, normalized):
    print(f"Original: {src}")
    print(f"Cleaned:  {dst}")
    print()

В результате получаются очищенные строки, например:

"The Gantry, 1340 3rd St, San Francisco, CA"

"845 Sutter St APT 509, San Francisco, CA"

"1350 Washington St, San Francisco, CA"

"Parkmerced 3711 19th Ave, San Francisco, CA"

"747 Geary St, Oakland, CA 94609"

Почему эти адреса трудно очищать

Простые строковые инструменты дают сбой, потому что дубликаты не совпадают побайтно. В одном варианте может быть “Street”, в другом — “St”. Если разделить по запятым и пытаться убрать повторы наивной проверкой равенства, дубликат останется. Если пойти «в лоб» с replace(), легко задеть корректные части вроде города или штата. Иными словами, дело не только в разделителях; это задача нормализации и проверки на вхождение.

Подход, который работает на этих примерах

Решение проходит в три осознанных шага. Сначала приводим разделители к одному виду, заменяя вертикальную черту на запятую. Это унифицирует способ разбиения адреса на компоненты. Затем нормализуем распространённые суффиксы улиц до единой формы с помощью регулярных выражений. Когда “Street” и “St” приведены к одному виду, потенциальные дубликаты становятся заметны. Наконец, убираем повторяющиеся части, проверяя вхождение среди фрагментов, разделённых запятыми. Если более ранний фрагмент полностью содержится в более позднем, ранний отбрасывается, а остаётся более информативный фрагмент.

Это правило вхождения — ключевое: в адресе “845 Sutter, 845 Sutter St APT 509, San Francisco, CA” короткий “845 Sutter” полностью содержится в полном “845 Sutter St APT 509”, поэтому сохраняем только второй. То же самое касается случая “Washington Street / Washington St” после нормализации.

Зачем это знать

Данные из реального мира далеки от идеала, и веб-скрапинг мгновенно это показывает. Нормализация адресов — классический пример, где мелкие несоответствия подрывают последующие задачи: сопоставление, агрегирование или геокодирование. Хотя ИИ способен гибче справляться с редкими выбросами, чёткий детерминированный проход вроде этого укрепляет базовые навыки и даёт понятный базовый ориентир, который можно осмысленно расширять. Когда тестовый набор растёт, вы можете добавлять больше вариантов суффиксов или подправлять правила вхождения, не меняя общую структуру.

Выводы

Сначала сделайте разделители единообразными, затем сведите близкие варианты к одной записи и только после этого удаляйте дубликаты по вхождению. Такая последовательность предотвращает чрезмерно агрессивные замены и помогает не потерять важный контекст вроде города, штата или ZIP. Для приведённых примеров приём даёт компактные, читаемые адреса и демонстрирует поддерживаемый способ очистки спарсенного текста без упора на одиночный split() или replace().

Статья основана на вопросе на StackOverflow от Adamzam15 и ответе André.