当我使用Koa作为web服务器,Puppeteer作为爬虫工具来编写一个简单的爬虫教程时,发生了戏剧性的一幕。
下面我将创建一个完整的Koa + Puppeteer爬虫教程页面,包含代码示例、执行演示和详细说明。

设计思路
- 左侧为教程内容区域
- 右侧为实时爬虫演示区域
- 使用卡片式布局展示代码示例
- 添加交互式爬虫演示功能
实现方案
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Koa + Puppeteer 爬虫教程</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Source+Code+Pro&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--primary: #4361ee;
--secondary: #3f37c9;
--success: #4cc9f0;
--dark: #2b2d42;
--light: #f8f9fa;
--danger: #e63946;
--gray: #6c757d;
--card-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
color: #333;
line-height: 1.6;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
padding: 40px 0;
background: linear-gradient(120deg, var(--primary), var(--secondary));
border-radius: 15px;
color: white;
margin-bottom: 30px;
box-shadow: var(--card-shadow);
}
header h1 {
font-size: 2.8rem;
margin-bottom: 15px;
}
header p {
font-size: 1.2rem;
max-width: 800px;
margin: 0 auto;
opacity: 0.9;
}
.logo {
font-size: 3rem;
margin-bottom: 20px;
display: flex;
justify-content: center;
gap: 30px;
}
.logo i {
background: rgba(255, 255, 255, 0.2);
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.content {
display: flex;
gap: 30px;
margin-bottom: 40px;
}
.tutorial {
flex: 3;
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: var(--card-shadow);
}
.demo {
flex: 2;
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: var(--card-shadow);
display: flex;
flex-direction: column;
}
h2 {
color: var(--secondary);
margin-bottom: 25px;
padding-bottom: 10px;
border-bottom: 2px solid var(--success);
font-size: 1.8rem;
}
h3 {
color: var(--primary);
margin: 25px 0 15px;
font-size: 1.4rem;
}
.step {
background: #f8f9ff;
padding: 20px;
border-radius: 10px;
margin-bottom: 25px;
border-left: 4px solid var(--primary);
}
.step-number {
display: inline-block;
background: var(--primary);
color: white;
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
border-radius: 50%;
margin-right: 10px;
font-weight: bold;
}
pre {
background: #2b2d42;
color: #f8f9fa;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
margin: 20px 0;
font-family: 'Source Code Pro', monospace;
font-size: 0.95rem;
}
code {
font-family: 'Source Code Pro', monospace;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
background: #1a1c2e;
padding: 10px 15px;
border-radius: 8px 8px 0 0;
margin-top: 20px;
color: white;
}
.code-header span {
font-size: 0.9rem;
}
.copy-btn {
background: var(--success);
border: none;
color: white;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.3s;
}
.copy-btn:hover {
background: #3ab7d8;
}
.demo-controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.url-input {
flex: 1;
padding: 12px 15px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border 0.3s;
}
.url-input:focus {
border-color: var(--primary);
outline: none;
}
.run-btn {
background: var(--primary);
color: white;
border: none;
padding: 12px 25px;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.run-btn:hover {
background: var(--secondary);
transform: translateY(-2px);
}
.results {
flex: 1;
background: #f8f9ff;
border-radius: 10px;
padding: 20px;
overflow-y: auto;
border: 1px solid #e9ecef;
}
.result-item {
padding: 15px;
margin-bottom: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.result-title {
color: var(--primary);
font-weight: 500;
margin-bottom: 8px;
}
.result-link {
color: var(--success);
font-size: 0.9rem;
display: block;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.result-snippet {
color: var(--gray);
font-size: 0.95rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin-top: 40px;
}
.feature-card {
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: var(--card-shadow);
transition: transform 0.3s, box-shadow 0.3s;
border-top: 4px solid var(--success);
}
.feature-card:hover {
transform: translateY(-10px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
}
.feature-icon {
font-size: 2.5rem;
color: var(--primary);
margin-bottom: 20px;
}
.feature-card h3 {
margin: 0 0 15px;
font-size: 1.4rem;
}
.browser-window {
background: #2b2d42;
border-radius: 10px;
overflow: hidden;
margin: 25px 0;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.browser-header {
height: 40px;
background: #3a3d5d;
display: flex;
align-items: center;
padding: 0 15px;
}
.browser-dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.dot-red { background: #ff5f56; }
.dot-yellow { background: #ffbd2e; }
.dot-green { background: #27c93f; }
.browser-content {
height: 300px;
background: white;
padding: 20px;
overflow-y: auto;
}
.browser-content h4 {
color: var(--primary);
margin-bottom: 15px;
}
.browser-content ul {
padding-left: 25px;
}
.browser-content li {
margin-bottom: 10px;
}
.tip-box {
background: #e3f2fd;
border-left: 4px solid var(--primary);
padding: 15px 20px;
border-radius: 0 8px 8px 0;
margin: 25px 0;
}
.tip-title {
font-weight: bold;
color: var(--primary);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.footer {
text-align: center;
padding: 30px 0;
color: var(--gray);
font-size: 0.95rem;
margin-top: 20px;
}
@media (max-width: 1000px) {
.content {
flex-direction: column;
}
.features {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">
<i class="fab fa-node-js"></i>
<i class="fab fa-js"></i>
<i class="fas fa-spider"></i>
</div>
<h1>Koa + Puppeteer 爬虫教程</h1>
<p>学习如何使用Koa.js框架和Puppeteer库构建强大的网络爬虫</p>
</header>
<div class="content">
<div class="tutorial">
<h2>Koa + Puppeteer 爬虫指南</h2>
<div class="step">
<h3><span class="step-number">1</span> Puppeteer 简介</h3>
<p>Puppeteer 是一个 Node.js 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以配置为有界面模式运行。</p>
<p>主要功能:</p>
<ul>
<li>生成页面截图和PDF</li>
<li>爬取单页应用(SPA)并生成预渲染内容</li>
<li>自动化表单提交、UI测试、键盘输入等</li>
<li>创建最新的自动化测试环境</li>
</ul>
</div>
<div class="step">
<h3><span class="step-number">2</span> Koa.js 简介</h3>
<p>Koa 是由 Express 原班人马打造的下一代 Node.js Web 框架,旨在为 Web 应用和 API 提供更小、更富有表现力、更健壮的基石。</p>
<p>主要特点:</p>
<ul>
<li>轻量级,无捆绑任何中间件</li>
<li>使用 async/await 语法,优雅地处理异步</li>
<li>错误处理更友好</li>
<li>核心代码简洁,易于扩展</li>
</ul>
</div>
<div class="step">
<h3><span class="step-number">3</span> 项目初始化</h3>
<p>创建项目并安装所需依赖:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn">复制</button>
</div>
<pre><code># 创建项目目录
mkdir koa-puppeteer-crawler
cd koa-puppeteer-crawler
# 初始化项目
npm init -y
# 安装依赖
npm install koa @koa/router puppeteer</code></pre>
</div>
<div class="step">
<h3><span class="step-number">4</span> 创建基本爬虫服务</h3>
<p>创建 <code>index.js</code> 文件,设置 Koa 服务器和爬虫路由:</p>
<div class="code-header">
<span>index.js</span>
<button class="copy-btn">复制</button>
</div>
<pre><code>const Koa = require('koa');
const Router = require('@koa/router');
const puppeteer = require('puppeteer');
const app = new Koa();
const router = new Router();
// 爬虫路由
router.get('/crawl', async (ctx) => {
// 从查询参数获取URL
const url = ctx.query.url || 'https://example.com';
// 启动浏览器
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// 获取页面数据
const pageData = await page.evaluate(() => {
return {
title: document.title,
content: document.body.innerText.substring(0, 1000) + '...',
links: Array.from(document.querySelectorAll('a')).map(a => a.href)
};
});
ctx.body = {
success: true,
data: pageData
};
} catch (error) {
ctx.status = 500;
ctx.body = {
success: false,
message: error.message
};
} finally {
await browser.close();
}
});
app.use(router.routes());
app.use(router.allowedMethods());
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});</code></pre>
</div>
<div class="step">
<h3><span class="step-number">5</span> 运行爬虫服务</h3>
<p>启动服务器:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn">复制</button>
</div>
<pre><code>node index.js</code></pre>
<p>访问爬虫接口:</p>
<pre><code>http://localhost:3000/crawl?url=https://example.com</code></pre>
</div>
<div class="step">
<h3><span class="step-number">6</span> 高级爬虫技巧</h3>
<p>处理动态加载内容:</p>
<pre><code>// 等待特定元素出现
await page.waitForSelector('.results-container', { timeout: 5000 });
// 滚动页面加载更多内容
await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
});
// 点击"加载更多"按钮
await page.click('.load-more-button');</code></pre>
<p>处理登录认证:</p>
<pre><code>// 输入用户名和密码
await page.type('#username', 'myuser');
await page.type('#password', 'mypassword');
// 提交表单
await page.click('#login-button');
await page.waitForNavigation();</code></pre>
</div>
<div class="tip-box">
<div class="tip-title">
<i class="fas fa-lightbulb"></i>
<span>最佳实践建议</span>
</div>
<ul>
<li>使用 <code>page.setUserAgent()</code> 设置合理的用户代理</li>
<li>使用 <code>page.setViewport()</code> 设置视口大小</li>
<li>添加请求延迟避免被封禁</li>
<li>使用代理IP处理高频请求</li>
<li>合理使用 <code>waitForSelector</code> 和 <code>waitForNavigation</code></li>
<li>使用try-catch处理异常</li>
</ul>
</div>
</div>
<div class="demo">
<h2>爬虫演示</h2>
<div class="browser-window">
<div class="browser-header">
<div class="browser-dot dot-red"></div>
<div class="browser-dot dot-yellow"></div>
<div class="browser-dot dot-green"></div>
</div>
<div class="browser-content">
<h4>Puppeteer 爬虫功能演示</h4>
<p>在右侧输入URL,点击"运行爬虫"查看效果:</p>
<ul>
<li>爬取页面标题和内容</li>
<li>提取所有链接</li>
<li>处理JavaScript渲染的页面</li>
<li>模拟用户交互</li>
</ul>
<p>示例URL:</p>
<ul>
<li>https://example.com</li>
<li>https://news.ycombinator.com</li>
<li>https://quotes.toscrape.com</li>
</ul>
</div>
</div>
<div class="demo-controls">
<input type="text" class="url-input" id="crawlUrl" placeholder="输入要爬取的URL (例如: https://example.com)" value="https://example.com">
<button class="run-btn" id="runCrawler">
<i class="fas fa-play"></i>
<span>运行爬虫</span>
</button>
</div>
<div class="results" id="results">
<p>爬取结果将显示在这里...</p>
</div>
</div>
</div>
<div class="features">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-bolt"></i>
</div>
<h3>高效灵活</h3>
<p>Koa的轻量级架构结合Puppeteer的强大浏览器控制能力,可以构建高效灵活的爬虫系统,处理各种复杂的网站结构。</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-laptop-code"></i>
</div>
<h3>JavaScript渲染支持</h3>
<p>Puppeteer可以完整渲染JavaScript生成的内容,轻松爬取单页应用(SPA)和动态加载的网站,这是传统爬虫难以实现的。</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h3>自动化测试</h3>
<p>除了爬虫功能,Puppeteer还可用于自动化测试、性能监控和UI测试,为项目提供全方位的自动化支持。</p>
</div>
</div>
<div class="footer">
<p>© 2023 Koa + Puppeteer 爬虫教程 | 本教程仅用于学习目的,请遵守目标网站的robots.txt和法律法规</p>
</div>
</div>
<script>
// 模拟爬虫结果
document.getElementById('runCrawler').addEventListener('click', function() {
const urlInput = document.getElementById('crawlUrl').value;
const resultsDiv = document.getElementById('results');
if (!urlInput) {
alert('请输入URL');
return;
}
// 显示加载状态
resultsDiv.innerHTML = '<p class="result-item"><i class="fas fa-spinner fa-spin"></i> 正在爬取数据,请稍候...</p>';
// 模拟网络请求延迟
setTimeout(() => {
// 模拟爬取结果
const mockData = {
title: "示例域名 | " + new URL(urlInput).hostname,
content: "这是一个示例网站。这段文本是从爬虫返回的模拟内容。在实际应用中,这里将显示从目标网站抓取的真实内容。",
links: [
"https://" + new URL(urlInput).hostname + "/about",
"https://" + new URL(urlInput).hostname + "/products",
"https://" + new URL(urlInput).hostname + "/contact"
]
};
// 显示结果
let html = `
<div class="result-item">
<div class="result-title">${mockData.title}</div>
<div class="result-snippet">${mockData.content}</div>
</div>
<div class="result-item">
<div class="result-title">提取到的链接 (${mockData.links.length})</div>
`;
mockData.links.forEach(link => {
html += `<div class="result-link">${link}</div>`;
});
html += `</div>`;
resultsDiv.innerHTML = html;
}, 2000);
});
// 复制代码功能
document.querySelectorAll('.copy-btn').forEach(button => {
button.addEventListener('click', function() {
const codeBlock = this.parentElement.nextElementSibling;
const textToCopy = codeBlock.textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = this.textContent;
this.textContent = '已复制!';
this.style.background = '#27c93f';
setTimeout(() => {
this.textContent = originalText;
this.style.background = '';
}, 1500);
}).catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动复制代码');
});
});
});
</script>
</body>
</html>
功能说明
这个教程页面包含以下部分:
1、头部区域:展示标题和介绍
2、、教程内容区:
- Puppeteer和Koa简介
- 项目初始化步骤
- 核心爬虫代码实现
- 运行和测试方法
- 高级爬虫技巧
- 最佳实践建议
3、演示区域:
- 浏览器窗口模拟
- URL输入框
- 运行爬虫按钮
- 结果展示区域
4、功能卡片:
- 高效灵活
- JavaScript渲染支持
- 自动化测试
5、交互功能:
- 代码复制按钮
- 模拟爬虫运行
- 结果展示
页面采用了响应式设计,可以在不同设备上正常显示,使用了现代化的UI设计风格,包括卡片式布局、柔和的阴影和渐变色背景。
在浏览器中打开此HTML文件即可查看完整的教程页面,我们可以通过右侧的演示区域模拟爬虫运行效果。