ChatGPT插件提示词抓取实战:从API探查到数据存储的完整爬虫指南
1. 项目概述与核心价值最近在折腾一些AI应用开发发现一个挺有意思的现象很多开发者想基于ChatGPT的插件生态做点东西但第一步就卡住了——不知道从哪儿能找到足够多、足够好的真实插件提示词Prompts来研究。官方文档给的例子就那么几个社区分享的又零零散散不成体系。这时候如果你在GitHub上搜一下大概率会看到一个叫simonw/scrape-chatgpt-plugin-prompts的项目。这项目名字直译过来就是“抓取ChatGPT插件提示词”光看名字很多搞AI应用开发、提示词工程或者数据采集的朋友眼睛就该亮了。简单来说这是一个用Python写的工具脚本它的核心任务就是自动化地从OpenAI官方的ChatGPT插件商店Plugin Store里把各个插件的介绍、功能描述尤其是它们向ChatGPT暴露的“提示词模板”给扒下来整理成结构化的数据比如JSON或SQLite数据库。你别看它功能听起来单一但对于想深入理解插件生态、分析热门插件模式甚至是自己训练相关模型的开发者来说这简直就是开了一座金矿。我自己在尝试开发一个智能工作流聚合插件前就是靠这个工具批量获取了上百个插件的提示词数据才快速摸清了主流插件的交互逻辑和设计范式省去了大量手动收集和整理的繁琐工作。这个项目适合谁呢首先是AI应用开发者特别是对构建ChatGPT插件感兴趣的朋友你可以通过它快速了解“别人家的插件是怎么跟ChatGPT对话的”。其次是提示词工程师和数据分析师这些结构化的提示词数据是研究自然语言指令如何转化为API调用的绝佳样本。最后任何对网络爬虫、数据采集自动化感兴趣的技术爱好者也能从这个项目中学习到如何优雅地处理一个动态加载、结构相对复杂的现代网页。2. 项目整体设计与技术思路拆解2.1 核心目标与挑战分析这个项目的核心目标非常明确从https://chatgpt.com/plugins这个官方页面或其相关接口中提取所有可用插件的详细信息并重点保存每个插件的“提示词”或“功能描述”这些描述本质上就是用户在使用插件时需要遵循或可以参考的指令模板。听起来像是一个典型的爬虫任务对吧但实际操作起来有几个关键挑战需要解决动态内容加载现代前端应用大量使用JavaScript动态渲染内容。插件商店的列表和详情很可能不是直接嵌在初始HTML中的而是通过API异步获取。这意味着简单的requests.get()加上BeautifulSoup解析可能拿不到完整数据。反爬机制像OpenAI这样的网站很可能设有基本的反爬措施比如请求头校验、频率限制等。粗暴的、高频率的访问可能导致IP被暂时封禁。数据结构化抓取到的原始数据可能是JSON、也可能是HTML片段需要清晰地解析出插件名称、开发者、描述、图标链接以及最重要的——那个用于触发和指导插件功能的“提示文本”。可持续性与维护插件商店会更新插件会上架下架。爬虫脚本需要有一定的健壮性能够处理页面结构的小幅变动并且方便地重新运行以获取最新数据。2.2 技术栈选型与方案考量原作者Simon Willisonsimonw是位经验丰富的开发者他的技术选型通常兼顾实用性和优雅性。针对上述挑战这个项目很可能采用了以下方案核心爬取库httpx或requestsplaywright/selenium为什么是httpx相比于经典的requestshttpx支持HTTP/2异步客户端并且API设计非常现代友好。对于需要处理可能存在的复杂HTTP交互的场景httpx是更佳选择。项目很可能优先使用httpx尝试直接调用背后数据API。为什么可能需要playwright如果数据确实是通过前端JavaScript动态加载且API接口难以直接模拟那么就需要一个能驱动真实浏览器如Chromium的工具。playwright相比selenium更现代API更简洁跨浏览器支持好是处理动态页面的首选。脚本中可能会先尝试用httpx获取失败或数据不全时再降级到使用playwright。数据解析库BeautifulSoup4或直接处理JSON如果数据以HTML片段形式返回BeautifulSoup4是解析和提取信息的标准工具。更理想的情况是直接找到返回结构化JSON数据的内部API接口这样只需用Python内置的json库即可更稳定高效。数据存储sqlite-utils或直接写入JSON文件Simon Willison 是sqlite-utils库的作者这是一个让操作SQLite数据库变得像操作JSON一样简单的神器。将抓取的数据存入SQLite数据库便于后续的查询、分析和去重。这几乎是他的“标准操作”。作为替代或补充也可能将数据保存为简单的JSON Lines.jsonl文件每行一个插件记录易于流式处理。调度与健壮性添加延迟、处理异常脚本中必然会包含time.sleep(random.uniform(1, 3))之类的随机延迟以模拟人类操作避免触发反爬。会有完善的try...except块来处理网络超时、解析错误、元素缺失等情况确保一个插件的抓取失败不会导致整个脚本崩溃。这个设计思路体现了一种分层策略优先寻找最简洁高效的官方数据接口若不奏效则使用更强大但也更重的浏览器自动化方案作为保障。存储方案则优先考虑结构化、可查询的数据库为数据分析铺平道路。3. 核心代码解析与实操要点3.1 环境准备与依赖安装要运行这个项目首先需要一个Python环境建议3.8以上。我们通过克隆仓库和安装依赖来开始。# 克隆项目仓库 git clone https://github.com/simonw/scrape-chatgpt-plugin-prompts.git cd scrape-chatgpt-plugin-prompts # 创建虚拟环境推荐 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装依赖 pip install -r requirements.txt通常requirements.txt文件会包含类似以下内容httpx0.24.0 beautifulsoup44.12.0 sqlite-utils3.35 playwright1.36.0 # 如果用了playwright可能还需要安装浏览器 # playwright install chromium注意playwright体积较大因为它会下载浏览器内核。如果你确认目标数据可以通过API直接获取后面会教你判断可以尝试先注释掉playwright的安装让脚本先用httpx模式运行。3.2 核心抓取逻辑剖析我们来看项目核心脚本假设名为scrape.py的关键部分。虽然无法看到原版代码但我们可以根据目标重构出最具可能性的实现逻辑。第一步发现数据源真正的挑战不是写爬虫而是找到数据在哪。打开浏览器开发者工具F12访问插件商店页面切换到“网络”(Network)选项卡过滤XHR/Fetch请求刷新页面。你会看到一系列网络请求。寻找包含plugins、listings、catalog等关键词的请求查看其“预览”(Preview)或“响应”(Response)如果里面是结构化的JSON数据并且包含插件名称、描述等信息恭喜你找到了最理想的抓取入口。假设我们找到了一个API端点https://chatgpt.com/backend-api/plugins它返回一个JSON数组每个元素是一个插件的基本信息但可能不包含详细的“提示词”。第二步获取插件列表import httpx import json async def fetch_plugin_list(): headers { User-Agent: Mozilla/5.0 ..., # 模拟浏览器 Accept: application/json, } async with httpx.AsyncClient(timeout30.0) as client: try: # 假设的API实际需要探查 list_url https://chatgpt.com/backend-api/plugins resp await client.get(list_url, headersheaders) resp.raise_for_status() plugin_list resp.json() return plugin_list except httpx.RequestError as e: print(f获取插件列表失败: {e}) return []这段代码使用httpx的异步客户端高效地获取插件列表。设置合理的User-Agent和超时时间是基础礼仪。第三步获取每个插件的详情插件列表可能只包含ID和名字。我们需要遍历这个列表为每个插件去获取其详情页信息那里才可能有完整的“提示词”。async def fetch_plugin_detail(plugin_id): detail_url fhttps://chatgpt.com/backend-api/plugins/{plugin_id} async with httpx.AsyncClient() as client: try: resp await client.get(detail_url) resp.raise_for_status() detail_data resp.json() # 从detail_data中提取我们关心的字段 prompt_text extract_prompt(detail_data) # 关键提取函数 return { id: plugin_id, name: detail_data.get(name), description: detail_data.get(description), prompt_for_model: prompt_text, # 抓取的核心目标 developer: detail_data.get(author), icon_url: detail_data.get(icon_url), } except Exception as e: print(f获取插件 {plugin_id} 详情失败: {e}) return None这里的extract_prompt函数是关键它需要你仔细分析详情API返回的JSON结构找到存放提示词文本的字段。这个字段名可能是prompt、instruction、how_to_use甚至是manifest中的某个描述。第四步降级策略使用Playwright如果找不到直接的API或者API返回的数据不包含提示词我们就需要动用playwright来模拟浏览器访问每个插件的详情页面例如https://chatgpt.com/plugin/xxx然后从渲染后的HTML中提取信息。from playwright.async_api import async_playwright async def scrape_with_playwright(plugin_slug): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) # 无头模式 page await browser.new_page() await page.goto(fhttps://chatgpt.com/plugin/{plugin_slug}) # 等待关键内容加载 await page.wait_for_selector(div[data-testidplugin-description], timeout10000) # 使用page.evaluate在浏览器上下文执行JS提取数据 plugin_data await page.evaluate(() { const descEl document.querySelector(div[data-testidplugin-description]); const promptEl document.querySelector(.prompt-text); // 这个选择器需要实际探查 return { description: descEl ? descEl.innerText : , prompt: promptEl ? promptEl.innerText : , // ... 其他字段 }; }) await browser.close() return plugin_data使用playwright时最关键的步骤是找到正确的CSS选择器。你需要仔细查看详情页的HTML结构找到包含提示词的那个元素的唯一标识。这可能是一个特定的类名、>from sqlite_utils import Database def save_to_database(plugin_data_list, db_pathplugins.db): db Database(db_path) table db[plugins] # 如果表不存在会自动创建并根据第一次插入的数据推断列类型 table.insert_all(plugin_data_list, pkid, replaceTrue) # replaceTrue 表示更新已存在记录 print(f已保存 {len(plugin_data_list)} 条记录到 {db_path})sqlite-utils的insert_all方法非常智能能自动处理表结构。pkid指定主键replaceTrue确保插件信息更新时旧记录会被新记录替换。实操心得在正式大规模抓取前先用几个插件ID做测试把抓取和保存的流程跑通。务必检查保存到数据库或文件里的“提示词”字段是否是你想要的内容格式是否正确比如是否包含了多余的HTML标签。这个验证步骤能避免白忙活一场。4. 完整实操流程与步骤详解4.1 步骤一探查与逆向工程这是最重要的一步决定了后续所有工作的难度。不要一上来就写代码。手动浏览用Chrome或Edge浏览器正常访问ChatGPT插件商店。观察页面是如何加载的。打开开发者工具按F12重点看“网络”(Network)面板。筛选请求在筛选框输入json或api查看所有可能的数据请求。识别关键请求逐个点击请求在“预览”(Preview)标签页查看其内容。寻找包含插件列表或插件详情的JSON响应。关注请求的URL模式、请求头特别是Authorization、Cookie等。复制为cURL找到目标请求后右键点击它选择“复制” - “复制为cURL(bash)”。这个命令包含了所有头信息和参数可以直接在终端测试或转换成Pythonhttpx/requests代码。有很多在线工具如curlconverter.com可以帮你做这个转换。分析详情页点击一个插件进入其详情页。同样在开发者工具中查看这个新页面加载了哪些数据请求。同时在“元素”(Elements)面板中搜索prompt、instruction、如何使用等关键词定位到提示词文本所在的HTML元素记录其CSS选择器路径。4.2 步骤二编写并测试核心抓取函数根据第一步的发现选择主攻方向API优先。建立项目结构创建一个新的目录初始化虚拟环境安装httpx,beautifulsoup4,sqlite-utils。编写列表抓取函数基于你找到的列表API写出类似上面fetch_plugin_list的函数。运行测试确保能拿到插件ID列表。编写详情抓取函数基于详情API或详情页URL写出fetch_plugin_detail函数。重点测试提示词提取逻辑。用一个已知的插件ID进行测试打印出提取到的所有字段确认数据完整准确。加入错误处理与延迟在遍历插件ID的循环中加入asyncio.sleep()来避免请求过快。用try...except包裹每个插件的抓取过程记录失败日志让脚本能继续运行。4.3 步骤三集成数据存储设计数据表结构想清楚你要存哪些字段。至少包括id主键、name、description、prompt_text、developer、url、scraped_at抓取时间。集成sqlite-utils在抓取循环中每成功获取一个插件详情就将其组织成字典添加到一个列表中。每抓取10个或全部完成后一次性写入数据库。运行完整脚本使用asyncio.run(main())来运行你的异步主函数。观察控制台输出检查是否有大量失败请求。4.4 步骤四处理动态渲染如果需要如果API路线走不通就必须启用playwright方案。安装Playwrightpip install playwright然后playwright install chromium。修改详情抓取函数在fetch_plugin_detail函数中如果API请求失败或返回数据不完整则调用一个后备函数fetch_detail_with_playwright(plugin_id_or_slug)。编写Playwright抓取逻辑如前面示例使用playwright打开详情页等待特定元素出现然后提取数据。这里的选择器必须精准最好使用>问题现象可能原因排查步骤与解决方案获取列表API返回403/401错误请求头不完整或缺少认证1. 检查复制的cURL命令中的Headers确保User-Agent,Accept,Cookie如果需要等与浏览器一致。2. 某些API可能需要一个简单的Bearer Token检查网络请求的Authorization头。3. 尝试添加Referer头为插件商店主页URL。列表API返回空数组或奇怪数据API端点已变更或需要特定参数1. 重新进行网络抓包确认当前有效的API URL和参数。2. 检查请求的查询参数Query Parameters可能需要分页参数如offset,limit。3. 尝试用浏览器直接访问该API URL在已登录状态下看是否能返回正确数据。详情API返回404插件ID格式不对或详情端点路径不同1. 确认从列表API中获取的插件ID字段名正确。2. 查看浏览器中插件详情页面对应的API请求确定其URL模式。3. 有些系统使用slugURL友好名称而非数字ID。提示词字段提取为空JSON字段路径或HTML选择器错误1. 将API返回的完整JSON或Playwright抓取的页面HTML保存到文件仔细分析结构。2. 使用jsonpath或手动遍历字典来定位字段。3. 对于HTML尝试多个可能的选择器并用page.content()保存完整HTML进行调试。脚本运行速度慢同步请求、未使用异步或延迟太短1. 将requests改为httpx的异步模式并使用asyncio.gather并发处理多个请求注意控制并发数建议5-10。2. 适当增加请求间隔的随机延迟但并发可以抵消部分影响。3. Playwright模式本身就很慢这是固有缺点。数据库写入错误数据字典字段不一致或类型问题1. 确保每次插入的数据字典具有相同的键。2.sqlite-utils会根据第一批数据推断表结构如果后续数据出现新字段或类型冲突会报错。可以尝试先定义表结构或使用alterTrue参数。5.2 高级技巧与优化建议使用请求会话Sessionhttpx.AsyncClient本身就是一个会话可以自动管理cookies和连接池提升效率。确保在整个抓取过程中复用同一个client实例。实现优雅的重试机制网络请求不稳定对于短暂的错误如超时、5xx错误可以使用tenacity或backoff库实现自动重试增加脚本的健壮性。from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) async def fetch_with_retry(url): # ... 请求逻辑增量抓取与更新在数据库表中添加scraped_at字段。每次运行脚本时可以先查询已抓取的插件ID只抓取新的或长时间未更新的插件。这需要对列表API返回的数据进行比对。将配置参数化将API基础URL、请求头、延迟时间、数据库路径等写入配置文件如config.yaml或环境变量方便调整而不必修改代码。数据清洗与后处理抓取到的提示词文本可能包含多余的空格、换行或HTML标签。在存入数据库前进行简单的清洗比如使用BeautifulSoup.get_text()去除HTML标签用str.strip()清理空白符。遵守道德与法律在脚本开头添加明显的注释说明此工具仅用于教育和个人研究目的。避免在短时间内发起海量请求尊重目标服务器的负载。考虑将最终抓取的数据集去除可能涉及隐私的部分开源供社区研究这比单纯分享爬虫代码更有价值。通过以上步骤你不仅能成功运行simonw/scrape-chatgpt-plugin-prompts这类项目更能透彻理解其背后的设计思路和实现细节。无论你是想直接使用这个工具还是借鉴其方法来抓取其他类似平台的数据这套从探查、逆向、编码到优化的完整流程都是非常实用的实战经验。数据抓取的核心往往不在于代码多复杂而在于对目标系统行为的准确分析和耐心调试。