Вывести список контактов из файла .vcf (VCARD)
Фрагмент файла с расширением .vcf (VCARD)
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=D0=9C=D0=A2=D0=A1;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=9C=D0=A2=D0=A1
TEL;CELL;PREF:+78002500123
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=D0=A1=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D1=81=D0=BF=D0=B0=D1=81=D0=B5=
=D0=BD=D0=B8=D1=8F;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=A1=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D1=81=D0=BF=D0=B0=D1=81=D0=B5=
=D0=BD=D0=B8=D1=8F
TEL;CELL;PREF:112
END:VCARD
Мой код
import vobject
with open('test.vcf') as source_file:
vcf_read_components = vobject.readComponents(source_file)
for item in vcf_read_components:
print(item)
Происходит ошибка парсинга
Traceback (most recent call last):
File "/home/xxx/experiments/book.py", line 5, in <module>
for item in vcf_read_components:
File "/home/xxx/experiments/venv/lib/python3.11/site-packages/vobject/base.py", line 1166, in readComponents
vline = textLineToContentLine(line, n)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/xxx/experiments/venv/lib/python3.11/site-packages/vobject/base.py", line 984, in textLineToContentLine
return ContentLine(*parseLine(text, n), **{"encoded": True, "lineNumber": n})
^^^^^^^^^^^^^^^^^^
File "/home/xxx/experiments/venv/lib/python3.11/site-packages/vobject/base.py", line 864, in parseLine
raise ParseError("Failed to parse line: {0!s}".format(line), lineNumber)
vobject.base.ParseError: At line 19: Failed to parse line: =D0=BD=D0=B8=D1=8F;;;
<VCARD| [<VERSION{}2.1>, <FN{'CHARSET': ['UTF-8']}МТС>, <N{'CHARSET': ['UTF-8']} МТС >, <TEL{}+78002500123>]>
Уточнение от 15.11.24, когда у контакта несколько номеров.
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=94=D0=BE=D1=80=D1=84;=D0=9C=D0=B0=D0=BA=D1=81;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=9C=D0=B0=D0=BA=D1=81=20=D0=94=D0=BE=D1=80=D1=84
TEL;CELL:+79128051234
TEL;CELL:+79097484321
END:VCARD
import vobject
with open('test.vcf') as source_file:
vcf_read_components = vobject.readComponents(source_file, allowQP=True)
for item in vcf_read_components:
print(item)
print(item.fn.value)
print(item.tel.value)
item.tel.value
возвращает только первый из них
<VCARD| [<VERSION{}2.1>, <FN{'CHARSET': ['UTF-8']}Макс Дорф>, <N{'CHARSET': ['UTF-8']} Макс Дорф >, <TEL{}+79128051234>, <TEL{}+79097484321>]>
Макс Дорф
+79128051234
Ответы (3 шт):
Автор решения: Oopss
→ Ссылка
=D0=A1=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D1=81=D0=BF=D0=B0=D1=81=D0=B5 ->= = <-Здесь умирает парсер D0=BD=D0=B8=D1=8F;;;
import vobject
import re
s = '''BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=D0=9C=D0=A2=D0=A1;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=9C=D0=A2=D0=A1
TEL;CELL;PREF:+78002500123
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;=D0=A1=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D1=81=D0=BF=
=D0=B0=D1=81=D0=B5=D0=BD=D0=B8=D1=8F;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=A1=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D1=81=D0=BF=
=D0=B0=D1=81=D0=B5=D0=BD=D0=B8=D1=8F
TEL;CELL;PREF:112
END:VCARD
BEGIN:VCARD
VERSION:2.1
N:;0423;;;
FN:;0423
TEL;CELL:+79272686770
TEL:+79277767901
X-CLASS:private
X-CATEGORIES:
REV:20121127T052113Z
END:VCARD
BEGIN:VCARD
VERSION:2.1
N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=94=D0=BE=D1=80=D1=84;=D0=9C=D0=B0=D0=BA=D1=81;;;
FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=D0=9C=D0=B0=D0=BA=D1=81=20=D0=94=D0=BE=D1=80=D1=84
TEL;CELL:+79128051234
TEL;CELL:+79097484321
END:VCARD'''
# Регулярное выражение для поиска частей между BEGIN:VCARD и END:VCARD
pattern = re.compile(r'(BEGIN:VCARD.*?END:VCARD)', re.DOTALL)
pattern_remove_eq = re.compile(r'=\n=')
s = pattern_remove_eq.sub('=', s)
# Разделение строки на части
vcard_parts = pattern.findall(s)
# Вывод каждой части
for i, part in enumerate(vcard_parts, 1):
print(i, '------------')
v = vobject.readOne(part)
v.prettyPrint()
print(v.n.value)
print(v.fn.value)
if len(v.tel_list) > 1:
#Списком
print(f'Список {v.tel_list}')
#Или так по одному
for t in v.tel_list:
print(f'Каждый {t.value}')
else:
print(v.tel.value)
1 ------------
VCARD
VERSION: 2.1
N: МТС
params for N:
CHARSET ['UTF-8']
FN: МТС
params for FN:
CHARSET ['UTF-8']
TEL: +78002500123
МТС
МТС
+78002500123
2 ------------
VCARD
VERSION: 2.1
N: Служба спасения
params for N:
CHARSET ['UTF-8']
FN: Служба спасения
params for FN:
CHARSET ['UTF-8']
TEL: 112
Служба спасения
Служба спасения
112
3 ------------
VCARD
VERSION: 2.1
N: 0423
FN: ;0423
TEL: +79272686770
TEL: +79277767901
X-CLASS: private
X-CATEGORIES:
REV: 20121127T052113Z
0423
;0423
Список [<TEL{}+79272686770>, <TEL{}+79277767901>]
Каждый +79272686770
Каждый +79277767901
4 ------------
VCARD
VERSION: 2.1
N: Макс Дорф
params for N:
CHARSET ['UTF-8']
FN: Макс Дорф
params for FN:
CHARSET ['UTF-8']
TEL: +79128051234
TEL: +79097484321
Макс Дорф
Макс Дорф
Список [<TEL{}+79128051234>, <TEL{}+79097484321>]
Каждый +79128051234
Каждый +79097484321
Автор решения: strawdog
→ Ссылка
Для решения подобной проблемы обычно достаточно разрешить обработку quoted-printable
данных:
vobject.readComponents(source_file, allowQP=True)
Автор решения: ykoavlil
→ Ссылка
Учел советы (еще раз всем спасибо!) и немного дополнил. Привожу код с комментариями:
import vobject
import datetime
with open('test.vcf') as source_file:
contacts = vobject.readComponents(source_file, allowQP=True)
for contact in contacts:
# Имя, отчество и фамилия
print(contact.fn.value)
# Если у контакта указан день рождения выводим его в российском формате даты (DD.MM.YYYY)
# Через разницу текущая дата и день рождения определяем сколько лет
# Обрабатываем исключение если данного поля нет
try:
birthday = datetime.date.fromisoformat(contact.bday.value)
today = datetime.date.today()
years = (today - birthday).days // 365
print(f'{birthday.strftime("%d.%m.%Y")} ({years} лет)')
except AttributeError:
pass
# Номеров телефонов может быть несколько выводим их все
for phone_number in contact.tel_list:
print(phone_number.value)
# На основе типа определяем просто его вывести (если строка) или нужно склеивать из строк (список)
# Обрабатываем исключение если его нет
try:
for address in contact.adr_list:
street = address.value.street
if isinstance(street, str):
print(street)
else:
print(''.join(street))
except AttributeError:
pass
print()
Результат:
Василий Петрович
10.12.1964 (59 лет)
+11111111111
Иван Иванович Иванов
+22222222222
+33333333333
Ленина 1 кв. 1
Октябрьская 1 кв. 1