MENU

取得データの欠損検知|空欄・型・重複を確認

当ページのリンクには広告(PR)が含まれていることがあります。
取得データの欠損を検知する方法|空欄・件数・型をチェックのアイキャッチ
お悩み女子

取得件数は出ているのに、集計すると数字が合いません。空欄や同じデータを見つけるには、どこを見ればよいですか。

取得データの欠損は、保存や集計の前に列名・件数・値の型・識別子の重複を検査すると見つけやすくなります。空欄を0に置き換えたり、同じ名前をまとめたりする前に、何が欠けたのかを確認します。

商品データの架空CSVを使い、欠損と重複を検出して、不一致があれば出力を止める例を示します。Windows 11とPython 3.12.10でコードを実行しています。外部サイトからの取得は行わず、検査する条件を手元のデータで確かめています。

この記事でわかること
  • 列名・件数・空欄・型を確認する順番
  • 0円と未取得の空欄の区別
  • ID・URL・タイトルによる重複の考え方
  • 異常があれば集計用CSVを作らないコード
目次

取得データの正常条件と欠損検知

空欄があるだけでは、処理の失敗とは言い切れません。必須項目と、空欄を許す項目を先に分けます。

列名と必須項目

今回の列はid、title、price、urlです。idは商品を区別する文字列、titleは名前、priceは0以上の価格、urlは取得元を確認するための項目です。この例では4つとも必須にします。

在庫がない商品の価格表示が消えるなど、取得元で空欄が正当な場合もあります。その場合は未取得・対象外・値が0を別に扱います。画面の仕様を見ずに、欠損を一律で埋めると意味が変わります。

件数の基準

HTTPが200でも、一覧の目印が変わって0件になることがあります。件数0を正常終了にしない条件が必要です。前回の正常な取得件数が分かるなら、急に減ったときも止めて確認します。

コードでは練習用の前回件数を4件とし、0件または半分未満なら停止します。半分という値は例の基準であり、一般的な正解ではありません。日々増減する情報なら、曜日・掲載終了・対象期間に合わせて基準を変えます。

0と空欄の違い

0円は値がある状態で、空欄は値が分からない状態です。Pythonでif not valueだけを見ると、数値の0まで未取得と扱う場合があります。この例はCSVの文字列が空かどうかを先に検査し、その後で数値に直します。

価格の検査にはDecimalを使います。小数も扱える数値型で、NaNやInfinityのような通常の価格にできない値はis_finite()で除外します。カンマや通貨記号を認めるかは、取り込みの規則として別に決めます。

取得データの重複を判定する識別子

同じタイトルがあっても、別の商品かもしれません。何を同じデータとみなすかで、削除してよい行が変わります。

スクロールできます
判定に使う値使いやすい場面確認点
ID取得元が安定したIDを持つ先頭ゼロとIDの付け直し
URL記事や商品詳細ページの一覧追跡用パラメータや別URL
タイトルIDがないときの候補探し同名商品・表記ゆれ

この例はIDを一意のキーにします。同じIDで名前・価格・URLまで同じなら完全重複として1件にします。同じIDで価格が違えば、どちらが新しいかを決められないので、全体の出力を止めます。

URLの#topのようなフラグメントは、ページ内の位置を指す部分なので、この例では取り除いて比べます。一方、?の後ろのパラメータは商品の種類を表す場合もあるため、一律では消しません。URLだけで同一商品を保証する処理ではありません。

先頭の行を残す処理は短く書けますが、重複の理由を調べずに捨てないほうが確実です。ページ送りで前ページを再取得したのか、元の一覧に同じ商品があるのかを分けて確認します。

取得データの欠損検知|6行の練習用CSV

欠損、型の不一致、完全重複、同じIDの内容違いを、意図的に混ぜます。実際の商品や価格ではありません。

次をsource.csvとしてUTF-8で保存します。価格が0の004は有効な値です。001は同じ内容の重複と、価格の違う重複の両方を入れています。example.testは説明用で、コードからこのURLへ通信しません。

id,title,price,url
001,ノート,1200,https://example.test/p/001#top
001,ノート,1200,https://example.test/p/001
002,,500,https://example.test/p/002
003,付箋,時価,https://example.test/p/003
004,ボールペン,0,https://example.test/p/004
001,ノート,1300,https://example.test/p/001

データの前後の空白はstrip()で取り除きます。ただし、文字を消すと意味が変わる項目ではこの処理も見直します。たとえば自由記述や固定桁のコードは、元の値も残してから整形するほうが追跡しやすくなります。

取得データの欠損検知|異常があれば出力を止めるコード

次をcheck_data.pyに保存し、source.csvと同じ練習フォルダで動かします。追加ライブラリは不要です。

import csv
from decimal import Decimal, InvalidOperation
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit

REQUIRED = ["id", "title", "price", "url"]
PREVIOUS_COUNT = 4  # 練習用の前回件数。実運用では正常取得時の値

def inspect_csv(path):
    with path.open(encoding="utf-8-sig", newline="") as f:
        reader = csv.DictReader(f)
        names = reader.fieldnames or []
        if len(names) != len(set(names)) or not set(REQUIRED).issubset(names):
            raise ValueError("列名の不足または重複があります")
        rows = list(reader)
    if not rows or len(rows) < PREVIOUS_COUNT * 0.5:
        raise ValueError("件数が少ないため、取得処理を確認してください")
    accepted, seen, errors, duplicates = [], {}, [], 0
    for number, row in enumerate(rows, start=1):
        try:
            if None in row or any(row[k] is None for k in REQUIRED):
                raise ValueError("列数が一致しません")
            item = {k: row[k].strip() for k in REQUIRED}
            for key in REQUIRED:
                if not item[key]:
                    raise ValueError(f"{key}が空です")
            try:
                amount = Decimal(item["price"])
            except InvalidOperation:
                raise ValueError("priceが数値ではありません") from None
            if not amount.is_finite() or amount < 0:
                raise ValueError("priceは0以上の有限の数値にします")
            parts = urlsplit(item["url"])
            if parts.scheme not in {"http", "https"} or not parts.netloc:
                raise ValueError("URLの形式が不正です")
            item["url"] = urlunsplit(parts._replace(fragment=""))
            fingerprint = (item["title"], amount, item["url"])
            if item["id"] in seen:
                if seen[item["id"]] != fingerprint:
                    raise ValueError("同じidで内容が異なります")
                duplicates += 1
                continue
            seen[item["id"]] = fingerprint
            accepted.append(item)
        except ValueError as e:
            errors.append(f"{number}件目: {e}")
    return rows, accepted, duplicates, errors

try:
    rows, accepted, duplicates, errors = inspect_csv(Path("source.csv"))
    print(f"取得: {len(rows)}件 / 完全重複: {duplicates}件 / エラー: {len(errors)}件")
    if errors:
        print("\n".join(errors))
        print("出力中止: 元データと取得処理を確認します")
        raise SystemExit(1)
    with Path("clean.csv").open("x", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=REQUIRED)
        writer.writeheader()
        writer.writerows(accepted)
    print(f"採用: {len(accepted)}件 / 保存: clean.csv")
except (OSError, ValueError, csv.Error) as e:
    print(f"出力中止: {e}")
    raise SystemExit(1)

検査結果は、採用候補・完全重複の件数・エラーの一覧に分けています。エラーが1件でもあれば保存しません。途中まで読めた行だけを出力すると、不足した結果を完成品と取り違えるためです。

同じIDの内容が違う場合は、先の行も後の行も判断待ちです。このコードは全体を止めるので、先の行を正しいものとして確定しません。エラー行だけを別の確認表に出す運用に変える場合も、元データを追える形を残します。

列数が違う行も検出します。DictReaderでは、列が足りない値にNoneが入る場合や、余った列がNoneキーへ入る場合があります。空文字と列不足を分けて見ることで、CSVの壊れ方も判断できます。

取得データの欠損検知|失敗例と修正後の結果

まず意図的な不備があるCSVを実行し、出力が作られないことを見ます。その後、別の練習フォルダで修正済みデータを確認します。

不備のあるデータの停止結果

py -3.12 check_data.py
取得: 6件 / 完全重複: 1件 / エラー: 3件
3件目: titleが空です
4件目: priceが数値ではありません
6件目: 同じidで内容が異なります
出力中止: 元データと取得処理を確認します

完全重複は1件、空欄・型・内容違いのエラーは3件でした。clean.csvは作られず、終了コードは1です。元のsource.csvは書き換えません。件目は見出しを除いたデータの順番を示します。

修正後の保存結果

空欄を元情報で確認し、時価を数値と混同せず、同じIDの価格違いを確認してから、別のフォルダに次の3行を用意します。値を推測して埋めた結果ではなく、成功時を確認する別の練習データです。

id,title,price,url
001,ノート,1200,https://example.test/p/001
002,付箋,500,https://example.test/p/002
004,ボールペン,0,https://example.test/p/004
取得: 3件 / 完全重複: 0件 / エラー: 0件
採用: 3件 / 保存: clean.csv

0円の行も採用されました。さらに、データ0件、必須列の欠落、NaNの価格では、保存前に停止することを確認しています。取得側へ組み込む場合は、この検査に合格した後で集計や通知へ渡します。

なべくん

欠損を消して数字を合わせるより、どの項目が取れなかったかを残すほうが、次の修正がしやすくなります。

取得データの欠損検知|よくある質問

実行前に迷いやすい点を確認します。

空欄を0で埋めてもよいですか?

業務上の意味が同じと確認できた場合だけにします。未取得の価格を0にすると、実際に無料の商品と区別できません。欠損件数を残し、原因の確認と補完の判断を分けます。

同じURLなら重複として削除できますか?

同じURLでも取得日時や選択条件が違う場合があります。ID、URL、内容、取得時刻を見て判断します。タイトル一致だけで削除すると同名の別データを失う可能性があるため、候補の抽出にとどめる方法もあります。

集計前の欠損検知と出力の確認

出力ができたかに加え、入力が正常だったかを確かめます。

この記事のまとめ

正常条件:必須列、件数、値の型を先に決める
欠損:空欄と0を区別し、推測して埋めない
重複:IDと内容を照合し、食い違いを止める
出力:異常のある結果を完成品として渡さない

最初は数行の正常例と異常例を用意し、止まる条件を1つずつ確かめてみてください。

次に進める作業に合わせて、こちらの記事も参考にしてください。

参考資料(2026年10月確認)
csv:DictReader | Python
decimal | Python
urllib.parse | Python

この記事を書いた人

Watanabeのアバター Watanabe サイト運営者

2020年よりブログ開始。
SEOが思いのほか性にあっていたようで現在に至る。
モットーは「勝率の高い選択をする」
AIは活用するが吉、最後は人間が息を吹き込む。
アートと科学を追求し、日々精進。
―――
収益:6~7桁をウゴウゴ。
サイト:ペラサイト~中規模サイトまで運営中。
案件:1000円以上の案件をメインに取組中。
打ち手:ブラックSEO~ホワイトSEOまで
―――

目次