欢迎来到「财富猿」—— 一个专注于技术开发与财富创造的博客!

一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站

243次阅读
没有评论
一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站
一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站

当看到哪吒2票房曲线宛如升旗仪式,作为程序猿的我坐不住了!

昨晚刷微博时,一条热搜让我笑喷:”建议哪吒2票房统计改用升旗仪式BGM”。点开实时票房一看,好家伙!96.27亿的曲线笔直向上,这哪是电影票房,分明是程序员的K线图啊!

一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站

作为技术宅的我突然上头:我要做个实时票房监控站!破百亿时自动触发天幕烟花!说干就干,抄起键盘就是一顿输出…


🚀 技术选型:AI编程的正确打开方式

凌晨2点的我打开Cursor(VSCode究极进化版),在对话框输入:

我需要一个实时票房监控网站,要求:
1. 每30秒自动刷新数据
2. 破百亿时全屏烟花特效
3. 使用现代前端框架
4. 绕过猫眼反爬机制

AI助手deepseek秒回方案:

建议技术栈:
前端:Astro + React + Tailwind(轻量高性能)
代理:Cloudflare Worker(解决跨域+反爬)
动画:Framer Motion + Canvas烟花特效
部署:Vercel(自动SSL+CDN加速)

看着这行云流水的方案,我直呼好家伙!这AI怕是偷偷刷了LeetCode?


🔍 破解猫眼反爬:与字体加密斗智斗勇

正当我美滋滋调用猫眼API时,一盆冷水浇下——返回的数据全是”口口口”!原来猫眼用了字体反爬,数字都是加密字体!

一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站

和AI大战三百回合后,我们写出字体解密算法(已失效):

// 字体映射缓存
const fontMappingCache = new Map();

// 历史值缓存
const valueHistoryCache = new Map();

// 字体映射表 - 更新为更完整的映射
const FONT_MAPPINGS = {
  '75e5b39d': {
    'ec68': '3', 'e5ac': '0', 'e274': '1', 'e6d5': '2',
    'f615': '4', 'eab3': '5', 'f66d': '6', 'ef74': '7',
    'e1b7': '8', 'e916': '.', 'f05a': '8', 'e132': '9',
    'eac9': '0', 'f477': '1', 'f47a': '2', 'ee5d': '3',
    'e0b8': '5', 'e260': '6', 'f1a1': '7', 'e6f0': '8',
    'eff9': '9'
  },
  '20a70494': {
    'e886': '0', 'f23f': '1', 'eba2': '2', 'f16b': '3',
    'ec4b': '4', 'e583': '5', 'ed8f': '6', 'f11c': '7',
    'f3e8': '8', 'f7d2': '9', 'e916': '.', 'f05a': '8',
    'e8d7': '0', 'f85e': '1', 'e8ee': '2', 'e99c': '3',
    'e9ea': '4', 'f1fc': '5', 'eb92': '6', 'f726': '7',
    'f7ff': '8', 'e132': '9', 'eac9': '0', 'f477': '1'
  },
  '432017e7': {
    'f0f0': '0', 'f70e': '1', 'ed4f': '2', 'ed98': '3',
    'e85f': '4', 'efe9': '5', 'f7b3': '6', 'f11c': '7',
    'f3e8': '8', 'f7d2': '9', 'e916': '.', 'f05a': '8',
    'e132': '9', 'eac9': '0', 'ee5d': '3', 'e0b8': '5'
  },
  'e3dfe524': {
    'e886': '0', 'f23f': '1', 'eba2': '2', 'f16b': '3',
    'ec4b': '4', 'e583': '5', 'ed8f': '6', 'f11c': '7',
    'f3e8': '8', 'f7d2': '9', 'e916': '.', 'f05a': '8',
    'e3ec': '4', 'eb19': '5', 'ed30': '6', 'e3df': '7',
    'ea60': '8', 'ef28': '3', 'ea6f': '4', 'e132': '9'
  },
  '2a70c44b': {
    'e886': '0', 'f23f': '1', 'eba2': '2', 'f16b': '3',
    'ec4b': '4', 'e583': '5', 'ed8f': '6', 'f11c': '7',
    'f3e8': '8', 'f7d2': '9', 'e916': '.', 'f05a': '8',
    'e83d': '0', 'e132': '9'
  }
};

// 数据波动检测配置
const FLUCTUATION_CONFIG = {
  MAX_PERCENTAGE: 100, // 增加到100%以适应大盘数据的波动
  MIN_VALUE: 0.1,
  MAX_VALUE: 1000000, // 增加最大值范围
  HISTORY_SIZE: 5  // 增加历史记录大小以提高准确性
};

/**
 * 优化的字体ID提取函数
 */
const extractFontId = (fontStyle) => {
  if (!fontStyle) return null;
  const cached = fontMappingCache.get('lastFontId');
  if (cached && fontStyle.includes(cached)) return cached;

  // 增加更多的字体URL匹配模式
  const fontUrls = fontStyle.match(/url\(['"]*([^'")]+)['"]*\)/g) || [];
  for (const fontUrl of fontUrls) {
    const url = fontUrl.match(/url\(['"]*([^'")]+)['"]*\)/)[1];
    const fontId = url.split('/').pop().split('.')[0];
    if (FONT_MAPPINGS[fontId]) {
      fontMappingCache.set('lastFontId', fontId);
      return fontId;
    }
  }

  return null;
};

/**
 * 优化的数值波动检查函数
 */
const isFluctuationReasonable = (currentValue, prevValue, key) => {
  if (!prevValue) return true;
  if (currentValue < FLUCTUATION_CONFIG.MIN_VALUE || currentValue > FLUCTUATION_CONFIG.MAX_VALUE) return false;

  const percentageChange = Math.abs((currentValue - prevValue) / prevValue * 100);

  // 检查是否是单位变化(万到亿)
  if (percentageChange > FLUCTUATION_CONFIG.MAX_PERCENTAGE) {
    const scaledCurrent = currentValue * (currentValue < prevValue ? 10000 : 0.0001);
    const scaledPercentageChange = Math.abs((scaledCurrent - prevValue) / prevValue * 100);
    if (scaledPercentageChange < FLUCTUATION_CONFIG.MAX_PERCENTAGE) {
      return true;
    }
    console.warn(`数据波动异常 [${key}]:`, { current: currentValue, previous: prevValue, change: percentageChange.toFixed(2) + '%' });
    return false;
  }

  return true;
};

/**
 * 优化的HTML实体解码函数
 */
const decodeHtmlEntity = (text, fontStyle, key = 'default') => {

  if (!text || !fontStyle) return '0';

  try {
    const fontId = extractFontId(fontStyle);
    if (!fontId) {
      console.warn(`未找到有效的字体ID [${key}]`);
      return valueHistoryCache.get(key)?.[0]?.toString() || '0';
    }

    // 获取字体映射
    let numMap = fontMappingCache.get(fontId);
    if (!numMap) {
      numMap = FONT_MAPPINGS[fontId];
      if (!numMap) {
        console.warn(`未找到字体映射: ${fontId}`);
        return valueHistoryCache.get(key)?.[0]?.toString() || '0';
      }
      fontMappingCache.set(fontId, numMap);
    }

    // 记录原始编码用于调试
    const originalCodes = text.match(/&#x([0-9a-f]{4});/gi) || [];
    console.log(`原始编码 [${key}]:`, originalCodes.join(', '));

    // 使用正则一次性替换所有编码
    const decodedText = text.replace(/&#x([0-9a-f]{4});/gi, (match, code) => {
      const decoded = numMap[code.toLowerCase()];
      if (!decoded) {
        console.warn(`未找到字符映射 [${key}]: ${code}`);
        return '0';
      }
      return decoded;
    });

    console.log(`解码结果 [${key}]:`, decodedText);

    // 获取历史值并检查波动
    const history = valueHistoryCache.get(key) || [];
    const currentValue = parseFloat(decodedText);

    // 针对大盘票房数据的特殊处理
    if (key === 'nationBox' || key === 'nationSplitBox') {
      // 如果是大盘数据,允许更大的波动范围
      if (currentValue > 0 && currentValue < FLUCTUATION_CONFIG.MAX_VALUE) {
        // 检查数值是否合理
        if (history.length > 0) {
          const prevValue = history[0];
          const percentageChange = Math.abs((currentValue - prevValue) / prevValue * 100);
          if (percentageChange > FLUCTUATION_CONFIG.MAX_PERCENTAGE) {
            console.warn(`大盘数据波动过大 [${key}]:`, {
              current: currentValue,
              previous: prevValue,
              change: `${percentageChange.toFixed(2)}%`
            });
            return prevValue.toString();
          }
        }

        history.unshift(currentValue);
        if (history.length > FLUCTUATION_CONFIG.HISTORY_SIZE) {
          history.pop();
        }
        valueHistoryCache.set(key, history);
        return decodedText;
      }
    } else if (!isFluctuationReasonable(currentValue, history[0], key)) {
      return history[0]?.toString() || '0';
    }

    // 更新历史值
    history.unshift(currentValue);
    if (history.length > FLUCTUATION_CONFIG.HISTORY_SIZE) {
      history.pop();
    }
    valueHistoryCache.set(key, history);

    return decodedText;
  } catch (error) {
    console.error('解码错误:', error);
    return valueHistoryCache.get(key)?.[0]?.toString() || '0';
  }
};

这段代码就像密码本,把猫眼的加密字体转成真实数字。期间AI甚至教我如何动态更新字体映射表,这学习能力比我带的实习生都强!


💻 前端实现:让数据会跳舞

用Astro搭建的页面加载速度惊人,搭配React的动态组件,数据刷新如丝般顺滑:

<AnimatedNumber value={currentBoxOffice}>
  {(value) => (
    <motion.div 
      className="text-6xl font-bold text-gradient"
      animate={{ scale: [1, 1.2, 1] }}
      transition={{ duration: 0.5 }}
    >
      ¥{value.toFixed(2)}亿
    </motion.div>
  )}
</AnimatedNumber>

当票房变化时,数字会先放大再缩回,配合渐变色背景,仿佛数字在呼吸!


🎆 百亿烟花:代码人的浪漫

最让我惊喜的是canvas-confetti配合framer-motion实现的烟花效果,破百亿瞬间触发:

// 增强票房动画效果
const triggerCelebration = () => {
  // 多彩五彩纸屑
  const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'];

  // 从底部发射
  confetti({
    particleCount: 100,
    spread: 70,
    origin: { y: 0.9 },
    colors: colors,
  });

  // 从左侧发射
  setTimeout(() => {
    confetti({
      particleCount: 50,
      angle: 60,
      spread: 55,
      origin: { x: 0, y: 0.5 },
      colors: colors,
    });
  }, 250);

  // 从右侧发射
  setTimeout(() => {
    confetti({
      particleCount: 50,
      angle: 120,
      spread: 55,
      origin: { x: 1, y: 0.5 },
      colors: colors,
    });
  }, 400);

  // 从中间向四周爆发
  setTimeout(() => {
    confetti({
      particleCount: 150,
      spread: 360,
      startVelocity: 45,
      decay: 0.9,
      gravity: 1,
      drift: 0,
      ticks: 200,
      origin: { x: 0.5, y: 0.5 },
      colors: colors,
      shapes: ['square', 'circle'],
    });
  }, 600);

  // 最后的烟花效果
  setTimeout(() => {
    const end = Date.now() + 1000;
    const colors = ['#ff0000', '#ffd700', '#00ff00', '#0000ff', '#ff00ff'];

    (function frame() {
      confetti({
        particleCount: 5,
        angle: 60,
        spread: 55,
        origin: { x: 0 },
        colors: colors
      });
      confetti({
        particleCount: 5,
        angle: 120,
        spread: 55,
        origin: { x: 1 },
        colors: colors
      });

      if (Date.now() < end) {
        requestAnimationFrame(frame);
      }
    }());
  }, 800);
};

当票房突破100亿的瞬间,整个屏幕绽放金色烟花,配合《哪吒》经典BGM,程序员的浪漫就是这么朴实无华!

一夜爆肝!我用AI助手7小时做出了《哪吒2》百亿票房监控网站

🚨 踩坑实录:那些年我们遇到的坑

  1. 跨域问题:猫眼的API直接调用会CORS报错,最后用Cloudflare Worker做代理:
const targetUrl = `......`
const headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
        'Referer': 'https://piaofang.maoyan.com/dashboard/movie',
        'Origin': 'https://piaofang.maoyan.com',
        'Host': 'piaofang.maoyan.com',
        'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin'
      };
 const response = await fetch(targetUrl, { 
        headers,
        cf: {
          cacheTtl: 5,
          cacheEverything: true
        }
      });

🌈 成果展示:从代码到百万浏览

经过3小时爆肝,最终成果:

💡 后记:AI时代程序员生存指南

这次经历让我深刻体会到:

  1. AI不是替代者,而是增幅器:它能在10分钟内写出我1小时才能完成的代码
  2. 前端已进入”组装时代”:合理使用现成组件库+AI生成,效率提升10倍不止
  3. 数据可视化是刚需:一个动效精美的数字,抵得过千行控制台日志

最后分享一句哪吒的台词:”我命由我不由天!”——在AI时代,程序员不是被取代,而是进化成了更强大的物种。

正文完
 1
评论(没有评论)