[{"data":1,"prerenderedAt":1427},["ShallowReactive",2],{"blog-post-/zh/blogs/openai-serp-api-web-search-ai-summary":3},{"id":4,"title":5,"body":6,"description":1410,"extension":1411,"meta":1412,"navigation":118,"ogImage":1414,"path":1423,"seo":1424,"stem":1425,"__hash__":1426},"zh/blogs/zh/10.openai-serp-api-web-search-ai-summary.md","构建股票分析的网络搜索+AI摘要工具",{"type":7,"value":8,"toc":1392},"minimal",[9,13,17,20,24,27,56,59,74,78,81,86,89,138,142,145,297,301,304,497,501,504,739,743,746,909,913,916,930,933,1282,1285,1288,1292,1295,1306,1310,1313,1324,1327,1341,1345,1348,1359,1362,1365,1368,1382,1385,1388],[10,11,12],"h2",{"id":12},"引言",[14,15,16],"p",{},"在当今快节奏的金融市场中，分析师需要快速处理大量信息以做出明智的决策。传统的手动搜索网络、阅读文章和综合信息的方法既耗时又容易错过关键见解。这就是为什么将AI与网络搜索功能相结合可以创建一个强大的股票分析工具。",[14,18,19],{},"在这篇博客文章中，我将分享如何为公司的实验性股票分析项目构建一个网络搜索+AI摘要工具。这个工具帮助分析师快速收集和综合他们正在研究的股票信息，节省了大量手动工作时间，并提供了更全面的见解。",[10,21,23],{"id":22},"结合网络搜索与ai的强大力量","结合网络搜索与AI的强大力量",[14,25,26],{},"在深入实现之前，让我们了解为什么这种组合特别强大：",[28,29,30,38,44,50],"ol",{},[31,32,33,37],"li",{},[34,35,36],"strong",{},"实时信息访问","：SERP（搜索引擎结果页面）API提供来自网络的最新信息",[31,39,40,43],{},[34,41,42],{},"上下文理解","：像GPT-4这样的大型语言模型可以理解信息的上下文和相关性",[31,45,46,49],{},[34,47,48],{},"综合能力","：AI可以总结、提取关键点，并识别多个来源的趋势",[31,51,52,55],{},[34,53,54],{},"可定制分析","：系统可以定制为关注股票分析的特定方面（财务、新闻情绪、市场趋势）",[14,57,58],{},"对于股票分析而言，这种组合使分析师能够：",[60,61,62,65,68,71],"ul",{},[31,63,64],{},"快速收集有关公司的最新新闻和发展",[31,66,67],{},"分析多个来源的市场情绪",[31,69,70],{},"识别可能隐藏在各种文章中的潜在风险或机会",[31,72,73],{},"在几秒钟而不是几小时内生成全面的研究摘要",[10,75,77],{"id":76},"实现构建工具","实现：构建工具",[14,79,80],{},"让我们一步步地了解如何构建这个工具。",[82,83,85],"h3",{"id":84},"_1-设置环境","1. 设置环境",[14,87,88],{},"首先，我们需要设置环境并安装必要的依赖项：",[90,91,96],"pre",{"className":92,"code":93,"language":94,"meta":95,"style":95},"language-javascript shiki shiki-themes dracula","// 安装所需的包\nnpm install axios openai dotenv\n\n// 创建.env文件存储API密钥\nOPENAI_API_KEY=你的openai_api密钥\nSERP_API_KEY=你的serp_api密钥\n","javascript","",[97,98,99,107,113,120,126,132],"code",{"__ignoreMap":95},[100,101,104],"span",{"class":102,"line":103},"line",1,[100,105,106],{},"// 安装所需的包\n",[100,108,110],{"class":102,"line":109},2,[100,111,112],{},"npm install axios openai dotenv\n",[100,114,116],{"class":102,"line":115},3,[100,117,119],{"emptyLinePlaceholder":118},true,"\n",[100,121,123],{"class":102,"line":122},4,[100,124,125],{},"// 创建.env文件存储API密钥\n",[100,127,129],{"class":102,"line":128},5,[100,130,131],{},"OPENAI_API_KEY=你的openai_api密钥\n",[100,133,135],{"class":102,"line":134},6,[100,136,137],{},"SERP_API_KEY=你的serp_api密钥\n",[82,139,141],{"id":140},"_2-配置serp-api","2. 配置SERP API",[14,143,144],{},"我们将使用SERP API获取搜索结果。有几个提供商可用，但对于这个项目，我使用了SerpAPI，它提供来自搜索引擎的结构化数据：",[90,146,148],{"className":92,"code":147,"language":94,"meta":95,"style":95},"const axios = require('axios')\nrequire('dotenv').config()\n\nasync function searchWeb(query, numResults = 5) {\n  try {\n    const response = await axios.get('https://serpapi.com/search', {\n      params: {\n        q: query,\n        api_key: process.env.SERP_API_KEY,\n        num: numResults,\n      },\n    })\n\n    // 提取有机搜索结果\n    const searchResults = response.data.organic_results.map((result) => ({\n      title: result.title,\n      link: result.link,\n      snippet: result.snippet,\n    }))\n\n    return searchResults\n  } catch (error) {\n    console.error('网络搜索错误:', error)\n    throw error\n  }\n}\n",[97,149,150,155,160,164,169,174,179,185,191,197,203,209,215,220,226,232,238,244,250,256,261,267,273,279,285,291],{"__ignoreMap":95},[100,151,152],{"class":102,"line":103},[100,153,154],{},"const axios = require('axios')\n",[100,156,157],{"class":102,"line":109},[100,158,159],{},"require('dotenv').config()\n",[100,161,162],{"class":102,"line":115},[100,163,119],{"emptyLinePlaceholder":118},[100,165,166],{"class":102,"line":122},[100,167,168],{},"async function searchWeb(query, numResults = 5) {\n",[100,170,171],{"class":102,"line":128},[100,172,173],{},"  try {\n",[100,175,176],{"class":102,"line":134},[100,177,178],{},"    const response = await axios.get('https://serpapi.com/search', {\n",[100,180,182],{"class":102,"line":181},7,[100,183,184],{},"      params: {\n",[100,186,188],{"class":102,"line":187},8,[100,189,190],{},"        q: query,\n",[100,192,194],{"class":102,"line":193},9,[100,195,196],{},"        api_key: process.env.SERP_API_KEY,\n",[100,198,200],{"class":102,"line":199},10,[100,201,202],{},"        num: numResults,\n",[100,204,206],{"class":102,"line":205},11,[100,207,208],{},"      },\n",[100,210,212],{"class":102,"line":211},12,[100,213,214],{},"    })\n",[100,216,218],{"class":102,"line":217},13,[100,219,119],{"emptyLinePlaceholder":118},[100,221,223],{"class":102,"line":222},14,[100,224,225],{},"    // 提取有机搜索结果\n",[100,227,229],{"class":102,"line":228},15,[100,230,231],{},"    const searchResults = response.data.organic_results.map((result) => ({\n",[100,233,235],{"class":102,"line":234},16,[100,236,237],{},"      title: result.title,\n",[100,239,241],{"class":102,"line":240},17,[100,242,243],{},"      link: result.link,\n",[100,245,247],{"class":102,"line":246},18,[100,248,249],{},"      snippet: result.snippet,\n",[100,251,253],{"class":102,"line":252},19,[100,254,255],{},"    }))\n",[100,257,259],{"class":102,"line":258},20,[100,260,119],{"emptyLinePlaceholder":118},[100,262,264],{"class":102,"line":263},21,[100,265,266],{},"    return searchResults\n",[100,268,270],{"class":102,"line":269},22,[100,271,272],{},"  } catch (error) {\n",[100,274,276],{"class":102,"line":275},23,[100,277,278],{},"    console.error('网络搜索错误:', error)\n",[100,280,282],{"class":102,"line":281},24,[100,283,284],{},"    throw error\n",[100,286,288],{"class":102,"line":287},25,[100,289,290],{},"  }\n",[100,292,294],{"class":102,"line":293},26,[100,295,296],{},"}\n",[82,298,300],{"id":299},"_3-从搜索结果中获取内容","3. 从搜索结果中获取内容",[14,302,303],{},"一旦我们有了搜索结果，我们需要从网页中获取实际内容：",[90,305,307],{"className":92,"code":306,"language":94,"meta":95,"style":95},"const axios = require('axios')\nconst cheerio = require('cheerio')\n\nasync function fetchContent(url) {\n  try {\n    const response = await axios.get(url)\n    const $ = cheerio.load(response.data)\n\n    // 移除脚本标签、样式标签和其他非内容元素\n    $('script, style, meta, link').remove()\n\n    // 提取主要内容（这是一种简化的方法）\n    // 对于生产环境，你可能想使用更复杂的内容提取方法\n    const content = $('body').text().replace(/\\s+/g, ' ').trim()\n\n    return content\n  } catch (error) {\n    console.error(`从${url}获取内容时出错:`, error)\n    return '' // 如果无法获取内容，则返回空字符串\n  }\n}\n\nasync function fetchContentsFromSearchResults(searchResults) {\n  const contents = []\n\n  for (const result of searchResults) {\n    const content = await fetchContent(result.link)\n    if (content) {\n      contents.push({\n        title: result.title,\n        url: result.link,\n        content: content.substring(0, 8000), // 限制内容长度\n      })\n    }\n  }\n\n  return contents\n}\n",[97,308,309,313,318,322,327,331,336,341,345,350,355,359,364,369,374,378,383,387,392,397,401,405,409,414,419,423,428,434,440,446,452,458,464,470,476,481,486,492],{"__ignoreMap":95},[100,310,311],{"class":102,"line":103},[100,312,154],{},[100,314,315],{"class":102,"line":109},[100,316,317],{},"const cheerio = require('cheerio')\n",[100,319,320],{"class":102,"line":115},[100,321,119],{"emptyLinePlaceholder":118},[100,323,324],{"class":102,"line":122},[100,325,326],{},"async function fetchContent(url) {\n",[100,328,329],{"class":102,"line":128},[100,330,173],{},[100,332,333],{"class":102,"line":134},[100,334,335],{},"    const response = await axios.get(url)\n",[100,337,338],{"class":102,"line":181},[100,339,340],{},"    const $ = cheerio.load(response.data)\n",[100,342,343],{"class":102,"line":187},[100,344,119],{"emptyLinePlaceholder":118},[100,346,347],{"class":102,"line":193},[100,348,349],{},"    // 移除脚本标签、样式标签和其他非内容元素\n",[100,351,352],{"class":102,"line":199},[100,353,354],{},"    $('script, style, meta, link').remove()\n",[100,356,357],{"class":102,"line":205},[100,358,119],{"emptyLinePlaceholder":118},[100,360,361],{"class":102,"line":211},[100,362,363],{},"    // 提取主要内容（这是一种简化的方法）\n",[100,365,366],{"class":102,"line":217},[100,367,368],{},"    // 对于生产环境，你可能想使用更复杂的内容提取方法\n",[100,370,371],{"class":102,"line":222},[100,372,373],{},"    const content = $('body').text().replace(/\\s+/g, ' ').trim()\n",[100,375,376],{"class":102,"line":228},[100,377,119],{"emptyLinePlaceholder":118},[100,379,380],{"class":102,"line":234},[100,381,382],{},"    return content\n",[100,384,385],{"class":102,"line":240},[100,386,272],{},[100,388,389],{"class":102,"line":246},[100,390,391],{},"    console.error(`从${url}获取内容时出错:`, error)\n",[100,393,394],{"class":102,"line":252},[100,395,396],{},"    return '' // 如果无法获取内容，则返回空字符串\n",[100,398,399],{"class":102,"line":258},[100,400,290],{},[100,402,403],{"class":102,"line":263},[100,404,296],{},[100,406,407],{"class":102,"line":269},[100,408,119],{"emptyLinePlaceholder":118},[100,410,411],{"class":102,"line":275},[100,412,413],{},"async function fetchContentsFromSearchResults(searchResults) {\n",[100,415,416],{"class":102,"line":281},[100,417,418],{},"  const contents = []\n",[100,420,421],{"class":102,"line":287},[100,422,119],{"emptyLinePlaceholder":118},[100,424,425],{"class":102,"line":293},[100,426,427],{},"  for (const result of searchResults) {\n",[100,429,431],{"class":102,"line":430},27,[100,432,433],{},"    const content = await fetchContent(result.link)\n",[100,435,437],{"class":102,"line":436},28,[100,438,439],{},"    if (content) {\n",[100,441,443],{"class":102,"line":442},29,[100,444,445],{},"      contents.push({\n",[100,447,449],{"class":102,"line":448},30,[100,450,451],{},"        title: result.title,\n",[100,453,455],{"class":102,"line":454},31,[100,456,457],{},"        url: result.link,\n",[100,459,461],{"class":102,"line":460},32,[100,462,463],{},"        content: content.substring(0, 8000), // 限制内容长度\n",[100,465,467],{"class":102,"line":466},33,[100,468,469],{},"      })\n",[100,471,473],{"class":102,"line":472},34,[100,474,475],{},"    }\n",[100,477,479],{"class":102,"line":478},35,[100,480,290],{},[100,482,484],{"class":102,"line":483},36,[100,485,119],{"emptyLinePlaceholder":118},[100,487,489],{"class":102,"line":488},37,[100,490,491],{},"  return contents\n",[100,493,495],{"class":102,"line":494},38,[100,496,296],{},[82,498,500],{"id":499},"_4-集成openai-api","4. 集成OpenAI API",[14,502,503],{},"现在，我们将使用OpenAI的API来总结和分析内容：",[90,505,507],{"className":92,"code":506,"language":94,"meta":95,"style":95},"const { OpenAI } = require('openai')\nrequire('dotenv').config()\n\nconst openai = new OpenAI({\n  apiKey: process.env.OPENAI_API_KEY,\n})\n\nasync function summarizeWithAI(stockSymbol, contents) {\n  // 为AI准备内容\n  const contentText = contents\n    .map((item) => `来源: ${item.title} (${item.url})\\n${item.content}\\n\\n`)\n    .join('')\n\n  // 为AI创建提示\n  const prompt = `\n    你是一位金融分析师助手。以下是关于股票${stockSymbol}的网络搜索结果。\n    请分析这些结果并提供：\n    \n    1. 最近关键发展的摘要\n    2. 市场情绪分析（积极、消极、中性）\n    3. 对股价的潜在影响\n    4. 提到的关键财务指标\n    5. 识别出的任何风险或机会\n    \n    使你的分析简洁、事实性强，并专注于对投资者有价值的信息。\n    \n    搜索结果:\n    ${contentText}\n  `\n\n  try {\n    const response = await openai.chat.completions.create({\n      model: 'gpt-4',\n      messages: [\n        { role: 'system', content: '你是一位金融分析师助手，帮助分析来自网络搜索结果的股票信息。' },\n        { role: 'user', content: prompt },\n      ],\n      temperature: 0.2, // 较低的温度以获得更事实性的回应\n      max_tokens: 1500,\n    })\n\n    return response.choices[0].message.content\n  } catch (error) {\n    console.error('生成AI摘要时出错:', error)\n    throw error\n  }\n}\n",[97,508,509,514,518,522,527,532,537,541,546,551,556,561,566,570,575,580,585,590,595,600,605,610,615,620,624,629,633,638,643,648,652,656,661,666,671,676,681,686,691,697,702,707,713,718,724,729,734],{"__ignoreMap":95},[100,510,511],{"class":102,"line":103},[100,512,513],{},"const { OpenAI } = require('openai')\n",[100,515,516],{"class":102,"line":109},[100,517,159],{},[100,519,520],{"class":102,"line":115},[100,521,119],{"emptyLinePlaceholder":118},[100,523,524],{"class":102,"line":122},[100,525,526],{},"const openai = new OpenAI({\n",[100,528,529],{"class":102,"line":128},[100,530,531],{},"  apiKey: process.env.OPENAI_API_KEY,\n",[100,533,534],{"class":102,"line":134},[100,535,536],{},"})\n",[100,538,539],{"class":102,"line":181},[100,540,119],{"emptyLinePlaceholder":118},[100,542,543],{"class":102,"line":187},[100,544,545],{},"async function summarizeWithAI(stockSymbol, contents) {\n",[100,547,548],{"class":102,"line":193},[100,549,550],{},"  // 为AI准备内容\n",[100,552,553],{"class":102,"line":199},[100,554,555],{},"  const contentText = contents\n",[100,557,558],{"class":102,"line":205},[100,559,560],{},"    .map((item) => `来源: ${item.title} (${item.url})\\n${item.content}\\n\\n`)\n",[100,562,563],{"class":102,"line":211},[100,564,565],{},"    .join('')\n",[100,567,568],{"class":102,"line":217},[100,569,119],{"emptyLinePlaceholder":118},[100,571,572],{"class":102,"line":222},[100,573,574],{},"  // 为AI创建提示\n",[100,576,577],{"class":102,"line":228},[100,578,579],{},"  const prompt = `\n",[100,581,582],{"class":102,"line":234},[100,583,584],{},"    你是一位金融分析师助手。以下是关于股票${stockSymbol}的网络搜索结果。\n",[100,586,587],{"class":102,"line":240},[100,588,589],{},"    请分析这些结果并提供：\n",[100,591,592],{"class":102,"line":246},[100,593,594],{},"    \n",[100,596,597],{"class":102,"line":252},[100,598,599],{},"    1. 最近关键发展的摘要\n",[100,601,602],{"class":102,"line":258},[100,603,604],{},"    2. 市场情绪分析（积极、消极、中性）\n",[100,606,607],{"class":102,"line":263},[100,608,609],{},"    3. 对股价的潜在影响\n",[100,611,612],{"class":102,"line":269},[100,613,614],{},"    4. 提到的关键财务指标\n",[100,616,617],{"class":102,"line":275},[100,618,619],{},"    5. 识别出的任何风险或机会\n",[100,621,622],{"class":102,"line":281},[100,623,594],{},[100,625,626],{"class":102,"line":287},[100,627,628],{},"    使你的分析简洁、事实性强，并专注于对投资者有价值的信息。\n",[100,630,631],{"class":102,"line":293},[100,632,594],{},[100,634,635],{"class":102,"line":430},[100,636,637],{},"    搜索结果:\n",[100,639,640],{"class":102,"line":436},[100,641,642],{},"    ${contentText}\n",[100,644,645],{"class":102,"line":442},[100,646,647],{},"  `\n",[100,649,650],{"class":102,"line":448},[100,651,119],{"emptyLinePlaceholder":118},[100,653,654],{"class":102,"line":454},[100,655,173],{},[100,657,658],{"class":102,"line":460},[100,659,660],{},"    const response = await openai.chat.completions.create({\n",[100,662,663],{"class":102,"line":466},[100,664,665],{},"      model: 'gpt-4',\n",[100,667,668],{"class":102,"line":472},[100,669,670],{},"      messages: [\n",[100,672,673],{"class":102,"line":478},[100,674,675],{},"        { role: 'system', content: '你是一位金融分析师助手，帮助分析来自网络搜索结果的股票信息。' },\n",[100,677,678],{"class":102,"line":483},[100,679,680],{},"        { role: 'user', content: prompt },\n",[100,682,683],{"class":102,"line":488},[100,684,685],{},"      ],\n",[100,687,688],{"class":102,"line":494},[100,689,690],{},"      temperature: 0.2, // 较低的温度以获得更事实性的回应\n",[100,692,694],{"class":102,"line":693},39,[100,695,696],{},"      max_tokens: 1500,\n",[100,698,700],{"class":102,"line":699},40,[100,701,214],{},[100,703,705],{"class":102,"line":704},41,[100,706,119],{"emptyLinePlaceholder":118},[100,708,710],{"class":102,"line":709},42,[100,711,712],{},"    return response.choices[0].message.content\n",[100,714,716],{"class":102,"line":715},43,[100,717,272],{},[100,719,721],{"class":102,"line":720},44,[100,722,723],{},"    console.error('生成AI摘要时出错:', error)\n",[100,725,727],{"class":102,"line":726},45,[100,728,284],{},[100,730,732],{"class":102,"line":731},46,[100,733,290],{},[100,735,737],{"class":102,"line":736},47,[100,738,296],{},[82,740,742],{"id":741},"_5-整合所有内容","5. 整合所有内容",[14,744,745],{},"最后，让我们创建将所有内容整合在一起的主函数：",[90,747,749],{"className":92,"code":748,"language":94,"meta":95,"style":95},"async function analyzeStock(stockSymbol) {\n  try {\n    console.log(`分析股票: ${stockSymbol}...`)\n\n    // 步骤1：搜索有关股票的最新信息\n    const searchQuery = `${stockSymbol} 股票 新闻 财务分析 最新发展`\n    const searchResults = await searchWeb(searchQuery, 8)\n\n    // 步骤2：从搜索结果中获取内容\n    const contents = await fetchContentsFromSearchResults(searchResults)\n\n    // 步骤3：生成AI摘要和分析\n    const analysis = await summarizeWithAI(stockSymbol, contents)\n\n    return {\n      stockSymbol,\n      searchResults,\n      analysis,\n    }\n  } catch (error) {\n    console.error(`分析股票${stockSymbol}时出错:`, error)\n    throw error\n  }\n}\n\n// 使用示例\nanalyzeStock('AAPL')\n  .then((result) => {\n    console.log('分析完成:')\n    console.log(result.analysis)\n  })\n  .catch((error) => {\n    console.error('分析失败:', error)\n  })\n",[97,750,751,756,760,765,769,774,779,784,788,793,798,802,807,812,816,821,826,831,836,840,844,849,853,857,861,865,870,875,880,885,890,895,900,905],{"__ignoreMap":95},[100,752,753],{"class":102,"line":103},[100,754,755],{},"async function analyzeStock(stockSymbol) {\n",[100,757,758],{"class":102,"line":109},[100,759,173],{},[100,761,762],{"class":102,"line":115},[100,763,764],{},"    console.log(`分析股票: ${stockSymbol}...`)\n",[100,766,767],{"class":102,"line":122},[100,768,119],{"emptyLinePlaceholder":118},[100,770,771],{"class":102,"line":128},[100,772,773],{},"    // 步骤1：搜索有关股票的最新信息\n",[100,775,776],{"class":102,"line":134},[100,777,778],{},"    const searchQuery = `${stockSymbol} 股票 新闻 财务分析 最新发展`\n",[100,780,781],{"class":102,"line":181},[100,782,783],{},"    const searchResults = await searchWeb(searchQuery, 8)\n",[100,785,786],{"class":102,"line":187},[100,787,119],{"emptyLinePlaceholder":118},[100,789,790],{"class":102,"line":193},[100,791,792],{},"    // 步骤2：从搜索结果中获取内容\n",[100,794,795],{"class":102,"line":199},[100,796,797],{},"    const contents = await fetchContentsFromSearchResults(searchResults)\n",[100,799,800],{"class":102,"line":205},[100,801,119],{"emptyLinePlaceholder":118},[100,803,804],{"class":102,"line":211},[100,805,806],{},"    // 步骤3：生成AI摘要和分析\n",[100,808,809],{"class":102,"line":217},[100,810,811],{},"    const analysis = await summarizeWithAI(stockSymbol, contents)\n",[100,813,814],{"class":102,"line":222},[100,815,119],{"emptyLinePlaceholder":118},[100,817,818],{"class":102,"line":228},[100,819,820],{},"    return {\n",[100,822,823],{"class":102,"line":234},[100,824,825],{},"      stockSymbol,\n",[100,827,828],{"class":102,"line":240},[100,829,830],{},"      searchResults,\n",[100,832,833],{"class":102,"line":246},[100,834,835],{},"      analysis,\n",[100,837,838],{"class":102,"line":252},[100,839,475],{},[100,841,842],{"class":102,"line":258},[100,843,272],{},[100,845,846],{"class":102,"line":263},[100,847,848],{},"    console.error(`分析股票${stockSymbol}时出错:`, error)\n",[100,850,851],{"class":102,"line":269},[100,852,284],{},[100,854,855],{"class":102,"line":275},[100,856,290],{},[100,858,859],{"class":102,"line":281},[100,860,296],{},[100,862,863],{"class":102,"line":287},[100,864,119],{"emptyLinePlaceholder":118},[100,866,867],{"class":102,"line":293},[100,868,869],{},"// 使用示例\n",[100,871,872],{"class":102,"line":430},[100,873,874],{},"analyzeStock('AAPL')\n",[100,876,877],{"class":102,"line":436},[100,878,879],{},"  .then((result) => {\n",[100,881,882],{"class":102,"line":442},[100,883,884],{},"    console.log('分析完成:')\n",[100,886,887],{"class":102,"line":448},[100,888,889],{},"    console.log(result.analysis)\n",[100,891,892],{"class":102,"line":454},[100,893,894],{},"  })\n",[100,896,897],{"class":102,"line":460},[100,898,899],{},"  .catch((error) => {\n",[100,901,902],{"class":102,"line":466},[100,903,904],{},"    console.error('分析失败:', error)\n",[100,906,907],{"class":102,"line":472},[100,908,894],{},[10,910,912],{"id":911},"实际应用股票分析仪表板","实际应用：股票分析仪表板",[14,914,915],{},"对于我们公司的实验性项目，我们将此功能集成到一个仪表板中，使分析师能够：",[28,917,918,921,924,927],{},[31,919,920],{},"输入多个股票代码进行分析",[31,922,923],{},"自定义搜索参数（时间范围、关注领域）",[31,925,926],{},"并排比较AI生成的摘要",[31,928,929],{},"保存并跟踪分析结果，以识别趋势",[14,931,932],{},"仪表板看起来像这样：",[90,934,936],{"className":92,"code":935,"language":94,"meta":95,"style":95},"// React组件示例（简化版）\nfunction StockAnalysisDashboard() {\n  const [stocks, setStocks] = useState([])\n  const [loading, setLoading] = useState({})\n  const [analyses, setAnalyses] = useState({})\n\n  const addStock = (symbol) => {\n    if (!stocks.includes(symbol)) {\n      setStocks([...stocks, symbol])\n      analyzeStockAndUpdateState(symbol)\n    }\n  }\n\n  const analyzeStockAndUpdateState = async (symbol) => {\n    setLoading((prev) => ({ ...prev, [symbol]: true }))\n    try {\n      const result = await analyzeStock(symbol)\n      setAnalyses((prev) => ({ ...prev, [symbol]: result }))\n    } catch (error) {\n      console.error(`分析${symbol}时出错:`, error)\n    } finally {\n      setLoading((prev) => ({ ...prev, [symbol]: false }))\n    }\n  }\n\n  return (\n    \u003Cdiv className=\"dashboard\">\n      \u003Ch1>股票分析仪表板\u003C/h1>\n\n      \u003Cdiv className=\"stock-input\">\n        \u003Cinput\n          type=\"text\"\n          placeholder=\"输入股票代码（例如，AAPL）\"\n          onKeyPress={(e) => e.key === 'Enter' && addStock(e.target.value)}\n        />\n      \u003C/div>\n\n      \u003Cdiv className=\"stock-analyses\">\n        {stocks.map((symbol) => (\n          \u003Cdiv key={symbol} className=\"stock-card\">\n            \u003Ch2>{symbol}\u003C/h2>\n            {loading[symbol] ? (\n              \u003Cp>加载分析中...\u003C/p>\n            ) : analyses[symbol] ? (\n              \u003Cdiv>\n                \u003Ch3>AI分析\u003C/h3>\n                \u003Cdiv className=\"analysis-content\">{analyses[symbol].analysis}\u003C/div>\n                \u003Ch3>来源\u003C/h3>\n                \u003Cul>\n                  {analyses[symbol].searchResults.map((result, i) => (\n                    \u003Cli key={i}>\n                      \u003Ca href={result.link} target=\"_blank\" rel=\"noopener noreferrer\">\n                        {result.title}\n                      \u003C/a>\n                    \u003C/li>\n                  ))}\n                \u003C/ul>\n              \u003C/div>\n            ) : (\n              \u003Cp>没有可用的分析\u003C/p>\n            )}\n          \u003C/div>\n        ))}\n      \u003C/div>\n    \u003C/div>\n  )\n}\n",[97,937,938,943,948,953,958,963,967,972,977,982,987,991,995,999,1004,1009,1014,1019,1024,1029,1034,1039,1044,1048,1052,1056,1061,1066,1071,1075,1080,1085,1090,1095,1100,1105,1110,1114,1119,1124,1129,1134,1139,1144,1149,1154,1159,1164,1170,1176,1182,1188,1194,1200,1206,1212,1218,1224,1230,1236,1242,1248,1254,1260,1265,1271,1277],{"__ignoreMap":95},[100,939,940],{"class":102,"line":103},[100,941,942],{},"// React组件示例（简化版）\n",[100,944,945],{"class":102,"line":109},[100,946,947],{},"function StockAnalysisDashboard() {\n",[100,949,950],{"class":102,"line":115},[100,951,952],{},"  const [stocks, setStocks] = useState([])\n",[100,954,955],{"class":102,"line":122},[100,956,957],{},"  const [loading, setLoading] = useState({})\n",[100,959,960],{"class":102,"line":128},[100,961,962],{},"  const [analyses, setAnalyses] = useState({})\n",[100,964,965],{"class":102,"line":134},[100,966,119],{"emptyLinePlaceholder":118},[100,968,969],{"class":102,"line":181},[100,970,971],{},"  const addStock = (symbol) => {\n",[100,973,974],{"class":102,"line":187},[100,975,976],{},"    if (!stocks.includes(symbol)) {\n",[100,978,979],{"class":102,"line":193},[100,980,981],{},"      setStocks([...stocks, symbol])\n",[100,983,984],{"class":102,"line":199},[100,985,986],{},"      analyzeStockAndUpdateState(symbol)\n",[100,988,989],{"class":102,"line":205},[100,990,475],{},[100,992,993],{"class":102,"line":211},[100,994,290],{},[100,996,997],{"class":102,"line":217},[100,998,119],{"emptyLinePlaceholder":118},[100,1000,1001],{"class":102,"line":222},[100,1002,1003],{},"  const analyzeStockAndUpdateState = async (symbol) => {\n",[100,1005,1006],{"class":102,"line":228},[100,1007,1008],{},"    setLoading((prev) => ({ ...prev, [symbol]: true }))\n",[100,1010,1011],{"class":102,"line":234},[100,1012,1013],{},"    try {\n",[100,1015,1016],{"class":102,"line":240},[100,1017,1018],{},"      const result = await analyzeStock(symbol)\n",[100,1020,1021],{"class":102,"line":246},[100,1022,1023],{},"      setAnalyses((prev) => ({ ...prev, [symbol]: result }))\n",[100,1025,1026],{"class":102,"line":252},[100,1027,1028],{},"    } catch (error) {\n",[100,1030,1031],{"class":102,"line":258},[100,1032,1033],{},"      console.error(`分析${symbol}时出错:`, error)\n",[100,1035,1036],{"class":102,"line":263},[100,1037,1038],{},"    } finally {\n",[100,1040,1041],{"class":102,"line":269},[100,1042,1043],{},"      setLoading((prev) => ({ ...prev, [symbol]: false }))\n",[100,1045,1046],{"class":102,"line":275},[100,1047,475],{},[100,1049,1050],{"class":102,"line":281},[100,1051,290],{},[100,1053,1054],{"class":102,"line":287},[100,1055,119],{"emptyLinePlaceholder":118},[100,1057,1058],{"class":102,"line":293},[100,1059,1060],{},"  return (\n",[100,1062,1063],{"class":102,"line":430},[100,1064,1065],{},"    \u003Cdiv className=\"dashboard\">\n",[100,1067,1068],{"class":102,"line":436},[100,1069,1070],{},"      \u003Ch1>股票分析仪表板\u003C/h1>\n",[100,1072,1073],{"class":102,"line":442},[100,1074,119],{"emptyLinePlaceholder":118},[100,1076,1077],{"class":102,"line":448},[100,1078,1079],{},"      \u003Cdiv className=\"stock-input\">\n",[100,1081,1082],{"class":102,"line":454},[100,1083,1084],{},"        \u003Cinput\n",[100,1086,1087],{"class":102,"line":460},[100,1088,1089],{},"          type=\"text\"\n",[100,1091,1092],{"class":102,"line":466},[100,1093,1094],{},"          placeholder=\"输入股票代码（例如，AAPL）\"\n",[100,1096,1097],{"class":102,"line":472},[100,1098,1099],{},"          onKeyPress={(e) => e.key === 'Enter' && addStock(e.target.value)}\n",[100,1101,1102],{"class":102,"line":478},[100,1103,1104],{},"        />\n",[100,1106,1107],{"class":102,"line":483},[100,1108,1109],{},"      \u003C/div>\n",[100,1111,1112],{"class":102,"line":488},[100,1113,119],{"emptyLinePlaceholder":118},[100,1115,1116],{"class":102,"line":494},[100,1117,1118],{},"      \u003Cdiv className=\"stock-analyses\">\n",[100,1120,1121],{"class":102,"line":693},[100,1122,1123],{},"        {stocks.map((symbol) => (\n",[100,1125,1126],{"class":102,"line":699},[100,1127,1128],{},"          \u003Cdiv key={symbol} className=\"stock-card\">\n",[100,1130,1131],{"class":102,"line":704},[100,1132,1133],{},"            \u003Ch2>{symbol}\u003C/h2>\n",[100,1135,1136],{"class":102,"line":709},[100,1137,1138],{},"            {loading[symbol] ? (\n",[100,1140,1141],{"class":102,"line":715},[100,1142,1143],{},"              \u003Cp>加载分析中...\u003C/p>\n",[100,1145,1146],{"class":102,"line":720},[100,1147,1148],{},"            ) : analyses[symbol] ? (\n",[100,1150,1151],{"class":102,"line":726},[100,1152,1153],{},"              \u003Cdiv>\n",[100,1155,1156],{"class":102,"line":731},[100,1157,1158],{},"                \u003Ch3>AI分析\u003C/h3>\n",[100,1160,1161],{"class":102,"line":736},[100,1162,1163],{},"                \u003Cdiv className=\"analysis-content\">{analyses[symbol].analysis}\u003C/div>\n",[100,1165,1167],{"class":102,"line":1166},48,[100,1168,1169],{},"                \u003Ch3>来源\u003C/h3>\n",[100,1171,1173],{"class":102,"line":1172},49,[100,1174,1175],{},"                \u003Cul>\n",[100,1177,1179],{"class":102,"line":1178},50,[100,1180,1181],{},"                  {analyses[symbol].searchResults.map((result, i) => (\n",[100,1183,1185],{"class":102,"line":1184},51,[100,1186,1187],{},"                    \u003Cli key={i}>\n",[100,1189,1191],{"class":102,"line":1190},52,[100,1192,1193],{},"                      \u003Ca href={result.link} target=\"_blank\" rel=\"noopener noreferrer\">\n",[100,1195,1197],{"class":102,"line":1196},53,[100,1198,1199],{},"                        {result.title}\n",[100,1201,1203],{"class":102,"line":1202},54,[100,1204,1205],{},"                      \u003C/a>\n",[100,1207,1209],{"class":102,"line":1208},55,[100,1210,1211],{},"                    \u003C/li>\n",[100,1213,1215],{"class":102,"line":1214},56,[100,1216,1217],{},"                  ))}\n",[100,1219,1221],{"class":102,"line":1220},57,[100,1222,1223],{},"                \u003C/ul>\n",[100,1225,1227],{"class":102,"line":1226},58,[100,1228,1229],{},"              \u003C/div>\n",[100,1231,1233],{"class":102,"line":1232},59,[100,1234,1235],{},"            ) : (\n",[100,1237,1239],{"class":102,"line":1238},60,[100,1240,1241],{},"              \u003Cp>没有可用的分析\u003C/p>\n",[100,1243,1245],{"class":102,"line":1244},61,[100,1246,1247],{},"            )}\n",[100,1249,1251],{"class":102,"line":1250},62,[100,1252,1253],{},"          \u003C/div>\n",[100,1255,1257],{"class":102,"line":1256},63,[100,1258,1259],{},"        ))}\n",[100,1261,1263],{"class":102,"line":1262},64,[100,1264,1109],{},[100,1266,1268],{"class":102,"line":1267},65,[100,1269,1270],{},"    \u003C/div>\n",[100,1272,1274],{"class":102,"line":1273},66,[100,1275,1276],{},"  )\n",[100,1278,1280],{"class":102,"line":1279},67,[100,1281,296],{},[10,1283,1284],{"id":1284},"挑战与考虑因素",[14,1286,1287],{},"在构建这个工具的过程中，我们遇到了几个值得注意的挑战：",[82,1289,1291],{"id":1290},"_1-api速率限制和成本","1. API速率限制和成本",[14,1293,1294],{},"SERP API和OpenAI的API都有速率限制和使用成本。对于生产系统，你需要：",[60,1296,1297,1300,1303],{},[31,1298,1299],{},"实现缓存以避免重复搜索",[31,1301,1302],{},"设置使用监控和警报",[31,1304,1305],{},"考虑多个股票的批处理",[82,1307,1309],{"id":1308},"_2-内容提取质量","2. 内容提取质量",[14,1311,1312],{},"从网页中提取有意义的内容可能具有挑战性，原因如下：",[60,1314,1315,1318,1321],{},[31,1316,1317],{},"金融新闻网站的付费墙",[31,1319,1320],{},"通过JavaScript加载的动态内容",[31,1322,1323],{},"不同网站之间的页面结构各异",[14,1325,1326],{},"我们通过以下方式改进了内容提取：",[60,1328,1329,1335,1338],{},[31,1330,1331,1332],{},"使用更复杂的库，如",[97,1333,1334],{},"mozilla/readability",[31,1336,1337],{},"为常见的金融新闻来源实现特定网站的提取器",[31,1339,1340],{},"当无法获取完整内容时，回退到元描述",[82,1342,1344],{"id":1343},"_3-确保分析质量","3. 确保分析质量",[14,1346,1347],{},"为了提高AI生成分析的质量：",[60,1349,1350,1353,1356],{},[31,1351,1352],{},"我们根据金融分析师的反馈微调了提示",[31,1354,1355],{},"通过交叉引用关键主张实现事实检查",[31,1357,1358],{},"添加来源归属，以明确信息来源",[10,1360,1361],{"id":1361},"结论",[14,1363,1364],{},"将网络搜索功能与AI摘要相结合，创建了一个强大的股票分析工具，可以节省数小时的研究时间，并提供更全面的见解。我们在这里概述的实现只是一个起点——有很多方法可以扩展和改进这个系统。",[14,1366,1367],{},"一些潜在的扩展包括：",[60,1369,1370,1373,1376,1379],{},[31,1371,1372],{},"添加专门针对金融新闻的情感分析",[31,1374,1375],{},"纳入历史股价数据进行相关性分析",[31,1377,1378],{},"扩展到包括来自Twitter/X等平台的社交媒体情绪",[31,1380,1381],{},"创建可能影响股价的重大新闻警报",[14,1383,1384],{},"随着AI能力的不断进步，像这样的工具将变得越来越复杂和有价值，用于金融分析和决策。",[14,1386,1387],{},"你是否构建过类似的工具或有改进的想法？我很想在评论中听到你的经验！",[1389,1390,1391],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":95,"searchDepth":109,"depth":109,"links":1393},[1394,1395,1396,1403,1404,1409],{"id":12,"depth":109,"text":12},{"id":22,"depth":109,"text":23},{"id":76,"depth":109,"text":77,"children":1397},[1398,1399,1400,1401,1402],{"id":84,"depth":115,"text":85},{"id":140,"depth":115,"text":141},{"id":299,"depth":115,"text":300},{"id":499,"depth":115,"text":500},{"id":741,"depth":115,"text":742},{"id":911,"depth":109,"text":912},{"id":1284,"depth":109,"text":1284,"children":1405},[1406,1407,1408],{"id":1290,"depth":115,"text":1291},{"id":1308,"depth":115,"text":1309},{"id":1343,"depth":115,"text":1344},{"id":1361,"depth":109,"text":1361},"如何结合OpenAI和SERP API创建强大的网络搜索和AI摘要工具，用于股票分析和研究","md",{"date":1413,"image":1414,"alt":1415,"tags":1416,"category":1419,"topics":1420,"published":118,"featured":118},"4th May 2025","/blogs-img/blog6.jpg","OpenAI和SERP API集成",[1417,1418],"AI","Engineering","ai-native-systems",[1421,1422],"ai-native","execution","/blogs/zh/openai-serp-api-web-search-ai-summary",{"title":5,"description":1410},"blogs/zh/10.openai-serp-api-web-search-ai-summary","3dnvz0LExYWntz9aav6FJd1X0m0ATWD77_80kMtWlK4",1781532113990]