在 AI 恐慌的时代,熟练度是一种累赘
近日我在一项限时一小时的小组作业里被教授控诉使用 AI 且不承认,而她的理由让我大跌眼镜。
背景
我这个学期正在上一节 CIS(计算机信息系统)课程,内容是 Python 入门,上课方法是网课。开课时的第一周,教授便给我们分配了一项作业:介绍自己并且告知我们是否有课外经验,好让她了解我们的能力以及对我们的作业有多大的期待。
我于数年前便学习过 Python,且学习了很长一段时间爬虫、网页自动化操作和 JavaScript 逆向(关于 JavaScript 逆向,你可以在 这里 找到一些我过往写过的文章。我也有当时学习这三个内容的笔记,未来可以发出来)。
于是我填写了表单,表示我学习过 Python 并且懂得如何使用许多库,其中便包括了这次的主角:BeautifulSoup。
起因
教授要求我们在一小时内完成一个小组作业,要求如下:
网页抓取书籍数据
目标网站: Books to Scrape
任务:
- 从网站的 第一页 提取书籍信息,包括:
- 书籍的 标题
- 价格
- 库存状态
- 将每本书的数据存储为一个 字典
输出示例:
plaintext
1
2 A Light in the Attic : {'Price': '£51.77', 'In Stock': 'In stock'}
Tipping the Velvet : {'Price': '£53.74', 'In Stock': 'In stock'}作业说明:
- 遵循课上讨论的步骤和方法
- 你可以使用 AI 工具来获取关于从网站抓取数据的指导或澄清,但 请勿复制完整的解决方案。任何 AI 提供的建议 都必须经过修改,以符合课上讨论的方法和步骤,以此证明你自己的理解并应用了所学知识
- 直接提交来自外部来源的解决方案将得零分
- 如果你使用了 AI,请 明确注明 你使用的 AI 工具,包含你提供的提示词,以及 AI 生成的输出内容
- 请像课上演示的那样,一步步展示你的进度。只提交完整的解答而没有展示过程或小测试将不被接受
进阶挑战:
- 从网站的 前 5 页 提取书籍信息
- 同时 提取 评分
这里有一些内容需要补充:
- 教授本身不懂 HTML,也没讲过 HTML
- 关于网页爬虫的内容,教授只讲了一堂课
这也是为什么教授允许我们使用 AI 工具:因为一些内容她根本没提到,需要 AI 辅助。
回到小组。我因为本来就懂爬虫,也懂怎么使用 BeautifulSoup(不过我更喜欢 lxml~),所以我直接告诉了小组成员我们要如何编写这套代码。不过这里出现了一个小插曲:我误以为需要进入到书本详情页才能获取到标题等内容,还白白写了一套「获取所有链接、访问链接再抓取数据」的代码。实际上直接在首页抓就好了。后面被小组成员提醒才赶忙修回来。
好了这不是重点!
这是首页源代码里一本书的内容:
1 | <article class="product_pod"> |
这里需要特别说一下首页标题:
1 | <h3><a href="catalogue/sapiens-a-brief-history-of-humankind_996/index.html" title="Sapiens: A Brief History of Humankind">Sapiens: A Brief History ...</a></h3> |
如果直接 a.text 的话,只会得到被裁剪的标题。真正的标题在其中的 title 属性里。那直接写 a["title"] 就可以了吗?当然不是了,不是所有的 a 标题都带有 title 属性,真跑起来的话不就报错了?
这里有两个方法:
1 | if soup.find("a")["title"]: |
1 | soup.find("h3").find("a")["title"] |
用第一个办法最保险,但是对于这个简单的作业,我直接写的第二个。这里虽然是我不够严谨了,不过当时实际敲代码的并不是我。单是口头指导屏幕另一边的小组成员怎么写东西、要点击什么东西就已经够累人了,我还需要告诉他们为什么我要这么做、用的方法是做什么使的…… 因为小组成员好像没有一个人知道怎么做哎!反正代码向来都是能用就可以,我觉得这个处理方式没有任何问题。
其余内容没什么好说的,我直接让小组成员开始写字典、照教授所要的方法打印出来字典…… 唯一的问题是打印出来的价格会多出一个神秘符号 Â,感觉是编码问题。我们加了一个 .replace("Â", "") 来去掉它。
这时候我们已经快到了截止时间。我们不仅需要在这个时间里写完代码,还需要填写成员评估表单交上去。这是最终代码(因为是 ipynb,所以一个代码块等同于一个单元格):
所有代码
python
1 %pip install requests python
1
2
3
4
5
6
7 import requests
url = "https://books.toscrape.com/"
response = requests.get(url)
response
response返回的内容: plaintext
1 <Response [200]> python
1 from bs4 import BeautifulSoup python
1
2
3 soup = BeautifulSoup(response.text, 'html.parser')
soup
soup返回的内容: html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--[if lt IE 7]> <html lang="en-us" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html lang="en-us" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html lang="en-us" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en-us"> <!--<![endif]-->
<head>
<title>
All products | Books to Scrape - Sandbox
</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta content="24th Jun 2016 09:29" name="created"/>
<meta content="" name="description"/>
<meta content="width=device-width" name="viewport"/>
<meta content="NOARCHIVE,NOCACHE" name="robots"/>
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href="static/oscar/favicon.ico" rel="shortcut icon"/>
<link href="static/oscar/css/styles.css" rel="stylesheet" type="text/css"/>
<link href="static/oscar/js/bootstrap-datetimepicker/bootstrap-datetimepicker.css" rel="stylesheet"/>
<link href="static/oscar/css/datetimepicker.css" rel="stylesheet" type="text/css"/>
</head>
<body class="default" id="default">
...
});
</script>
<!-- Version: N/A -->
</body>
</html>
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings... python
1
2 books = soup.find_all("article", class_="product_pod")
books
books返回的内容: plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 [<article class="product_pod">
<div class="image_container">
<a href="catalogue/a-light-in-the-attic_1000/index.html"><img alt="A Light in the Attic" class="thumbnail" src="media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"/></a>
</div>
<p class="star-rating Three">
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
<i class="icon-star"></i>
</p>
<h3><a href="catalogue/a-light-in-the-attic_1000/index.html" title="A Light in the Attic">A Light in the ...</a></h3>
<div class="product_price">
<p class="price_color">£51.77</p>
<p class="instock availability">
<i class="icon-ok"></i>
In stock
</p>
<form>
<button class="btn btn-primary btn-block" data-loading-text="Adding..." type="submit">Add to basket</button>
</form>
</div>
</article>,
...
<form>
<button class="btn btn-primary btn-block" data-loading-text="Adding..." type="submit">Add to basket</button>
</form>
</div>
</article>]
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings... python
1
2
3
4
5
6
7
8
9
10
11 books_data = {}
for book in books:
title = book.find("h3").find("a")["title"]
price = book.find("p", class_="price_color").text.replace("Â", "")
availability = book.find("p", class_="instock availability").text.strip()
books_data[title] = { "price": price, "in stock": availability } python
1
2
3 for title, info in books_data.items():
print(title, ":", info)最终
plaintext
1
2
3
4 A Light in the Attic : {'price': '£51.77', 'in stock': 'In stock'}
Tipping the Velvet : {'price': '£53.74', 'in stock': 'In stock'}
Soumission : {'price': '£50.10', 'in stock': 'In stock'}
Sharp Objects : {'price': '£47.82', 'in stock': 'In stock'}
数日后成绩下来了,我们拿了 F,而这是教授的反馈:
作业说明明确指出:「就像课堂上演示的那样,一步步展示你的过程。不展示过程或小测试的完整解答将不被接受。」你提交的作业不包含任何测试或循序渐进的过程。你只是把完整的解答拆分到了不同的单元格里。依然没有测试。此外,也未披露使用了 AI。
关于你的代码:这一行是从哪里来的?
books = soup.find_all('article', class_='product_pod')。还有,你是怎么在 for 循环下直接写出这些的:book.h3.a['title']book.find('p', class_='price_color').textbook.find('p', class_='instock availability').text.strip()像我在练习中做过多次的那样,针对这些代码的测试在哪里?
我当时看到的第一反应是:什么叫他妈的我们没有披露使用 AI?
第一次回复
这节课的 WhatsApp 群组里有人表示,我们不是唯一拿了 F,并且被指控使用了 AI 的小组。
一些拿了分的小组提到教授要求我们像她的笔记那样,记录每一步尝试。他们直接照着她的笔记捣鼓,拿了满分。但这和我有什么关系?我又不是新手,我为什么还要退一步、装自己什么也不懂?难道抄教授的思路就好了?
也有人向教授说话,说因为教授自己也不懂 HTML,因此不期待我们懂 HTML,才叫我们用 AI 来了解 HTML。这番言论一说出来便遭到了其他已经学习过网页开发的校友的嘲讽:教授自己都不懂自己分配的作业,反过来指责我们这些懂的人用了 AI。这不搞笑吗? 这是我自己想出来的代码,用我学过的东西、自己写出来的!现在你告诉我,我未披露自己使用了 AI?要知道,AI 可是有一定的可能性,被喂了我所曾经想出来的代码方案哎!
我的一位小组成员(以下简称甲)找到我,希望我可以和她一起出面、找教授说清楚。我告诉甲我们的代码绝对不是 AI 生成的,我也已经就我的想法给出了我的步骤。如果不是因为教授要求我们给出步骤,我甚至不会去打印那么多次变量、将它们分成多个单元格。有必要去装模作样测试那么多次吗?没有!
不过呢,我因为次日还有个演讲,完全忘记了发邮件这件事…… 直到演讲前的一小时,甲给我发了她和教授之间的对话:
甲:
晚上好,
我阅读了您关于第 5 次作业的反馈。关于「逐步展示过程」,就我们的理解而言,我们确实是照做了。我觉得我们可能没理解题目到底要求我们做什么。
我们并没有使用 AI,所以我不知道您为什么会觉得我们用了。
能不能请您再给我们一次机会来修正那段代码或提交类似的代码?在作业里拿个 0 分并不能激励我继续前进。
致以诚挚的问候,
甲
教授:
嗨甲,
这次作业的要求非常明确,而且我在上课一开始就强调过,我期望看到你们的 过程,而不仅仅是最终的解决方案。这次作业旨在评估你们 循序渐进的过程,而不仅仅是最终结果。我已经在反馈中解释了我对 AI 使用的顾虑。其他披露了使用 AI 的小组实际上也使用了相同的代码。让我给你一个更详细的解释。
使用
h3.a["title"]这一点,如果没有 AI 的协助,你们也是想不出来的。如果你们是独立完成的,你们很可能会从像first_book.find("a", class_="title")这样的代码开始,观察到它不起作用,然后再转向其他方法。它之所以不起作用,是因为标题的 HTML 结构与价格和库存的结构不同。然而,你们并没有关于这部分的 AI 使用披露,也没有尝试用我教过的方式来提取标题。这种用法是直接由 AI 生成的解决方案,这也违反了作业要求。我从来没有教过将标签(h3, a)当作函数 / 属性来使用。这些信息是从哪里来的?为什么在提取价格和库存时使用的是book.find('p', class_="price_color")和book.find('p', class_='instock availability'),却偏偏在提取标题时使用h3.a["title"]?如果你们参考过我的教学课件,你们就不会那样去构建代码中的字典部分。你们使用的这种风格是一种 优化过的方法:先通过
soup.find_all('article', class_="product_pod")一次性提取所有项目,然后再遍历每一项进行抓取。你们是怎么知道可以这样做的?然而,这并不是我在课上展示的内容。我演示的是如何通过一步步审查 HTML 结构来单独定位每一条信息。我并没有教过你们如何先提取所有数据,然后再在一个单一的循环中检索内部的子数据点。你们提交的方法并不能反映我所教授的过程或方法论。很遗憾,我无法接受你们修改后的重新提交。课程政策不允许重新提交作业。
祝好,
教授
我看到后立马想到(以至于我之后的演讲因为头脑混乱讲的很差):
- 这个教授的整个底层逻辑有问题
- 她不懂爬虫、不懂 AI,也不懂学习!
教授的指控和逻辑
先说教授的底层逻辑。她默认学生们什么都不会,没有教过的内容等同于学生们不会。这里有两个问题:
-
学生们要是不会,去问 AI 完全没有问题。但是她没有考虑到会的学生啊!我都会了,我还要去搜 AI、告诉你我用了 AI 吗?肯定不是啊!
在大学环境里,「教授没教」怎么可能去等同于「学生不可能知晓」呢? 我们完全可以去提前阅读文档、验证假设、应用这些知识。更何况这是编程课程,自主学习本来就应该被鼓励才对。
按照她的逻辑,独立实验是被禁止的、主观能动性是受惩罚的、学术成长也是被视为作弊的证据。要求我们的思维过程必须与她教授的路径一致,这既不合理、在学术上站不住脚,也绝对不是高等教育的运作方式。
-
既然原先就这么默认,那么起初的编程经验调查的意义何在?只是为了完成学校部门的要求吗?
这是上文我重点提及的代码:
1 | <h3><a href="catalogue/sapiens-a-brief-history-of-humankind_996/index.html" title="Sapiens: A Brief History of Humankind">Sapiens: A Brief History ...</a></h3> |
她认为我所写的 h3.a["title"] 是学生无法独立想出的内容、是直接用 AI 生成的解决方案。就和我上文所说的那样,莫名其妙,毫无逻辑。宣称学生们想不出来,根本是否认了他们思考的权利。再加上,为什么死认我们使用 AI,而不是阅读 BeautifulSoup 的文档?难道阅读文档在现在这个时代也过时了?
教授所要求的「展示步骤」也是愚蠢至极,根本无法像她所想的那样防住使用 AI 作弊的学生:我只要把教授的课件全部炼化,然后让 AI 生成相同效果的代码不就好了? 并且会大大打击那些已经学会了如何写代码的学生。已经有数个学生反映她的课对已经学习过编程的人来说是一种折磨:我就是会啊,你还想我怎样!
可怕的是,教授对我们使用 AI 的指控没有任何可靠的证据,完全出于她自己的臆测。她将这一条写在解释之前,因此我猜测这是她认为我们有罪的最大证据:其他披露了使用 AI 的小组实际上也使用了相同的代码。 额…… 不然呢?BeautifulSoup 提取标签属性值的方法本来就那几个啊。难道和 AI 一样说 1 + 1 = 2 的人,也全都是 AI?
而最让我意外的是,许多被无辜指控的学生都放弃了澄清。当时向着教授说话的不在少数,这些观点也险些让我也失去了沟通的勇气。我可以为我任何的不足负责,但我绝不为我未曾做过的事道歉。如果对方要用臆测代替证据,她所要负起的,不止是一次误判的责任,更是对学术公正和个体尊严的损害。
第二次回复
既然找出了教授所有的逻辑漏洞,我也得负起小组领导的职责、向教授再一次沟通。这是我最终的邮件:
尊敬的教授:
我已阅读了您发给我的队友甲的关于我们作业反馈的邮件。我写这封信是为了澄清关于我们要代码逻辑的误解以及对我们使用 AI 的指控,因为是我主导了我们组的编码逻辑。
关于基于特定语法(如
h3.a['title'])而指控我们使用 AI 一事,我必须澄清这段代码并非由 AI 生成。我在 HW1 的学生概况调查中曾表示过我有网络爬虫的经验。我最初尝试使用soup.find("a")["title"]来提取标题,但我很快意识到这会导致报错,因为文档结构中并非所有的<a>标签都拥有 "title" 属性。随后我将代码改进为soup.find("h3").find("a")["title"],因为所有带有 "title" 属性的<a>标签都嵌套在"h3"标签内。之后在循环中我使用了book.find("h3").find("a")["title"],因为我写了一个for循环从soup.find_all("article", class_="product_pod")中获取所有的书籍。由于当时时间紧迫,我便保留了这段代码未做改动。您还提到其他披露使用了 AI 的小组也提交了类似的代码。我认为相关性并不意味着因果关系。
a["title"]这种语法是标准的简写形式,并且可以说是浏览这种特定 HTML 结构最直接的方式。关于我们没有展示逐步推进过程的顾虑,这是对我们在时间限制下构建代码方式的误解。正如提交的 Notebook 所示,我在单独的单元格中展示了
response、soup和books列表的输出。然而,对于循环内部具体的提取逻辑,我们在组内协调上遇到了一些挑战,这拖慢了我们的进度。尽管我尽力引导团队完成,但当我们开始编写最终的提取脚本时,距离截止时间已经非常近了。在时间压力下,我不得不依靠我之前的经验,直接基于分析编写了循环。如果我们的作业能被重新评估,我将不胜感激。
非常感谢。
诚以此致,
我
十分私人的想法还是留给自己(以及你们,嘻嘻)吧…… 没必要增加更多的误会,只要回复教授的问题以及指出其他问题即可。
说来好笑,我最初拟的版本被室友吐槽「不像人在说话」。她认为我是博客写多了,现在写邮件也会在里面穿插一些大道理,并且有许多重复的内容。我认为没有办法啊,有些东西我不重复讲一下不能表达出我的急切。总之让她帮我修改了一下,也是上文这个版本。
至于后续如何,也不是特别重要了。值得感叹的是,就算是到了 AI 时代,能够熟练使用某项技能也会被古板的教育系统视为「有罪」。