前几天有个朋友发了段代码给我,说Trae生成的爬虫脚本一直报错,让我帮忙看看。我扫了一眼发现问题根本不在模型能力上,而是他提问的那句提示词太随意了——就一句“用Python写个爬虫”,目标网页结构、字段需求、存储格式全都没说,模型只能靠猜来生成代码,猜不准才是正常的。这篇是这个系列的第五篇,前面几篇聊过Trae的基础用法和通用提示词框架,这次专门聚焦爬虫场景,把一套能稳定产出可运行爬虫代码的提示词优化方法拆开讲透。适合刚接触AI编程、想用Trae抓点公开数据练手、又总是被生成代码气到的朋友。核心就一句话:提示词不是命令,是给模型的信息清单。
1. 为什么一句“写个爬虫”得到的代码总跑不起来
1.1 模型不是在“编程”,而是在“填补信息缺口”
先说一个很多人容易误解的点:Trae这类AI编程工具里的语言模型,并不是像人一样“理解”了你的需求然后胸有成竹地写代码,它本质上是在根据你给的所有上下文,预测一段最可能被需要的文本序列。你把上下文压缩成“写个爬虫”四个字,它就只能在训练数据里找最普遍的爬虫写法来凑数——requests.get,然后print(resp.text),完事。它不是在给你写代码,它是在给你猜代码。
这个机制的必然结果是:你提供的信息越少,模型就越依赖“一般情况”。但爬虫恰恰是最不“一般”的编程场景之一,每个网站的前端结构、数据渲染方式、反爬策略都不一样。用“写个爬虫”这种需求去启动生成,等于让一个没见过现场的师傅去工地装插座,他只能按规范套路来,遇到实际墙体、线路、开关型号,照样傻眼。
1.2 爬虫代码里到底藏着哪些变量
我自己列过一份清单,随便数一下就知道为什么一句话不够用:
- 目标网址的完整URL,以及分页参数是什么格式(?page=2 还是 /page/2/)
- 数据在HTML里的具体位置,用CSS选择器还是XPath能找到
- 页面是静态渲染还是JS动态加载,数据在源码里根本找不到
- 页面编码是UTF-8还是GBK,写错了解析出来全是乱码
- 服务器是否校验UA、Referer、Cookie,不带对应请求头就返回403
- 请求频率要不要控制,会不会触发限流或者封IP
- 数据保存成什么格式,CSV、JSON还是塞进数据库
- 本地Python版本和依赖库装没装齐
这每一项都是变量,不是常量。模型不在现场,它并不知道你的目标网站是哪一个、长什么样、有什么脾气。所以提示词优化的本质,是把这些变量的决定权从模型手里夺回来,交给你和你的浏览器开发者工具。
1.3 为什么同样的提示词,在Trae里效果差这么多
我自己用Trae写了几个月的爬虫脚本,最深的感受是:Trae的对话模式会放大提示词质量的差异。因为它在Builder模式下会自动创建文件、装依赖、执行命令,一套流程跑下来,如果提示词给得不到位,它会连着出错、改错、越改越偏,最后生成的代码可能比不用AI还难维护。反而字数多一点、结构清楚一点的提示词,能让整个对话流畅得像在跟一个靠谱的同事配合。
另外有个实操细节:很多人报错之后喜欢自己总结一句“有错,帮我修一下”,这等于又把信息缺口堵上了。正确做法是把终端的报错信息原样复制粘贴进对话,再附上你的修改方向。这种“反馈型提示词”本身就是提示词工程的一部分,后面第4部分会专门演示。
2. 动手问Trae之前,先在浏览器里做两分钟侦察
2.1 怎么判断目标页是静态HTML还是接口返回
很多人在这一步就直接卡住了,但其实非常简单,跟着做一遍就会。
打开目标网址,按F12调出开发者工具,切到Elements面板,按Ctrl+F搜索页面某个可见文字(比如一条名言的原文)。如果在Elements里能搜到这个文字,说明数据在HTML源码里直接存在,用requests加BeautifulSoup就能解析;如果搜不到,说明数据是由JavaScript异步请求接口后渲染的,你直接requests这个URL是拿不到内容的。
拿不到内容怎么办?切到Network面板,刷新页面,筛选XHR或Fetch请求,找到那个返回JSON数据的接口。这个接口信息极其宝贵,后面第5部分会展开讲。现在这一步的产出只有一个:你明确了数据是静态HTML还是接口返回。这个结论直接决定了Trae该生成requests加BeautifulSoup的脚本,还是该生成直接请求接口解析JSON的脚本。
2.2 记下这三个信息再开始问
进入对话之前,我建议你在一个临时记事本里写下三样东西,哪怕只是随手记几个关键词:
第一,列表页的URL规律。比如目标站是 https://quotes.toscrape.com/,翻页之后变成 https://quotes.toscrape.com/page/2/,那你就知道分页是路径参数,不是查询参数。
第二,数据的HTML容器。在Elements里右键检查一条数据,找到包裹它的标签和class。比如名言正文在 span.text 里,作者在 small.author 里,标签那一栏可能在 div.tags 的 a.tag 里。这些就是之后让Trae写CSS选择器用的原材料。
第三,请求头的关键信息。在Network面板里点击页面文档请求,看一下Request Headers里的User-Agent。很多同学写爬虫不写UA,被服务器拒绝后一脸懵。提前记下来,写进提示词里,能省掉一轮报错。
这三条信息不用整理得多漂亮,能看懂就行。它们是给模型减少猜测空间的弹药。
2.3 别让环境问题浪费你的第一轮对话
还有一部分返工不是代码逻辑的问题,而是运行环境没准备好。我在Trae里跑Python脚本之前,习惯性先建一个虚拟环境并在终端里启动它:
python -m venv .venv .venv\Scripts\activate # Windows source .venv/bin/activate # macOS / Linux pip install requests beautifulsoup4为什么不直接装在全局环境里?因为爬虫项目经常是一两个脚本,不值得污染全局Python;而且Trae能识别当前激活的虚拟环境,你后面让它“运行脚本”时,它会自动在这个环境里执行,依赖才不会报ModuleNotFoundError。把环境信息作为提示词的一部分直接告诉Trae,比如“我的Python 3.11环境在Windows终端跑,requests和bs4已安装”,它就不会给你生成需要额外安装其他库的方案。
3. 把一句“写个爬虫”改成一条能落地的提示词
3.1 先看一个反面案例
直接下一句特别简单的提示词给Trae试试:“用Python写一个爬虫爬取quotes.toscrape.com第一页的名言。”
你会发现它确实能给出代码,而且结构还挺完整,但大概率会出现这么几个毛病:没带User-Agent,请求头就是一个裸requests.get;没有main入口的概念,代码散在顶层;CSV写入用的默认编码,中文在Excel里整个乱掉;异常处理几乎没有,服务器一旦返回403,脚本直接崩。为什么会这样?因为信息太少,模型只能调用它对“常见爬虫”的平均印象,而这个平均印象刚好就是新手教程里最偷懒的写法。
这不是模型蠢,是你把决策权让渡给它了。你得把目标网站的关键特征、输出字段、运行方式都写清楚,它才能按你的规矩办事。
3.2 一套可以复用的爬虫提示词模板
下面这个模板是我实际项目里用了很久的基础版,每次换目标网站基本只改加粗那几处:
你是一位有五年经验的Python爬虫工程师。请帮我写一个Python脚本,满足以下要求: 1. 目标网址:https://quotes.toscrape.com/ 2. 我们需要抓取第一页的所有名言数据,并保存到 quotes.csv。 3. 每条名言需要输出以下字段: - text:名言正文 - author:作者 - tags:标签列表,多个标签用 | 分隔 4. 我已经检查过页面:数据在HTML源码中直接存在,不需要处理JS渲染。 每一个名言区块中,正文出现在 span.text 里,作者在 small.author 里, 标签在 div.tags 下的 a.tag 里。 5. 运行环境:本机Python 3.11,已安装 requests 和 beautifulsoup4,项目在 Windows 终端运行。 6. 技术要求: - 使用 requests + BeautifulSoup 实现 - 请求时带上 User-Agent,避免被服务器拒绝 - 输出CSV时使用 utf-8-sig 编码,确保Excel打开不会乱码 - 必须有 main() 函数入口,并通过 if __name__ == '__main__' 调用 - 当 status_code 不是200时,打印错误并退出 7. 请先简要说明你的实现思路,然后给出完整代码,并在代码中加中文注释。这段提示词看起来啰嗦,但它把一个模糊需求变成了一份完整、可执行的需求说明书。模型生成时会严格按照这些约束来组织代码,第一版能跑通的概率大幅提升。
3.3 模板里的每个字段,都在帮你省一次返工
可能有人觉得,这里面的内容写不写差别也不大吧?我直接拿实际返工次数说话:
| 容易出问题的点 | 模板里的对应措施 | 省下的返工内容 |
|---|---|---|
| 服务器403 | 要求带上User-Agent | 不用额外处理请求头 |
| CSV中文乱码 | 指定utf-8-sig编码 | 不用二次转码 |
| 脚本无法调试 | 要求main入口和异常处理 | 不用重构代码结构 |
| 选择器抓错字段 | 主动提供标签位置信息 | 不用反复贴HTML比对 |
每一个字段背后都是一个真实的踩坑点。目标网址给完整,模型可以基于训练数据里的结构知识做预判;页面结构给清楚,模型写选择器时不会瞎猜;运行环境给明白,模型不会让你装没必要的库。写提示词这件事,本质就是把你已经从浏览器侦察里得到的信息,一字不漏地转交给模型。
4. 实操演示:一次对话让Trae跑通列表页爬虫
4.1 第一轮:把模板发给Trae
以quotes.toscrape.com为例,我把上面那段模板原样发进Trae,它在Builder模式下给出的代码大致长这样:
import requests from bs4 import BeautifulSoup import csv def fetch_page(url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } resp = requests.get(url, headers=headers, timeout=10) if resp.status_code != 200: print(f"请求失败,状态码:{resp.status_code}") return None resp.encoding = "utf-8" return resp.text def parse_quotes(html): soup = BeautifulSoup(html, "html.parser") quotes = [] for item in soup.select("div.quote"): text = item.select_one("span.text").get_text(strip=True) author = item.select_one("small.author").get_text(strip=True) tags = "|".join(tag.get_text(strip=True) for tag in item.select("a.tag")) quotes.append([text, author, tags]) return quotes def save_to_csv(quotes): with open("quotes.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["text", "author", "tags"]) writer.writerows(quotes) def main(): url = "https://quotes.toscrape.com/" html = fetch_page(url) if not html: return quotes = parse_quotes(html) save_to_csv(quotes) print(f"成功保存 {len(quotes)} 条名言") if __name__ == "__main__": main()第一版能到这个程度,我已经很满意了。注意它会自动带上UA、处理编码、给main入口,这些都是提示词里明确约束过的。现在直接在Trae的终端里跑:
python quotes_spider.py正常输出是“成功保存 25 条名言”,同时当前目录下生成quotes.csv。到这里,最核心的流程已经走通了。
4.2 第二、三轮:用“报错信息+修改要求”驱动迭代
第一次跑不会一帆风顺,这是常态。最常见的几种情况:
第一种,返回403。这时候不要只说“报错了帮我看看”,直接把完整报错贴给Trae,再加上一句“我已经带了User-Agent但还是被拒,请加一个更完整的浏览器请求头,并增加失败后的重试机制,每次请求前随机sleep 0.5到1.5秒”。
Trae给出的修复思路一般是先构造一个更接近真实浏览器的Headers,把Accept、Accept-Language、Referer等字段补齐,再加一个循环重试的逻辑。它改完的代码里会出现类似这样的片段:
import time import random def fetch_page_with_retry(url, retries=3): for attempt in range(retries): resp = requests.get(url, headers=headers, timeout=10) if resp.status_code == 200: return resp.text print(f"第 {attempt+1} 次请求失败:{resp.status_code}") time.sleep(random.uniform(0.5, 1.5)) return None加了随机延时之后,对目标服务器的压力也小得多,这个习惯建议保留。
第二种,CSV打开中文乱码。这个一般不是生成代码的锅,而是你Excel默认编码问题。直接用上面模板里的utf-8-sig写入,问题基本不会出现。如果已经生成了错误编码的文件,让Trae用pandas重新读一遍再写出来也很快。
第三种,ModuleNotFoundError。先检查自己有没有激活虚拟环境,activate之后重新跑一次。这种情况通常不是代码问题,没必要让模型背锅。
4.3 让爬虫支持翻页,一句话就能加进去
第一页跑通之后,顺理成章的需求是抓多页。继续对话,追加一句:
“请把脚本改造为支持翻页抓取前5页,每页之间随机延时0.5到2秒,并把所有数据保存在同一个CSV文件里。”
它会很自然地把URL改成循环拼接:
for page in range(1, 6): url = f"https://quotes.toscrape.com/page/{page}/" html = fetch_page_with_retry(url) # 解析并追加保存这里有一点值得留意:追加保存时要注意CSV的模式。第一次写入要带表头,后面追加不需要表头,否则每个页面的数据后面都跟着一行重复表头。Trae在处理这种细节时可能不周到,你可以主动在提示词里补充“只在文件不存在时写表头,后续追加数据不要重复写表头”。这种预期式的约束,能让迭代少很多轮。
到这一步,你已经拥有一个能抓5页、带重试、带随机延时、输出干净CSV的爬虫脚本。整个过程中,你几乎没写几行代码,但你时刻清楚自己在干什么,这就是提示词优化带来的掌控感。
5. 动态加载页面:别急着上Selenium,先让Trae请求接口
5.1 先找接口,直接请求JSON比坐等浏览器渲染高效得多
很多网站的列表数据并不是写死在HTML里的,而是页面加载时用JavaScript向后端接口要数据,然后渲染成DOM。前面第2部分的侦察方法说过,如果在Elements里搜不到页面文字,就去Network面板找XHR。
一旦找到那个接口,比如常见的 https://example.com/api/list?page=1&size=20 ,返回结构是JSON,有data数组,里面包含 title、views、created_at 这些字段,你就可以直接让Trae跳过页面解析,直接请求这个接口。
我给Trae的提示词会这样写:
我抓到一个接口:GET https://example.com/api/list?page=1&size=20 返回JSON结构如下: { "code": 0, "data": [ {"title": "标题", "views": 123, "created_at": "2025-01-01"} ] } 请用 requests 请求这个接口,解析 data 数组里的字段,保存为CSV。 需要处理翻页:每次请求 page 加1,直到返回的 data 为空。 每页请求之间随机sleep 1到2秒。这个方式的优势非常明显:接口直连返回的是结构化JSON,字段名一目了然,不需要解析HTML标签;请求量小,速度极快;代码量更少,出错概率更低。能走接口就不要走Selenium,这是我做爬虫一贯的顺序。
5.2 什么时候才需要上自动化浏览器
只有下面这几种情况,我才会考虑用Playwright这类自动化浏览器方案:接口参数带签名,需要先执行一段JS生成token;页面数据要靠下拉滚动才能逐步加载;接口做了严格的调用方校验,直接请求拿不到数据。
如果你真的需要让Trae生成Playwright脚本,提示词里要特别强调两点。第一,等待元素要用显式等待,不要固定sleep几秒,不然网络慢一点就超时,网络快一点又白等。第二,只做正常的数据读取,不要对目标站点做恶意请求。下面是一个可以用在提示词里的描述:
请使用Playwright生成脚本流程: 打开目标页面后,等待 div.list-item 这个节点出现, 然后提取每个节点的标题和链接。翻页时点击下一页按钮, 每次点击后等待列表节点重新出现。最后把所有数据写入CSV。5.3 给Trae看一段真实HTML,胜过你描述十句
我在实战中还有一个特别管用的技巧:与其费劲描述页面结构,不如直接从Elements里复制一小段真实HTML贴进对话,然后告诉Trae“请基于这段HTML结构写选择器”。
比如:
<div class="quote"> <span class="text">“The world as we have created is a process of our thinking.”</span> <small class="author">Albert Einstein</small> <div class="tags"> <a class="tag">change</a> <a class="tag">deep-thoughts</a> </div> </div>配合一句“每页有多个这样的div.quote,请解析每条名言的正文、作者和所有标签”。模型的CSS选择器准确率会直线上升,因为它不再靠记忆猜测,而是直接基于你给的样本在写。遇到结构稍微复杂的页面,这招能省掉至少两轮返工。
6. 把Trae生成的代码养成“能长期用”的习惯
6.1 五步快速验收清单
Trae生成的代码能跑,不等于可以放心用。我每次拿到代码,都会花两分钟过一遍这五个检查点,你也完全可以照抄这个习惯:
- 依赖是否清晰:最好让Trae生成一个requirements.txt,记录requests、beautifulsoup4这些核心依赖,下次换机器不用猜。
- 有没有main入口:散落在顶层的脚本以后没法复用,有main入口才方便调试。
- 异常处理是否足够:至少要有status_code判断和网络超时处理,否则一次网络抖动就全盘崩溃。
- 请求频率是否受控:抓多页时必须有随机延时,这是对目标站负责,也是对自己IP负责。
- 输出文件是否规范:路径和编码要明确,CSV用utf-8-sig,JSON用ensure_ascii=False。
这些检查点不用自己会写,你可以直接追加一句话让Trae自查:“请检查这个脚本是否具备以上5个条件,不满足的帮我补齐。”
6.2 让Trae自己给自己做Code Review
有一个我特别喜欢用的技巧,是把生成完的代码重新丢回对话框,让它以资深工程师的身份审查它自己写的代码。
提示词可以这样写:
请以资深爬虫工程师的身份审查上面这段代码,重点检查: 异常处理、请求频率、资源释放、代码可维护性。 列出你认为最值得修改的前5个问题, 对每个问题给出修改后的代码片段,并说明为什么这样改。实测下来,这种自审模式经常能挖出一些安全隐患和坏味道,比如没关闭连接池、重试逻辑不够健壮、函数过长等。你不需要全盘接受它的建议,但它给出的“我建议把请求逻辑封装成一个类”这类方向,往往值得参考。整个过程中你还是没写几行代码,但对代码的把控感完全不一样。
6.3 维护一个自己的“提示词片段库”
日积月累之后,你会发现爬虫提示词有很大一部分是重复的。我在本地维护了一个Markdown文件,专门收藏高频片段:
- 请求头模板:包含常见浏览器的完整Headers
- 错误处理模板:重试上限、随机退避、超时设置
- 存储模板:CSV追加写入、JSON按行写入、utf-8-sig编码
- 翻页模板:路径参数和查询参数两种形式的URL拼接
- 接口解析模板:应对分页JSON返回,直到data为空停止
写新爬虫的时候,复制对应模板再改URL和字段名,比每次从零敲提示词快得多。这个片段库其实就是你自己的私有提示词框架,越用越顺手,也越积越值钱。
7. 我的几点真实体会
整个流程走下来,我最想强调的一句话是:让AI看着目标写代码,而不是凭空想代码。浏览器里的DevTools是你的信息来源,报错信息是你的反馈来源,提示词只是把这两者翻译给模型听的中间层。很多朋友追求“一句话生成完美脚本”,这本身就不现实。人写爬虫都要反复调试,凭什么AI一次就能写出无懈可击的代码?放弃这个幻想,踏踏实实用迭代思维去推进,反而效率最高。还有一个小建议:遇到拿不准的页面结构,去Elements里复制真实HTML给Trae,比你自己描述十句都有用。网上的那些测试模型能力的趣味提示词,偶尔玩一下当放松挺好,但真到爬虫这种要交付结果的场景,信息密度才是提示词好坏的唯一标准。下次再打开Trae准备写爬虫前,先花两分钟做侦察,再花一分钟组织提示词,你会明显感觉到返回结果的可用度完全不同。