牙叔教程 简单易懂
大漠老师在掘金写了一个课程: 现代 Web 布局
好评如潮
但是来喽,
课程里面有一些微不足道的错误, 趁着ChatGPT的热度,
我来道一道, 大漠老师文章里的小错误,
底部有本文的代码仓库链接
流程图草稿
由于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
}
}
和网页内容比对一下, 是正确的
爬取第一篇文章
这是文章对应的属性
我们要以下属性
- title 用来保存错误的文件名
- markdown_show 文章正文
代码, 和上面一模一样, 只是字段不一样;
瞧这代码, 基本没变
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函数即可开始下载课程所有的章节。
- 我都没有给ChatGPT说我的config字段的配置是什么, 他自己就推测出来了,Oh my God
- sanitize-filename, 这又是啥? 我都没见过, 就问你服不服气? 我是服了
我们来执行以下他给的代码, 我略加修改, 运行看效果
$ npm run main
> my-project@1.0.0 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 + 'nn';
}
}
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 = '## 文件名nn' + filename + 'nn' +
'## 段落nn' + chunk + 'nn' +
'## 错误原因nn' + text + 'nn' +
'## 改正方法nn';
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渠道
这咋了? 这是掘金给我标记成爬虫了吧?
我只爬了大漠老师的一个小册啊, 🙀
本文代码仓库
github: github.com/steelan9199…
Git小技巧
如果你要删除远端的文件夹或者文件, 并且保留本地的文件, 按照这个步骤做
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