Clineに自分をエミュレートさせて技術記事を代筆させてみたらビビった
なんか驚き屋っぽくてアレなんだけど、今回はさすがに驚く権利があると思うので、至急記事を書く。
やろうとしたこと
- 毎回手元の検証結果から技術記事を構成するのがだるい
- 自分のブログを適当に読ませておいて、その構成と文体を真似させればいいのでは
手元に 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断片からこのプロンプトを作って、記事の雛形を書かせる
- そもそも自分が読みたいフォーマットがこれなので、他人の記事をこのプロンプトを使って再生成させて自分が読む
失業ってほどではないけど、技術ライターという立場ではヒリついている。
Discussion