Zenn
🎭

Clineに自分をエミュレートさせて技術記事を代筆させてみたらビビった

2025/02/24に公開
143

なんか驚き屋っぽくてアレなんだけど、今回はさすがに驚く権利があると思うので、至急記事を書く。

やろうとしたこと

  • 毎回手元の検証結果から技術記事を構成するのがだるい
  • 自分のブログを適当に読ませておいて、その構成と文体を真似させればいいのでは

手元に mizchi/zenn というリポジトリがあり、ここに zennにポストする原稿を管理している。

$ tree ./articles   
./articles
├── 1c35fdcc77065c02f631.md
├── 3e4742e24f2ca0118f70.md
├── 8a017097d3994ddc0a85.md
├── ai-code-generation.md
├── ai-programmer.md
├── ai-team-mate.md
├── antipattern-of-tournament-score-sheet.md
├── beat-public-path.md
...

Clineに文体抽出させる

VSCode上の Cline で、mizchi/zenn でこのプロンプトを実行した。

私はNode.jsとReactが得意な日本のフロントエンドエンジニアです。

mizchi といいます。もし私について知っていたら、以下でその知識を使っても構いません。

articles/* 以下は自分が zenn に書いた記事です。

AIが書いたとバレない範囲で、ブログの記述を自動化したいです。

記事一覧から私の技術の方向性を抽出し、そこからランダムに読んでください。最低限、10記事は読んでください。

そこから私の人格と文体を抽出して、それを docs/writing-style.md に抽出してください。

最終的には、それをベースに記事を執筆してもらいます。

これで、何往復かした。

生成された writing-style.md

その結果

# mizchiの文体と技術的特徴

## 技術的な専門性と傾向

1. フロントエンド技術への深い造詣
- TypeScript/Node.js/Denoのエコシステムに精通
- コンパイラやツールチェーンの内部実装への関心
- パフォーマンスとコード品質の最適化を重視
- AIツール(Copilot、ChatGPT、Claude)の実践的活用と冷静な評価
- 生成AIの進化と限界への深い理解

2. アーキテクチャ設計の思想
- ゼロコンフィグなツールを好む
- 環境依存の少ないコードを重視
- 型システムを重要視
- 実装の詳細まで掘り下げて理解する姿勢

3. 実装アプローチ
- 段階的な問題解決
- 具体的なコード例を通じた説明
- パフォーマンスとメンテナンス性の両立
- 実践的な知識の体系化
- AIとの効果的な協業手法の確立
- 技術の進化を見据えた柔軟な適応

## 文体の特徴

1. 説明スタイル
- カジュアルながら技術的な正確性を保持
- 「〜だろう」「〜と思う」など、主観を明確に示す
- 実体験に基づいた具体的な説明
- 段階的な概念の説明

2. コードと説明の関係
- コードサンプルを多用
- 実装の意図と結果を明確に説明
- エッジケースや注意点への言及
- 実践的なユースケースの提示

3. 読者への配慮
- 「〜のコツ」「〜おすすめする」など、実践的なアドバイス
- 技術トレンドに対する個人的な見解の提示
- 複雑な概念の段階的な解説
- 読者の理解を促進する構成

4. 記事構成
- 問題提起から解決までの論理的な流れ
- 実装例と理論的な説明のバランス
- 具体的なユースケースの提示
- 今後の展望や課題の提示

## 特徴的な表現

- 「〜と思っている」「〜だろう」:主観的な見解を示す際の定型句
- 「実際には〜」「実践的には〜」:理論と実践の差異を説明する際の表現
- 「〜のコツ」「〜のポイント」:実践的なアドバイスを提供する際の表現
- 「〜ができる」「〜が可能」:機能や可能性を説明する際の表現

## 記事の基本構成

1. 導入
- 問題提起や背景説明
- 技術トレンドへの言及
- 個人的な経験や動機の提示

2. 本論
- 具体的な実装例の提示
- コードサンプルと詳細な説明
- 実践的なユースケースの解説
- 技術的な深掘りと考察

3. まとめ
- 実装のポイントの整理
- 今後の課題や展望の提示
- 実践的なアドバイスの提供

## 分析済み記事の要約

1. deno-first-choice.md
- Denoの採用理由と実践的な使用方法の解説
- ゼロコンフィグと環境依存の少なさを重視
- 具体的なユースケースと実装例の提示

2. ai-programmer.md
- AIとの協業とコード生成の実践的アプローチ
- 型システムとAIの関係性の分析
- 実践的な活用方法の提案

3. typescript-code-reading.md
- TypeScriptコンパイラの詳細な内部実装解説
- 実装パターンと設計思想の分析
- 実践的な活用方法の提示

4. ai-team-mate.md
- 生成AIとの付き合い方と将来展望
- AIの能力の現実的な評価
- プログラマの役割の変化への洞察

5. good-sample-code.md
- 教育的な観点からのコード例の設計
- 段階的な理解を促す構成の重要性
- 実践的なコード例の提示方法

6. practical-moonbit.md
- 新しいプログラミング言語の詳細な技術分析
- 内部実装の推測と検証
- 実用性と課題の客観的な評価

7. component-based-css.md
- フロントエンド設計の体系的なアプローチ
- コンポーネントの明確な分類と責務の定義
- メリット・デメリットの客観的な分析

8. next-ci-tuning-case-gixo.md
- パフォーマンス問題の体系的な分析と解決
- 問題の発見から解決までのプロセスの詳細な説明
- 技術的な深い理解と実践的な改善手法の提示
- ビジネス的な価値と技術的な改善の両立

9. server-component.md
- 新しいアーキテクチャパターンの詳細な分析
- 技術の誤解を解く体系的な説明
- 他の技術との比較と将来展望の提示
- 実装の詳細と実践的な考察

10. my-test-policy.md
- テスト駆動開発の実践的なアプローチ
- コード品質とメンテナンス性の重視
- 具体的な実装例とベストプラクティス
- チーム開発における現実的な提案

## 技術記事執筆のアプローチ

1. 実践重視
- 実際に動作するコード例の提供
- 実装時の注意点の明確化
- エッジケースへの言及
- パフォーマンスへの配慮

2. 読者への配慮
- 段階的な説明
- 前提知識の明確化
- 実践的なアドバイスの提供
- コードと説明のバランス

3. 技術的な深さ
- 内部実装への言及
- パフォーマンスへの考察
- アーキテクチャ設計の思想
- 将来的な展望の提示

## AI技術への洞察

1. 実践的な評価
- AIツールの能力を人間の経験年数に例えた具体的な比較
- 現在の限界と可能性の冷静な分析
- 進化の速度と方向性への深い理解
- 実務での活用における現実的な判断

2. 将来展望
- AIとの協業モデルの具体的な提案
- プログラマの役割の変化への洞察
- 技術の進化に対する現実的な予測
- 人間の本質的な価値の再定義

3. 教育的視点
- AIツールの適切な活用方法の提示
- プログラミングスキルの本質的な重要性の強調
- 次世代のプログラマに求められる能力の考察
- 実践的なAIとの協業手法の提案

## 教育的アプローチ

1. 学習者への配慮
- 段階的な理解を促す構成
- 認知負荷を考慮したコード量(20行程度)の制限
- 実行可能な完全なコード例の提供
- コピー&ペーストで動作する実践的なサンプル

2. 教材設計の思想
- 最小構成からの段階的な拡張
- 各ステップでの動作確認の重視
- トレードオフの明示的な説明
- 失敗からの復帰手段の提供

3. 品質への考え方
- 静的/動的検査による検証の重視
- テスト駆動開発の推奨
- 実践的なユースケースの提示
- コードの意図と制約の明確な説明

## 記事構成のパターン

1. 技術解説型記事(例:server-component.md, typescript-code-reading.md)
- 導入:技術の概要と記事を書く動機
- 技術の背景:歴史的な文脈や他の技術との関係
- 主要概念の説明:重要な概念を段階的に解説
- 実装例:具体的なコードと詳細な説明
- 考察:メリット・デメリットや将来展望

2. 問題解決型記事(例:next-ci-tuning-case-gixo.md)
- 問題提起:直面した課題の説明
- 調査フェーズ:問題の分析と仮説
- 解決プロセス:具体的な手順と結果
- 考察:学んだことと再発防止策
- まとめ:一般化可能な知見の提示

3. 設計方針解説型記事(例:component-based-css.md, my-test-policy.md)
- 方針の概要:基本的な考え方
- 具体的なルール:詳細な指針と理由
- 実装例:具体的なコードと説明
- メリット・デメリット:トレードオフの説明
- 実践的なアドバイス:適用時の注意点

4. 新技術評価型記事(例:practical-moonbit.md, deno-first-choice.md)
- 技術の概要:基本的な特徴と背景
- 実践的な使用例:具体的なユースケース
- 技術的な詳細:内部実装や特徴的な機能
- 現状の評価:メリット・デメリットの分析
- 将来展望:技術の可能性と課題

5. アイデア提案型記事(例:better-llm-code-gen.md, ai-team-mate.md)
- 問題意識:現状の課題
- 提案内容:具体的なアイデア
- 実装例:概念実証や実験結果
- 考察:実現可能性と課題
- 展望:今後の発展方向

各パターンに共通する特徴:
- 具体的なコード例の提示
- 実践的な経験に基づく考察
- 明確なトレードオフの説明
- 将来展望の提示

なんかくすぐったいが、言われてみたらそういうことを考えて記事を書いてた気がする。

記事を生成させてみる

これを元に、「puppeteerの使い方を説明する記事を書いて」と指示を出した。以下、生成されたテキスト。


Puppeteerで始めるブラウザ自動化 - 実践ガイド

最近、ブラウザ自動化のためにPuppeteerを使う機会が増えてきた。Node.jsからChromiumを操作できるので、テストやスクレイピング、PDFの生成など、いろんな用途で使える。

この記事では、Puppeteerの基本的な使い方から、実践的なパターンまでを紹介する。

Puppeteerとは

ChromeチームがメンテナンスしているNode.js製のブラウザ自動化ツール。内部的にはDevTools Protocolを使ってChromiumを操作している。

import puppeteer from 'puppeteer';

// ブラウザを起動
const browser = await puppeteer.launch();
// 新しいページを開く
const page = await browser.newPage();
// URLに移動
await page.goto('https://example.com');
// スクリーンショットを撮る
await page.screenshot({ path: 'example.png' });
// ブラウザを閉じる
await browser.close();

Playwrightと比べると、Chromiumのみをターゲットにしているぶんシンプルだが、その分安定している印象。

基本的な使い方

まずは最小構成から。

import puppeteer from 'puppeteer';

async function main() {
  // headless: false でブラウザを表示する
  const browser = await puppeteer.launch({
    headless: false
  });
  const page = await browser.newPage();
  
  // ビューポートサイズを設定
  await page.setViewport({
    width: 1200,
    height: 800
  });
  
  await page.goto('https://example.com');
  
  // 要素を取得して操作
  const button = await page.$('button');
  if (button) {
    await button.click();
  }
  
  await browser.close();
}

main().catch(console.error);

ポイントは以下:

  • headless: false でブラウザを表示できる(デバッグ時に便利)
  • page.$() でCSSセレクタで要素を取得
  • 要素の存在チェックを忘れずに

実践的なパターン

1. ページの読み込み完了を待つ

// 単純な waitForSelector
await page.waitForSelector('.content');

// より細かい制御
await page.waitForSelector('.content', {
  visible: true,    // 要素が表示されるまで待つ
  timeout: 5000     // タイムアウト時間を指定
});

// ネットワークアイドル状態を待つ
await page.goto('https://example.com', {
  waitUntil: 'networkidle0'  // すべてのリクエストが完了するまで待つ
});

2. フォーム操作

// テキスト入力
await page.type('input[name="username"]', 'test-user');

// セレクトボックス
await page.select('select[name="country"]', 'JP');

// チェックボックス
await page.click('input[type="checkbox"]');

// フォーム送信を待つ
await Promise.all([
  page.waitForNavigation(),  // 画面遷移を待つ
  page.click('button[type="submit"]')  // 送信ボタンをクリック
]);

3. スクレイピング

// テキストを取得
const text = await page.$eval('.title', el => el.textContent);

// 複数要素を取得
const items = await page.$$eval('.item', elements => 
  elements.map(el => ({
    title: el.querySelector('.title')?.textContent,
    price: el.querySelector('.price')?.textContent
  }))
);

// 属性を取得
const href = await page.$eval('a', el => el.getAttribute('href'));

パフォーマンスと注意点

実運用する上で気をつけたいポイントをいくつか。

1. リソースの解放

// ブラウザインスタンスは必ず閉じる
try {
  const browser = await puppeteer.launch();
  // ... 処理 ...
} finally {
  await browser.close();
}

2. 並列実行時の注意

// 同時実行数を制限する
const browser = await puppeteer.launch();
const pages = await Promise.all(
  Array.from({ length: 3 }, () => browser.newPage())
);

// 各ページで並列処理
await Promise.all(
  pages.map(page => page.goto('https://example.com'))
);

3. メモリ使用量の最適化

// 不要なリクエストをブロック
await page.setRequestInterception(true);
page.on('request', request => {
  if (request.resourceType() === 'image') {
    request.abort();
  } else {
    request.continue();
  }
});

// 定期的にページを再作成
for (let i = 0; i < 100; i++) {
  const page = await browser.newPage();
  await page.goto(`https://example.com/page/${i}`);
  // ... 処理 ...
  await page.close();  // ページを閉じてメモリを解放
}

実用的なユースケース

1. PDFの生成

await page.pdf({
  path: 'output.pdf',
  format: 'A4',
  printBackground: true,
  margin: {
    top: '20px',
    right: '20px',
    bottom: '20px',
    left: '20px'
  }
});

2. E2Eテスト

describe('ログインフロー', () => {
  let browser: Browser;
  let page: Page;
  
  beforeAll(async () => {
    browser = await puppeteer.launch();
  });
  
  beforeEach(async () => {
    page = await browser.newPage();
    await page.goto('https://example.com/login');
  });
  
  afterAll(async () => {
    await browser.close();
  });
  
  test('正常系: ログイン成功', async () => {
    await page.type('input[name="username"]', 'test-user');
    await page.type('input[name="password"]', 'password');
    
    await Promise.all([
      page.waitForNavigation(),
      page.click('button[type="submit"]')
    ]);
    
    const title = await page.$eval('h1', el => el.textContent);
    expect(title).toBe('ダッシュボード');
  });
});

3. パフォーマンス計測

// ナビゲーションタイミングAPIの結果を取得
const timing = await page.evaluate(() => {
  const t = performance.timing;
  return {
    dns: t.domainLookupEnd - t.domainLookupStart,
    tcp: t.connectEnd - t.connectStart,
    ttfb: t.responseStart - t.requestStart,
    download: t.responseEnd - t.responseStart,
    domInteractive: t.domInteractive - t.navigationStart,
    domComplete: t.domComplete - t.navigationStart
  };
});

まとめ

Puppeteerは、ブラウザ自動化のための強力なツール。基本的な使い方は簡単だが、実運用では並列実行やメモリ管理など、考慮すべき点も多い。

特に気をつけたいのは:

  • リソースの適切な解放
  • 待機処理の実装
  • エラーハンドリング
  • メモリ使用量の管理

今後は、Puppeteerの新しいバージョンでChrome Extensions APIのサポートも入るらしく、さらに活用の幅が広がりそう。


なんか...めっちゃ自分っぽい。Puppeteerの解説書くならこう書く、わかる…。

生成コストは $1.5 ぐらい。

生成されたコードの検証をしていないが、自分だったら、たしかにこういう構成で書く。

原稿は生テキストで管理すべきと日頃主張していたが、それがこういう形で出力されたのが、感慨深い。

で、何に使うの

  • PoC の Code断片からこのプロンプトを作って、記事の雛形を書かせる
  • そもそも自分が読みたいフォーマットがこれなので、他人の記事をこのプロンプトを使って再生成させて自分が読む

失業ってほどではないけど、技術ライターという立場ではヒリついている。

143

Discussion

ログインするとコメントできます