

# 同じ求人を繰り返し通知せずに新着求人通知を受け取る方法

空欄ではないプラットフォームと求人IDの新しい組み合わせごとに通知処理を1度だけ作成し、既存の通知のうち条件を満たす保留中のものや失敗後の再試行を処理するには、収集した公開求人をそれぞれ永続的な台帳に保存します。

収集処理では公開求人を集めますが、日付で絞り込んでも、現在募集中であることは確認できません。また、異なる掲載IDが実際には同じ募集を指す場合もあります。この方法は、重複の防止や処理を厳密に1回だけ行うことを保証しません。配信結果が不明な場合は、再試行する前に確認が必要です。

```text
New Collection Batch
   |
   +---> New Nonblank Platform + Job ID? ---> Create Pending Notice Work (Once)
   |
   +---> Existing Eligible Pending/Failed ID? ---> Retry Notice Work
```

同じ求人を繰り返し通知せずに新着求人通知を受け取る方法

## 基本用語

次の基本用語を理解すると、求人の追跡データを正確に保ち、適切に管理できます。

- 求人掲載：求人サイトで公開された1件の求人広告です。求人IDという文字列で識別します。
- Actor：公開ウェブページから求人データを取得する、ホスティングされた収集プログラムです。
- Task：対象キーワードや勤務地など、Actorが使用する検索入力をまとめて保存したものです。
- 初回確認時刻：追跡システムが特定の掲載IDを初めて記録した正確な時刻です。求人サイトで最初に公開された時刻とは異なります。
- 通知状態：求人の通知が保留中、送信成功、送信失敗のいずれであるかを示す記録です。

## 収集元の設定

新着求人を追跡する前に、求人データを毎日安定して取得できるようにする必要があります。[LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)を使うと、未加工の求人掲載データを収集できます。

追跡台帳を簡単に検証できるように、検索範囲を絞ったプリセットから始めます。既定のTaskは複数のキーワードを使用しますが、最初は対象キーワードを1つにするように入力を変更してください。

```json
{
  "searchTerm": "software engineer",
  "location": "United States",
  "maxItems": 3,
  "postedWithin": "7d", "distance": 25,
  "sortBy": "date",
  "jobType": "any",
  "workplaceType": "any",
  "experienceLevel": "any",
  "maxPagesPerSearch": 5,
  "saveOnlyUniqueItems": true,
  "balanceKeywordCoverage": true,
  "fetchJobDetails": true,
  "requestDelayMillis": 500
}
```

収集元の設定

`saveOnlyUniqueItems`を`true`に設定すると、1回の実行内で重複する求人掲載が除かれます。ただし、日をまたいで同じ求人が繰り返し取得されることは防げません。`maxItems`の上限は、1回の実行で使うすべての検索語を合わせた件数に適用されます。実行が件数などの上限に達した場合や、収集元で障害が発生した場合は、一部の求人を取得できない可能性があります。収集漏れや収集失敗は、新着求人が0件だったと判断せず、データが不明な状態として扱ってください。

実行が成功したら、追跡処理に使う次の主要フィールドを抽出します。

収集プログラムは、求人掲載ごとに所定のフィールドを出力します。

| フィールド名 | 説明 |
| --- | --- |
| `jobId` | 収集元プラットフォームの求人識別子 |
| `jobUrl` | 収集元プラットフォームの求人リンク |
| `title` | 職種名 |
| `companyName` | 採用企業名 |
| `locationRaw` | 未加工の勤務地テキスト |
| `postedAt` | 算出または解析した公開時刻 |
| `postedAtRaw` | 収集元に表示された、公開時期を示す元の表記 |
| `scrapedAt` | 収集時刻 |
| `descriptionStatus` | 求人詳細の取得状態 |

解析または算出した時刻とともに、元の表記も保存します。公開からの経過時間を示す相対表記は、最初に公開された正確な時刻ではありません。利用者側で管理する初回確認、最終確認、操作の各フィールドは別にします。現時点で把握されている3行のデータを用いた限定的な受け入れ確認で確認できるのは、収集元の識別情報と時刻データの形式だけです。本番環境で複数日にわたる通知が機能することを実証したものではありません。

## 毎日の追跡を行う8つの手順

次の8つの手順で、新たに取得した求人を処理し、重複送信を避けながら通知を管理します。

1. 通知対象を定義する：通知対象とは、収集した掲載IDのうち、職種名や勤務地などの基本条件を満たし、まだ処理されていないものです。絞り込みのルールは実行記録と一緒に保管します。
2. 収集元の行を保存し、実行結果を確認する：バッチ処理の未加工の出力を保存します。行を処理する前に、収集エラー、件数上限への到達、警告がないかを確認します。バッチ処理の失敗は、市場の状況が不明であることを意味します。新着求人が0件という意味ではありません。
3. 空欄ではないプラットフォームと文字列のjobIdをキーとする追跡テーブルを作成します。IDがないデータは、それぞれ未解決の観測記録として分けて保持し、空欄同士をまとめてはいけません。利用者側のフィールドとして`first_seen_at`、`last_seen_at`、`jobUrl`、`review_status`、`alert_state`、`alert_attempt_at`、`error_note`を保持します。収集元データを取得した時刻である`scrapedAt`は、ワークフローで操作した時刻とは異なります。
4. 取得したバッチのIDを処理する：現在のバッチに含まれる重複を除いた各キーについて、追跡テーブルに存在するかを確認します。存在する場合は、`last_seen_at`だけを更新します。存在しない場合は、新しい行を作成し、`review_status`をpending、`alert_state`をpendingに設定します。`first_seen_at`は上書きしてはいけません。
5. 送信前に対象条件を確認する：[勤務地やリモート勤務の可否](/ja/articles/%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E6%B1%82%E4%BA%BA-%E5%B1%85%E4%BD%8F%E5%9C%B0%E6%9D%A1%E4%BB%B6%E3%81%AE%E7%A2%BA%E8%AA%8D/)などの必須条件を確認します。必須条件を確認できない場合は、`review_status`をpendingのままにします。求人がルールを満たさない場合は、明確な理由とともにexcludedと記録し、以降の実行で再確認しないようにします。
6. プラットフォームと求人IDの組み合わせにひもづく、固定のキーを通知に付けます。条件を満たす保留中の通知だけを送信します。配信サービスが処理を確認した後でのみ、送信済みと記録します。送信済みとは、配信サービスへの送信依頼が確認された状態であり、受信トレイへの到着を示すものではありません。HTTP 200で受け付けられたというだけでは、配信確認にはなりません。確認結果が不明な場合は配信結果も不明であり、再試行する前に調べる必要があります。厳密に1回だけ配信される保証はありません。
7. 配信失敗を安全に処理する：通知の送信に失敗した場合は、`first_seen_at`を消去したり、収集元から求人を再収集したりせず、後で配信を再試行します。収集エラーと配信エラーは分けて管理します。安全に再開できるように、各手順の完了後に状態を保存します。
8. 指標を明確に報告する：指標は、収集した求人の重複を除いた件数、新たに確認したID数、条件を満たす通知数、送信確認済みの通知数、保留中または失敗した通知数に分けます。新しい通知がないということは、今日、新たに条件を満たすと判定された求人が見つからなかったという意味です。求人市場全体で採用が止まったという意味ではありません。

## 架空のデータによる具体例

すべてのID、日付、出来事は架空のものです。10月3日より前の初期状態には、10月1日に初めて確認して通知を送信済みの`J1`と、10月2日に初めて確認して通知を送信済みの`J2`という2つのキーだけがあります。4つのキーが含まれるのは最終的な台帳であり、初期状態ではありません。

10月3日の収集では、キーが`J1`、`J2`、`J3`、`J3`である4行の未加工・インポートデータが返されます。これは、観測記録が4件、重複を除いたキーが3件、既存のキーが2件、未確認だったキーが1件（`J3`）ということです。`J3`は確認の結果、条件を満たします。最初の通知試行は受け付けられなかったことが確認できたため、この架空の例では後で安全に再試行できます。

10月4日の収集では、`J2`、`J3`、`J4`の3つのキーが返されます。これは、重複を除いたキーが3件、既存のキーが2件、未確認だったキーが1件（`J4`）ということです。`J4`は勤務地が欠損しているため確認保留となり、通知は行いません。10月4日に配信サービスへの送信依頼が1件確認されても、それは受信トレイへの配信を意味しません。

10月5日は収集に失敗しますが、履歴は変更しません。この失敗をもって求人がないとは主張できません。

追跡用のマスターテーブル：

| キー | 初回確認時刻 | 最終確認時刻 | 確認状態 | 通知状態 | 試行時刻 | エラーメモ |
| --- | --- | --- | --- | --- | --- | --- |
| linkedin:J1 | 2026-10-01T08:00:00Z | 2026-10-03T08:00:00Z | Passed | Sent | 2026-10-01T08:05:00Z | None |
| linkedin:J2 | 2026-10-02T08:00:00Z | 2026-10-04T08:00:00Z | Passed | Sent | 2026-10-02T08:05:00Z | None |
| linkedin:J3 | 2026-10-03T08:00:00Z | 2026-10-04T08:00:00Z | Passed | Sent | 2026-10-04T08:05:00Z | 10月4日の再試行に成功 |
| linkedin:J4 | 2026-10-04T08:00:00Z | 2026-10-04T08:00:00Z | Pending | Pending | None | 勤務地が欠損 |

通知の試行と確認結果を別に記録するログ：

| キー | 試行時刻 | イベントの種類 | サービスの応答 | 処理後の状態 |
| --- | --- | --- | --- | --- |
| linkedin:J3 | 2026-10-03T08:05:00Z | 配信試行 | 500 Rejected | Failed |
| linkedin:J3 | 2026-10-04T08:05:00Z | 配信の再試行 | 200 Accepted | Sent |

処理の集計：

- 10月3日に初めて確認したID：1件（`J3`）
- 10月4日に初めて確認したID：1件（`J4`）
- これらの日に確認できた配信サービスの処理：1件（`J3`の再試行）
- 追跡中のキーの総数：4

## 実際に始めるには

同じ求人の繰り返し通知を避けるための管理を始めるには、[LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)、またはそのプリセットTaskである[Scrape Fresh LinkedIn Jobs From the Last 7 Days](https://apify.com/chronometrica/linkedin-jobs-scraper/examples/scrape-fresh-linkedin-jobs-from-the-last-7-days)を開きます。対象キーワードを1つに設定し、取得件数の上限を少なくします。そして、次の収集バッチを実行する前に追跡テーブルを作成してください。

## 出典と参考資料

- [収集元のActor：LinkedIn Jobs Scraper](https://apify.com/chronometrica/linkedin-jobs-scraper)
- [Scrape Fresh LinkedIn JobsのTaskプリセット](https://apify.com/chronometrica/linkedin-jobs-scraper/examples/scrape-fresh-linkedin-jobs-from-the-last-7-days)
- [求人通知の重複に関するコミュニティでの議論](https://www.reddit.com/r/n8nforbeginners/comments/1teylba/built_a_linkedin_job_notifier_using_n8n_local_db/)