Nstproxy 现已升级为 Nstdata。了解此次变化。
周一至周五 09:00 - 18:00(UTC+08:00) 公司地址
19H MAXGRAND PLAZA NO.3 TAI YAU STREET SAN PO KONG, KOWLOON HONG KONG ©2026 NST LABS TECH LTD. 保留所有权利。
Kai Watanabe Scraping Infrastructure Evangelist
网站到向量数据库:在一个流程中爬取、分块、嵌入 TL;DR
一个网站到向量数据库的管道存储可搜索的块和来源元数据;它不会将整个网站放入一个向量中。
爬取一个有限的页面集合,验证Markdown,将其分割为稳定的块,嵌入每个块,然后将其更新插入到Pinecone命名空间。
嵌入模型的输出维度必须与Pinecone密集索引匹配,重复摄取应重用确定性ID,而不是创建重复项。
这是Nstdata现有的爬取到Pinecone概述 的构建导向的补充;这里的重点是数据契约和安全刷新路径。
介绍:你将构建的内容
Nstdata Crawl是这个工作流中的收集层;它的产品概述 描述了用于下游数据管道的页面和站点检索。
本教程将一个公共文档站点转变为Pinecone中的可搜索记录。流程是URL → 爬取 → Markdown → 块 → 嵌入 → Pinecone → 查询 。它停在检索阶段;聊天用户界面、用户权限和生产监控是单独的应用层。
向量数据库通常不会将一个网站作为一个巨大的文档存储。每条记录是一个段落的向量,加上一个ID和元数据,帮助你找到源并在之后进行更新。Nstdata Crawl提供页面收集和Markdown转换阶段;Pinecone存储和检索向量。有关当前Crawl端点和任务响应,请使用Nstdata Crawl API参考 。
网站内容如何变为向量数据
步骤 示例值 存储原因 页面 https://docs.example.com/auth来源身份和引文 Markdown 标题,说明,代码示例 可读的源内容 块 一个连贯的部分 检索单元 嵌入 数字向量 语义相似性搜索 元数据 URL,标题,部分,版本,哈希 过滤,引文,更新 Pinecone记录 稳定ID + 值 + 元数据 更新插入和查询目标
仅在符合你的数据模型和隐私要求时,才在元数据中保留原始页面文本。在这个示例中,块文本附加到每个向量的元数据中,以便检索时可以返回引文准备好的段落。对于较大的记录设计,请评估当前的Pinecone数据建模和更新插入指南 。
先决条件:爬取API、嵌入和Pinecone
创建一个Nstdata Crawl令牌,一个OpenAI API密钥和一个Pinecone项目。创建一个具有与嵌入模型输出匹配的维度的密集Pinecone索引。下面的示例使用text-embedding-3-small的默认1,536维输出;如果你设置了自定义的嵌入维度,请将索引配置为相同的值。安装库并固定在你的部署锁定文件中解析的确切版本:
python -m venv .venv
source .venv/bin/activate
pip install nstdata-ai-crawl openai pinecone
export NSTDATA_API_TOKEN = "..."
export OPENAI_API_KEY = "..."
export PINECONE_API_KEY = "..."
export PINECONE_INDEX_HOST = "..."
下面的SDK名称和API形状遵循当前供应商文档。在写作环境中无法使用API凭证,因此整个云工作流未能端到端运行。
第1步:将网站爬取为Markdown 对于已知的URL,使用文档化的同步Python客户端。对于文档站点,在第一次验证运行中以较低的maxPages提交一个有限的站点爬取。站点级API返回任务ID;轮询状态然后获取页面结果。Crawl参考文档包括POST /api/v1/crawl、GET /api/v1/crawl/status/{crawlId}和GET /api/v1/crawl/pages/{crawlId}。
import os
from nstdata_ai_crawl import CrawlRequestDto , Format , NstDataClient
with NstDataClient ( os . environ [ "NSTDATA_API_TOKEN" ] ) as client :
submitted = client . submit_crawl_task ( CrawlRequestDto (
url = "https://docs.example.com/" ,
formats = [ Format . MARKDOWN ] ,
maxDepth = 2 ,
maxPages = 100 ,
includeUrls = [ "*docs.example.com/*" ] ,
excludeUrls = [ "*docs.example.com/search*" , "*docs.example.com/login*" ] ,
ignoreQuery = True ,
onlyMainContent = True ,
timeout = 60_000 ,
) )
crawl_id = submitted . id
print ( "submitted crawl" , crawl_id )
SDK示例确认了提交方法和请求字段。对于生产,添加轮询,使用单调截止日期、指数退避和最大等待;然后分页返回的页面列表直到其nextCursor为空。页面记录可能携带内容引用而不是完整的Markdown,因此在分块之前,通过文档化的存储读取端点获取引用的工件。检查每个页面的success、状态、元数据和内容,而不是将已完成的父爬取视为每个页面均可用的证明。
爬取指南的大型输出参考响应使用字段,如 markdownRef 和经过身份验证的 GET /api/v1/crawl/storage/read?st={ref}。在您的 SDK 版本的适配器中保留该检索步骤;不要假设每个响应都嵌入完整的主体。避免爬取仅登录或其他未经授权的内容。
步骤 2:在嵌入之前清理和规范化 保留标题路径、代码区块、表格和源链接。仅删除明显不属于文档的重复元素。基本的规范化助手故意保守:
import re
def normalize_markdown ( markdown : str ) - > str :
markdown = markdown . replace ( "\r\n" , "\n" )
markdown = re . sub ( r"\n{3,}" , "\n\n" , markdown )
return markdown . strip ( )
不要仅仅为了减少标记计数而删除每个链接或表格。API 文档通常在参数表或链接版本页面中存储有用的答案。捕获最终的标准 URL、标题、语言和产品版本作为元数据,连同规范化的主体一起。
步骤 3:按标题意识划分块 第一版可以通过标题划分 Markdown,同时让代码区块保持在一起。这个例子为每个标题部分创建一个记录;当一个部分超过您的嵌入提供者的输入限制时,添加一个注意分词的第二遍。
def split_sections ( markdown : str ) :
heading_path , lines , fenced = [ ] , [ ] , False
for line in markdown . splitlines ( ) :
if line . strip ( ) . startswith ( "```" ) :
fenced = not fenced
if line . startswith ( "#" ) and not fenced and lines :
text = "\n" . join ( lines ) . strip ( )
if text :
yield " / " . join ( heading_path ) , text
level = len ( line ) - len ( line . lstrip ( "#" ) )
heading_path = heading_path [ : level - 1 ] + [ line [ level : ] . strip ( ) ]
lines = [ line ]
else :
lines . append ( line )
text = "\n" . join ( lines ) . strip ( )
if text :
yield " / " . join ( heading_path ) , text
这是一个结构优先的起始实现,而不是一个通用的块切割器。它不保证最大标记计数或合并小部分。LangChain 文本切割器指南 提供了另一个框架实现以进行比较。对大型部分添加递归切割,并测试在代码块和表格上的边界行为。使用与您的嵌入模型关联的分词器跟踪标记大小,而不是将字符计数视为确切的标记计数。
对于以下部分,假设页面结果适配器生成了一个名为 page 的经过验证的记录,具有 url、title、markdown、crawled_at 和 content_hash 字段。只需一次物化分割器输出:sections = list(split_sections(page.markdown))。之前的站点爬取步骤提交了一个任务;您的适配器必须轮询它,读取引用的 Markdown 文档,并在嵌入之前构造此页面记录。
步骤 4:生成嵌入 OpenAI 嵌入指南 记录了嵌入端点。使用一个固定模型嵌入每个块,并保留模型名称和索引版本,以便后续模型更改不会悄然混合不兼容的向量空间。
from openai import OpenAI
embedder = OpenAI ( )
texts = [ chunk_text for _ , chunk_text in sections ]
response = embedder . embeddings . create (
model = "text-embedding-3-small" ,
input = texts ,
)
vectors = [ item . embedding for item in response . data ]
assert len ( vectors ) == len ( texts )
批处理可以减少请求开销,但使用提供者限制对大小进行批量处理,并对瞬时错误进行重试。写入之前检查向量长度:
expected_dimension = 1536
if any ( len ( vector ) != expected_dimension for vector in vectors ) :
raise ValueError ( "嵌入维度与 Pinecone 索引不匹配" )
步骤 5:创建 Pinecone 索引 在 Pinecone 控制台或其当前控制平面 API 中创建一个 密集的 索引。将索引维度设置为嵌入输出维度,并选择您的检索设计所需的相似性度量。数据平面操作需要主机 URL。首先使用开发索引,这样失败的实验就不会污染生产数据。
步骤 6:使用稳定 ID 插入块 Pinecone 当前的 Python 示例接受包含 id、values 和可选 metadata 的密集记录。使用命名空间分隔文档集合或租户。稳定的前缀使得在更改源页面之前删除所有块成为可能。
import hashlib
import os
from pinecone import Pinecone
pc = Pinecone ( api_key = os . environ [ "PINECONE_API_KEY" ] )
index = pc . Index ( host = os . environ [ "PINECONE_INDEX_HOST" ] )
namespace = "public-docs-v1"
records = [ ]
for ( heading , text ) , vector in zip ( sections , vectors , strict = True ) :
page_key = hashlib . sha256 ( source_url . encode ( ) ) . hexdigest ( ) [ : 16 ]
chunk_key = hashlib . sha256 ( ( heading + "\0" + text ) . encode ( ) ) . hexdigest ( ) [ : 20 ]
records . append ( {
"id" : f" { page_key } # { chunk_key } " ,
"values" : vector ,
"metadata" : {
"text" : text ,
"source_url" : source_url ,
"title" : page_title ,
"heading_path" : heading ,
"crawled_at" : crawled_at ,
"content_hash" : page_hash ,
} ,
} )
index . upsert ( vectors = records , namespace = namespace )
Upsert 会覆盖具有相同 ID 的现有记录。它不会自动删除在页面编辑后消失的旧块 ID。在替换集经过验证后,保持先前的页面到 ID 列表,并删除旧 ID。有关 Pinecone 文档批量和命名空间的信息,请参见其 upsert 指南 。
第 7 步:查询网站索引 使用相同的模型嵌入问题并查询 Pinecone。返回元数据,以便应用程序可以显示源链接和标题,而不是生成裸答案。
question = "API 如何认证请求?"
query_vector = embedder . embeddings . create (
model = "text-embedding-3-small" ,
input = question ,
) . data [ 0 ] . embedding
matches = index . query (
vector = query_vector ,
top_k = 5 ,
include_metadata = True ,
namespace = namespace ,
)
for match in matches . matches :
print ( match . metadata [ "source_url" ] , match . metadata [ "heading_path" ] )
print ( match . metadata [ "text" ] )
将 top_k 视为可调取回参数,而不是质量保证。使用一小部分真实问题进行评估,并检查每个返回的段落是否支持答案。对于受访问控制的站点,在将上下文返回给模型之前,还应应用租户和文档权限。
第 8 步:处理更新和删除 存储规范化页面内容的哈希。当下次接受的爬虫返回相同的哈希时,跳过嵌入。当哈希更改时,创建新的块列表,执行插入,验证它可查询,然后删除以前的 ID。在 Pinecone 之外保持一个清单,包含规范 URL、内容哈希、爬取时间戳、模型版本和块 ID。如果源爬虫失败,保留最后一个已知好的索引并标记该页面以供审查;超时不得成为意外删除。
常见错误和成本模型 维度不匹配 说明索引和嵌入输出不一致。重复泛滥通常意味着 ID 是随机的或包含了摄取时间戳。丢失引用通常意味着 source_url 在分块过程中丢失。返回无记录的元数据查询可能指向命名空间不匹配,而不是嵌入问题。
通过单独的术语估算每接受页面的成本:使用爬取 URL、启用时的代理流量、新增或更改文本的嵌入、Pinecone 写入和存储以及查询时模型调用。一页可能生成许多块,因此仅靠页面量并不是可靠的存储估算。准备预算时,请参见当前 Nstdata 爬取定价 和当前 Pinecone 计费文档;费率和计划限制可能会更改。
Pinecone 与其他向量存储在此工作流程中的比较 当您希望使用托管的向量索引而不想操作数据库流程时,Pinecone 是一种管理选择。Chroma 适用于本地原型;Qdrant 和 Weaviate 提供其他托管或自托管部署;pgvector 适合已经操作 PostgreSQL 的团队。比较过滤需求、操作、备份、区域和权限模型,而不仅仅是向量搜索延迟。本教程使用 Pinecone,因为关键字针对该集成,而不是因为一个数据库在每个工作负载中都是最佳的。
结论 可靠的网站到向量数据库的流程取决于各个阶段之间的合同:爬取生成页面工件,您的代码验证并分块,嵌入模型生成已知维度的向量,Pinecone 在稳定 ID 下存储每个向量及其源元数据。在摄取一个大型站点之前,构建 URL 清单并更新/删除行为。这少量的记账工作可以防止后续的过时段落和引用失效。
常见问题解答 问:我如何将网站爬取到 Pinecone?
发现并爬取允许的页面,验证并分块其文本,生成嵌入,然后将每个向量与稳定的 ID 和源元数据一起更新。
Pinecone 存储向量记录和相关元数据;您的应用程序决定是否将分块文本存储为元数据或在单独的内容存储中。
没有通用的最佳大小;根据代表性问题和嵌入模型的输入限制调优分块边界和检索设置。
比较标准化内容哈希,重新嵌入更改的页面,更新替换分块,并在验证后删除先前的分块 ID。
可以,但请明智地使用命名空间或元数据过滤器,并在返回检索到的内容之前在您的应用程序中强制执行租户权限。
支持 JavaScript 渲染,成功率达 99.8%
将任意网站转换为 Markdown、HTML、JSON、链接、PDF 等格式,无需管理爬取基础设施。 开始免费爬取 →