1. 从1750个AI创业公司首页里我到底想看出什么门道第一次冒出把上千个AI创业公司首页摆在一起看这个念头是在连续刷了几十个同类产品落地页之后。那种感觉很奇怪——明明是不同的公司、不同的赛道、不同的创始人但页面滑下来视觉节奏几乎一模一样顶部一句大号加粗的价值主张下面跟一行灰色副标题再往下是产品截图或者一段循环播放的演示动画配色清一色深色底加霓虹渐变按钮不是紫就是蓝。刷到第二十个的时候我已经分不清自己在看哪家了。这件事本身就是一个值得研究的现象。AI创业公司在2023到2025年之间经历了一轮爆发式增长产品同质化严重连带着它们的门面——首页——也高度趋同。但趋同是个模糊的直觉判断我需要把它变成可量化、可浏览、可对比的东西。于是我决定做一个小项目抓取1750个AI创业公司的首页截图用CLIP模型提取视觉和文本的联合特征再做一个可视化浏览器让人可以像翻卡片一样横向对比这些页面从中找出风格上的聚类和规律。这个项目的核心不是爬虫也不是前端而是如何把非结构化的视觉风格变成可检索、可聚类的向量。CLIP在这里扮演了关键角色它能把一张首页截图和一段文字描述映射到同一个语义空间里这样我就能用深色极简风渐变紫大量留白这样的自然语言去搜索页面而不是靠人工打标签。关键词里提到的clip模型微调clip文本编码节点怎么输入内容clip多模态模型这些搜索词说明很多人卡在同一个地方知道CLIP能干什么但不知道怎么把它接到自己的实际项目里。这篇文章我会完整拆解这个项目的设计思路、CLIP的接入方式、可视化浏览器的实现路径以及我在过程中踩过的坑。适合对多模态模型应用感兴趣、想做一个类似视觉检索工具的读者也适合单纯好奇AI创业公司首页到底长什么样的朋友。你不需要有很深的机器学习背景但需要对Python和基本的前端概念有一点了解。2. 为什么选CLIP而不是自己训一个分类器2.1 传统图像分类思路在这个场景下为什么行不通最直接的想法是我定义几个风格标签比如深色模式浅色模式渐变背景插画风3D渲染风然后训练一个图像分类器把1750张截图分到这些类别里。这个思路在工业界很常见但在这个项目里有一个致命问题标签体系是我拍脑袋定的而我想发现的是我事先不知道的风格规律。如果我先定义了深色/浅色这两个类那我就永远只能看到这两种风格那些介于两者之间的、或者我根本没想到的风格维度就被彻底忽略了。这就像做用户调研时只给选择题不给开放题你得到的永远是你已经知道的答案。CLIP的零样本能力恰好解决了这个问题。它不需要我预先定义类别我可以用任意自然语言描述去检索图像。比如我突然想看带有手绘涂鸦元素的首页直接输入这句话CLIP就能给出相似度排序。这种开放式检索的能力才是发现未知风格模式的前提。2.2 CLIP的图文对齐机制用大白话讲清楚CLIP的全称是Contrastive Language-Image Pre-training原论文标题是Learning Transferable Visual Models From Natural Language Supervision。它的核心训练目标很朴素给一批图片文本配对数据让配对的图文向量在语义空间里靠近不配对的远离。训练完之后一张图片和一个文本描述之间的相似度就可以用它们向量的余弦相似度来衡量。你可以把它想象成一个翻译器它把图片翻译成一种语义坐标把文字也翻译成同一种语义坐标然后比较两个坐标有多近。这个坐标空间有512维ViT-B/32版本每一维都不对应人类能理解的具体含义但整体上语义相近的图文会聚在一起。这里有一个很多人容易混淆的点CLIP不是生成模型它不生成图片也不生成文字它只做匹配。所以clip文本编码节点怎么输入内容这个问题答案就是你把一段自然语言字符串喂给文本编码器它输出一个512维向量你把一张图片喂给图像编码器它也输出一个512维向量然后算这两个向量的余弦相似度。就这么简单。2.3 为什么不用BLIP或者自己微调有人会问BLIP、LLaVA这些多模态模型不是更强吗确实它们在图像描述生成等任务上表现更好但它们的推理成本高得多。我要处理1750张图而且要做实时检索用BLIP的话每次查询都要跑一遍大模型延迟和成本都不可接受。CLIP的图像编码器是轻量的ViT-B/32单张图在GPU上几毫秒就能出向量而且向量可以预先算好存起来查询时只需要编码文本再算相似度速度极快。至于clip模型微调我在这个项目里没有做微调。原因是微调需要标注数据而我的目标恰恰是发现未知模式没有标注数据可用。而且CLIP的零样本能力在通用视觉风格描述上已经足够好微调反而可能过拟合到我有限的标注上损失泛化性。如果你的场景是特定垂直领域比如只识别某种工业缺陷那微调是有价值的但做开放式风格探索原版CLIP就够了。3. 1750张首页截图是怎么拿到手的3.1 数据来源的确定与筛选标准数据来源我选的是几个公开的AI产品目录和创业公司数据库这些站点本身就在聚合AI工具和创业项目省去了我从零搜集的功夫。筛选标准有三条第一必须是AI相关的产品或服务第二首页必须能正常访问且返回200状态码第三页面必须有实质内容排除那些只有Coming Soon的占位页。最终拿到1750个有效URL。这个数字不是刻意凑的是筛选完之后自然剩下的。我原本的目标是2000个但有大约12%的站点要么已经下线要么需要登录才能访问要么返回的是错误页。这个损耗率在网页抓取里算正常的做类似项目时要有心理预期。3.2 截图环节的技术选型和参数设置截图我用的是Playwright而不是Selenium。原因很简单Playwright对现代前端框架React、Vue、Next.js的渲染支持更好等待策略更智能而且自带无头模式下的字体渲染优化。Selenium在遇到大量动态加载的页面时经常截到一半白屏。关键参数设置如下from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page(viewport{width: 1440, height: 900}) page.goto(url, wait_untilnetworkidle, timeout30000) page.wait_for_timeout(2000) page.screenshot(pathfscreenshots/{idx}.png, full_pageFalse)几个参数值得说明。viewport设为1440x900这是桌面端最常见的浏览尺寸能反映大多数用户看到的首屏效果。wait_untilnetworkidle表示等网络请求基本停止后再截图避免截到加载中的状态。wait_for_timeout(2000)是额外等2秒给那些有入场动画的页面留出时间——很多AI公司首页喜欢做滚动触发的渐入效果不等的话会截到元素还没出现的样子。注意不要用full_pageTrue截整页。整页截图会把页脚、Cookie横幅、聊天窗口都包含进来这些噪声会干扰后续的风格分析。首屏才是首页设计的核心战场。3.3 截图后的清洗工作1750张截图里有大约60张是明显有问题的有的截到了验证码页面有的截到了Cookie同意弹窗遮住整个屏幕有的因为字体加载失败显示成方块。这些我通过人工抽查加简单的图像熵检测筛掉了。图像熵太低说明画面信息量太少大概率是白屏或纯色页。清洗完之后剩下1690张有效截图。这个数量对于风格聚类来说足够了再多的话边际收益递减而且人工抽查的成本会急剧上升。4. 用CLIP把截图变成可检索的向量4.1 图像编码批量提取特征向量CLIP的图像编码部分我用的是openai/clip-vit-base-patch32这个版本。选它而不是更大的ViT-L/14是因为ViT-B/32的向量维度是512存储和计算都更轻量而且在风格这种粗粒度语义上B/32和L/14的差距并不明显。L/14更适合需要精细区分的任务比如区分不同品种的狗。批量提取的代码大致是这样import torch from PIL import Image from transformers import CLIPProcessor, CLIPModel model CLIPModel.from_pretrained(openai/clip-vit-base-patch32) processor CLIPProcessor.from_pretrained(openai/clip-vit-base-patch32) model.eval() def extract_image_features(image_paths, batch_size32): all_features [] for i in range(0, len(image_paths), batch_size): batch [Image.open(p).convert(RGB) for p in image_paths[i:ibatch_size]] inputs processor(imagesbatch, return_tensorspt, paddingTrue) with torch.no_grad(): features model.get_image_features(**inputs) features features / features.norm(dim-1, keepdimTrue) all_features.append(features.cpu()) return torch.cat(all_features, dim0)这里有两个细节。第一features / features.norm(...)是做L2归一化归一化之后余弦相似度就等价于点积计算更快。第二torch.no_grad()必须加否则显存会爆。1750张图在单张消费级显卡上跑完大概需要3到5分钟具体取决于显卡型号。4.2 文本编码怎么把深色极简风变成向量文本编码的接口和图像编码几乎对称def extract_text_features(texts): inputs processor(texttexts, return_tensorspt, paddingTrue, truncationTrue) with torch.no_grad(): features model.get_text_features(**inputs) features features / features.norm(dim-1, keepdimTrue) return features.cpu()关于clip文本编码节点怎么输入内容这里要强调一点CLIP的文本编码器对输入长度有限制最多77个token。超过的部分会被截断。所以你不能把一整段产品描述塞进去要用简短的风格描述词比如dark minimalist landing page with purple gradient这种。另外CLIP的文本编码器对大小写不敏感但标点符号会影响结果建议用简洁的短语而不是完整句子。还有一个实操经验用多个描述词组合比用单个词效果更好。比如你想找深色风格的页面只输入dark会召回很多无关结果但输入dark background with neon accent就精准得多。这是因为CLIP的语义空间里单个词的向量位置比较模糊多个词组合能锚定更具体的区域。4.3 向量存储与相似度检索1690个512维向量用FAISS建索引是最省事的方案。FAISS是Facebook开源的向量检索库支持十亿级别的向量毫秒级检索。我这个量级其实用numpy暴力算也行但用FAISS可以留出扩展空间以后加到几万个页面也不用改架构。import faiss import numpy as np vectors all_features.numpy().astype(float32) index faiss.IndexFlatIP(512) # IP inner product因为已经归一化了 index.add(vectors) faiss.write_index(index, homepage_vectors.index)检索的时候把查询文本编码成向量然后query_vec extract_text_features([dark minimalist with purple gradient]).numpy().astype(float32) distances, indices index.search(query_vec, k20)返回的indices就是最相似的20张截图的编号distances是相似度分数。实测下来相似度在0.3以上的基本就是视觉风格确实接近的0.25到0.3之间是有点关联但不完全一致0.25以下基本就是噪声了。这个阈值不是绝对的跟你的查询词具体程度有关。5. 可视化浏览器让风格对比变得直观5.1 为什么不做成传统的搜索结果列表如果只是返回一个图片列表那这个项目和普通的图库搜索没区别。我想要的是能看出风格分布的界面。所以可视化浏览器做了三件事第一用UMAP把512维向量降到2维在平面上展示所有页面的分布相似风格的页面会聚在一起第二支持点击任意页面查看它的最近邻看它跟哪些页面最像第三支持用自然语言查询高亮匹配的页面。UMAP降维的效果比t-SNE好因为UMAP保留了更多的全局结构。t-SNE倾向于把数据打散成均匀的簇而UMAP能看出簇与簇之间的过渡关系。对于风格分析来说这种过渡关系恰恰是最有意思的——你能看到深色极简是怎么渐变到深色科技感再渐变到深色赛博朋克的。5.2 前端实现的关键取舍前端我用的是普通的HTML加Canvas没有上React或Vue。原因是这个工具的使用者只有我自己和少数几个同事不需要复杂的组件状态管理用原生JS反而更直接。散点图用Canvas画每个点就是一张截图的缩略图鼠标悬停时放大显示。这里有一个性能上的坑1690张缩略图如果全部以原图加载页面会卡死。我的做法是生成两套缩略图一套是32x32的极小图用于散点图上的点一套是320x200的中等图用于悬停预览。极小图全部预加载中等图按需加载。这样首屏加载时间控制在2秒以内。5.3 查询交互的设计细节查询框支持自然语言输入输入后实时返回匹配结果。这里有一个体验上的优化不要等用户输入完整句子再查询而是每输入一个词就更新一次结果。因为CLIP的文本编码很快单条文本在CPU上也就几十毫秒实时更新完全可行。这样用户可以边输入边调整描述词快速找到自己想要的角度。另外我加了一个排除词功能。比如你搜gradient但不想看到紫色渐变可以加一个排除词purple。实现方式是在相似度计算时把排除词向量的相似度减去。这个功能在实际使用中非常有用因为AI创业公司首页的紫色渐变实在太多了。6. 从向量分布里读出的几个风格规律6.1 深色模式占据绝对主导但内部分化明显1690个页面里大约72%使用了深色背景。这个比例远高于一般网站的平均水平。深色模式在AI创业公司中的流行一方面是因为它显得技术感强另一方面是因为很多AI产品的演示界面本身就是深色的首页配色跟产品保持一致。但深色内部的分化很有意思。粗略分的话有纯黑极简背景接近#000000几乎没有装饰元素、深灰科技背景是#1a1a2e这类深灰蓝有细微的网格或粒子背景、深色渐变背景有从深紫到深蓝的渐变三个子类。这三个子类在UMAP图上是三个相邻但可区分的簇。6.2 渐变色的使用有明确的代际特征把页面按公司成立时间排序后我发现渐变色的使用有明显的代际差异。2022年之前成立的公司首页渐变用得比较克制通常是按钮或标题文字用渐变2023年之后成立的公司整页背景渐变的比例大幅上升。这可能跟设计工具的普及有关——Figma和Webflow让复杂渐变的实现成本大幅降低。从色相上看紫色系#8B5CF6到#6366F1是最常见的其次是蓝色系和青色系。橙色和红色系极少见可能是因为这些颜色在科技语境里容易显得警告或促销。6.3 留白策略的两极分化用CLIP检索大量留白和信息密集这两个极端能明显看到两类页面。一类是苹果风格的极简首屏只有一句话和一个按钮大量空白另一类是Notion风格的密集首屏就塞满了功能截图、客户logo、数据指标。有趣的是这两类页面的产品类型有相关性面向开发者的工具倾向于极简面向企业决策者的产品倾向于密集。这个发现对我的实际工作有直接帮助。后来我自己做产品首页时会先想清楚目标用户是谁然后参考对应簇里的页面设计而不是盲目跟风。7. 实操中踩过的坑和对应的解法7.1 截图时的动态内容导致向量不稳定有些首页有轮播图或者自动播放的视频不同时间截图得到的内容不一样。这导致同一个页面在不同批次截图时CLIP向量差异很大。我的解法是对每个页面截三张图间隔5秒然后取三张图向量的平均作为最终向量。这样虽然增加了计算量但向量稳定性大幅提升。7.2 CLIP对文字内容的敏感度超出预期我原本以为CLIP主要捕捉视觉风格但实测发现它对页面上的文字内容也很敏感。两个视觉风格几乎一样的页面如果标题文字不同CLIP相似度可能只有0.6左右。这意味着如果你想纯按视觉风格聚类需要把文字区域遮掉再编码。我的做法是用OCR检测文字区域然后用背景色填充再送进CLIP。这样得到的向量更纯粹地反映视觉风格。7.3 相似度阈值的设定需要按查询词调整前面提到0.3是个经验阈值但实际使用中发现抽象查询词如modern的相似度普遍偏低具体查询词如purple gradient button on dark background的相似度普遍偏高。所以阈值不能一刀切要根据查询词的具体程度动态调整。我的做法是取返回结果的前10%作为高亮显示而不是用固定阈值。7.4 向量索引的更新问题FAISS的IndexFlatIP不支持动态删除只能追加。当我发现某些截图质量有问题需要剔除时只能重建整个索引。对于1690个向量来说重建很快但如果数据量到十万级就需要用支持删除的索引类型比如IndexIVFFlat。这个坑在项目初期不明显但数据量上来之后会变成问题。8. 这个项目还能怎么扩展最直接的扩展是加入时间维度。如果能拿到每个公司的成立时间或融资时间就可以在UMAP图上用颜色表示时间看风格演变的时间线。我初步试了一下确实能看到2023年之后紫色渐变的簇明显变大。另一个扩展方向是加入文本内容的CLIP编码。现在我只用了图像编码如果把首页的标题文字也用CLIP文本编码器编码然后跟图像向量拼接就能同时按视觉风格和文案风格检索。比如搜强调效率的极简页面就能同时匹配视觉极简和文案强调效率的页面。还有一个想法是做一个风格迁移建议功能输入你自己的首页截图系统找出最相似的几个页面然后分析它们在配色、布局、字体上的具体参数给出可操作的修改建议。这个功能的技术难度不大但实用性很强尤其适合正在做首页的设计师。我个人在实际操作中的体会是CLIP这类多模态模型最大的价值不是替代人工判断而是把模糊的直觉变成可操作的数据。以前我说这个页面感觉很AI现在我能说这个页面在CLIP空间里距离深色渐变科技风的簇中心只有0.15距离浅色极简风有0.42。这种量化能力才是做产品决策时真正有用的东西。