我的代码地址:https://gitee.com/mus-z/ts_learn
原课地址:https://coding.imooc.com/class/evaluation/412.html?page=9
项目的话小demo基本上按照目录对应来看源码
项目我基本上只有最终版本,nodemodules搞起来太慢了所以就是直接在原有改了小节的内容
文章目录
一、环境配置
下载node,去官网下载安装包安装即可
npm包管理工具是node自带的,下面是我的node版本号
然后通过npm安装ts
npm install typescript -g
通过命令tsc demo.ts
可以在同目录下编译出demo.js
为了省去编译,直接运行,使用ts-node
工具进行全局安装
然后直接ts-node demo.ts
就可以运行了
二、ts学习
1.静态类型的深度理解
对于变量的静态类型是固定的不可变的,变量受限于静态类型
可以帮助我们判断变量的类型、属性
2.基础类型和对象类型
//基础类型number string null undefined symbol boolean void等
const count: number = -123;
const n: string = "123"
//对象类型
const t: {
name: string,
age: number
} = {
name: 'name1',
age: 0
}
//对象的数组
const numbers: (number | undefined)[] = [1, 2, 3, , 4]
//对象的类
class Person {
constructor(prop: {
name: string, age: number }) {
let {
name, age } = prop
this.name = name
this.age = age
}
name: string
age: number
}
const d: Person = new Person(t)
const s: Person = {
name: 'name1',
age: 0
}
console.log(t, d, s)
//对象的函数
const getTotal:
(x: number, y: number) => number
= function (x, y) {
return x + y
}
console.log(getTotal(1, 2))
3.类型推断和类型注解
inference and annotation
namespace demo03 {
//类型注解,把count注解成数字
let count: number;
count = 123;
//类型推断,自动分析变量类型
let countInference = 123;
console.log(typeof countInference)//number
//但是即便有推断,也要习惯用注解
function getTotal(x:number,y:number)
//:number //这个推断了
{
return x+y
}
const t=getTotal(1,5)
console.log(t,typeof t)
}
上面用命名空间解决命名冲突
4.函数相关类型
namespace demo04 {
// function hello(){}
// const hello1=function(){}
// const hello2=()=>{}
function add({
f, s }: {
f: number, s: number } = {
f: 1, s: 0 })//给参数解构并注释并给初始值
: number {
//函数返回值的注解
return f + s
}
const total: number = add({
f: 1, s: 2 })*add()//接受结果的注解
console.log(total)
function say(): void {
console.log('hello')
//return null;//void不能有返回值
}
function error(): never {
//never表示永远不能跑完的函数
if (1) throw new Error("1")
else while (true) {
}
}
}
5.Interface接口
interface和type的区别:https://www.jb51.net/article/163299.htm
interface Person{
name:string;
}
//通用interface接口去表述对象,而不是type类型别名
type Person_={
readonly name:string;//只读readonly
}
interface Person{
//合并声明Person接口,创建可选的age
age?:number;
//可以加入一个模型,需要包括所有已有情况,对象也可以写name、age之外的属性
//[propName:string]:string|number|undefined;
say():void;//接口加入方法
}
const getP=(person:Person):void=>{
console.log(person.name)
}
const setP=(person:Person,name:string):void=>{
person.name=name
console.log(person)
}
const person={
name:'dell',age:10,say(){
}}
getP(person)
setP(person,'flank')
//下面是一个校验特性,需要理解
//getP({name:'sn',sex:'0',say(){}})//接口不使用模型,字面量会报错,不匹配sex属性
const p2={
name:'sn',sex:'0',age:10,say(){
}}
getP(p2)//缓存变量不会报错,只需要包含函数所需属性即可
另外可以结合类class应用接口来看
//类的继承接口
class User implements Person{
constructor(name:string,age?:number){
this.name=name
if(age)this.age=age
}
name: string
age?: number | undefined
say(): void {
throw new Error("Method not implemented.")
}
}
const u:User={
name:'uuu',
say:()=>{
},
}
console.log(new User('user',8),u)
以及其他功能
//接口继承接口
interface Teacher extends Person{
teach():string
}
const t:Teacher={
name:'tea',
say:()=>{
},
teach:()=>'t',
}
//接口定义函数
interface sayHi {
(word:string):string,
//接受string返回string
}
const say:sayHi=(x)=>{
return x+'x'}
6.类的定义与继承
ts的类和es6的类很类似,但是又不一样
namespace demo05{
class Person{
protected name:string='dell';
getName():string{
return this.name
}
getp=()=>{
return 'Person'}
}
const person=new Person()
console.log(person,person.getName(),person.getp())
//console.log(person.name)//会报错,因为给name属性加了private
//类的继承,子类Teacher继承父类Person
class Teacher extends Person{
name='tt'
getTeacherName(){
return 'teacher'
}
//重写覆盖了getp方法
getp=()=>{
return 'Teacher'}
getName(){
//想拿到父类就用super
return super.getName()+'_t'
}
}
const teacher=new Teacher()
//下面给的都是子类实例里的属性
console.log(teacher.getName(),teacher.getTeacherName(),teacher.getp())
console.log((teacher as Person).getName())
}
7.类中的访问类型和构造器
private、protected、public三种访问修饰符以及不写修饰符的形式默认为public
- public: 公有,在类里面、子类、类外部都可以访问
- protected: 保护类型,在类里面子类里面可以访问,在类外部没法访问
- private: 私有,在类里面可以访问,在子类、类外部都没法访问
namespace demo07 {
class Person {
name: string = 'dell';//默认情况下是public
//public name:string='dell'
private test = 'test'
testF() {
return this.test }
protected pro: number = 123
}
const p = new Person()
p.name = 'dell2'
console.log(p.name)
//p.test//报错
//p.test='test_X'//报错
console.log(p.testF())//可以
class Teacher extends Person {
// c
// constructor(c: number) {
// //构造器用于new实例的情况,初始化属性
// super()//派生类必须使用
// this.c = c
// }
constructor(public c: number) {
//这里加public等效于上边的声明,直接创建了c属性
//构造器用于new实例的情况,初始化属性
super()//派生类必须使用
}
say() {
console.log(super.testF())//可以输出
//console.log(super.test,super.pro)//不可以
console.log(this.pro)//不修改值的话可以访问到123
}
//test='test_t'//test是private的不能使用
protected pro = 0 //protected的可以修改和访问父类的pro,注意子类的权限
}
const t = new Teacher(10)
t.say()
//t.pro//外部不能访问
console.log(t)
}
8.静态属性,setter和getter,单例模式
namespace demo08{
class Person{
constructor(private _name:string){
}
get name(){
//某些操作解码_name
return this._name
}
set name(n:string){
//某些操作过滤n
this._name=n
}
//其实getter和setter可以用类方法代替的,不过这样方便对外暴露公共属性以及增加过滤
}
const person =new Person('dell')
console.log(person.name)
person.name='dx'
console.log(person)
//单例模式,只有一个实例
class Demo{
private static instance:Demo;//缓存实例,私有静态变量
private constructor(){
}
static getInstance(){
if(!this.instance)this.instance=new Demo()
return this.instance
}
static cal(){
console.log(this)}//类中,static方法中的this指向类本身
}
const d0=Demo.getInstance()
const d1=Demo.getInstance()
console.log(d0===d1)
Demo.cal()
}
9.抽象类、接口
namespace demo09 {
//抽象类被继承
abstract class Geom {
abstract getArea(): number;//抽象方法
say() {
//非抽象方法
console.log(this)
}
}
class Circle extends Geom {
getArea(): number {
return 3.14
}
}
class Square extends Geom {
getArea(): number {
return 4
}
}
let c = new Circle()
c.say()
console.log(c, c.getArea())
let s = new Square()
s.say()
console.log(s, s.getArea())
//接口被实现继承
interface Person{
name:string
}
interface Teacher extends Person{
teachAge:number
}
interface Student extends Person{
age: number,
}
const t :Teacher= {
name: 'dell',
teachAge:5,
}
const st :Student= {
name: 'lee',
age: 18
}
const getUserInfo = (user: Person) => {
console.log(user.name)
}
getUserInfo(t)
getUserInfo(st)
}
https://blog.csdn.net/weixin_44036436/article/details/103816884
三、爬虫功能
1.初始化
npm init -y
tsc -init
npm install typescript -D
npm install ts-node -D
加入scripts命令之后
安装工具superagent
npm install superagent --save
但是这时候发现,会报错,ts并不能直接引入js的工具,需要@types/superagent
npm install @types/superagent --save
2.获取html
打算拿到五个文章的标题以及图片的名称
//ts -> .d.ts ->js
//superagent是js的,ts不会分析
//需要@types
import superAgent from 'superagent'
class Crowller{
private url='http://www.dell-lee.com/'
private rawHtml=''
async getRawHtml(){
const res=await superAgent.get(this.url)
this.rawHtml=res.text
//res.text是正文内容
}
constructor(){
//console.log('crowller!')
this.getRawHtml()
}
}
const crowller=new Crowller()
3.提取信息
使用cheerio库
npm install cheerio -D
npm install @types/cheerio -D
类似用jquery的方式使用cheerio库,本人jq基本上没咋学就是看输出结果和视频做的
//ts -> .d.ts ->js
//superagent是js的,ts不会分析
//需要@types
import superAgent from 'superagent'
import cheerio from 'cheerio'
interface course{
title:string,
pannel:string|undefined,
index:number,
}
class Crowller{
private url='http://www.dell-lee.com/'
private rawHtml=''
private $:cheerio.Root|null=null;
getJsonInof(html:string){
const courseInfos:course[]=[]
this.$=cheerio.load(html)//解析成类似jq的方式
const {
$}=this
const items=$('.course-item')
//console.log(items)
items.map((index,ele)=>{
const descs=$(ele).find('.course-desc')
const title=descs.eq(0).text()
const src=$(ele).find('.course-img').attr('src')
const pannel=src?.split(/[\/\.]/)[2]
// console.log(pannel)
// console.log(title)
courseInfos.push({
title,pannel,index
})
})
return courseInfos
}
async getRawHtml(){
const res=await superAgent.get(this.url)
this.rawHtml=res.text
//res.text是正文内容
//console.log(this.rawHtml)
const jsonInfo=this.getJsonInof(this.rawHtml)
const result={
time:new Date().valueOf(),
data:jsonInfo
}
return result
}
constructor(){
//console.log('crowller!')
this.getRawHtml().then(res=>{
console.log(res)
})
}
}
const crowller=new Crowller()
4.整理代码和存储过程
降低耦合性,用一个主函数来运行爬取html、整理对象、储存数据的过程
//ts -> .d.ts ->js
//superagent是js的,ts不会分析
//需要@types
import superAgent from 'superagent'
import cheerio from 'cheerio'
import fs from 'fs'
import path from 'path'
interface course {
//课的数据结构
title: string,
pannel: string | undefined,
index: number,
}
interface CourseResult {
//整理的数据结构
time: number,
data: course[],
}
interface Content {
//储存的数据结构
[propName: number]: course[],
}
class Crowller {
private url = 'http://www.dell-lee.com/'
private $: cheerio.Root | null = null;
async getRawHtml() {
//拿到html
const res = await superAgent.get(this.url)
return res.text
//res.text是正文内容
}
getJsonInof(html: string) {
//把html字符串解析出变成所需对象
const courseInfos: course[] = []
this.$ = cheerio.load(html)//解析成类似jq的方式
const {
$ } = this
const items = $('.course-item')
//console.log(items)
items.map((index, ele) => {
const descs = $(ele).find('.course-desc')
const title = descs.eq(0).text()
const src = $(ele).find('.course-img').attr('src')
const pannel = src?.split(/[\/\.]/)[2]
// console.log(pannel)
// console.log(title)
courseInfos.push({
title, pannel, index
})
})
return courseInfos
}
saveContent(result: CourseResult) {
//储存文件的操作
//存到根目录的data下面
const filePath = path.resolve(__dirname, '../data/course.json')
let fileContent: Content = {
}
if (fs.existsSync(filePath)) {
fileContent = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
}
fileContent[result.time] = result.data
fs.writeFileSync(filePath, JSON.stringify(fileContent))
}
async initSpiderProcess() {
//主过程,降低耦合
const html = await this.getRawHtml()//拿到html
const jsonInfo = this.getJsonInof(html)//提取html数据
const result = {
//封装对象
time: new Date().valueOf(),
data: jsonInfo
}
this.saveContent(result)//储存对象到data/course中
return result
}
run() {
//启动爬虫
//console.log('crowller!')
this.initSpiderProcess().then(res => {
console.log(res)
})
}
}
const crowller = new Crowller()//实例化爬虫
crowller.run()
5.使用组合设计模式优化代码
Crowller只负责主进程和初始化
新建一个Analyzer作为解析、定义接口、处理存储的类
//crowlle.ts
import superAgent from 'superagent'
import path from 'path'
import MyAnalyzer from './myanalyzer'
import HtmlAnalyzer from './htmlanalyzer'
export interface AnalyzerInterface {
//通用的analyzer接口
analyze:(html:string,filePath:string)=>string;
}
class Crowller {
async getRawHtml() {
//拿到html
const res = await superAgent.get(this.url)
return res.text
}
async initSpiderProcess(analyzer:AnalyzerInterface) {
//主过程,降低耦合
const html = await this.getRawHtml()//拿到html
const result=analyzer.analyze(html,this.filePath)
return result
}
constructor(private url:string,private filePath:string){
}
run(analyzer:AnalyzerInterface) {
//启动爬虫
this.initSpiderProcess(analyzer).then(res=>console.log(res))
}
}
const url='http://www.dell-lee.com/'
const pathName=path.resolve(__dirname, '../data/course.json')
const crowller = new Crowller(url,pathName)//实例化爬虫
const analyzer=new MyAnalyzer()//分析器
crowller.run(myanalyzer)
crowller.run(new HtmlAnalyzer())
下面是整理好的分析器MyAnalyzer
import cheerio from 'cheerio'
import fs from 'fs'
import {
AnalyzerInterface} from './crowlle'
interface course {
//课的数据结构
title: string,
pannel: string | undefined,
index: number,
}
interface CourseResult {
//整理的数据结构
time: number,
data: course[],
}
interface Content {
//储存的数据结构
[propName: number]: course[],
}
export default class MyAnalyzer implements AnalyzerInterface{
private getJsonInof(html: string) {
//把html字符串解析出变成所需对象
const courseInfos: course[] = []
const $ = cheerio.load(html)//解析成类似jq的方式
const items = $('.course-item')
//console.log(items)
items.map((index, ele) => {
const descs = $(ele).find('.course-desc')
const title = descs.eq(0).text()
const src = $(ele).find('.course-img').attr('src')
const pannel = src?.split(/[\/\.]/)[2]
// console.log(pannel)
// console.log(title)
courseInfos.push({
title, pannel, index
})
})
return courseInfos
}
saveContent(result: CourseResult,filePath:string) {
//储存文件的操作
//存到根目录的data下面
let fileContent: Content = {
}
if (fs.existsSync(filePath)) {
fileContent = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
}
fileContent[result.time] = result.data
fs.writeFileSync(filePath, JSON.stringify(fileContent))
return JSON.stringify(fileContent)
}
analyze(html:string,filePath:string){
const jsonInfo = this.getJsonInof(html)//提取html数据
const result = {
//封装对象
time: new Date().valueOf(),
data: jsonInfo
}
return this.saveContent(result,filePath)
}
}
如果我打算更换页面和整理方式,只需要再写一个有analyze方法的Analyzer就可以了
比如这样一个模板,是符合要求的(HtmlAnalyzer)
import {
AnalyzerInterface} from './crowlle'
export default class HtmlAnalyzer implements AnalyzerInterface{
analyze(html:string){
return html
}
}
原视频是把储存作为主操作,但是身为一个爬虫不一定直接就会储存,这样的功能可以包含在Analyzer中,而且可以保证Crowller的单纯性,只有初始化拉取某网站的html以及运行分析器Analyzer的程序,只把储存作为可选功能放到分析器中当然也可以像视频中的老师一样,把返回的result用fs.write写出去,但是我考虑到的是本身我的Analyzer中就有文件读写的复杂逻辑,再那边一起处理清楚,result做成状态码的形式也挺好
6.单例模式改写
主要是Analyzer的变动
import {
AnalyzerInterface} from './crowlle'
export default class HtmlAnalyzer implements AnalyzerInterface{
/
private constructor(){
}
private static instance:HtmlAnalyzer;
public static getInstance(){
if(!this.instance){
this.instance=new HtmlAnalyzer