项目简介
把去年用python写的pixiv爬虫项目用AI和serverless架构重写了一下,支持定时任务,支持下载,支持代理访问。
✨ 核心特性
- 无服务器架构:基于 Vercel + Cloudflare Workers,零运维成本
- 智能爬取:支持热度过滤,自动发现优质内容
- ️ 图片代理:解决跨域问题,提供高速图片访问
- 数据存储:集成 Supabase 数据库,支持复杂查询
- 批量下载:支持图片批量下载到 Cloudflare R2
- ⏰ 定时任务:自动爬取排行榜,无需人工干预
️ 系统架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Vercel API │ │ Cloudflare Cron │ │ Supabase DB │
│ (主服务) │◄──►│ (定时任务) │◄──►│ (数据存储) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 图片代理服务 │ │ 爬虫调度器 │ │ 数据分析API │
│ (跨域解决方案) │ │ (任务分发) │ │ (统计查询) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
核心功能实现
1. 智能爬虫引擎
我们的爬虫不仅能获取基础信息,还能智能分析内容质量:
/**
* Pixiv 爬虫核心类
* 支持智能推荐和热度计算
*/
export class PixivCrawler {
private headers: any;
private logManager: any;
private taskId: string;
constructor(pid: string, headers: any, logManager: any, taskId: string) {
this.headers = headers;
this.logManager = logManager;
this.taskId = taskId;
}
/**
* 获取作品详细信息并计算热度
* @param pid 作品ID
* @returns 作品信息和热度分数
*/
async getIllustDetail(pid: string) {
try {
const url = `https://www.pixiv.net/ajax/illust/${pid}`;
const response = await fetch(url, { headers: this.headers });
const data = await response.json();
if (data.error) {
throw new Error(`API错误: ${data.message}`);
}
const illust = data.body;
// 计算热度分数
const popularity = this.calculatePopularity(
illust.likeCount,
illust.bookmarkCount,
illust.viewCount
);
return {
pid: illust.id,
title: illust.title,
tags: illust.tags.tags.map((tag: any) => tag.tag),
likeCount: illust.likeCount,
bookmarkCount: illust.bookmarkCount,
viewCount: illust.viewCount,
popularity,
createDate: illust.createDate
};
} catch (error) {
this.logManager.addLog(`获取作品 ${pid} 失败: ${error.message}`, 'error', this.taskId);
throw error;
}
}
/**
* 热度计算算法
* 综合考虑点赞、收藏、浏览量
*/
private calculatePopularity(likes: number, bookmarks: number, views: number): number {
if (views === 0) return 0;
const likeRate = likes / views;
const bookmarkRate = bookmarks / views;
// 加权计算热度分数
return (likeRate * 0.3 + bookmarkRate * 0.7) * Math.log10(views + 1);
}
}
2. 图片代理服务
解决跨域问题,提供高速图片访问:
/**
* Pixiv 图片代理服务
* 支持多尺寸智能选择和自动降级
*/
export class PixivProxy {
private headers: any;
constructor() {
this.headers = {
'Referer': 'https://www.pixiv.net/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
};
}
/**
* 代理访问 Pixiv 图片
* @param pid 图片ID
* @param size 期望尺寸
* @returns 图片数据流
*/
async proxyImage(pid: string, size: string = 'regular') {
// 尺寸优先级:thumb_mini -> small -> regular -> original
const sizeOptions = ['thumb_mini', 'small', 'regular', 'original'];
const startIndex = sizeOptions.indexOf(size);
if (startIndex === -1) {
throw new Error(`不支持的图片尺寸: ${size}`);
}
// 按优先级尝试获取图片
for (let i = startIndex; i < sizeOptions.length; i++) {
try {
const currentSize = sizeOptions[i];
const imageUrl = await this.getImageUrl(pid, currentSize);
if (imageUrl) {
const imageResponse = await fetch(imageUrl, {
headers: this.headers
});
if (imageResponse.ok) {
return {
data: imageResponse.body,
contentType: imageResponse.headers.get('content-type'),
size: currentSize
};
}
}
} catch (error) {
console.log(`尺寸 ${sizeOptions[i]} 获取失败,尝试下一个尺寸`);
continue;
}
}
throw new Error(`无法获取图片 ${pid} 的任何尺寸版本`);
}
/**
* 获取指定尺寸的图片URL
*/
private async getImageUrl(pid: string, size: string): Promise<string | null> {
try {
const response = await fetch(`https://www.pixiv.net/ajax/illust/${pid}`, {
headers: this.headers
});
const data = await response.json();
const urls = data.body?.urls;
return urls?.[size] || null;
} catch (error) {
return null;
}
}
}
3. API 接口设计
提供完整的 RESTful API:
/**
* 主 API 处理器
* 支持多种操作:爬取、下载、代理、统计
*/
export default async function handler(req: VercelRequest, res: VercelResponse) {
// 设置 CORS 头
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
const { action, pid, size } = req.query;
try {
switch (action) {
case 'proxy-image':
// 图片代理访问
if (!pid) {
res.status(400).json({ error: '缺少 pid 参数' });
return;
}
const proxy = new PixivProxy();
const imageResult = await proxy.proxyImage(pid as string, size as string);
res.setHeader('Content-Type', imageResult.contentType);
res.setHeader('Cache-Control', 'public, max-age=86400'); // 缓存1天
return imageResult.data.pipe(res);
case 'get-pic':
// 获取图片信息
const crawler = new PixivCrawler(pid as string, getPixivHeaders(), logManager, 'api_request');
const illustInfo = await crawler.getIllustDetail(pid as string);
res.status(200).json({
success: true,
data: illustInfo
});
break;
case 'stats':
// 获取统计信息
const supabase = new SupabaseService();
const stats = await supabase.getStats();
res.status(200).json({
success: true,
stats
});
break;
default:
// 返回 Web 界面
const htmlContent = getWebInterface();
res.setHeader('Content-Type', 'text/html');
res.status(200).send(htmlContent);
}
} catch (error) {
res.status(500).json({
error: '服务器内部错误',
message: error.message
});
}
}
4. 定时任务调度
使用 Cloudflare Cron Worker 实现自动化:
/**
* Cloudflare Cron Worker
* 定时执行爬取任务
*/
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
console.log('定时任务开始执行:', new Date().toISOString());
try {
// 每日排行榜爬取
if (shouldRunDailyRanking(event.cron)) {
await triggerRankingCrawl('daily', env);
}
// 每周排行榜爬取
if (shouldRunWeeklyRanking(event.cron)) {
await triggerRankingCrawl('weekly', env);
}
// 清理过期日志
if (shouldCleanLogs(event.cron)) {
await cleanExpiredLogs(env);
}
} catch (error) {
console.error('定时任务执行失败:', error);
}
}
};
/**
* 触发排行榜爬取
*/
async function triggerRankingCrawl(type: 'daily' | 'weekly' | 'monthly', env: Env) {
const endpoint = `${env.MAIN_SERVICE_URL}/api/?action=${type}`;
try {
const response = await fetch(endpoint, {
method: 'GET',
headers: {
'Authorization': `Bearer ${env.API_TOKEN}`
}
});
if (response.ok) {
console.log(`${type} 排行榜爬取任务已触发`);
} else {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error(`触发 ${type} 排行榜爬取失败:`, error);
}
}
3分钟快速部署
步骤1:克隆项目
git clone https://github.com/your-username/serverless_pixiv_crawler.git
cd serverless_pixiv_crawler
npm install
步骤2:配置环境变量
复制 .env.example 为 .env:
# Supabase 数据库配置
SUPABASE_URL=your_supabase_url_here
SUPABASE_ANON_KEY=your_supabase_anon_key_here
# Pixiv 配置
PIXIV_COOKIE=your_pixiv_cookie_here
# Cloudflare R2 配置(可选)
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_ACCESS_KEY_ID=your_access_key
CLOUDFLARE_SECRET_ACCESS_KEY=your_secret_key
CLOUDFLARE_BUCKET_NAME=your_bucket_name
步骤3:部署到 Vercel
# 安装 Vercel CLI
npm i -g vercel
# 登录并部署
vercel login
vercel --prod
步骤4:部署定时任务(可选)
cd cron_worker
npm install
# 配置 Cloudflare Workers
npx wrangler login
npx wrangler deploy
功能演示
1. Web 管理界面
部署完成后,访问你的 Vercel 域名,你将看到一个现代化的管理界面:
- 实时统计:显示爬取数量、成功率等关键指标
- 任务管理:启动、停止、监控爬取任务
- 日志查看:实时查看系统运行日志
- 数据搜索:快速查找和筛选爬取的内容
2. API 接口使用
// 获取图片信息
fetch('https://your-domain.vercel.app/api/?action=get-pic&pid=123456')
.then(res => res.json())
.then(data => console.log(data));
// 代理访问图片
const imageUrl = 'https://your-domain.vercel.app/api/?action=proxy-image&pid=123456&size=regular';
document.getElementById('image').src = imageUrl;
// 启动爬取任务
fetch('https://your-domain.vercel.app/api/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
pid: '123456',
targetNum: 1000,
popularityThreshold: 0.22
})
});
3. 数据分析
系统自动收集和分析数据,提供丰富的统计信息:
-- 热门标签统计
SELECT tag, COUNT(*) as count
FROM illustrations
CROSS JOIN LATERAL unnest(tags) as tag
GROUP BY tag
ORDER BY count DESC
LIMIT 20;
-- 热度分布分析
SELECT
CASE
WHEN popularity >= 0.8 THEN '超高热度'
WHEN popularity >= 0.5 THEN '高热度'
WHEN popularity >= 0.2 THEN '中等热度'
ELSE '低热度'
END as level,
COUNT(*) as count
FROM illustrations
GROUP BY level;
高级特性
1. 智能推荐算法
系统内置智能推荐算法,能够: - 内容发现:基于已有数据发现相似优质内容 - 热度预测:预测内容未来的热度趋势 - 风格分析:识别和分类不同的艺术风格
2. 防封机制
- 请求头轮换:模拟真实浏览器行为
- ⏱️ 智能延迟:动态调整请求间隔
- ️ 错误重试:智能处理网络异常
3. 数据质量保证
- ✅ 自动去重:避免重复数据
- 内容验证:确保数据完整性
- 质量评分:为每个内容计算质量分数
成本分析
这个项目的最大优势是完全免费:
| 服务 | 免费额度 | 足够支撑 |
|---|---|---|
| Vercel | 100GB 带宽/月 | 中小型项目 |
| Supabase | 500MB 数据库 | 50万条记录 |
| Cloudflare Workers | 10万请求/天 | 大部分使用场景 |
| Cloudflare R2 | 10GB 存储 | 数万张图片 |
故障排除
常见问题
- 部署失败
- 检查环境变量配置
- 确认 Supabase 连接正常
- 爬取失败
- 验证 Pixiv Cookie 有效性
- 检查网络连接状态
- 图片无法显示
- 确认代理服务正常运行
- 检查跨域配置
性能优化建议
- 启用缓存:合理设置 CDN 缓存策略
- 监控指标:定期检查系统性能指标
- 定期清理:清理过期数据和日志
总结
这个 Serverless Pixiv 爬虫项目展示了现代化 Web 开发的强大能力:
- ✅ 零运维成本:完全基于云服务,无需管理服务器
- ✅ 高可扩展性:自动伸缩,应对流量波动
- ✅ 功能完整:爬取、存储、分析、展示一体化
- ✅ 部署简单:3分钟即可完成部署
无论你是想学习 Serverless 架构,还是需要一个实用的数据收集工具,这个项目都是一个很好的起点。
相关资源
免责声明:本项目仅供学习和研究使用,请遵守相关网站的服务条款,合理控制爬取频率。
如果这篇文章对你有帮助,请给项目点个 ⭐ Star!

喜欢这篇文章?
如果这篇文章帮到了你,可以请我喝杯咖啡,支持我继续写下去。
微信赞赏
评论