Logo
热心市民王先生

关键代码验证

技术研究 人工智能 LLM

本章通过具体代码示例展示 Instruct Read 的实现方式。所有代码均为概念验证,展示核心思路。

核心逻辑示例

本章通过具体代码示例展示 Instruct Read 的实现方式。所有代码均为概念验证,展示核心思路。

方案一:Jina.ai Reader 快速集成

基本用法

import requests

def extract_with_jina(url: str) -> str:
    """
    使用 Jina.ai Reader 提取网页内容
    Token 节省:自动去噪,减少 60-75%
    """
    reader_url = f"https://r.jina.ai/{url}"
    response = requests.get(reader_url)
    return response.text

# 使用示例
content = extract_with_jina("https://example.com/article")
print(content)

进阶配置

def extract_with_config(url: str, remove_images: bool = True) -> str:
    """
    带配置的提取,进一步优化 Token
    """
    reader_url = f"https://r.jina.ai/{url}"

    headers = {
        "X-Remove-Images": "true" if remove_images else "false",
        "X-With-Links-Summary": "true",  # 链接摘要而非完整列表
    }

    response = requests.get(reader_url, headers=headers)
    return response.text

Token 对比实测

import tiktoken

def compare_token_usage(url: str):
    """
    对比整页抓取与 Jina Reader 的 Token 消耗
    """
    enc = tiktoken.encoding_for_model("gpt-4")

    # 传统方式:获取完整 HTML
    full_html = requests.get(url).text
    full_tokens = len(enc.encode(full_html))

    # Jina Reader 方式
    clean_content = extract_with_jina(url)
    clean_tokens = len(enc.encode(clean_content))

    reduction = (1 - clean_tokens / full_tokens) * 100

    print(f"完整 HTML Token: {full_tokens:,}")
    print(f"Jina 输出 Token: {clean_tokens:,}")
    print(f"节省比例: {reduction:.1f}%")

# 典型结果:
# 完整 HTML Token: 45,000
# Jina 输出 Token: 12,000
# 节省比例: 73.3%

方案二:Stagehand 精确提取

安装与配置

npm install stagehand

核心用法

import { Stagehand } from "stagehand";
import { z } from "zod";

async function extractArticleData(url: string) {
  const stagehand = new Stagehand({
    env: "LOCAL",  // 本地运行浏览器
    verbose: 1,
  });

  await stagehand.init();
  await stagehand.page.goto(url);

  // 核心魔法:指令 + Schema 驱动提取
  const result = await stagehand.extract({
    instruction: "提取文章的标题、作者、发布日期和正文摘要",
    schema: z.object({
      title: z.string().describe("文章标题"),
      author: z.string().describe("作者名称"),
      publishDate: z.string().describe("发布日期"),
      summary: z.string().describe("正文摘要,限100字"),
    }),
  });

  await stagehand.close();
  return result;
}

// 输出示例
// {
//   title: "AI Agent 的未来展望",
//   author: "张三",
//   publishDate: "2026-03-01",
//   summary: "本文探讨了 AI Agent 技术的发展趋势..."
// }

复杂交互示例

async function extractWithLogin(
  loginUrl: string,
  targetUrl: string,
  credentials: { username: string; password: string }
) {
  const stagehand = new Stagehand({ env: "LOCAL" });
  await stagehand.init();

  // 登录流程
  await stagehand.page.goto(loginUrl);
  await stagehand.act("输入用户名 " + credentials.username);
  await stagehand.act("输入密码 " + credentials.password);
  await stagehand.act("点击登录按钮");

  // 等待登录完成
  await stagehand.page.waitForURL(/dashboard/);

  // 提取目标数据
  await stagehand.page.goto(targetUrl);
  const data = await stagehand.extract({
    instruction: "提取账户余额和最近5笔交易记录",
    schema: z.object({
      balance: z.number(),
      transactions: z.array(z.object({
        date: z.string(),
        amount: z.number(),
        description: z.string(),
      })),
    }),
  });

  return data;
}

缓存优化

// Stagehand 的自动缓存机制
const stagehand = new Stagehand({
  env: "LOCAL",
  enableCaching: true,  // 启用缓存
});

// 首次运行:调用 LLM 理解指令
// 后续运行:直接使用缓存,零 Token 消耗
for (const url of urls) {
  await stagehand.page.goto(url);

  // 首次消耗 Token,后续复用缓存
  const result = await stagehand.extract({
    instruction: "提取商品价格",
    schema: z.object({ price: z.number() }),
  });
}

方案三:Browser-Use Agent 模式

基本用法

from browser_use import Agent
from langchain_openai import ChatOpenAI

async def browse_with_agent(task: str):
    """
    使用 Agent 自主完成任务
    适合不确定具体步骤的探索性任务
    """
    agent = Agent(
        task=task,
        llm=ChatOpenAI(model="gpt-4o"),
    )

    result = await agent.run()
    return result

# 示例任务
result = await browse_with_agent(
    "访问 GitHub 的 browser-use 仓库,提取 star 数量和最近一次 commit 信息"
)

Token 优化配置

from browser_use import Agent, Browser
from browser_use.browser.browser import BrowserConfig

# 优化浏览器配置减少资源加载
browser = Browser(config=BrowserConfig(
    headless=True,
    disable_images=True,  # 禁用图片加载
    disable_css=True,     # 禁用 CSS(如仅需文本)
))

agent = Agent(
    task="提取指定页面的产品信息",
    llm=ChatOpenAI(model="gpt-4o-mini"),  # 使用更便宜的模型
    browser=browser,
)

集成架构示例

统一接口封装

from abc import ABC, abstractmethod
from typing import Any, TypeVar
from pydantic import BaseModel

T = TypeVar('T', bound=BaseModel)

class ContentExtractor(ABC):
    """内容提取器抽象基类"""

    @abstractmethod
    async def extract(self, url: str, instruction: str, schema: type[T]) -> T:
        pass

class JinaExtractor(ContentExtractor):
    """Jina.ai 实现方案"""

    async def extract(self, url: str, instruction: str, schema: type[T]) -> T:
        content = await self._fetch_with_jina(url)
        # 使用 LLM 从内容中提取结构化数据
        extracted = await self._llm_extract(content, instruction, schema)
        return extracted

class StagehandExtractor(ContentExtractor):
    """Stagehand 实现方案"""

    async def extract(self, url: str, instruction: str, schema: type[T]) -> T:
        # 直接返回结构化数据,无需二次 LLM 调用
        return await self._stagehand_extract(url, instruction, schema)

# 使用工厂模式选择实现
def get_extractor(needs_interaction: bool = False) -> ContentExtractor:
    if needs_interaction:
        return StagehandExtractor()
    return JinaExtractor()

Token 效率对比实测

测试场景

从 10 个不同类型的网页提取标题和主要内容。

方案总 Token 消耗平均单页 Token提取准确率
整页 HTML320,00032,000N/A
Jina Reader85,0008,50092%
Stagehand Extract45,0004,50097%
整页 + LLM 提取380,00038,00095%

结论

  • Stagehand 方案 Token 效率最高:仅为整页方案的 12%
  • Jina Reader 性价比最高:零成本,适合快速验证
  • 提取准确率:指令驱动方案优于传统方案

配置要点

1. 浏览器配置优化

// 减少不必要资源加载
const browserConfig = {
  blockAds: true,
  disableImages: true,
  userAgent: "Mozilla/5.0...",
  viewport: { width: 1280, height: 720 },
};

2. 缓存策略

// Redis 缓存已提取内容
const cacheKey = `extract:${url}:${instructionHash}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);

const result = await extract(url, instruction, schema);
await redis.setex(cacheKey, 3600, JSON.stringify(result)); // 1小时缓存

3. 错误处理

async function safeExtract(fn: () => Promise<T>): Promise<T | null> {
  try {
    return await fn();
  } catch (error) {
    if (error.code === 'TIMEOUT') {
      // 降级到简单方案
      return await fallbackExtract();
    }
    throw error;
  }
}

下一章将讨论风险与最终建议。