「生成AIを使えば、プログラミングの知識がなくても一発で完璧なシステムが組み上がる」――昨今、メディアやSNSではこのような夢のような話が飛び交っている。しかし、実際に手を動かしている開発者やビジネスパーソンなら、これが多分に誇張された表現であることに気づいているはずだ。
結論から書くと、AIは超高速でコードを吐き出す「優秀な作業員」だが、システムの運用フェーズを見据えた「仕様の穴」を見抜き、全体を最適化するアーキテクチャを決定する「司令塔」の役割は、依然として人間だ。AIに丸投げしたコードは、一見動いているように見えても、裏側で致命的なデータの取りこぼしや非効率な空振りを起こしているケースが多々ある。
今回は、ある公式サイトのニュースページから過去データをスクレイピングし、データベース(MariaDB)に自動蓄積するシステムをAI(LLM)と共に構築した対話の記録を余すことなく大公開する。
第1章:アイデアの着想と、AIによる実現性のブレスト
あるサイトのニュース情報を活用したい
【1回目の指示】
ある公式サイトにイベントや新曲リリースなどを伝えるニュースページがある。
過去のデータを取得し、新曲発売のタイムスパンの推移や毎年行っているライブやツアーの状況などを分析すると将来の予測に役立つのではないかと思われる。
そこで下記事項をブレストしたい。
①ニュース取得システム。
②取得した情報の分析にどの位AIが使えるのか?どう使うのか?
③将来の予測にどれくらい役立てることが可能か?
【AIからの回答】
ご提示いただいた3つのブレスト事項(①システムの可否・考慮点、②AIの活用方法、③将来予測の実現性)に対する検討結果です。
① ニュース取得システムの可否および考慮点
結論: 技術的に十分可能です。
考慮点: 1日に1〜2回(または数時間おき)の定期バッチ処理**で十分です。
実装方法: サイト構造の変更に備え、Webスクレイピングだけでなく、もし提供されていればRSSフィードの活用や、変更検知APIの利用を第一候補とします。
②取得した情報の分析におけるAIの活用方法
結論: テキストデータの構造化と要約に極めて有効です。
具体的な活用方法:
非構造化データのLLMによるパース: ニュースの本文から「イベント名」「日付」「場所」「シングル名」「リリース日」などのキー情報を抽出・構造化し、データベース(JSON/RDB)へ格納します。
カテゴリ分類: 記事を「ライブ」「リリース」「メディア出演」「その他」へ自動分類します。
③ 将来の予測にどれくらい役立てることが可能か
結論: 周期性のあるイベント(リリース周期や周年ライブ)の時期予測には高い精度で役立てることが可能です。ただし、サプライズ要素の強い突発的な事象の予測は限定的となります。
予測の実現性:
過去のデータ(時系列データ)に基づくパターン認識は、AI(特に統計的アプローチやLLMを用いたコンテキスト分析)が最も得意とする領域です。
過去数年分のデータを蓄積することで、「〇月頃にアナウンス、〇月頃にリリース、それに伴うツアーは〇月〜〇月」というタイムラインの予測モデルを提示可能になります。
2回目:「過去データの遡り」という制約とデータ保存の設計
AIからの前向きな回答を受け、私はより具体的な運用を想定した「2回目の確認事項」を投げかけました 。新規ニュースの監視だけでなく、初回は過去数年分のデータを過去にさかのぼって初期取得(クローリング)することを想定していたからです 。
【2回目の指示】
① ニュース取得システムの可否および考慮点
新規分の取得ではなく、初回過去にさかのぼって取得していくことを想定していた。なので、過去のニュース1件を一分おきにと考えたが、例えば1時間に一度20件分取得するといった方が良いか?
②分類は各公式サイトでやり方が異なっているが、既に分類分けされている。例えば、様々なニュースにある特定の文言の記載があれば、それらは分類が異なっても同じイベントに関連するニュースだとAIは認識が可能かと思う。
その上で、無料で出来るレベルでプログラムからAIを呼び出したとして、どの程度実用的にAIを活用できるだろうか?
③ここ2年程は、ある程度定期的にイベントが発表・発売されているが、それ以前は少し不安定だった。
どちらかと言うとそういう定常的ではない事象の予測が出来ると本来は嬉しい。
ただ、何が出来るかは別としてデータの収集は行っておきたいと思った。
【AIからの回答】
① 過去データの初期取得(クローリング)と保存設計
結論(取得頻度): 1時間に1回・20件ずつの取得(IP分散やスリープ処理を含む)の方が、セッションのオーバーヘッドが少なく、サーバーへの「不審な連続アクセス」とみなされるリスクを低減できるため推奨します。
結論(保存方法): データベース(RDB)に専用テーブルを作成して保管するのが最適です。
重要事項(テーブル設計):
後述の「関連付け」をスムーズに行うため、以下の設計を推奨します。
各サイト固有のカテゴリ(「グッズ」「リリース」等)をそのまま保持するカラム。
AIが判定した共通タグを格納するリレーションテーブル、またはJSON型カラム。
② 無料枠LLMによる関連付け・分類の実用性
結論: 極めて高い実用性で実現可能です。
理由と活用度:
Google AI StudioのGemini API(Flashモデル等)の無料クォータ(1分あたり15リクエスト等)の範囲内で十分処理可能です。
具体的な活用方法:
「AAAA」という明示的な文言だけでなく、例えば「BBBBの延期(対象:AAAA購入者)」や「新曲MV公開」といった、文脈から判断する必要があるニュースに対しても、「これはAAAAに関連する情報である」と高精度に紐付け(タギング)できます。
③ 非定常な事象(イレギュラー)の予測とデータ収集方針
結論: イレギュラー(遅延、アルバム変更等)の「事前予測」をAIのみで行うことは現時点では困難ですが、**「兆候の検知」および「予測モデルの修正」にはデータが不可欠です。**
重要事項(データ収集方針):
外部要因データの検討: 運営会社の決算期、JASRACの登録情報などの外部データも、将来的に取り込めるよう「タイムスタンプ(日時)」をキーとした拡張性のあるデータ構造にしておきます。
定常イベントの自動予測: 発表から開催までのタイムスパン(例:アナウンスからX日後に初披露、Y日後に発売)の自動計算は、収集データから即座に実用化可能です。
第2章:システム設計の具体化と最初のプロトタイプ作成
3回目:スクレイピング・格納システムの要件定義
AIから「無料枠のAPIでも十分に実用可能である」という太鼓判を押され、私は具体的なシステム化のフェーズへと進むことにしました 。前提となるニュースサイトの先頭ページのHTMLをAIに添付し、「初回起動時」と「二度目以降の起動時」で明確に挙動を分ける、以下のような機能要望を伝えました 。
【3回目の確認事項】
状況は理解した。
それでは、過去のニュースをスクレイピングしてDBに保管するシステムを設計したい。 前提としてニュースサイトの構造になるかと思うが、まずは各サイトの先頭ページのhtmlを添付した。
システムへの要望は、今までのブレストで一応必要な部分は上がっていると思う。
その上で、機能としては、①初回起動時は、1時間に一度起動して、20件分のニュースを読み込みローカルDBに格納する。これをサイトの終了まで実施する。
②二度目以降の起動では、その前の起動時点からニュースに追加があった場合にその追加分をローカルDBに格納する。なお、月が変わった場合には、当初はニュースが0件も有り得る。
【AIの回答】
ご提示いただいたニュースサイトのHTML構造および要件に基づき、スクレイピング・格納システムの設計(結論と重要事項)を提示します。
システム構成・アーキテクチャ方針
クローラー種別: HTML構造が比較的シンプルで、サーバーサイドでレンダリングされているため、JavaScript実行(Selenium等)は不要です。**Python (BeautifulSoup + Requests)** または **Node.js (Cheerio)** による軽量なHTMLパースバッチとして実装します。
データベース: ローカル環境での運用の柔軟性を考慮し、**SQLite** または **PostgreSQL (ローカルインスタンス)** を採用します。
2. データベース(テーブル)設計
ニュースの一元管理とAI分析への拡張性を考慮し、以下の構造とします。
`news_articles`(ニュース本体テーブル)
<<フィールド定義>>>
3. データ収集・更新ロジックの設計
① 初回起動モード(過去データの遡り収集)
動作: クローラーの引数に対象の「年・月(`YYYYMM`)」を指定してバッチを起動します。
ページング遷移:
各サイト共通で、リクエストパラメータ(例:`dy=202606`)で月指定が可能です。
1時間に1回、20件ずつ処理する際、HTML内の「前月(BackMonth)」リンクの要素を解析、またはプログラム側で月をデクリメント(過去へ遡る)しながら順次リクエストを送出します。
ページ内にニュースが存在しなくなる(最古のデータに到達する)まで、この処理を繰り返します。
② 定常起動モード(最新差分の収集)
動作: 最新月(現在月)のページのみにリクエストを送信します。
差分判定: 取得した記事の `article_id` が、すでにDB(`news_articles`)に存在するかをチェックします。
存在しない(新しい)データのみをDBへ `INSERT` します。すでに存在するIDに到達した時点で、その回のクローリングを正常終了(早期リターン)させます。
月替わりの考慮:
月初など「当月のニュースが0件」のケースを考慮し、当月ページの取得件数が0件であった場合は、自動的に「前月」のページもスキャンし、前月末の未取得差分がないかを確認するロジックを組み込みます。
4回目:データベースの変更(MariaDBへの選定)
基本設計を確認した私は、データベースを使い慣れた「MySQL(MariaDB)」へ変更するよう指定し、実装コードの作成を依頼しました 。
【4回目の確認事項】
出来たら、MySQL(MariaDB)でお願いしたい。 その上で先に進めて欲しい。
【AIからの回答】
承知いたしました。データベースを MySQL(MariaDB)に選定し、これまでの要件を網羅した詳細なシステム設計および実装に向けた仕様を定義します。
1. データベース物理設計(MySQL/MariaDB)
大量の過去データ蓄積および今後のAI連携(タグ付け)を考慮し、インデックスとデータ型を最適化したテーブル構造です。
ニュース主テーブル
<<create tableのSQL分>>
将来のAI分析・タグ連携用テーブル(拡張用)
<<create tableのSQL分>>
2. データ取得・更新ロジック仕様
プログラム側(Python等)で制御するバッチ処理のシーケンスおよび条件分岐の設計です。
① 初回起動モード(過去データ遡り)
制御ロジック: 1. コマンドライン引数等で「対象グループ」と「開始年月(YYYYMM)」を指定。
2. 1時間に1回、タイマー(Cron等)またはスリープ処理で起動。
3. 各グループの特性に合わせてリクエストを送信:
1. 取得した件数から20件ずつに分割(スライス)し、1秒以上のインターバルを空けながらDBへ `INSERT IGNORE`。
2. 対象年月の全件処理が終われば、年月を1ヶ月過去(`YYYYMM – 1`)へデクリメントして次回起動へ引き継ぐ。データが0件の月が連続する(最古データ到達)までループ。
② 定常起動モード(最新差分更新)
制御ロジック:
1. 定期実行(例: 毎時または数時間おき)で起動。
2. 現在月(当月)のデータ(APIまたはHTML)を取得。
3. `INSERT IGNORE` を実行し、MySQLから返却される「影響のあった行数(Affected Rows)」を監視。
4. 「追加件数が0件(すべて重複)」または「取得データの末尾まで既存レコードと一致」 した時点で、差分なしと判断して即座に処理を正常終了(早期リターン)する。
5. 月替わりの考慮:** 当月の取得件数が0件、かつ現在の日付が「上旬(例: 5日まで)」の場合は、前月末のシステム停止時間中の取りこぼしを防ぐため、自動的に「前月(先月)」のパラメータでもリクエストを発行し、同様に差分チェックを行う。
5回目: Python実装コードの作成依頼
Pythonでの実装コードの作成を依頼しました 。
【私からの変更要望】
開発言語のご希望: Python 実行環境: ローカルPC(Windows)上のスケジュールタスク DBへのコネクションは添付した。 以上。
【AIからの回答】
承知いたしました。開発言語 Python、実行環境 Windowsスケジュールタスク(タスクスケジューラ)、およびご提示いただいた MariaDBの接続情報を前提とした、実装用プロダクトコード(スクリプト)を作成しました。
3坂道それぞれのサイト構造(乃木坂46のAPI、櫻坂/日向坂のHTML)に対応し、初回起動時の「過去遡り(1時間ごとに1月分、20件ずつ徐々に登録)」と、2回目以降の「最新差分更新」を自動判定して動作する構造にしています。
1. 事前準備(必要なライブラリのインストール)
Windowsのコマンドプロンプトで以下を実行し、MySQL/MariaDB接続用およびスクレイピング用のライブラリをインストールしてください。
“`
pip install mysql-connector-python beautifulsoup4 requests
“`
2. Python実装スクリプト
データベース接続情報や過去遡り状態の管理ロジックを含めたコードです。ローカルの適当なフォルダに保存してください。
<<250行ほどのPythonプログラムの提示>>
3. Windowsタスクスケジューラへの登録手順
このスクリプトを「1時間に1回」自動起動するようにWindows側に設定します。
1. スタートメニューから「タスク スケジューラ」を起動します。
2. 右側の操作パネルから「基本タスクの作成」をクリックします。
3. 名前: `坂道ニュースクローラー` 等を入力し「次へ」。
4. トリガー: 「毎日」を選択し「次へ」。
5. 開始日時: 任意の時間を設定し「次へ」。
6. 操作: 「プログラムの開始」を選択し「次へ」。
7. プログラム/スクリプト: `python` と入力します。
8. 引数の追加(オプション): 保存したスクリプトのフルパスを入力し「次へ」。
9. 「[完了]をクリックしたときに、このタスクのプロパティダイアログを開く」にチェックを入れて「完了」をクリック。
10. 開いたプロパティ画面の「トリガー」タブを選択し、設定したトリガーを選んで「編集」をクリック。
11. 詳細設定にある「タスクを繰り返し実行する間隔」にチェックを入れ、「1時間」を選択。継続時間を「無期限」にして「OK」をクリックします。
これで自動運用が開始されます。最初は1時間に一度起動するたびに、状態管理JSONファイルを参照しながら過去データを自動で1ヶ月(最大20件)ずつ蓄積し、最古のデータに到達すると自動的に「毎日・毎時の最新差分更新」へと切り替わります。
第3章:【最初の罠】バグ
6回目:デバッグ実行の推奨
「システム出来ちゃった?とりあえず動くか確認してみよう」と考えた私は、本番運用前にまずは手動でテスト実行を行いたい旨を伝えました 。
【6回目の確認事項】
初回は一回デバッグモードで実施したいが大丈夫か?
【AIからの回答】
結論:完全に大丈夫です。むしろ推奨します。
ローカル環境での初回実行時は、タスクスケジューラに登録する前にコマンドプロンプト(手動)から直接実行してログ(出力結果)を確認すること(デバッグ実行)が、システム開発におけるベストプラクティスです。
安全にデバッグを行うために、コードを書き換えずに**そのまま実行して確認して問題ありません**。
デバッグ実行時の挙動と確認ポイント
1. 状態管理ファイルの生成確認
スクリプトと同じフォルダに `crawler_state.json` というファイルが自動生成されます。手動で1回実行したあと、このファイルを開いて以下のように年月が1ヶ月進んでいる(過去に遡っている)か確認してください。
{
“AAAAAAAA”: {“current_ym”: “202605”, “is_finished”: false},
“BBBBBBBB”: {“current_ym”: “202605”, “is_finished”: false},
“CCCCCCCC”: {“current_ym”: “202605”, “is_finished”: false}
}
2. データベースの格納確認
HeidiSQLやphpMyAdminなどのツール、またはコマンドラインから以下のSQLを実行し、3グループ合計で最大60件(各20件ずつ)のデータが正しく MariaDB に格納されているか確認してください。
<pre>
SELECT group_id, COUNT(*) FROM news_articles GROUP BY group_id;
</pre>
3. コンソール出力の確認
実行時、画面に以下のようなログが表示され、エラー(RedテキストやException)が出ていなければ成功です。
処理開始: AAAAAAAA
【初回過去遡りモード】対象年月: 202606
取得件数: 15件 / 新規登録: 15件
処理開始: BBBBBBBB
完全に安全にテストしたい場合(オプション)
もし「最初はデータベースを汚さずに、スクレイピングが成功するかだけを見たい」という場合は、`save_to_db` 関数を呼び出している箇所の直前に `print(articles)` を入れてデータの中身だけを画面に表示させ、挙動を確認することも可能です。現状のままでも `INSERT IGNORE` を採用しているため、同じデータが重複してDBに壊れるリスクはありません。
手動での実行結果に問題がないことを確認できたら、安心してWindowsタスクスケジューラへ移行してください。
7回目:「20件の切り出し」と「月のデクリメント」が噛み合っていない
AIの言葉通り、私は実際にコマンドプロンプトからスクリプトを手動実行しました 。 確かにデータベースにはグループごとに20件ずつ、計60件のデータが正常に格納されていました 。しかし、コンソールに出力されたログと、自動生成されたcrawler_state.jsonを突き合わせた瞬間、私はある強烈な「違和感」を感じた。
【7回目の確認事項】
実施した結果下記の様な状況である。
一点確認させて欲しい。
次回起動時、crawler\_state.jsonの内容が202605になっているので、2026年5月を処理して、2026年6月の残件を取得し漏れることは無いか?
— 実行後の確認 ①crawler\_state.jsonは更新されていた。 202605になっている。 ②DBにはグループ毎に20件ずつ計60件格納されている。 ③実行ログも下記表示 C:\Users\lupinmk\DATA\ニュースの活用>python.exe crawler.py
— 処理開始: AAAAAAAA — 【初回過去遡りモード】対象年月: 202606 取得件数: 45件 / 新規登録: 20件
— 処理開始: BBBBBBBB — 【初回過去遡りモード】対象年月: 202606 取得件数: 41件 / 新規登録: 20件
— 処理開始: CCCCCCCC — 【初回過去遡りモード】対象年月: 202606 取得件数: 43件 / 新規登録: 20件 C:\Users\lupinmk\DATA\ニュースの活用> 以上。
【AIからの回答】
デバッグ実行のご確認とログのご提示、ありがとうございます。
結論:ご指摘の通り「取得漏れ」が発生するバグがロジック内に存在します。即座に修正が必要です。
大変鋭いご指摘です。現在のロジックでは「1月につき最大20件しか処理しないまま、無条件で次の月(202605)へ進む」仕様になってしまっており、2026年6月の残り(21件〜45件目)が未取得のまま置き去りになります。
原因と対策
原因は「月単位のデクリメント」と「20件ずつのスライス(分割)」の連動が考慮されていなかったことです。
正しい挙動(修正方針):
「その月のデータがまだ残っている(例:全45件中20件しか登録していない)」場合は、年月を減らさずにキープし、次回の起動時に「同じ月」を再スキャンして未登録の差分(残りの25件)を回収するように修正します(`INSERT IGNORE` を使っているため、最初の20件は自動でスキップされ、残りの件数が綺麗に登録されます)。
修正版プログラムコード(制御部分のみ)
スクリプト下部の `5. メイン制御ロジック`(`main` 関数内の【初回過去遡りモード】部分)を、以下のコードに差し替えてください。
<<修正プログラムの提示>>
次回デバッグ時の対応方法
現在の `crawler_state.json` をメモ帳などで開き、一度値を 202606 に書き戻して上書き保存してください。その上で上記修正版を実行していただくと、今度は以下のようなログになり、6月の残件が正しく回収され始めます。
次回実行時: 新規登録: 20件(21〜40件目が登録され、月は `202606` のまま維持)
次々回実行時: 新規登録: 5件(41〜45件目が登録。20件未満のため、ここで初めて月が `202605` に更新される)
設計上の不備を見抜いていただき感謝いたします。修正の上、再度手動実行をお試しください。
8回目:複数グループの独立性の担保
このロジックで本当に大丈夫か不安に思った私は、「グループによってデータの残件数が異なっているが、処理が混ざって壊れたりしないか」という疑問を重ねて投げかけた 。
【8回目の確認事項】
グループによって残件数は異なるが、大丈夫か?
【AIからの回答】
結論:完全に大丈夫です。グループごとに独立して管理されます。
ご安心ください。提供したプログラムの内部構造は、3つのグループ(乃木坂・櫻坂・日向坂)がループで1グループずつ個別に処理される設計になっています。
状態管理ファイル(`crawler_state.json`)のデータ構造を見ると分かりやすいですが、グループごとに `current_ym`(現在の対象月)と `is_finished`(完了フラグ)が完全に独立して記録されています。
グループごとの挙動イメージ
例えば、次回起動した際に以下のようなデータ特性だった場合、プログラムは以下のように自動で個別制御を行います。
AAAAAAAA(残25件): 新規登録が「20件」になるため、月を `202606` のまま据え置きます。
BBBBBBBB(残21件): 新規登録が「20件」になるため、月を `202606` のまま据え置きます。
CCCCCCCC(残23件): 新規登録が「20件」になるため、月を `202606` のまま据え置きます。
さらにその次の回(3回目)の起動時には:
AAAAAAAA: 残り5件を登録 ➔ 新規登録が20件未満(5件)になったため、月を `202605` へ進める。
BBBBBBBB: 残り1件を登録 ➔ 新規登録が20件未満(1件)になったため、月を `202605` へ進める。
CCCCCCCC: 残り3件を登録 ➔ 新規登録が20件未満(3件)になったため、月を `202605` へ進める。
このように、各サイトのニュース総数や残件数がバラバラであっても、それぞれが自分のペースで「20件未満になるまでその月を粘り、回収し終わったら個別に過去へ進む」という動きをします。
そのため、特定のグループのせいで他のグループのデータが巻き添えで漏れたり、進みがズレて壊れたりすることはありません。そのまま運用していただいて大丈夫です。
第4章:AIの「逆順提案」に潜む罠
9回目:修正版が動かない理由と、AIの浅薄な「逆順提案」
AIの指示通り、状態管理ファイルを「202606」に書き戻し、制御ロジックを差し替えて再度実行したが、今度はデータベースの件数が全く増えなくなっていた 。
【9回目の確認】
差分が登録されていない。DB上も変化なし。
C:\Users\llll\DATA\ニュースの活用>python.exe crawler.py
処理開始: AAAAAAAA 【初回過去遡りモード】対象年月: 202606 取得総数: 45件 / 今回の新規登録: 0件 【202606】の全件回収が完了したため、対象月を過去へ進めます。
処理開始: BBBBBBBB 【初回過去遡りモード】対象年月: 202606 取得総数: 41件 / 今回の新規登録: 0件 【202606】の全件回収が完了したため、対象月を過去へ進めます。
処理開始: CCCCCCCC 【初回過去遡りモード】対象年月: 202606 取得総数: 43件 / 今回の新規登録: 0件 【202606】の全件回収が完了したため、対象月を過去へ進めます。
【AIからの回答】
結論:原因は「配列(リスト)の順序」にあります。サイトから取得した最新の20件を再度スキャンしてしまったため、すべて重複(新規登録: 0件)と判定されています。
ログを確認したところ、各グループとも「取得総数は40〜45件あるにもかかわらず、新規登録が0件」となり、そのまま次の月へ進んでしまっています。
原因の解説
各公式サイトおよびAPIからデータを取得した際、配列(リスト)の先頭(`articles[:20]`)には「日付が一番新しいニュース(最新の20件)」が並んでいます。
1回目の実行:最新の20件(1〜20件目)をDBに登録しました。
2回目の実行(今回): 再びサイトにリクエストを送り、またリストの先頭から20件(1〜20件目)を切り出しました。これは前回登録したものと全く同じデータであるため、MySQLの `INSERT IGNORE` によってすべて弾かれ、新規登録が「0件」になりました。
プログラム側が「0件(20件未満)だったから、この月はもう回収し尽くした」と誤判定し、残りの21件目以降を置き去りにして過去の月(202605)へ進めてしまっています。
対策(修正方針)
過去データを遡る際は、常に「その月の古いデータ(配列の後ろ側)」から順番に20件ずつ処理する、あるいはDBの既存件数をカウントして、取得位置をずらす(オフセットする)必要があります。
今回は、もっとも確実でシンプルな方法として「配列(リスト)の前後を逆順(古い順)に並び替えてから処理する」、または「すでにDBにある件数分をスキップして切り出す」ロジックに修正します。
まずは、すでに `202605` に進んでしまった `crawler_state.json` をもう一度 `202606` に書き戻して保存してください。
その上で、プログラムの `5. メイン制御ロジック` を以下の「位置ズレ修正版」に差し替えてください。
<<修正版コード>>
この修正により、すでに登録されている件数(例: 20件)を自動的にスキップして、21件目〜40件目を正確に狙い撃ちして取得できるようになります。
10回目:AIのロジックの穴「中間データの虫食い」
AIはサラッと「逆順(古い順)に並び替えるか、既存件数分をスキップ(オフセット)すれば良い」と言い、コードを出してきました 。しかし、そのコードのロジックを脳内でシミュレーションした結果、新たなバグに気付く 。もし「逆順にするだけ」の対策を採用した場合、中間のデータが永久に収集されないことに気づいたのだ 。
【10回目の確認】
オフセットを変更するのであれば大丈夫だと思うが、逆順にするだけだと、 45件のうち現在先頭の20件が登録されている状態で、後ろの20件が処理される。 次の実行では、再度うしろの20件が再度取得されて、全て一致するので0件になり5件処理されないまま、次の月に移ってしまわないか?
【AIからの回答】
結論:おっしゃる通りです。逆順にするだけでは「中間の5件」が完全に漏れ、バグが再発します。ご指摘が100%正しいです。
私の考察(逆順にするという選択肢の提示)に重大なロジックミスがありました。
もし逆順(古い順)にするだけで処理しようとした場合、以下の図のように「中間のデータ」を跨いでしまい、未検出のまま翌月に進んでしまいます。
1回目(修正前): 1〜20件目(最新側)を登録。
2回目(逆順のみ): 26〜45件目(最古側)を登録。この時、新規登録は「20件」となるため、コードは「まだ残件がある」と判断して月を維持します。
3回目(逆順のみ): 再び後ろから20件(26〜45件目)を切り出しますが、これは前回登録済みのため新規登録が「0件」になります。ここでプログラムが「回収完了」と誤判定し、**21〜25件目の中間データ5件が未登録のまま**次の月へ進んでしまいます。
思考が浅く、お恥ずかしい限りです。設計の穴を正確に見抜いていただき、本当にありがとうございます。
正解は「オフセット方式」の一択です
前回提示した「DBの既存件数をカウントして切り出し位置をずらす(オフセット方式)」であれば、この問題は完全にクリアされます。
オフセット方式における、次回(2回目)以降の正確な挙動のタイムラインです。
2回目の実行(次回): DB内既存件数が「20件」とカウントされるため、配列の `[20:40]`(21件目〜40件目)を正確に切り出します。新規登録は「20件」となり、合計40件。サイト総数(45件)に達していないため、月(202606)を維持します。
3回目の実行: DB内既存件数が「40件」となるため、配列の `[40:60]`、つまり残りの `[40:45]`(41件目〜45件目)の5件を綺麗に切り出して登録します。登録後の合計件数(45件)がサイト総数(45件)に達するため、ここで安全に対象月を過去(202605)へ進めます。
提示した「位置ズレ修正版(オフセット方式)」のコードであれば、件数の切りこぼしなく、グループごとの件数の違いにも対応して100%安全に動作します。
お手数をおかけしますが、`crawler_state.json` を `202606` に戻した上で、オフセット方式の修正版コードでの実行をお願いいたします。
第5章:リソース効率の追求と「よくばり回収モード」への改良
11回目:1時間ごとの起動枠を無駄にする「空振り」への違和感
オフセット方式を導入し、再度プログラムを実行したところ、データは漏れなく格納されるようになった 。しかし、出力された以下の実行結果を見た私は、またしても設計上の「もったいない挙動」に気付く 。
【11回目の確認事項】
一点確認させてください。
202606の残件を実施したが、本来1回につき20件処理するので、全てのグループで202605の処理を進めるのかと思ったら、202606の処理で終了している。
大きな問題ではないかと思うが、やや違和感を感じる。
実行結果 C:\Users\llll\DATA\ニュースの活用>python.exe crawler.py
処理開始: AAAAAAAA 【初回過去遡りモード】対象年月: 202606 取得総数: 45件 / DB内既存: 40件 / 今回の処理対象: 5件 今回の新規登録: 5件 【202606】の全件(計45件)の回収が完了したため、対象月を過去へ進めます。
処理開始: BBBBBBBB 【初回過去遡りモード】対象年月: 202606 取得総数: 41件 / DB内既存: 40件 / 今回の処理対象: 1件 今回の新規登録: 1件 【202606】の全件(計41件)の回収が完了したため、対象月を過去へ進めます。
処理開始: CCCCCCCC 【初回過去遡りモード】対象年月: 202606 取得総数: 43件 / DB内既存: 40件 / 今回の処理対象: 3件 今回の新規登録: 3件 【202606】の全件(計43件)の回収が完了したため、対象月を過去へ進めます。
【AIからの回答】
運用前にこの挙動に気づいていただけて非常に助かりました。
結論:これはバグではなく「現在のプログラムの仕様(割り切り)」による挙動ですが、ご指摘の通り「1回につき20件処理する」という当初の要件(リソースの有効活用)から見ると、効率が悪い動き(違和感)になっています。
なぜここで処理が終了したのか?(現状の仕様)
現在のコードは、「1回の起動につき、1つの月しか見ない」というシンプルな設計になっています。
そのため、今回の実行では以下のような動きになりました。
1. `202606` をスキャンして残りの数件(5件、1件、3件)を処理。
2. 「今月分はすべて回収した」と判断して状態管理(JSON)を `202605` に更新。
3. ここで次の月(202605)の処理へは進まずに、その回のプログラム自体を終了。
これでもデータは漏れませんが、せっかく1回につき20件処理できる枠があるのに、1件や3件だけでその回の実行を終えてしまうため、タスクスケジューラの「1回分(1時間分)」を少し無駄にしてしまっています。
違和感を解消する修正方針(よくばり回収モードへの変更)
「もし当月の残件を処理した結果、上限の20件に満たなかった場合は、そのまま連続して次の月(202605)のデータも読み込み、合計20件になるまで枠を使い切ってから終了する」というロジック(再帰的、またはループによる連続処理)に改良することが可能です。
もし、「せっかく1時間に1回動かすなら、毎回きっちり20件(またはそれに近い件数)を限界まで処理して、早く過去データを溜めたい」と思われる場合は、この「残り枠の使い切りロジック」へコードを修正いたします。
現状の「1回1月キッチリ型」のままで進めるか、枠を使い切る「よくばり回収型」に修正するか、どちらがご希望に合致しそうでしょうか?
12回目:データ蓄積完了後のロードマップと「限界まで20件回収型」の実装
この問いかけに対し、私は全データ収集完了までにかかる具体的な日数を試算し、定常運用フェーズへ移行した後の負荷まで見据えた「12回目の確認事項」を返しました 。
【12回目の確認事項】
当初は1回/1時間起動するとすると、1か月/3回なので、深夜は起動しない様にすれば、おおよそ1年分/3日になるので、AAAAAAAAだと収集完了まで45日、CCCCCCCCで30日、BBBBBBBBで24日かかる。逆にBBBBBBBBは、収集が終わっても20日間は本来1日1~2回の起動で良いのに1日12回起動と言うことになる。
なので、①仕様はこのままなら引数でグループ指定できるようにする。もしくは、②仕様を毎回20件収集にする。のどちらかにするのが良いかと思う。
ただ、全データ収集後は1日のニュースが概ね0~数件なので、①が良いかとも思う。因みに、全データ収集後は仮にトップページを確認して追加が0件の場合は、詳細は読み込みに行かない仕様になっているだろうか?
【AIからの回答】
システム運用のロードマップとリソース効率を考慮した極めて実践的な着眼点です。全データ収集完了までの期間(24日〜45日)と、その後の定常運用フェーズ(1日0〜数件)のギャップを的確に捉えられています。
いただいたご質問への回答と、アーキテクトとしての最適な着地案を提示します。
1. ご質問への回答:全データ収集後の挙動について
> 全データ収集後は仮にトップページを確認して追加が0件の場合は、詳細は読み込みに行かない仕様になっているだろうか?
結論から言うと、現在のコードでも「詳細(個別ページ)」は見に行かない仕様になっています。
解説: `fetch_sakurazaka` や `fetch_hinatazaka` 関数が行っているのは、月一覧ページ(例:`?dy=202606`)のHTMLを1回ダウンロードし、そこに並んでいるタイトルや概要(添付HTMLの `lead` や `c-news__text` の中身)を切り出しているだけです。
各ニュースの「個別URL」へさらにリンクを叩いて中身を読みに行く処理(二次リクエスト)は、サーバー負荷を避けるため最初から実装していません。
さらに定常モード(最新差分更新)では、一覧を取得したあとDBに `INSERT IGNORE` し、新規登録が「0件」であればそこで処理を終えるため、負荷は最小限(各サイト1リクエストのみ)に抑えられます。
2. 2つの改善案の比較とアーキテクトとしての推奨
ご提示いただいた2つの案について、全データ収集後の「定常運用の楽さ」を見据えて評価します。
案 メリット デメリット
① 引数でグループ指定にする グループごとにタスクを完全に分けて、不要になったグループのタスクを止められる。 Windowsのタスクスケジューラに3つのタスクを登録・管理する必要があり、少々煩雑。
② 毎回必ず20件収集する スクリプトが1つで済み、45日かかるはずだった過去収集がわずか数日で高速に終わる。 実装ロジックがやや複雑になる。
アーキテクトとしての推奨:【案②(毎回20件使い切り型)】
全データ収集完了後は、各グループとも追加が0〜数件になるため、1つのスクリプトのままでも一瞬で処理が終わるようになります。
そのため、過去収集フェーズの期間だけタスクを止めたり変えたりする運用コストを払うよりも、「プログラム側を『毎回20件使い切るまで過去の月をループで掘り進める』ロジックに改良し、数日で一気に過去分を溜めきってしまう」方が、結果的に運用の手離れが最も良くなります。
3. 【案②】に対応した「限界まで20件回収型」 main関数コード
このロジックに修正すると、1回の起動で「6月の残り5件 ➔ 5月の15件」のように月を跨いで合計20件をきっちり回収するため、無駄な空振りが一切なくなり、最速で過去データの収集が完了します。
スクリプトの `main` 関数を以下に差し替えるだけで対応可能です。
<<修正プログラム>>
このロジックにアップデートして手動テストを行っていただければ、枠を完全に使い切る「美しい挙動」が確認できるはずです。
第6章:【ブレイクスルー】「2タスク分離」という最終アーキテクチャの確立
13回目:運用フェーズの混在によるリスクの指摘と人間側からの提案
限界まで20件を回収し、爆速で過去データを掘り進めるロジックを手に入れたことで、今度は「過去遡りフェーズ」と「日々の定常運用フェーズ」が完全にバッティングするという、運用上の重大なリスクが浮き彫りになります 。私は、月を跨ぐ際や未来に日付が進んだ際のデータの扱いについて、以下の2つの致命的な懸念をAIに確認した 。
【13回目の確認事項】
もう少し確認させて欲しい。
例えば、これから202605からの処理になるが、
①これから追加される2026年6月分のニュースはどういう扱いになる?
②途中で7月になるので、トップページは2026年7月だが、それはどういう扱いになる?
published_atに発行日付を持っているので、
1.パラメータで過去データ収集用と最新データ収集用を判断できるようにして、過去データ収集用と最新データ収集用を二つ別個にスケジュールする形にするのが運用的にきれいな気がするがどうだろうか?
ただ、その際は、状態管理ファイルの内容を再構成する(もしくは最新データは起動時の日付から取得する)必要があるかと思うが。
【AIからの回答】
ご指摘の通りです。運用上のフェーズ(過去遡り/日々の定常運用)が混ざることで生じるデータのバッティングや、月を跨ぐ際の挙動についての懸念は非常に的確です。
シニアシステムアーキテクトとして、ご質問の2点への回答と、ご提案いただいた「パラメータによる完全分離(2タスク運用)」の設計評価を提示します。
1. 現状のロジックにおける懸念点への回答
① これから(過去遡り中に)追加される2026年6月分のニュースの扱い
結論:過去遡りが完全に終わるまで、新規追加分は無視(未取得)されてしまいます。
理由:状態管理ファイル(`crawler_state.json`)が `202605` ➔ `202604` と過去へ進んでいくため、プログラムは過去のURL(`?dy=202605`など)しか見に行かなくなります。その間に公式サイトの「今月(202606)」に新しいニュースが増えても、そこへリクエストが飛ばないため、過去遡りフェーズがすべて完了する(数日〜数週間後)まで格納されません。
② 途中で7月になった場合のトップページの扱い
結論: ①と同様に、過去遡り中は**7月の新しいニュースも一切取得されません。
理由: プログラムが過去の月を掘り進めている間は、最新月(現在日時)を取得するロジックを通らないためです。
2. ご提案:過去用と最新用の「2タスク分離運用」の設計評価
> パラメータで過去データ収集用と最新データ収集用を判断できるようにして、過去データ収集用と最新データ収集用を二つ別個にスケジュールする形にするのが運用的にきれいな気がするがどうだろうか?
結論:この設計案が「最も美しく、最も安全」です。採用を強く推奨します。
1つのスクリプトで両方をやろうとすると、状態管理や条件分岐が複雑になりバグの温床になります。
「過去データを掘り進めるタスク」と「常に今日の最新差分を見張るタスク」を明確に分けることで、以下のメリットが生まれます。
過去データを何年間分遡っていようが、今日の新曲発表やライブ告知はリアルタイム(1時間ごと)に100%漏らさずキャッチできる。
過去データの遡りがすべて完了したら、過去用タスク(タスクスケジューラ)を削除するだけで綺麗な定常運用に移行できる。
3. 「2タスク分離型」への修正設計方針
この設計を実現するため、プログラムの起動時に「引数(パラメータ)」を受け取り、挙動を完全に切り替える方式にスマートに統合します。
引数の設計
`python crawler.py --mode past` :過去データ遡り専用(20件使い切り型)
`python crawler.py --mode latest` :最新差分更新専用(常に現在月および月初なら前月をチェック)
状態管理ファイル(`crawler_state.json`)の再構成
最新モード(`latest`)は状態管理ファイルを見ず、プログラム実行時の「システム日付(PCの現在時刻)」を元に動くようにします。これにより、状態管理ファイルは**過去遡りタスクの進捗管理(年月)のためだけに集中**させることができます。
ご提示いただいた「パラメータによる2タスク分離(過去用・最新用)」の設計思想に基づき、安全かつ運用効率を最大化した最終決定版の実装スクリプト(`crawler.py`)を作成しました。
このスクリプト1つで、Windowsタスクスケジューラ側の「引数(パラメータ)」を変えるだけで完全に独立した2つの挙動を実現します。
1. 最終版ソースコード(`crawler.py`)
<<修正プログラムの提示>>
2. Windowsタスクスケジューラの設定仕様(2タスク並行運用)
Windowsのタスクスケジューラには、以下の2つのタスクを別々に登録してください。
タスク①:最新ニュースリアルタイム監視(定常運用)
実行頻度: 1時間に1回(毎日・無期限)
プログラム/スクリプト: `python`
引数の追加: `C:\Users\lupinmk\DATA\ニュースの活用\crawler.py –mode latest`
意図: これにより、過去を何年遡っていようが、**今まさに更新された最新ニュースが毎時100%リアルタイムにDBに格納されます。
タスク②:過去データ爆速回収(期間限定運用)
実行頻度: 1時間に1回(※夜間停止させたい場合は、トリガーの継続時間を「12時間」にする、または「毎日10:00起動〜18:00まで1時間ごと実行」のように詳細設定してください)
プログラム/スクリプト: `python`
引数の追加: `C:\Users\lupinmk\DATA\ニュースの活用\crawler.py –mode past`
意図: 毎起動時にきっちり20件分の枠を使い切りながら、数日間で過去数年分のデータを安全に高速収集します。**全てのグループが最古データに達すると、以降は起動しても何も処理せず一瞬で終了する安全設計です。**(蓄積完了後はタスクごと削除して構いません)。
3. 手動デバッグ実行の手順
1. まず、`crawler_state.json` を開いて、3グループとも `”current_ym”: “202606”`、`”is_finished”: false` に書き戻して保存します。
2. コマンドプロンプトで以下を実行し、最新モードの挙動を確認します。
python crawler.py --mode latest
(※すでに6月分は前回のテストでDBに入っているため、新規登録: 0件 となれば正常です。これによって6月の残件数が壊れることはありません。)
3. 続いて、過去遡りモードをテストします。
python crawler.py --mode past
(※6月の残り【乃木坂: 5件、櫻坂: 1件、日向坂: 3件】を処理したあと、そのまま自動で2026年5月(202605)のデータ取得へ突入し、合計20件になるまで処理が継続する「違和感のない動き」がログで確認できれば完全成功です。)
検証がうまくいきましたら、スケジュール設定へ移行してください。データの蓄積を楽しみにしております。
結び:AI時代における「開発者(人間)」の役割
AIとのこの程度のやり取りで、あるサイトの過去から蓄積されたニュース情報をDBに格納するプログラムが作成できた。
pythonやSQLの知識、MariaDB(MySQL)が稼働している環境が必要だけれども、とても簡単に作れることを見て貰えたと思う。
ただ、簡単とは言え、AIが提示してくるプログラムは、一度で完成版が出てくる訳ではないことも認識してもらえたかと思う。
もう一点、AIとのやり取りに当たっては、ここでは見せていないが、Notionによるプロンプトの管理を活用している。
結果的に、無駄なやり取りが減って、一回のチャットでここまで作成できたのは、最初の頃に比べると非常に効率が良くなったと思う。
