Примечание
Я больше знаком с JavaScript, чем с Python, и основная логика одинакова независимо (синтаксис отличается), поэтому я написал здесь свои решения на JavaScript. Не стесняйтесь переводить на Python.
Выпуск
Этот вопрос немного сложнее, чем простой однострочный сценарий или регулярное выражение, но в зависимости от конкретных требований вы можете сойтись с чем-то элементарным.
Для начала, анализ электронной почты не сводится к одному регулярному выражению. Этот веб-сайт содержит несколько примеров регулярных выражений, которые будут соответствовать «многим» электронным письмам, но объясняет компромиссы (сложность и точность) и включает в себя стандартное регулярное выражение RFC 5322, которое теоретически должно соответствовать любое электронное письмо с последующим абзацем, почему вы не должны его использовать. Однако даже , что регулярное выражение предполагает, что доменное имя в форме IP-адреса может состоять только из кортежа из четырех целых чисел в диапазоне от 0 до 255 - это не позволяет для IPv6
Даже что-то простое:
{a, b}@domain.com
Может быть отключен, потому что технически в соответствии со спецификацией адреса электронной почты адрес электронной почты может содержать ЛЮБОЙ ASCII-символы, заключенные в кавычки. Ниже приведен действительный (один) адрес электронной почты:
"{a, b}"@domain.com
Для точного разбора электронной почты потребуется, чтобы вы читали символы по одной букве за раз и создавали конечный автомат, чтобы отслеживать, находитесь ли вы в двойных кавычках, в фигурной скобке, перед @
, после @
, парсинга доменного имени, парсинга IP-адреса и т. д. Таким образом, вы могли бы токенизировать адрес, найти свой фигурный скобочный токен и проанализировать его независимо.
Нечто рудиментарное
Регулярные выражения - это не способ обеспечить 100% точность и поддержку всех электронных писем, *, особенно *, если вы хотите поддерживать более одной электронной почты в одной строке. Но мы начнем с них и попробуем построить оттуда.
Вы, вероятно, пытались использовать регулярное выражение вроде:
/\{(([^,]+),?)+\}\@(\w+\.)+[A-Za-z]+/
- Соответствует одной фигурной скобке ...
- Вслед за одним или несколькими случаями:
- Один или несколько не запятых символов ...
- С последующим нулем или одной запятой
- За ним следует одна закрывающая фигурная скобка ...
- сопровождается одним
@
- Вслед за одним или несколькими случаями:
- Один или несколько символов "слова" ...
- За ним следует один
.
- Вслед за одним или несколькими буквенными символами
Это должно примерно соответствовать виду:
{one, two}@domain1.domain2.toplevel
Это обрабатывает проверки , далее идет вопрос извлечения всех действительных сообщений электронной почты. Обратите внимание, что у нас есть два набора скобок в именной части адреса электронной почты, которые являются вложенными: (([^,]+),?)
. Это вызывает проблемы для нас. Многие движки регулярных выражений не знают, как вернуть совпадения в этом случае. Рассмотрим, что происходит, когда я запускаю это в JavaScript с помощью консоли разработчика Chrome:
var regex = /\{(([^,]+),?)+\}\@(\w+\.)+[A-Za-z]+/
var matches = "{one, two}@domain.com".match(regex)
Array(4) [ "{one, two}@domain.com", " two", " two", "domain." ]
Ну, это не так. Он нашел two
дважды, но не нашел one
один раз! Чтобы это исправить, нам нужно устранить вложение и сделать это в два этапа.
var regexOne = /\{([^}]+)\}\@(\w+\.)+[A-Za-z]+/
"{one, two}@domain.com".match(regexOne)
Array(3) [ "{one, two}@domain.com", "one, two", "domain." ]
Теперь мы можем использовать совпадение и анализировать это отдельно:
// Note: It's important that this be a global regex (the /g modifier) since we expect the pattern to match multiple times
var regexTwo = /([^,]+,?)/g
var nameMatches = matches[1].match(regexTwo)
Array(2) [ "one,", " two" ]
Теперь мы можем обрезать их и получить наши имена:
nameMatches.map(name => name.replace(/, /g, "")
nameMatches
Array(2) [ "one", "two" ]
Для построения «доменной» части электронной почты нам понадобится аналогичная логика для всего, что есть после @
, так как это имеет потенциал для повторов, так же как у части имени был потенциал для повторов. Наш финальный код (в JavaScript) может выглядеть примерно так (вам придется конвертировать в Python самостоятельно):
function getEmails(input)
{
var emailRegex = /([^@]+)\@(.+)/;
var emailParts = input.match(emailRegex);
var name = emailParts[1];
var domain = emailParts[2];
var nameList;
if (/\{.+\}/.test(name))
{
// The name takes the form "{...}"
var nameRegex = /([^,]+,?)/g;
var nameParts = name.match(nameRegex);
nameList = nameParts.map(name => name.replace(/\{|\}|,| /g, ""));
}
else
{
// The name is not surrounded by curly braces
nameList = [name];
}
return nameList.map(name => `${name}@${domain}`);
}
Линии для нескольких электронных писем
Здесь все становится сложнее, и нам нужно принять немного меньшую точность, если мы не хотим создавать полноценный лексер / токенизатор.Поскольку наши электронные письма содержат запятые (в поле имени), мы не можем точно разделить запятые - если эти запятые не находятся в фигурных скобках.С моим знанием регулярных выражений, я не знаю, легко ли это сделать.Это может быть возможно с операторами lookahead или lookbehind, но кто-то еще должен будет заполнить меня по этому вопросу.
Однако с помощью регулярных выражений можно легко найти блок текста, содержащий постамперсандзапятая.Что-то вроде: @[^@{]+?,
В строке a@b.com, c@d.com
это будет соответствовать всей фразе @b.com,
- но важно то, что это дает нам место для разбиения нашей строки.Сложный момент - выяснить, как разбить вашу строку здесь.Что-то вроде этого будет работать большую часть времени:
var emails = "a@b.com, c@d.com"
var matches = emails.match(/@[^@{]+?,/g)
var split = emails.split(matches[0])
console.log(split) // Array(2) [ "a", " c@d.com" ]
split[0] = split[0] + matches[0] // Add back in what we split on
Это потенциальная ошибка, если у вас есть два письма в списке с одним и тем же доменом:
var emails = "a@b.com, c@b.com, d@e.com"
var matches = emails.match(@[^@{]+?,/g)
var split = emails.split(matches[0])
console.log(split) // Array(3) [ "a", " c", " d@e.com" ]
split[0] = split[0] + matches[0]
console.log(split) // Array(3) [ "a@b.com", " c", " d@e.com" ]
Но опять же, без построения лексера / токенизатора мы допускаем, что наше решение будет работать только для большинства случаев и не всех.
Однако, поскольку задачаразделить одну строку на несколько электронных писем проще, чем погрузиться в электронную почту, извлечь имя и проанализировать имя: мы можем написать действительно глупый лексер только для этой части:
var inBrackets = false
var emails = "{a, b}@c.com, d@e.com"
var split = []
var lastSplit = 0
for (var i = 0; i < emails.length; i++)
{
if (inBrackets && emails[i] === "}")
inBrackets = false;
if (!inBrackets && emails[i] === "{")
inBrackets = true;
if (!inBrackets && emails[i] === ",")
{
split.push(emails.substring(lastSplit, i))
lastSplit = i + 1 // Skip the comma
}
}
split.push(emails.substring(lastSplit))
console.log(split)
Опять же, это не будет идеальным решением, потому что адрес электронной почты может существовать следующим образом:
","@domain.com
Но, на 99%случаев использования этого простого лексера будет достаточно, и теперь мы можем создать «обычно работающее, но не идеальное» решение, подобное следующему:
function getEmails(input)
{
var emailRegex = /([^@]+)\@(.+)/;
var emailParts = input.match(emailRegex);
var name = emailParts[1];
var domain = emailParts[2];
var nameList;
if (/\{.+\}/.test(name))
{
// The name takes the form "{...}"
var nameRegex = /([^,]+,?)/g;
var nameParts = name.match(nameRegex);
nameList = nameParts.map(name => name.replace(/\{|\}|,| /g, ""));
}
else
{
// The name is not surrounded by curly braces
nameList = [name];
}
return nameList.map(name => `${name}@${domain}`);
}
function splitLine(line)
{
var inBrackets = false;
var split = [];
var lastSplit = 0;
for (var i = 0; i < line.length; i++)
{
if (inBrackets && line[i] === "}")
inBrackets = false;
if (!inBrackets && line[i] === "{")
inBrackets = true;
if (!inBrackets && line[i] === ",")
{
split.push(line.substring(lastSplit, i));
lastSplit = i + 1;
}
}
split.push(line.substring(lastSplit));
return split;
}
var line = "{a.b, c.d, e.f}@uni.somewhere, {x.y, z.k}@edu.com";
var emails = splitLine(line);
var finalList = [];
for (var i = 0; i < emails.length; i++)
{
finalList = finalList.concat(getEmails(emails[i]));
}
console.log(finalList);
// Outputs: [ "a.b@uni.somewhere", "c.d@uni.somewhere", "e.f@uni.somewhere", "x.y@edu.com", "z.k@edu.com" ]
Если вы хотите попробовать и реализовать полное решение лексера / токенизатора,вы можете посмотреть на простой / тупой лексер, я покупаюЭто как отправная точка.Общая идея заключается в том, что у вас есть конечный автомат (в моем случае у меня было только два состояния: inBrackets
и !inBrackets
), и вы читаете по одному письму за раз, но интерпретируете его по-разному в зависимости от текущего состояния.