Express && 爬虫

Express基础项目结构搭建

改良

使用Express开启一个服务器,让浏览器能够访问服务器上的接口

  "scripts": {
    "dev:build": "tsc -w",
    "dev:start": "nodemon node ./build/index.js",
    "dev": "concurrently npm:dev:*"
  },

第一次运行npm run dev时,由于build和start是并行运行的,当index.js还未生成,会报错找不到index.js

解决:

  "scripts": {
    "dev:build": "tsc -w",
    "dev:start": "nodemon node ./build/index.js",
    "dev": "tsc && concurrently npm:dev:*"
  },

在node执行js的时候先tsc编译一次TS代码

Express搭建服务器

npm install express --save
npm install @types/express --D
|-- build
|-- data
|-- node_modules
|-- src
|   |-- crowller.ts
|   |-- dellAnalyzer.ts
|   |-- index.ts
|   |-- router.ts
|-- package-lock.json
|-- package.json
|-- tsconfig.json
// index.js
import express, { Request, Response } from 'express';
import router from './router';

const app = express();
app.use(router);

app.listen(7001, () => {
  console.log('server is running');
});
// router.ts
import { Router, Request, Response } from 'express';
import Crowller from './crowller';
import DellAnalyzer from './dellAnalyzer';

const router = Router();

router.get('/', (req: Request, res: Response) => {
  res.send('hello world !');
});

router.get('/getData', (req: Request, res: Response) => {
  const secret = 'secretKey';
  const url = `http://www.dell-lee.com/typescript/demo.html?secret=${secret}`;
  const analyzer = DellAnalyzer.getInstance();
  new Crowller(url, analyzer);
  res.send('getData Success!');
});

export default router;

TS编写express代码

现在每次访问getData接口,就会生成新数据,为了防止过多的访问刷新,增加权限

router.post('/getData', (req: Request, res: Response) => {
  if (req.body.password === '123') {
    const secret = 'secretKey';
    const url = `http://www.dell-lee.com/typescript/demo.html?secret=${secret}`;
    const analyzer = DellAnalyzer.getInstance();
    new Crowller(url, analyzer);
    res.send('getData Success!');
  } else {
    res.send('password Error!');
  }
});

req.body返回值是undefined,然而编辑器中虽然引入了.d.ts文件,却没有报错。需要有body-parse这个express中间件npm install body-parse --save,中间件使用app.use(),必须在app.use(router之前)

// index.ts
import express, { Request, Response } from 'express';
import bodyParse from 'body-parser';
import router from './router';

const app = express();
app.use(bodyParse.urlencoded({ extended: false }));
app.use(router);

app.listen(7001, () => {
  console.log('server is running');
});
  • express库的类型定义文件 .d.ts 文件描述不准确(老框架)
  • 当使用中间件的时候,对req res进行了修改,实际上类型并不能改变(例如当对req增加了新属性,但是使用的时候类型没有更新,会报错)

Express类型定义文件

  • .d.ts 文件类型定义不准确
const { password } = req.body           // const password: any

// 修改对应.d.ts
ReqBody = { password: string | undefined }        // 随着node module的重新安装就失效了

解决:

interface RequestWithBody extends Request {
  body: {
  	// password: string | undefined;
    [key: string]: string | undefined;
  };
}
  • 中间件对req res进行了修改,但是类型不能修改(类型融合)
// index.ts
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req: Request, res: Response, next: NextFunction) => {
  req.teacherName = 'dell';
  next();
});
app.use(router);


// custom.d.ts
declare namespace Express {
  interface Request {
    teacherName: string;
  }
}
    
// touter.ts 编辑器提醒
res.send(`${req.teacherName}`);

登录功能的开发

npm install cookie-session --save
npm install @types/cookie-session -D
// router.ts   用户进行登录并且 爬取/展示 数据
import { Router, Request, Response } from 'express';
import Crowller from './crowller';
import DellAnalyzer from './dellAnalyzer';
import fs from 'fs';
import path from 'path';
import { json } from 'body-parser';

interface RequestWithBody extends Request {
  body: {
    [key: string]: string | undefined;
  };
}

const router = Router();

router.get('/', (req: Request, res: Response) => {
  const isLogin = req.session ? req.session.login : false;
  if (isLogin) {
    res.send(`
    <html>
      <body>
        <a href="/getData">爬取内容</a>
        <a href="/showData">展示内容</a>
        <a href="/logout">退出</a>
      </body>
    </html>
  `);
  } else {
    res.send(`
    <html>
      <body>
        <form method="post" action="/login">
          <input type="password" name="password" />
          <button>登录</button>
        </form>
      </body>
    </html>
  `);
  }
});

router.post('/login', (req: RequestWithBody, res: Response) => {
  const { password } = req.body;
  const isLogin = req.session ? req.session.login : false;
  if (isLogin) {
    res.send('已经登录过');
  } else {
    if (password === '123' && req.session) {
      req.session.login = true;
      res.send('登录成功');
    } else {
      res.send('登录失败');
    }
  }
});

router.get('/logout', (req: RequestWithBody, res: Response) => {
  if (req.session) {
    req.session.login = false;
  }
  res.redirect('/');
});

router.get('/getData', (req: RequestWithBody, res: Response) => {
  const isLogin = req.session ? req.session.login : false;
  if (isLogin) {
    const secret = 'secretKey';
    const url = `http://www.dell-lee.com/typescript/demo.html?secret=${secret}`;
    const analyzer = DellAnalyzer.getInstance();
    new Crowller(url, analyzer);
    res.send('getData Success!');
  } else {
    res.send('请登录后爬取内容');
  }
});

router.get('/showData', (req: RequestWithBody, res: Response) => {
  const isLogin = req.session ? req.session.login : false;
  if (isLogin) {
    try {
      const position = path.resolve(__dirname, '../data/course.json');
      const result = fs.readFileSync(position, 'utf8');
      res.json(JSON.parse(result));
    } catch (e) {
      res.send('尚未爬取到内容');
    }
  } else {
    res.send('请登录后查看内容');
  }
});

export default router;
// index.ts
import express, { Request, Response, NextFunction } from 'express';
import bodyParser from 'body-parser';
import cookieSession from 'cookie-session';
import router from './router';

// 问题1: express 库的类型定义文件 .d.ts 文件类型描述不准确
// 问题2: 当我使用中间件的时候,对 req 或者 res 做了修改之后呢,实际上类型并不能改变。

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(
  cookieSession({
    name: 'session',
    keys: ['caffrey go'],
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  })
);
app.use(router);

app.listen(7001, () => {
  console.log('server is running');
});
  • 优化

编写和使用自己的业务中间件

// router.ts
const checkLogin = (req: Request, res: Response, next: NextFunction) => {
  const isLogin = req.session ? req.session.login : false;
  if (isLogin) {
    next();
  } else {
    res.send('请先登录');
  }
};

//  router.get('/getData', checkLogin, (req: BodyRequest, res: Response) => {...}

接口统一

// util.ts
interface Result {
  success: boolean;
  errMsg?: string;
  data: any;
}

export const getResponseData = (data: any, errMsg?: string): Result => {
  if (errMsg) {
    return {
      success: false,
      errMsg,
      data
    };
  }
  return {
    success: true,
    data
  };
};

// res.json(getResponseData(false, '数据不存在'));
// res.json(getResponseData(true));
// res.json(getResponseData(JSON.parse(result)));
上次更新:
贡献者: Ahon-pan