创建TS项目
npm init -y // 初始化npm 项目tsc --init // 在目录下生成 tsconfig.json 文件 在这里我们可以对ts的默认配置做修改npm install ts-node -Dnpm install typescript -D
这里需要注意一下,我们如果在全局安装了 ts-node或者 ts,建议卸载掉,在项目中安装,以避免一些问题。
这是部分的package.json中的内容,
{"scripts": {"dev": "ts-node ./src/crawler.ts"},"devDependencies": {"ts-node": "^10.4.0","typescript": "^4.5.2"}}
声明文件 .d.ts
我们可以通过 npm install superagent -s 来安装superagent,这是一个是一个轻量级、灵活的、易读的、低学习曲线的客户端请求代理模块,使用在NodeJS环境中。
我们在代码中引入 import superagent from 'superagent'; 这时候会有报错提示:
并且我们在ts文件中调用 superagent.xxx 的时候没有任何的语法提示。这是因为superagent 是用js语法开发的,并不是ts语法。
在ts文件中,直接引入js文件的时候,ts并不知道我们所引入的类库中的方法是什么,方法中的参数是什么。
所以 ts 直接引入 js会飘红,所以,我们可以写一个 .d.ts 为后缀的类型定义文件。
ts -> .d.ts 翻译文件 -> js
所以,直接在ts中引入js,ts不知道怎么使用,我们可以提供这个翻译文件,我们只要把翻译文件安装上了,则ts会先去读取翻译文件,翻译文件会帮助ts去理解js中的内容,这样就可以用这个js文件了。
我们要先安装翻译文件:npm install @types/superagent -D, 我们安装了这个插件后其实已经没有报错了。
实际上,我们的JS文件中是没有这些类型声明的。翻译文件,相当于把js文件中的类型声明补全了。这2个结合起来,就可以让TS去识别js文件了。
翻译文件,一般都在 @types 这样的命名空间中。
爬虫内容准备
爬虫需要对页面上的数据做一些提取和分析,
我们可以通过安装 cheerio 这样的库,通过类似jQuery这样的语法,来提取页面上数据。
npm install cheerio -s
如果飘红提示没有翻译文件,我们可以:npm install @types/cheerio -D
1- 在这里我希望可以把我拿到的数据存储到data目录中去,所以我们在根目录中创建data目录
2- 我们需要引入node中的 fs 和 path 模块
import fs from 'fs';import path from 'path';import superagent from 'superagent';import cheerio from 'cheerio';interface Book {img: string | undefined;title: string;rating: number;}interface BookResult {time: number;data: Book[];}interface Content {[propName: number]: Book[];}class Crawler {private url = 'https://book.douban.com/top250';private filePath = path.resolve(__dirname, '../data/crawler.json');getJSONInfo(html: string) {const $ = cheerio.load(html);const bookItems = $('.indent table>tbody');const bookInfos: Book[] = [];bookItems.map((index, element) => {const book = $(element).find('td');const img = book.eq(0).find('img').attr('src');const title = book.eq(1).children('p.pl').text();const rating = parseFloat(book.eq(1).find('.rating_nums').text());bookInfos.push({ img, title, rating });});return {time: new Date().getTime(),data: bookInfos,};}async getRawHtml() {const result = await superagent.get(this.url);return result.text;}generateJsonContent(bookResult: BookResult) {let fileContent: Content = {};if (fs.existsSync(this.filePath)) {fileContent = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));}fileContent[bookResult.time] = bookResult.data;return fileContent;}writeFile(content: string) {fs.writeFileSync(this.filePath, content);}async initSpiderProcess() {const html = await this.getRawHtml();const result = this.getJSONInfo(html);const fileContent = this.generateJsonContent(result);this.writeFile(JSON.stringify(fileContent));}constructor() {this.initSpiderProcess();}}const crawler = new Crawler();
因为代码的定制性太强了,导致我们只能针对指定的页面做爬取分析,如果换了网页就无法爬取了,我们可以对代码进行一些改造。
把现在的代码拆分成2个部分,一部分是爬虫工具,一部分是爬取策略。
代码拆解:
1- 爬虫工具:
// crawler.tsimport fs from 'fs';import path from 'path';import superagent from 'superagent';import DoubanAnalyzer from './doubanAnalyzer';export interface Analyzer {analyzer: (html: string, filePath: string) => string;}class Crawler {private filePath = path.resolve(__dirname, '../data/crawler.json');async getRawHtml() {const result = await superagent.get(this.url);return result.text;}writeFile(content: string) {fs.writeFileSync(this.filePath, content);}async initSpiderProcess() {const html = await this.getRawHtml();const fileContent = this.analyzer.analyzer(html, this.filePath);this.writeFile(fileContent);}constructor(private url: string, private analyzer: Analyzer) {this.initSpiderProcess();}}const url = 'https://book.douban.com/top250';const analyzer = new DoubanAnalyzer();new Crawler(url, analyzer);/*爬虫需要对页面上的数据做一些提取和分析,我们可以通过安装 cheerio 这样的库,通过类似jQuery这样的语法,来提取页面上数据。npm install cheerio -s如果飘红提示没有翻译文件,我们可以:npm install @types/cheerio -D1- 在这里我希望可以把我拿到的数据存储到data目录中去,所以我们在根目录中创建data目录2- 我们需要引入node中的fs 和 path 模块因为代码的定制性太强了,导致我们只能针对指定的页面做爬取分析,如果换了网页就无法爬取了,我们可以对代码进行一些改造。把现在的代码拆分成2个部分,一部分是爬虫工具,一部分是爬取策略。*/
2- 爬取策略:
// doubanAnalyzer.tsimport fs from 'fs';import cheerio from 'cheerio';import { Analyzer } from './crawler';interface Book {img: string | undefined;title: string;rating: number;}interface BookResult {time: number;data: Book[];}interface Content {[propName: number]: Book[];}export default class DoubanAnalyzer implements Analyzer {private getJSONInfo(html: string) {const $ = cheerio.load(html);const bookItems = $('.indent table>tbody');const bookInfos: Book[] = [];bookItems.map((index, element) => {const book = $(element).find('td');const img = book.eq(0).find('img').attr('src');const title = book.eq(1).children('p.pl').text();const rating = parseFloat(book.eq(1).find('.rating_nums').text());bookInfos.push({ img, title, rating });});return {time: new Date().getTime(),data: bookInfos,};}generateJsonContent(bookResult: BookResult, filePath: string) {let fileContent: Content = {};if (fs.existsSync(filePath)) {fileContent = JSON.parse(fs.readFileSync(filePath, 'utf-8'));}fileContent[bookResult.time] = bookResult.data;return fileContent;}public analyzer(html: string, filePath: string) {const result = this.getJSONInfo(html);const fileContent = this.generateJsonContent(result, filePath);return JSON.stringify(fileContent);}}
如果我们想要爬取另外的网页内容,我们只需要去新建一个分析类就行了,而不需要去创建 爬虫工具:
// bookAnalyzer.tsimport { Analyzer } from './crawler';export default class BookAnalyzer implements Analyzer {public analyzer(html: string, filePath: string) {return html;}}
这种 爬取策略和爬虫工具组合使用的模式,在设计模式中被叫做:组合模式。
我们如果希望把代码给别人用就需要将代码编译成JS文件:
"scripts": {"dev": "ts-node ./src/crawler.ts","build": "tsc"},
这里我们运行 npm run build ts会主动去找到对应的ts文件编译成为js。
这样的做法有个不好的地方就是编译的文件会和ts文件在同一个目录下,我们可以为编译的文件制定一个目录。
当我们运行 build 命令的时候,tsc命令会结合tsconfig.json 文件对代码的编译过程进行控制。
我们可以再改文件中进行一些修改:
"outDir": "./build",
这样就可以把编译好的js文件放在build目录中了。
我们也可以通过修改 package.json 中的配置来监控ts文件的修改,事实构建:
"scripts": {"build": "tsc -w"},
可以听过nodemon 来监测项目文件的变化:
npm install nodemon -D
然后我们在package.json里面,
"scripts": {"start": "nodemon node ./build/crawler.js"},
