如何在异步函数中循环遍历 Cherrio 并填充外部变量?
How to loop through a Cherrio inside an async function and populate an outside variable?
我需要创建一个 API 网络废料 GitHub 的回购获取以下数据:
- 文件名;
- 文件扩展名;
- 文件大小(字节、千字节、兆字节等);
- 文件行数;
我将 Node 与 TypeScript 一起使用,因此,为了充分利用它,我决定创建一个名为 FileInterface 的接口,它具有上述四个属性。
当然,变量是该接口的数组:
let files: FileInterface[] = [];
以我自己的repo为例:https://github.com/raphaelalvarenga/git-hub-web-scraping
到目前为止一切顺利。
我已经指向具有 request-promise 依赖项的 HTML 的文件部分,并将它们存储在 Cheerio 变量中,这样我就可以遍历 "tr" 标签来创建一个环形。正如您所想,那些 "tr" 标签代表 "table" 标签内的每个 files/folders(如果您检查页面,很容易找到它)。该循环将填充一个名为:
的临时变量
let tempFile: FileInterface;
并且在循环的每个循环结束时,数组将被填充:
files.push(tempFile);
在GitHub repo 的初始页面中,我们可以找到文件名及其扩展名。但是行的大小和总数,我们不能。单击它们以重定向到文件页面时会发现它们。假设我们点击了 README.md:
好的,现在我们可以看到 README.md 有 2 行和 91 字节。
我的问题是,因为这需要很长时间,所以它需要是一个异步函数。但是我无法处理异步函数中 Cheerio 内容中的循环。
我尝试过的事情:
- 使用map和each方法循环遍历并压入数组文件;
- 在循环之前使用等待。我知道这个实际上行不通,因为它只是一个没有 return 任何东西的循环;
- 我最后尝试并认为可行的是 Promise。但是 TypeScript 指责 Promises return "Promise unknown" 类型并且我不允许在文件数组中填充结果,因为类型 "unknown" 和 "FilesInterface[]" 不相等。
下面我将放置到目前为止我创建的代码。如果你想下载和测试,我会上传 repo(link 在这个 post 的开头),但我需要警告这个代码在分支 "repo-request-bad-loop".它不在主人身上。不要忘记,因为 master 分支没有我提到的任何东西 =)
我正在 Insomnia 中向路由“/”发出请求并传递这个 object:
{
"action": "getRepoData",
"url": "https://github.com/raphaelalvarenga/git-hub-web-scraping"
}
index-controller.ts 文件:
如您所见,它调用了有问题的 getRowData 文件。就在这里。
getRowData.ts 文件:
我会尽力帮助你,虽然我不会打字稿。我稍微重新修改了 getRowData 函数,现在它对我有用了:
import cheerio from "cheerio";
import FileInterface from "../interfaces/file-interface";
import getFileRemainingData from "../routines/getFileRemaningData";
const getRowData = async (html: string): Promise<FileInterface[]> => {
const $ = cheerio.load(html);
const promises: any[] = $('.files .js-navigation-item').map(async (i: number, item: CheerioElement) => {
const tempFile: FileInterface = {name: "", extension: "", size: "", totalLines: ""};
const svgClasses = $(item).find(".icon > svg").attr("class");
const isFile = svgClasses?.split(" ")[1] === "octicon-file";
if (isFile) {
// Get the file name
const content: Cheerio = $(item).find("td.content a");
tempFile.name = content.text();
// Get the extension. In case the name is such as ".gitignore", the whole name will be considered
const [filename, extension] = tempFile.name.split(".");
tempFile.extension = filename === "" ? tempFile.name : extension;
// Get the total lines and the size. A new request to the file screen will be needed
const relativeLink = content.attr("href")
const FILEURL = `https://github.com${relativeLink}`;
const fileRemainingData: {totalLines: string, size: string} = await getFileRemainingData(FILEURL, tempFile);
tempFile.totalLines = fileRemainingData.totalLines;
tempFile.size = fileRemainingData.size;
} else {
// is not file
}
return tempFile;
}).get();
const files: FileInterface[] = await Promise.all(promises);
return files;
}
export default getRowData;
我需要创建一个 API 网络废料 GitHub 的回购获取以下数据:
- 文件名;
- 文件扩展名;
- 文件大小(字节、千字节、兆字节等);
- 文件行数;
我将 Node 与 TypeScript 一起使用,因此,为了充分利用它,我决定创建一个名为 FileInterface 的接口,它具有上述四个属性。
当然,变量是该接口的数组:
let files: FileInterface[] = [];
以我自己的repo为例:https://github.com/raphaelalvarenga/git-hub-web-scraping
到目前为止一切顺利。
我已经指向具有 request-promise 依赖项的 HTML 的文件部分,并将它们存储在 Cheerio 变量中,这样我就可以遍历 "tr" 标签来创建一个环形。正如您所想,那些 "tr" 标签代表 "table" 标签内的每个 files/folders(如果您检查页面,很容易找到它)。该循环将填充一个名为:
的临时变量let tempFile: FileInterface;
并且在循环的每个循环结束时,数组将被填充:
files.push(tempFile);
在GitHub repo 的初始页面中,我们可以找到文件名及其扩展名。但是行的大小和总数,我们不能。单击它们以重定向到文件页面时会发现它们。假设我们点击了 README.md:
好的,现在我们可以看到 README.md 有 2 行和 91 字节。
我的问题是,因为这需要很长时间,所以它需要是一个异步函数。但是我无法处理异步函数中 Cheerio 内容中的循环。
我尝试过的事情:
- 使用map和each方法循环遍历并压入数组文件;
- 在循环之前使用等待。我知道这个实际上行不通,因为它只是一个没有 return 任何东西的循环;
- 我最后尝试并认为可行的是 Promise。但是 TypeScript 指责 Promises return "Promise unknown" 类型并且我不允许在文件数组中填充结果,因为类型 "unknown" 和 "FilesInterface[]" 不相等。
下面我将放置到目前为止我创建的代码。如果你想下载和测试,我会上传 repo(link 在这个 post 的开头),但我需要警告这个代码在分支 "repo-request-bad-loop".它不在主人身上。不要忘记,因为 master 分支没有我提到的任何东西 =)
我正在 Insomnia 中向路由“/”发出请求并传递这个 object:
{
"action": "getRepoData",
"url": "https://github.com/raphaelalvarenga/git-hub-web-scraping"
}
index-controller.ts 文件:
如您所见,它调用了有问题的 getRowData 文件。就在这里。
getRowData.ts 文件:
我会尽力帮助你,虽然我不会打字稿。我稍微重新修改了 getRowData 函数,现在它对我有用了:
import cheerio from "cheerio";
import FileInterface from "../interfaces/file-interface";
import getFileRemainingData from "../routines/getFileRemaningData";
const getRowData = async (html: string): Promise<FileInterface[]> => {
const $ = cheerio.load(html);
const promises: any[] = $('.files .js-navigation-item').map(async (i: number, item: CheerioElement) => {
const tempFile: FileInterface = {name: "", extension: "", size: "", totalLines: ""};
const svgClasses = $(item).find(".icon > svg").attr("class");
const isFile = svgClasses?.split(" ")[1] === "octicon-file";
if (isFile) {
// Get the file name
const content: Cheerio = $(item).find("td.content a");
tempFile.name = content.text();
// Get the extension. In case the name is such as ".gitignore", the whole name will be considered
const [filename, extension] = tempFile.name.split(".");
tempFile.extension = filename === "" ? tempFile.name : extension;
// Get the total lines and the size. A new request to the file screen will be needed
const relativeLink = content.attr("href")
const FILEURL = `https://github.com${relativeLink}`;
const fileRemainingData: {totalLines: string, size: string} = await getFileRemainingData(FILEURL, tempFile);
tempFile.totalLines = fileRemainingData.totalLines;
tempFile.size = fileRemainingData.size;
} else {
// is not file
}
return tempFile;
}).get();
const files: FileInterface[] = await Promise.all(promises);
return files;
}
export default getRowData;