ChatGPT 网络抓取已悄然改变了大量数据提取代码的编写方式。传统方式十分脆弱:检查页面、手写 CSS 选择器,每次网站更新标记时就要重写一遍。新方式则依赖模型。你获取页面的干净副本,将其传给 OpenAI 模型,要求以结构化 JSON 格式返回所需字段。选择器存在于提示词中而非代码里,因此能够经受住那些会让硬编码解析器崩溃的布局变更。
有一个诚实的注意事项往往被营销宣传所略去:ChatGPT 无法替你抓取页面。它没有可靠的浏览器,会像任何其他客户端一样被封锁,而且直接询问它某个在线 URL 往往会返回一个听起来很有把握的幻觉答案,而非真实数据。因此本指南将任务清晰地分开:Crawlbase 通过受信任的 IP 负责抓取和渲染,OpenAI 模型负责理解内容,Python 将两者粘合在一起。以下所有内容均可直接运行,范围限于公开页面,并且专为处理过大而无法一次性发送给模型的页面而构建。
ChatGPT 网络抓取的实际工作原理
明确每个工具的职责很有帮助,因为将两者混淆正是大多数教程出错的地方。语言模型是推理层,而非网络客户端。给它干净的文本,它非常擅长从杂乱内容中提取结构化字段。让它自己去获取内容则会失败:没有渲染引擎、没有代理池、没有 CAPTCHA 处理能力,并且当它实际无法加载页面时,极易编造出看似合理的值。
因此,一个可运行的流水线有三个阶段。第一,使用 Crawling API 抓取并渲染目标页面,它在真实浏览器中运行页面,背后使用轮换住宅 IP,并返回完整的 HTML。第二,将 HTML 压缩为紧凑的内容,无论是纯文本还是 Markdown,这样就不必花费令牌来向模型发送导航和脚本标签。第三,提示 OpenAI 模型读取该内容,并返回符合你定义 schema 的 JSON。模型从不接触网络,它只读取你提供的内容。
保持这两项职责分离。Crawlbase 负责加载页面、渲染 JavaScript 并突破封锁。OpenAI 模型从不抓取任何内容:它只读取你传给它的 HTML 或 Markdown,并返回结构化数据。如果你直接向 ChatGPT 询问一个在线 URL,它无法可靠地加载,并可能捏造答案。
开始前的准备工作
这是一个面向初中级开发者的项目。你需要 Python 3.9 或更高版本、一个 Crawlbase 账户(用于获取普通令牌和 JavaScript 令牌)以及一个 OpenAI API 密钥。Crawlbase 免费套餐提供的请求次数完全足够跟随本教程操作,此处的模型调用使用的是小型、低成本模型。请将两个密钥设置为环境变量,而不是直接粘贴到脚本中。
python --version mkdir chatgpt-scraper && cd chatgpt-scraper pip install crawlbase openai beautifulsoup4 html2text export CRAWLBASE_TOKEN="your_normal_token" export CRAWLBASE_JS_TOKEN="your_javascript_token" export OPENAI_API_KEY="your_openai_key"
四个库完成实际工作。crawlbase 是 Crawling API 的客户端,openai 是模型调用的官方 SDK,beautifulsoup4 将渲染后的页面压缩为可读文本,html2text 在你希望模型看到标题和表格等结构时将 HTML 转换为 Markdown。你并不总是需要同时使用 BeautifulSoup 和 html2text,选择最适合该页面的表示方式即可。
第一步:使用 Crawling API 抓取渲染后的页面
首先获取页面的干净副本。对于任何在客户端渲染内容的网站(即大多数现代页面),使用 JavaScript 令牌,并传入 ajax_wait 和 page_wait,确保延迟加载的内容在 HTML 返回前有时间出现。下面的示例指向一个公开产品页面,替换成你实际使用的公开 URL 即可。
import os from crawlbase import CrawlingAPI api = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]}) target_url = "https://www.example.com/products/widget" def fetch_html(url): response = api.get(url, {"ajax_wait": "true", "page_wait": 4000}) if response["status_code"] != 200: raise RuntimeError(f"Fetch failed: {response['status_code']}") return response["body"].decode("utf-8", "ignore") if __name__ == "__main__": html = fetch_html(target_url) print(len(html), "bytes of rendered HTML")
运行后你应该能看到相当可观的字节数,如果打印一段内容,会看到真实内容而非空壳。这正是 ChatGPT 自己无法做到的步骤:Crawling API 渲染了 JavaScript,并通过受信任的 IP 路由了请求,使页面完整返回。有了干净的 HTML,模型就可以接手了。
第二步:将页面压缩为干净文本或 Markdown
原始 HTML 对语言模型来说大多是噪音:脚本、样式、SVG 路径和跟踪标签耗费令牌并稀释信号。先将它们去除。对于简单的字段提取,BeautifulSoup 的文本输出就足够了。当页面有有意义的结构(如规格表或嵌套列表)时,转换为 Markdown,让模型能看到层级关系。
from bs4 import BeautifulSoup import html2text def to_text(html): soup = BeautifulSoup(html, "html.parser") for tag in soup(["script", "style", "noscript", "svg"]): tag.decompose() return soup.get_text(separator=" ", strip=True) def to_markdown(html): converter = html2text.HTML2Text() converter.ignore_links = True converter.ignore_images = True return converter.handle(html)
这一步通常能将令牌数量减少一个数量级,从而实现更便宜、更快速、更准确的提取,因为模型不必在标记中跋涉。如果你想完全跳过这一步,Crawlbase 可以直接从抓取中返回干净的 LLM 就绪 Markdown,让页面以模型所需的形态到达,无需本地转换步骤。
第三步:提示 OpenAI 模型提取结构化 JSON
现在进行实际的 ChatGPT 网络抓取。将清理后的内容发送给 OpenAI 模型,并附上一个提示词,列出你需要的每个字段,并强制输出为 JSON。最重要的设置是要求返回 JSON 对象响应,这样你得到的是可解析的数据而非散文。低温度参数可以防止模型在应该逐字复制的值上发挥创意。
import json from openai import OpenAI client = OpenAI() SYSTEM = ( "You extract structured data from web page content. " "Return only valid JSON. Copy values verbatim from the text. " "If a field is not present, use null. Never invent data." ) def extract(content, fields): prompt = ( f"Extract these fields as JSON: {', '.join(fields)}.\n\n" f"Page content:\n{content}" ) response = client.chat.completions.create( model="gpt-4o-mini", temperature=0, response_format={"type": "json_object"}, messages=[ {"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}, ], ) return json.loads(response.choices[0].message.content)
将三个阶段串联起来,你就得到了一个完整的抓取器,无需硬编码任何选择器。
if __name__ == "__main__": html = fetch_html(target_url) text = to_text(html) data = extract(text, ["product_name", "price", "rating", "in_stock"]) print(json.dumps(data, indent=2))
结果已结构化,可直接存储。
{ "product_name": "Acme Widget Pro", "price": "$49.99", "rating": "4.6", "in_stock": true }
模型只能从它实际能看到的页面中提取数据。Crawling API 在真实浏览器中渲染 JavaScript,在服务器端通过住宅 IP 轮换,并在一次调用中返回完整 HTML 或 LLM 就绪 Markdown,使 ChatGPT 获得的是干净内容而非被拦截的空壳。从免费套餐开始,将其指向一个公开页面。
设计可靠提取的提示词
提示词现在是你抓取逻辑所在之处,因此值得认真撰写。有几种模式能决定输出是不稳定还是可信赖的数据。
定义明确的 schema,而非模糊的请求
"获取重要信息"会给模型留下猜测的空间。列出每个字段、其类型以及缺失时的处理方式。在提示词中传入 JSON 骨架甚至更有效,因为模型填充的是它能看到的形状,而非需要推断的形状。
schema = { "product_name": "string", "price": "string, include currency symbol", "rating": "number or null", "specs": "object of key-value pairs", } prompt = ( f"Fill this schema from the page content. " f"Use null for anything absent.\n\n" f"Schema:\n{json.dumps(schema, indent=2)}\n\n" f"Content:\n{content}" )
固定格式并禁止捏造
精确说明每个值的格式:价格保留货币符号,日期标准化为 YYYY-MM-DD,评分以数字形式返回。同样重要的是,告诉模型绝不要猜测。系统提示中"对缺失字段使用 null,永不捏造"的指令,正是防止幻觉值的关键,这是基于模型的提取中最大的单一风险。
降低温度并验证输出
设置 temperature=0,确保同一页面每次产生相同的 JSON。然后验证返回结果:确认它可以解析,检查必需的键是否存在,并验证类型。模型返回的是文本,因此在你的代码验证之前,将其输出视为不可信的输入,就像对待任何外部来源一样。
处理超出上下文窗口的大页面
上述模式在页面太大而无法放入单次模型调用时就会失效。长篇分类列表、包含数百条目的评论以及密集的文档可能会超出上下文窗口,或者每次请求的成本过高。解决方法是将内容拆分成块,逐块提取,然后合并结果。
def chunk_text(text, size=12000, overlap=500): chunks = [] start = 0 while start < len(text): end = start + size chunks.append(text[start:end]) start = end - overlap return chunks def extract_large(text, fields): results = [] for chunk in chunk_text(text): part = extract(chunk, fields) if part: results.append(part) return results
有几条规则能保证分块的可靠性。块之间重叠数百个字符,以防跨越边界的记录被截断。尽可能在段落或列表项等自然断点处分割,而非在单词中间。对于列表页面,让每个块返回一个条目数组并连接这些数组,然后在稳定的键(如产品 ID 或 URL)上去重,因为重叠会产生少量重复。这种模式与生产系统所使用的相同,更广泛的原理可参阅 AI 数据提取工作原理。
当页面进行反抓取时
以上假设抓取成功。在防御严密的网站上,至少在短期内不会成功。Crawling API 在你已编写的调用中处理渲染和 IP 轮换,可以清除大多数拦截。当你以较大量运行或针对异常激进的目标时,通过 Smart AI Proxy 路由请求,它会针对每个目标自适应策略以保持高成功率;或者当某个网站已有维护好的解析器且你希望在不调用 LLM 的情况下获得干净字段时,使用 Crawling API。
分工是需要牢记的核心:Crawlbase 负责突破防御并交付真实页面,OpenAI 模型负责读取它。将两者混淆,即让 ChatGPT 抓取 URL,正是产生被拦截请求和幻觉答案的原因。保持分离,每个工具就能做好它擅长的部分。如果你想比较不同模型系列在提取环节的表现,我们关于 使用 Gemini AI 进行网络抓取 的教程遵循相同的"先抓取后提取"模式。
核心要点
- 将任务一分为二。 Crawlbase 负责抓取和渲染页面,OpenAI 模型负责从干净内容中提取数据。模型从不接触网络。
- 提示前先压缩。 将 HTML 压缩为文本或 Markdown,这样令牌花在内容上而非脚本标签,从而实现更便宜、更准确的提取。
-
让提示词承担 schema 的职责。 列出每个字段和类型,强制 JSON 输出,设置
temperature=0,并告知模型对缺失值使用 null 而非捏造。 - 对大页面进行分块。 当内容超出上下文窗口时,带重叠地拆分,逐块提取,然后在稳定键上合并和去重。
- 验证输出。 模型返回的是文本,在存储之前确认其可解析且具有预期的键和类型。
- 坚守公开数据。 遵守每个网站的条款和 robots.txt,不涉及账户、个人数据或登录后的任何操作。
常见问题
ChatGPT 能直接抓取网站吗?
不能。ChatGPT 没有可靠的方式抓取在线页面:它会像任何其他客户端一样被拦截,当无法加载 URL 时往往会捏造答案。它擅长的是读取你提供的内容并返回结构化数据。因此,你需要用 Crawling API 等工具抓取页面,然后将干净的 HTML 或 Markdown 传给模型进行提取。
为什么用 Crawlbase 抓取而不用 Python requests?
因为普通请求在 JavaScript 密集的网站上只会返回空壳,在防御严密的网站上会被拦截。Crawling API 在真实浏览器中渲染页面,并通过轮换住宅 IP 路由请求,使模型看到的内容与人类用户看到的一致。没有这一步,模型提取的将是空白页面或机器人检测页面的内容。
提取应使用哪个 OpenAI 模型?
像 gpt-4o-mini 这样的小型快速模型能很好地处理大多数字段提取,并在大规模使用时控制成本。只有当页面需要真正的推理时,例如推断隐含而非明确陈述的字段,或协调相互矛盾的值时,才考虑使用更大的模型。从小模型开始,在你的页面上测量准确率,只有当小模型出现遗漏时再升级。
如何防止模型捏造值?
三管齐下。设置 temperature=0 以获得确定性输出,在系统提示中指示模型对缺失字段使用 null 且永不捏造数据,并在代码中验证返回的 JSON。要求模型逐字复制文本中的值而非进行总结,也能减少捏造答案。
如何处理单次请求过大的页面?
将清理后的内容拆分为有重叠的块,分别从每个块中提取,然后合并结果。对于列表页面,每个块返回一个数组并在稳定键(如产品 ID)上去重,因为重叠会产生少量重复。首先将 HTML 压缩为文本,通常也能将许多"过大"的页面缩减到单次调用可处理的范围内。
ChatGPT 网络抓取合法吗?
这取决于目标网站的服务条款、你所在的司法管辖区以及你对数据的用途。严格限于公开内容,遵守 robots.txt 和频率限制,绝不涉及账户、个人数据或登录后的任何内容。商业再利用时,应获得许可或正式数据协议,而非依赖抓取器。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
