牙叔教程 简单易懂
大漠老师在掘金写了一个课程: 现代 Web 布局
好评如潮
但是来喽,
课程里面有一些微不足道的错误, 趁着ChatGPT的热度,
我来道一道, 大漠老师文章里的小错误,
底部有本文的代码仓库链接
由于ChatGPT直接手文字, 所以我把这个流程图用文字描述一下
prompt提示语
我要创建一个项目目录, 项目的功能是找出课程中的错误, 课程是用多篇文章写的, 该课程在掘金平台上架了, 项目语言:Node.js, 我要用Nodejs爬取掘金平台上的这个课程"现代 Web 布局", 并找出课程里所有文章的错误, 并且保存到本地, 数据保存为json格式, 建议一个单独的文件夹放找出的错误, 每篇文章的错误放到一个单独json文件中, json文件内容是一个数组, 里面是对象,对象有以下属性: - 找出的错误 - 错误来自于那段话 - 错误的原因 - 如何改正错误 找错误的步骤 1. 通过Nodejs爬虫, 提取课程里所有的文章链接 2. 通过文章链接爬取文章的正文 3. 把正文发给ChatGPT, 让他找出错误 4. 把找到的错误保存到本地的指定收集错误的文件夹中 请给我一个项目目录 备注: 我不需要单元测试目录
ChatGPT生成的目录
好的,以下是一个可能的项目目录结构: ``` web-layout-error-finder/ ├── node_modules/ // 存放 Node.js 依赖包 ├── errors/ // 存放找到的错误 │ ├── article1.json // 第一篇文章的错误信息 │ ├── article2.json // 第二篇文章的错误信息 │ ├── ... ├── src/ // 存放源代码 │ ├── crawler.js // 爬虫模块 │ ├── errorFinder.js // 错误查找模块 │ ├── main.js // 程序入口 │ └── utils.js // 工具函数 ├── package.json // 项目配置文件 └── README.md // 项目文档 ``` 接下来,我们可以进一步解释每个目录和文件的作用: - `node_modules/`:存放 Node.js 应用程序所需的所有依赖项。通过运行 `npm install` 命令将它们安装到该目录。 - `errors/`:存放找到的错误。每篇文章对应一个 JSON 文件,里面是一个数组,数组中的每个元素都是一个对象,描述一条错误及其相关信息。 - `src/`:存放源代码。 - `crawler.js`:这个模块负责爬取掘金网站上的文章链接和文章内容。 - `errorFinder.js`:这个模块负责调用 ChatGPT API,找出文章中的错误,并将找到的错误保存到本地。 - `main.js`:这个模块是程序的入口,负责协调其他模块的工作。 - `utils.js`:这个模块包含一些小的工具函数,用于处理一些常见的操作。 - `package.json`:此文件是 Node.js 项目的配置文件。它包含项目名称、版本号、依赖项等信息。 - `README.md`:项目文档,包括说明、使用方法、注意事项等。 希望这个目录结构对您有所帮助!
由于是付费课程, 所以肯定要Cookie, 因此我的prompt是这样的
我要爬取这个网页的正文 https://juejin.cn/book/7161370789680250917?scrollMenuIndex=1 使用Nodejs, 爬取的时候需要添加cookie, 请给我爬取正文的代码
ChatGPT回答
好的,以下是使用 Node.js 爬取掘金图书正文的示例代码: javascript const axios = require('axios'); const cheerio = require('cheerio'); // 设置请求头和 cookie const headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Cookie': '填写你的 Cookie' }; // 要爬取的页面 URL const url = 'https://juejin.cn/book/7161370789680250917?scrollMenuIndex=1'; // 发送 GET 请求 axios.get(url, { headers }) .then(response => { // 使用 cheerio 加载 HTML 文档 const $ = cheerio.load(response.data); // 获取正文内容 const content = $('.main-section .section').html(); // 输出结果 console.log(content); }) .catch(error => { console.error(error); }); 上面的代码使用了 axios 库发送 HTTP 请求,并设置了请求头和 Cookie。然后,使用 cheerio 库加载 HTML 文档,并通过 jQuery 风格的选择器获取到了正文内容。 注意:上面的示例代码中,需要自行替换 Cookie 的值为自己的实际 Cookie 值。 希望这个示例对您有所帮助!
这里涉及到一点爬虫的知识, 找目录链接之类的知识, 打开开发者, 一个一看看就找出来了,
我不会提供url, 爬虫有风险, 你们懂的
这是我修改后的代码
const axios = require("axios"); const config = require("./config"); async function getDir() { const url = config.url.目录; const headers = config.headers.目录; let res = await axios.post( url, { booklet_id: "7161370789680250917", }, { headers: headers, } ); const sections = res.data.data.sections; console.log(sections[0]); return res; } getDir();
至此我们获取到了目录
{ id: 88871, section_id: '7161370789768347685', title: 'Web 布局技术演进:了解 Web 布局发展史 ', user_id: '1908407916041614', booklet_id: '7161370789680250917', status: 1, content: '', draft_content: '', draft_title: 'Web 布局技术演进:了解 Web 布局发展史 ', markdown_content: '', markdown_show: '', is_free: 1, read_time: 1786, read_count: 9234, comment_count: 38, ctime: 1667444849, mtime: 1667444849, is_update: 0, draft_read_time: 0, vid: '', reading_progress: { id: 0, booklet_id: '7161370789680250917', user_id: '2295436007442622', section_id: '7161370789768347685', reading_end: 0, reading_progress: 97, reading_position: 0, has_update: 1, last_rtime: 1669955278, ctime: 1669888750, mtime: 1669952433 } }
和网页内容比对一下, 是正确的
这是文章对应的属性
我们要以下属性
代码, 和上面一模一样, 只是字段不一样;
瞧这代码, 基本没变
async function getFirst() { const url = config.url.first; const headers = config.headers.first; let res = await axios.post( url, { section_id: "7161370789768347685", }, { headers: headers, } ); const section = res.data.data.section; console.log(section); return res; }
如果网络发生错误, 或者其他异常, 我们要重启程序;
还要再爬一遍数据, 所以我们先把文章都下载下来, 这样就不用再次下载了,
一次下载, 终身使用
这里其实不需要prompt, 但是我还是要用它, 就是这么有鸟性
prompt 请教ChatGPT如何批量下载文章
我要下载一个课程中的所有章节 提取课程的目录信息的代码是这样的 ``` async function getDir() { const url = config.url.目录; const headers = config.headers.目录; let res = await axios.post( url, { booklet_id: "7161370789680250917", }, { headers: headers, } ); const sections = res.data.data.sections; return sections; } ``` sections里面保存的是目录信息, sections是一个数组, 数组中都是对象, 数组的内容如下 ``` [ { "id": 88871, "section_id": "7161370789768347685", "title": "Web 布局技术演进:了解 Web 布局发展史 ", "user_id": "1908407916041614", "booklet_id": "7161370789680250917", "status": 1, "content": "", "draft_content": "", "draft_title": "Web 布局技术演进:了解 Web 布局发展史 ", "markdown_content": "", "markdown_show": "", "is_free": 1, "read_time": 1786, "read_count": 9234, "comment_count": 38, "ctime": 1667444849, "mtime": 1667444849, "is_update": 0, "draft_read_time": 0, "vid": "", }, ... ] ``` 我们要提取的目录中每个章节的以下属性 - section_id - title 提取到所有章节的属性以后, 我们要开始获取所有的文章数据, 获取文章数据的代码是这样的 ``` async function getSection(section_id) { const url = config.url.first; const headers = config.headers.first; let res = await axios.post( url, { section_id: section_id }, { headers: headers, } ); const section = res.data.data.section; return section; } ``` 获取到的section是一个对象, 内容一般是这样的 ``` { "title": "Flexbox 中的计算:通过扩展因子比例来扩展 Flex 项目", "markdown_show": "文章的正文..." } ``` 获取到section以后, 我们要保存数据到本地, 文件的数量由之前的, 从目录获取章节的数量决定, 每个章节的正文, 就是section的字段markdown_show的值, 我们要保存每个章节的内容,到一个指定的文件夹, 文件夹名字是sections 文件的名字是section的字段title的值, 记住要去掉title的值的所有特殊符号, 因为文件名不能含有特殊符号, 比如空格, 冒号等特殊字符; 现在请你编写程序, 下载课程所有的章节, 使用语言: Node.js
ChatGPT的回答
首先需要安装axios和fs模块,可以使用以下命令进行安装: Copy npm install axios fs 然后可以编写代码: Copy const axios = require('axios'); const fs = require('fs'); const sanitize = require("sanitize-filename"); const config = { url: { 目录: 'https://www.xxx.com/api/booklet/sections', first: 'https://www.xxx.com/api/section/first', }, headers: { 目录: { 'Content-Type': 'application/json;charset=UTF-8', 'Cookie': 'xxx' }, first: { 'Content-Type': 'application/json;charset=UTF-8', 'Cookie': 'xxx' } } }; async function getDir() { const url = config.url.目录; const headers = config.headers.目录; let res = await axios.post( url, { booklet_id: "7161370789680250917", }, { headers: headers, } ); const sections = res.data.data.sections; return sections; } async function getSection(section_id) { const url = config.url.first; const headers = config.headers.first; let res = await axios.post( url, { section_id: section_id }, { headers: headers, } ); const section = res.data.data.section; return section; } async function downloadCourse() { const sections = await getDir(); const dirPath = './sections'; if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); } for (let i = 0; i < sections.length; i++) { const section = await getSection(sections[i].section_id); const filename = sanitize(sections[i].title) + '.md'; fs.writeFileSync(dirPath + '/' + filename, section.markdown_show); console.log('Downloaded: ' + filename); } } downloadCourse(); 在代码中,首先通过调用getDir函数获取课程的目录信息,然后创建一个名为sections的文件夹用于保存所有的章节文件。 接着使用一个循环遍历每个章节,通过调用getSection函数获取章节的具体内容,将获取到的内容保存到本地的文件中。 在保存文件时,需要使用sanitize-filename模块对文件名进行处理,去除所有的特殊符号,保证文件名的合法性。 最后运行downloadCourse函数即可开始下载课程所有的章节。
我们来执行以下他给的代码, 我略加修改, 运行看效果
$ npm run main > [email protected] main > node src/main.js Downloaded: Web 布局技术演进:了解 Web 布局发展史.md Downloaded: 现代 Web 布局技术术语.md Downloaded: Flexbox 布局基础使用.md Downloaded: Flexbox 布局中的对齐方式.md Downloaded: Flexbox 布局中的 flex 属性的基础运用.md Downloaded: Flexbox 中的计算:通过扩展因子比例来扩展 Flex 项目.md Downloaded: Flexbox 中的计算:通过收缩因子比例收缩 Flex 项目.md Downloaded: Flexbox 布局中的 flex-basis:谁能决定 Flex 项目的大小?.md Downloaded: 使用 Flexbox 构建经典布局:10 种经典 Web 布局.md Downloaded: Grid 布局的基础知识.md Downloaded: 定义一个网格布局.md Downloaded: Grid 布局中的计算.md Downloaded: 可用于 Grid 布局中的函数.md Downloaded: 网格项目的放置和层叠.md Downloaded: Grid 布局中的对齐方式.md Downloaded: 网格布局中的子网格和嵌套网格.md Downloaded: 使用子网格构建 Web 布局.md Downloaded: 使用 Grid 构建经典布局:10 种经典布局.md Downloaded: 使用 Grid 构建创意性 Web 布局.md Downloaded: Flexbox or Grid:如何选择合适的布局?.md Downloaded: display:contents 改变 Flexbox 和 Grid 布局模式.md Downloaded: Web 中的向左向右:Flexbox 和 Grid 布局中的 LTR 与 RTL.md Downloaded: Web 中的向左向右:Web 布局中 LTR 切换到 RTL 常见错误.md Downloaded: 内在 Web 设计.md Downloaded: 创建不规则 Web 布局.md Downloaded: 如何构建响应式 UI?.md Downloaded: 下一代响应式 Web 设计:组件式驱动式 Web 设计.md Downloaded: 下一代响应式 Web 设计:容器查询.md
没有一点点错误, 执行以后, 完全达到了我想要的效果,
数据已经搞下来了, 接下来就开始找大漠老师的文章中的错误了
构造prompt, 让ChatGPT找文章的错误
在本地有一个文件夹sections, 里面是markdown文件, 你要遍历sections中的所有markdown文件, 找出文章中的错误; 找每篇文章的错误的步骤: 1. 从sections中读取markdown文件内容, 比如: Grid 布局中的计算.md 2. 通过https://api.openai.com/v1/chat/completions 接口, 传入markdown文件内容, 得到返回结果 3. 把返回的结果中的文本字段保存到文件夹errors中, 文件名就是你读取的那个文件的名字, 比如: Grid 布局中的计算.md 以上是找错误的大概步骤, 下面有一些注意事项 1. 由于completions 接口有次数限制, 每次的问题和答案都不能超过1024个字符, 所以你要把markdown文件内容分段, 每段最多1024个字符, 一共分成几段, 然后每段都调用一次completions 接口, 把返回的结果都保存到errors文件夹中 2. 你给completions 接口提交数据的时候, 要指示ChatGPT返回的格式, 格式要求如下: 1. 返回的必须是markdown格式的内容 2. 必须包含4个标题, 分别是: 错误所在的文件名, 错误的所在的段落, 错误的理由, 改正错误的方法 以上是我想到的, 你可以自己再想想, 有什么要补充的你可以补充, 在符合我的要求的情况下, 你可以自己决定怎么做 现在, 请你用Nodejs写代码, 完成以上要求.
ChatGPT的回答
首先需要安装axios和fs模块,可以使用以下命令进行安装: Copy npm install axios fs 然后可以编写代码: Copy const axios = require('axios'); const fs = require('fs'); const sanitize = require('sanitize-filename'); const config = { url: 'https://api.openai.com/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_API_KEY', }, }; async function findErrors() { const dirPath = './sections'; const errorsPath = './errors'; if (!fs.existsSync(errorsPath)) { fs.mkdirSync(errorsPath); } const files = fs.readdirSync(dirPath); for (let i = 0; i < files.length; i++) { const filePath = dirPath + '/' + files[i]; if (fs.statSync(filePath).isFile()) { const content = fs.readFileSync(filePath, 'utf-8'); const chunks = splitContent(content); let errors = ''; for (let j = 0; j < chunks.length; j++) { const result = await getCompletions(chunks[j]); if (result.choices.length > 0) { const text = result.choices[0].text; const errorText = generateErrorText(files[i], chunks[j], text); errors += errorText + '\n\n'; } } if (errors !== '') { const filename = sanitize(files[i]); fs.writeFileSync(errorsPath + '/' + filename, errors); console.log('Found errors in: ' + filename); } } } } function splitContent(content) { const chunks = []; const lines = content.split('\n'); let chunk = ''; for (let i = 0; i < lines.length; i++) { if (chunk.length + lines[i].length + 1 <= 1024) { chunk += lines[i] + '\n'; } else { chunks.push(chunk); chunk = lines[i] + '\n'; } } if (chunk !== '') { chunks.push(chunk); } return chunks; } async function getCompletions(prompt) { const data = { prompt: prompt, max_tokens: 1024, temperature: 0.7, n: 1, stop: '\n', format: 'markdown', }; const res = await axios.post(config.url, data, { headers: config.headers }); return res.data; } function generateErrorText(filename, chunk, text) { const errorText = '## 文件名\n\n' + filename + '\n\n' + '## 段落\n\n' + chunk + '\n\n' + '## 错误原因\n\n' + text + '\n\n' + '## 改正方法\n\n'; return errorText; } findErrors(); 在代码中,首先通过调用fs.readdirSync函数获取sections文件夹中的所有文件,然后循环遍历每个文件,读取其内容并将内容分成多个段落。 接着,调用getCompletions函数将每个段落提交给ChatGPT,获取返回结果中的文本,并使用generateErrorText函数将文件名、段落、错误原因和改正方法组合成错误文本。 将所有的错误文本组合成一个字符串,如果该字符串不为空,则将其保存到errors文件夹中,文件名与原文件名相同。 在调用getCompletions函数时,需要指定ChatGPT返回的格式为markdown格式,并且包含4个标题,分别是错误所在的文件名、错误的所在的段落、错误的理由和改正错误的方法。 注意,由于ChatGPT有次数限制,每次提交的数据不能超过1024个字符,因此需要将文章内容分成多个段落,并分别提交给ChatGPT。 最后运行findErrors函数即可开始查找所有文章中的错误,并将错误信息保存到本地的文件中。
这个代码也要略加修改才能用, 尤其要注意的一点是
max_tokens: 1024
这个限制的是chatgpt响应的tokens, 不包括你的prompt, 也就是你给chatgpt提出的问题;
但是,
总的字数是4096 tokens, 因此你这里如果过写3000, 那么你的问题就最多写1096,
再次但是,
prompt, 也就是问题, 你是可以控制的, chatgpt返回的值你控制不了,
因此, 为了尽可能的给chatgpt的回答, 提供足够的tokens, 我们的提问的字数应该尽量少一些;
代码里的错误
async function getCompletions(prompt) { const data = { prompt: prompt, max_tokens: 1024, temperature: 0.7, n: 1, stop: '\n', format: 'markdown', }; const res = await axios.post(config.url, data, { headers: config.headers }); return res.data; }
他这个api已经更新了, 但是chatgpt的库中的数据还没更新, 还是老接口, 这个接口要自己改一下
const data = { model: "gpt-3.5-turbo", messages: [{ role: "user", content: role + prompt + suffix }], temperature: 0.7, max_tokens: 3000, top_p: 1, frequency_penalty: 0, presence_penalty: 0, stream: false, }; const res = await axios.post("https://api.openai.com/v1/chat/completions", data, { headers: config.headers.chatgpt, httpsAgent: agent, }); return res.data.choices[0].message.content;
由于ChatGPT有字数限制, 因此我们把文章拆开了, 一小段一小段的纠错,
prompt用于文章纠错
const role = ` 现在给你看一篇文章, 如果文章里面有错误, 请你把它指出来, 你要按照以下要求回复内容: 1. 生成的回复内容长度禁止超过1500个字, 如果超出了, 后面的内容丢弃, 此条规则, 优先级最高; 2. 返回的必须是markdown格式的内容 3. 必须包含这些标题, 分别是: 错误所在的段落, 错误的理由, 改正错误的方法 4. 其中, 标题为"错误误所在的段落", 写段落中的10个字即可 分割线后面是你要分析的文章内容, 请你分析文章, 并且把错误指出来, 并且按照我的要求回复. ----------------------------------------------------------------------- `;
ChatGPT的回答
# 错误所在的段落 第二段 # 错误的理由 错误地描述了CSS的writing-mode、direction和HTML的dir对Flexbox布局中对齐属性的影响。 # 改正错误的方法 CSS的writing-mode、direction和HTML的dir对Flexbox布局中对齐属性并没有影响,它们只是影响Web排版的方向。因此,应该将描述修改为:在Flexbox布局中,对齐属性都会受到flex-direction属性的影响,其中justify-属性始终用于在主轴上对齐,align-属性始终用于在侧轴上对齐。
接下来就交给chatgpt, 批量查找文章的错误了,
api缓慢运行中, 大概过了很久, 很久, long, long, ago
刺客深深的体会到了, 什么叫做慢, 都运行几个小时了, 还没跑完, 早知道, 我就去找个速度快的接口, 向chargpt提问了, 现在这个特别慢,
...
又过了很久, 终于跑完了
他说大漠老师的CSS写的不规范, 我不要你觉得, 我要我觉得,
我觉得大漠老师写的CSS老棒了, 是我的100倍
## 错误所在的段落 第一段 ## 错误的理由 文中的CSS代码存在一些语法错误和不规范的写法。 ## 改正错误的方法 1. 文中的 `container-type: inline-size;` 应该改为 `contain: inline-size;`,正确的写法是 `contain`。 2. 文中在 `.card` 样式中设置 `border-radius: 0;`,然后在 `@container` 中再设置 `border-radius: 8px;`,这样不够简洁。可以将 `.card` 样式的 `border-radius` 放到 `:root` 中定义一个变量,然后在 `@container` 中使用 `clamp()` 函数改变变量的值,再使用变量设置 `border-radius`,这样更加简洁规范。 更正后的代码如下: ``` :root { --w: 760px; --max-radius: 8px; --min-radius: 0px; /* 这里的单位不能省略 */ --radius: calc(100vw - var(--w)); --responsive-radius: clamp(var(--min-radius), var(--radius), var(--max-radius)); --card-radius: 0; /* 定义卡片的圆角 */ } .card { border-radius: var(--card-radius); /* 使用定义好的变量 */ } @container (width > 700px) { :root { --card-radius: var(--max-radius); /* 使用clamp()函数改变变量的值 */ } } ``` 修改后的代码更加规范,也更加清晰易懂。
这个说大漠老师命令不规范
### 错误所在的段落 第二段 ### 错误的理由 该段代码中的命名方式不规范,容易混淆和出错。 ### 改正错误的方法 统一使用小写字母和连字符来命名,同时遵循语义化命名规则,使命名更加清晰易懂。 修改后的代码如下: ```CSS .title { grid-area: title; } .aside { grid-area: aside; } .main { grid-area: main; } .banner { grid-area: banner; } .fig-1 { grid-area: fig-1; } .fig-2 { grid-area: fig-2; } ``` 同时,在CSS中使用BEM命名规范或其他命名规范可以进一步提高代码的可读性和可维护性。
大漠老师用词不准
## 错误所在的段落 第一段 ## 错误的理由 该段落中的用语不准确,存在歧义和语病。 ## 改正错误的方法 1. 将“受众”改为“用户”更加准确和通俗易懂; 2. “受众”与“一个人”并不是完全对立的关系,可以改为“我们将更加注重用户个性化需求,提供更好的内容和体验。”; 3. “Web 真正的可移植”不太准确,可以改为“使得网站在各种设备上均可正常显示和使用”。 修改后的文章如下: 我们不再是为用户群体设计。我们对 "用户"一词的理解将发生变化,因为我们将更加注重用户个性化需求,提供更好的内容和体验。 组件驱动的响应式 Web 设计将使得网站在各种设备上均可正常显示和使用,并能适应甚至还没有发明的设备。与其在今天的技术范围内追赶和设计,我们将只为用户设计。
这种就不是错误了, 可能是提交给ChatGPT的字数太少, 上下文信息不足, 导致了ChatGPT的误判
## 错误所在的段落 第二段 ## 错误的理由 文章中错误地将 `inline` 写成了 `inline-flex`。 ## 改正错误的方法 将错误的 `inline-flex` 改为正确的 `inline` 即可。正确的句子为: 只需要在 HTML 元素上显式设置 `display` 的值为 `flex` 或 `inline` 即可。
到这里基本就完成了我最初的想法, 其中最大的问题是ChatGPT的接口太慢了, 大家如果要做这种批量的操作,
一定要先找到速度够快的ChatGPT渠道
这咋了? 这是掘金给我标记成爬虫了吧?
我只爬了大漠老师的一个小册啊,
码云: article-error-finder: 使用ChatGPT分析文章错误的流程
github: GitHub - steelan9199/article-error-finder
如果你要删除远端的文件夹或者文件, 并且保留本地的文件, 按照这个步骤做
1. 把文件myfile添加到 .gitignore 2. 如果是文件, 执行 git rm --cached myfile 3. 如果是文件夹, 执行 git rm --cached myfile -r 4. git commit -m "Remove myfile from remote repository" 5. git push