2가지 방법으로 성공했습니다.
방법 1
윈도우 컴퓨터에서 iCloud 제어판을 설치하고, Outlook에 연락처 동기화를 한 다음, Outlook에서 연락처를 삭제해냈습니다.
(어제는 안되더니, 오늘은 작동했습니다)
방법2
Gemini에게 방법을 찾아달라고 했고, Gemini가 작성해준 Python코드로 지워냈습니다.
(코드 안에, 본인의 애플 계정(이메일 주소)과 앱 전용 암호를 넣었습니다)
import requests
from requests.auth import HTTPBasicAuth
import xml.etree.ElementTree as ET
# ==========================================# 💡 본인의 정보를 정확히 입력하세요!# ==========================================
APPLE_ID = "your_apple_id@icloud.com"# Apple 계정 이메일
APP_SPECIFIC_PW = "xxxx-xxxx-xxxx-xxxx"# 앱 전용 비밀번호 (하이픈 포함)# ==========================================
auth = HTTPBasicAuth(APPLE_ID, APP_SPECIFIC_PW)
print("1. iCloud CardDAV 서버 인증 시도 중...")
# 1) 기본 연결 시도
base_url = "https://contacts.icloud.com/"
res = requests.request("PROPFIND", base_url, auth=auth, headers={'Depth': '0'})
# Redirection 또는 C****er Host 대응if res.status_code in [301, 302, 307] and'Location'in res.headers:
base_url = res.headers['Location']
res = requests.request("PROPFIND", base_url, auth=auth, headers={'Depth': '0'})
if res.status_code notin [200, 207]:
print(f"❌ 로그인 실패 (응답 코드: {res.status_code})")
print("👉 Apple ID 이메일과 '앱 전용 비밀번호'가 올바른지 다시 확인해 주세요.")
print("👉 Apple 계정 일반 비밀번호가 아닌, appleid.apple.com에서 생성한 16자리 암호여야 합니다.")
exit()
# Principal 및 주소록 경로 탐색
user_id = res.headers.get('X-NS-USER-ID')
ifnot user_id:
user_id = APPLE_ID.split('@')[0]
# 호스트 추출
server_host = res.url.rsplit('/', 1)[0] if res.url.endswith('/') else res.url
addressbook_url = f"{server_host}/{user_id}/dav/addressbooks/card/"
print("2. 25,000개 연락처 목록 가져오는 중... (데이터가 많아 10~30초 정도 소요될 수 있습니다)")
propfind_data = """<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
<D:prop><D:getetag/></D:prop>
</D:propfind>"""
res = requests.request("PROPFIND", addressbook_url, auth=auth, data=propfind_data, headers={'Depth': '1'})
if res.status_code notin [200, 207]:
# 대체 경로 시도
addressbook_url = f"https://contacts.icloud.com/{user_id}/dav/addressbooks/card/"
res = requests.request("PROPFIND", addressbook_url, auth=auth, data=propfind_data, headers={'Depth': '1'})
try:
tree = ET.fromstring(res.text)
urls = []
for response in tree.findall('{DAV:}response'):
href_node = response.find('{DAV:}href')
if href_node isnotNoneand href_node.text and href_node.text.endswith('.vcf'):
href = href_node.text
if href.startswith('http'):
urls.append(href)
else:
urls.append("https://contacts.icloud.com" + href)
total_count = len(urls)
print(f"✅ 총 {total_count}개의 연락처를 성공적으로 불러왔습니다.")
if total_count == 0:
print("삭제할 연락처가 없습니다.")
exit()
confirm = input(f"\n⚠️ 정말로 {total_count}개의 연락처를 전량 삭제하시겠습니까? (y/n): ")
if confirm.lower() != 'y':
print("작업이 취소되었습니다.")
exit()
print("\n3. 삭제 작업을 시작합니다...")
deleted = 0for url in urls:
del_res = requests.delete(url, auth=auth)
if del_res.status_code in [200, 204]:
deleted += 1if deleted % 500 == 0or deleted == total_count:
print(f"진행 상황: {deleted} / {total_count} 삭제 완료")
else:
print(f"삭제 실패 항목 발생: {url}")
print(f"\n🎉 작업 완료! 총 {deleted}개의 연락처가 성공적으로 삭제되었습니다.")
except Exception as e:
print(f"❌ 데이터 파싱 중 오류 발생: {e}")