当我使用Koa作为web服务器,Puppeteer作为爬虫工具来编写一个简单的爬虫教程时,发生了戏剧性的一幕。
下面我将创建一个完整的Koa + Puppeteer爬虫教程页面,包含代码示例、执行演示和详细说明。
设计思路
左侧为教程内容区域
右侧为实时爬虫演示区域
使用卡片式布局展示代码示例
添加交互式爬虫演示功能
实现方案
<!DOCTYPE html>
<htmllang="zh-CN">
<head>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>Koa + Puppeteer 爬虫教程</title>
<linkhref="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&family=Source+Code+Pro&display=swap"rel="stylesheet">
<linkrel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<scriptsrc="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:04px20pxrgba(0,0,0,0.1);
}
* {
margin:0;
padding:0;
box-sizing:border-box;
}
body{
font-family:'Roboto',sans-serif;
background:linear-gradient(135deg,#f5f7fa0%,#e4edf5100%);
color:#333;
line-height:1.6;
min-height:100vh;
padding:20px;
}
.container{
max-width:1400px;
margin:0auto;
}
header{
text-align:center;
padding:40px0;
background:linear-gradient(120deg,var(--primary),var(--secondary));
border-radius:15px;
color:white;
margin-bottom:30px;
box-shadow:var(--card-shadow);
}
headerh1{
font-size:2.8rem;
margin-bottom:15px;
}
headerp{
font-size:1.2rem;
max-width:800px;
margin:0auto;
opacity:0.9;
}
.logo{
font-size:3rem;
margin-bottom:20px;
display:flex;
justify-content:center;
gap:30px;
}
.logoi{
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:2pxsolidvar(--success);
font-size:1.8rem;
}
h3{
color:var(--primary);
margin:25px015px;
font-size:1.4rem;
}
.step{
background:#f8f9ff;
padding:20px;
border-radius:10px;
margin-bottom:25px;
border-left:4pxsolidvar(--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:20px0;
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:10px15px;
border-radius:8px8px00;
margin-top:20px;
color:white;
}
.code-headerspan{
font-size:0.9rem;
}
.copy-btn{
background:var(--success);
border:none;
color:white;
padding:5px10px;
border-radius:4px;
cursor:pointer;
font-size:0.85rem;
transition:all0.3s;
}
.copy-btn:hover{
background:#3ab7d8;
}
.demo-controls{
display:flex;
gap:15px;
margin-bottom:20px;
}
.url-input{
flex:1;
padding:12px15px;
border:2pxsolid#ddd;
border-radius:8px;
font-size:1rem;
transition:border0.3s;
}
.url-input:focus{
border-color:var(--primary);
outline:none;
}
.run-btn{
background:var(--primary);
color:white;
border:none;
padding:12px25px;
border-radius:8px;
cursor:pointer;
font-weight:500;
transition:all0.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:1pxsolid#e9ecef;
}
.result-item{
padding:15px;
margin-bottom:15px;
background:white;
border-radius:8px;
box-shadow:02px5pxrgba(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:transform0.3s,box-shadow0.3s;
border-top:4pxsolidvar(--success);
}
.feature-card:hover{
transform:translateY(-10px);
box-shadow:012px30pxrgba(0,0,0,0.15);
}
.feature-icon{
font-size:2.5rem;
color:var(--primary);
margin-bottom:20px;
}
.feature-cardh3{
margin:0015px;
font-size:1.4rem;
}
.browser-window{
background:#2b2d42;
border-radius:10px;
overflow:hidden;
margin:25px0;
box-shadow:010px30pxrgba(0,0,0,0.2);
}
.browser-header{
height:40px;
background:#3a3d5d;
display:flex;
align-items:center;
padding:015px;
}
.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-contenth4{
color:var(--primary);
margin-bottom:15px;
}
.browser-contentul{
padding-left:25px;
}
.browser-contentli{
margin-bottom:10px;
}
.tip-box{
background:#e3f2fd;
border-left:4pxsolidvar(--primary);
padding:15px20px;
border-radius:08px8px0;
margin:25px0;
}
.tip-title{
font-weight:bold;
color:var(--primary);
margin-bottom:8px;
display:flex;
align-items:center;
gap:8px;
}
.footer{
text-align:center;
padding:30px0;
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>
<divclass="container">
<header>
<divclass="logo">
<iclass="fab fa-node-js"></i>
<iclass="fab fa-js"></i>
<iclass="fas fa-spider"></i>
</div>
<h1>Koa + Puppeteer 爬虫教程</h1>
<p>学习如何使用Koa.js框架和Puppeteer库构建强大的网络爬虫</p>
</header>
<divclass="content">
<divclass="tutorial">
<h2>Koa + Puppeteer 爬虫指南</h2>
<divclass="step">
<h3><spanclass="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>
<divclass="step">
<h3><spanclass="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>
<divclass="step">
<h3><spanclass="step-number">3</span>项目初始化</h3>
<p>创建项目并安装所需依赖:</p>
<divclass="code-header">
<span>Terminal</span>
<buttonclass="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>
<divclass="step">
<h3><spanclass="step-number">4</span>创建基本爬虫服务</h3>
<p>创建<code>index.js</code>文件,设置 Koa 服务器和爬虫路由:</p>
<divclass="code-header">
<span>index.js</span>
<buttonclass="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>
<divclass="step">
<h3><spanclass="step-number">5</span>运行爬虫服务</h3>
<p>启动服务器:</p>
<divclass="code-header">
<span>Terminal</span>
<buttonclass="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>
<divclass="step">
<h3><spanclass="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>
<divclass="tip-box">
<divclass="tip-title">
<iclass="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>
<divclass="demo">
<h2>爬虫演示</h2>
<divclass="browser-window">
<divclass="browser-header">
<divclass="browser-dot dot-red"></div>
<divclass="browser-dot dot-yellow"></div>
<divclass="browser-dot dot-green"></div>
</div>
<divclass="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>
<divclass="demo-controls">
<inputtype="text"class="url-input"id="crawlUrl"placeholder="输入要爬取的URL (例如: https://example.com)"value="https://example.com">
<buttonclass="run-btn"id="runCrawler">
<iclass="fas fa-play"></i>
<span>运行爬虫</span>
</button>
</div>
<divclass="results"id="results">
<p>爬取结果将显示在这里...</p>
</div>
</div>
</div>
<divclass="features">
<divclass="feature-card">
<divclass="feature-icon">
<iclass="fas fa-bolt"></i>
</div>
<h3>高效灵活</h3>
<p>Koa的轻量级架构结合Puppeteer的强大浏览器控制能力,可以构建高效灵活的爬虫系统,处理各种复杂的网站结构。</p>
</div>
<divclass="feature-card">
<divclass="feature-icon">
<iclass="fas fa-laptop-code"></i>
</div>
<h3>JavaScript渲染支持</h3>
<p>Puppeteer可以完整渲染JavaScript生成的内容,轻松爬取单页应用(SPA)和动态加载的网站,这是传统爬虫难以实现的。</p>
</div>
<divclass="feature-card">
<divclass="feature-icon">
<iclass="fas fa-shield-alt"></i>
</div>
<h3>自动化测试</h3>
<p>除了爬虫功能,Puppeteer还可用于自动化测试、性能监控和UI测试,为项目提供全方位的自动化支持。</p>
</div>
</div>
<divclass="footer">
<p>© 2023 Koa + Puppeteer 爬虫教程 | 本教程仅用于学习目的,请遵守目标网站的robots.txt和法律法规</p>
</div>
</div>
<script>
// 模拟爬虫结果
document.getElementById('runCrawler').addEventListener('click',function() {
consturlInput=document.getElementById('crawlUrl').value;
constresultsDiv=document.getElementById('results');
if(!urlInput) {
alert('请输入URL');
return;
}
// 显示加载状态
resultsDiv.innerHTML='<p class="result-item"><i class="fas fa-spinner fa-spin"></i> 正在爬取数据,请稍候...</p>';
// 模拟网络请求延迟
setTimeout(()=>{
// 模拟爬取结果
constmockData={
title:"示例域名 | "+newURL(urlInput).hostname,
content:"这是一个示例网站。这段文本是从爬虫返回的模拟内容。在实际应用中,这里将显示从目标网站抓取的真实内容。",
links: [
"https://"+newURL(urlInput).hostname+"/about",
"https://"+newURL(urlInput).hostname+"/products",
"https://"+newURL(urlInput).hostname+"/contact"
]
};
// 显示结果
lethtml=`
<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() {
constcodeBlock=this.parentElement.nextElementSibling;
consttextToCopy=codeBlock.textContent;
navigator.clipboard.writeText(textToCopy).then(()=>{
constoriginalText=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文件即可查看完整的教程页面,我们可以通过右侧的演示区域模拟爬虫运行效果。