Fakerで作った見本CSV、company_emailの100行はどれも例示用ドメインではなかった

C001,中村 直樹,[email protected]。商品に同梱するサンプルCSVの1行目です。名前もアドレスも作り物なのに、abe.comは誰かが持っているドメインかもしれません。購入者が試しにメールを送ったら、届くのは見知らぬ他人です。
実際の販売や購入者対応の記録ではありません。Faker(ダミーの名前や住所を作るPythonのライブラリ)で100行のCSVを作り、メール欄のドメインを調べた検証メモです。
3つのメソッドで同じ100行を作る
環境は、uv 0.12.7、Python 3.14.7、Faker 40.44.0です。作業用のディレクトリで仮想環境を作ります。
mkdir sample-check && cd sample-check
uv venv -q .venv
uv pip install -q --python .venv/bin/python faker
インストール後にfaker.VERSIONで確かめたところ、Fakerは40.44.0、Pythonは3.14.7でした。
CSVを作るスクリプトはmake_sample.pyとして保存します。名前はfake.name()で固定し、メールだけをメソッドごとに差し替えます。seedも固定です。
make_sample.py の全文
import csv
import sys
from faker import Faker
mode = sys.argv[1] # company / free / default
fake = Faker("ja_JP")
Faker.seed(20261104)
makers = {
"company": fake.company_email,
"free": fake.free_email,
"default": fake.email,
}
with open(f"customers_{mode}.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["customer_id", "name", "email"])
for i in range(1, 101):
w.writerow([f"C{i:03d}", fake.name(), makers[mode]()])
print(f"customers_{mode}.csv: 100 rows")
for m in company free default; do .venv/bin/python make_sample.py $m; done
3つのCSVが100行ずつできます。
companyの先頭3行は次のとおりです。
customer_id,name,email
C001,中村 直樹,[email protected]
C002,小林 明美,[email protected]
C003,村上 洋介,[email protected]
defaultの1行目は、名前もローカル部分も同じまま[email protected]です。変わるのはドメインだけでした。
予約済みドメインかどうかを機械で見る
目で見て「それっぽい」と判断しても当てになりません。基準はRFC 2606です。example.com、example.net、example.orgと、.test、.example、.invalid、.localhostは、誰かへ割り当てない決まりです。この一覧の外のドメインは、見本から外す規則にしました。
check_domains.py の全文
import csv
import socket
import sys
from collections import Counter
RESERVED_EXACT = ("example.com", "example.net", "example.org")
RESERVED_SUFFIX = (".test", ".example", ".invalid", ".localhost")
def is_reserved(domain):
if domain in RESERVED_EXACT:
return True
if domain.endswith(tuple("." + d for d in RESERVED_EXACT)):
return True
return domain.endswith(RESERVED_SUFFIX)
def resolves(domain):
try:
socket.getaddrinfo(domain, None)
return True
except socket.gaierror:
return False
path = sys.argv[1]
with open(path, encoding="utf-8", newline="") as f:
rows = list(csv.DictReader(f))
domains = Counter(r["email"].rsplit("@", 1)[1] for r in rows)
bad = {d: n for d, n in domains.items() if not is_reserved(d)}
bad_rows = sum(bad.values())
print(f"{path}: rows={len(rows)} distinct_domains={len(domains)} non_reserved_rows={bad_rows}")
if bad:
live = [d for d in bad if resolves(d)]
print(f" non_reserved_domains={len(bad)} name_resolves={len(live)}")
for d, n in sorted(bad.items(), key=lambda x: (-x[1], x[0]))[:5]:
print(f" {d} x{n} resolves={resolves(d)}")
sys.exit(1 if bad else 0)
予約外のドメインが1つでもあれば終了コード1で止めます。resolvesはDNSで名前が引けるかを見るだけの補助で、合否には使っていません。
for m in company free default; do .venv/bin/python check_domains.py customers_$m.csv; echo "exit=$?"; done
customers_company.csv: rows=100 distinct_domains=70 non_reserved_rows=100
non_reserved_domains=70 name_resolves=49
yamamoto.com x4 resolves=True
abe.com x3 resolves=True
kobayashi.com x3 resolves=False
suzuki.com x3 resolves=True
tanaka.jp x3 resolves=False
exit=1
customers_free.csv: rows=100 distinct_domains=3 non_reserved_rows=100
non_reserved_domains=3 name_resolves=3
hotmail.com x37 resolves=True
yahoo.com x34 resolves=True
gmail.com x29 resolves=True
exit=1
customers_default.csv: rows=100 distinct_domains=3 non_reserved_rows=0
exit=0
company_emailは100行すべてが予約外でした。70種のドメインのうち、49種は名前が引けます。free_emailの3種はGmailなどの無料メールなので、当然です。通ったのはemail()だけ。
名前から安全は読み取れない
メソッド名だけ見ると、3つは同じ仲間です。ところがemailの引数にはsafe: bool = Trueがあり、デフォルトでexample.orgなどに寄せます。company_emailとfree_emailにはその引数がありません。
.venv/bin/python -c "
import inspect
from faker.providers.internet import Provider
print('email', inspect.signature(Provider.email))
print('company_email', inspect.signature(Provider.company_email))
print('free_email', inspect.signature(Provider.free_email))
"
email (self, safe: bool = True, domain: str | None = None) -> str
company_email (self) -> str
free_email (self) -> str
「ダミー用のライブラリだから、どれを呼んでも安全」とは言えませんでした。会社らしさを出したくてcompany_emailを選ぶほど、実在のドメインに近づきます。使うならemail()のデフォルトのままにするか、domain="example.com"のように明示します。作る側のスクリプトとは別に、検査を挟めば、予約外のドメインを公開前に見つけられます。
名前が引けない21種は、安全とは限らない
company_emailの70種のうち、名前が引けなかった21種はkobayashi.com、tanaka.jpなどです。これは「誰も持っていない」と同じ意味ではありません。登録済みでもAレコードを置いていない、このPCのDNSが一時的に答えなかった、という場合でもresolves=Falseになります。
数字がずれた件も書いておきます。名前を作らずcompany_email()を100回呼ぶだけの使い捨てスクリプトで同じseedを使うと、66ドメインのうち51種で名前が引けました。上の結果は70と49です。乱数の消費順が変わるので、同じseedでも、呼び出しの組み合わせが違えば行が変わります。
だから、検査は同梱するCSVそのものに回します。スクリプトを回し直して確かめた気になるのは危険です。seedを固定しても、結果は呼び出し方で変わります。
今回見ていないこと
- 名前列: 同姓同名の人は実在しうるので、
fake.name()の出力は検査していません - メールのローカル部分:
itoakiraのような部分も実在の誰かと重なりえます。ドメインが予約済みなら、届く先がないので許容しました - 電話番号と住所: Fakerは作れますが、今回は一切確認していません
- AIの生成物: 今回はFakerだけです。AIに「ダミーの顧客リストを作って」と頼んだ出力は試していません
AIに作らせたCSVにも同じ検査を当てられます。生成元が変わればドメインの出方も変わるのか。そこはまだ分かりません。
出典
- RFC 2606: Reserved Top Level DNS Names —
.test、.example、.invalid、.localhostと、example.com、example.net、example.orgの予約を確認しました。 - RFC 6761: Special-Use Domain Names —
exampleとinvalidなどを特別扱いするドメインとして確認しました。 - Faker: faker.providers.internet —
email、company_email、free_emailの提供元を確認しました。


