基于TypeScript从0到1搭建一款爬虫工具

简介: 基于TypeScript从0到1搭建一款爬虫工具

前言


今天,我们将使用TS这门语言搭建一款爬虫工具。目标网址是什么呢?我们去上网一搜,经过几番排查之后,我们选定了这一个网站。


https://www.hanju.run/


一个视频网站,我们的目的主要是爬取这个网站上视频的播放链接。下面,我们就开始进行第一步。


第一步


俗话说,万事开头难。不过对于这个项目而言,恰恰相反。你需要做以下几个事情:


  1. 我们需要创建一个项目文件夹
  2. 键入命令,初始化项目


npm init -y


  1. 局部安装typescript


npm install typescript -D


  1. 接着键入命令,生成ts配置文件


tsc --init


  1. 局部安装ts-node,用于命令行输出命令


npm install -D ts-node


  1. 在项目文件夹中创建一个src文件夹


然后我们在src文件夹中创建一个crawler.ts文件。


  1. 在package.json文件中修改快捷启动命令


"scripts": {
    "dev-t": "ts-node ./src/crawler.ts"
  }


第二步


接下来,我们将进行实战操作,也就是上文中crawler.ts文件是我们的主战场。

我们首先需要引用的这几个依赖,分别是


import superagent from "superagent";
import cheerio from "cheerio";
import fs from "fs";
import path from "path";


所以,我们会这样安装依赖:


superagent作用是获取远程网址html的内容。


npm install superagent


cheerio作用是可以通过jQ语法获取页面节点的内容。


npm install cheerio


剩余两个依赖fspath。它们是node内置依赖,直接引入即可。


我们完成了安装依赖,但是会发现你安装的依赖上会有红色报错。原因是这样的,

superagentcheerio内部都是用JS写的,并不是TS写的,而我们现在的环境是TS。所以我们需要翻译一下,我们将这种翻译文件又称类型定义文件(以.d.ts为后缀)。我们可以使用以下命令安装类型定义文件。


npm install -D @types/superagent


npm install -D @types/cheerio


接下来,我们就认认真真看源码了。


  1. 安装完两个依赖后,我们需要创建一个Crawler类,并且将其实例化。

import superagent from "superagent";
import cheerio from "cheerio";
import fs from "fs";
import path from "path";
class Crawler {
  constructor() {
  }
}
const crawler = new Crawler();


  1. 我们确定下要爬取的网址,然后赋给一个私有变量。最后我们会封装一个getRawHtml方法来获取对应网址的内容。
    getRawHtml方法中我们使用了async/await关键字,主要用于异步获取页面内容,然后返回值。


import superagent from "superagent";
import cheerio from "cheerio";
import fs from "fs";
import path from "path";
class Crawler {
  private url = "https://www.hanju.run/play/39221-4-0.html";
  async getRawHtml() {
    const result = await superagent.get(this.url);
    return result.text;
  }
  async initSpiderProcess() {
    const html = await this.getRawHtml();
  }
  constructor() {
    this.initSpiderProcess();
  }
}
const crawler = new Crawler();


  1. 使用cheerio依赖内置的方法获取对应的节点内容。


我们通过getRawHtml方法异步获取网页的内容,然后我们传给getJsonInfo这个方法,注意是string类型。我们这里通过cheerio.load(html)这条语句处理,就可以通过jQ语法来获取对应的节点内容。我们获取到了网页中视频的标题以及链接,通过键值对的方式添加到一个对象中。注:我们在这里定义了一个接口,定义键值对的类型。


import superagent from "superagent";
import cheerio from "cheerio";
import fs from "fs";
import path from "path";
interface Info {
  name: string;
  url: string;
}
class Crawler {
  private url = "https://www.hanju.run/play/39221-4-0.html";
  getJsonInfo(html: string) {
    const $ = cheerio.load(html);
    const info: Info[] = [];
    const scpt: string = String($(".play>script:nth-child(1)").html());
    const url = unescape(
      scpt.split(";")[3].split("(")[1].split(")")[0].replace(/\"/g, "")
    );
    const name: string = String($("title").html());
    info.push({
      name,
      url,
    });
    const result = {
      time: new Date().getTime(),
      data: info,
    };
    return result;
  }
  async getRawHtml() {
    const result = await superagent.get(this.url);
    return result.text;
  }
  async initSpiderProcess() {
    const html = await this.getRawHtml();
    const info = this.getJsonInfo(html);
  }
  constructor() {
    this.initSpiderProcess();
  }
}
const crawler = new Crawler();
  1. 我们首先要在项目根目录下创建一个data文件夹。然后我们将获取的内容我们存入文件夹内的url.json文件(文件自动生成)中。


我们将其封装成getJsonContent方法,在这里我们使用了path.resolve来获取文件的路径。fs.readFileSync来读取文件内容,fs.writeFileSync来将内容写入文件。注:我们分别定义了两个接口objJsonInfoResult


import superagent from "superagent";
import cheerio from "cheerio";
import fs from "fs";
import path from "path";
interface objJson {
  [propName: number]: Info[];
}
interface Info {
  name: string;
  url: string;
}
interface InfoResult {
  time: number;
  data: Info[];
}
class Crawler {
  private url = "https://www.hanju.run/play/39221-4-0.html";
  getJsonInfo(html: string) {
    const $ = cheerio.load(html);
    const info: Info[] = [];
    const scpt: string = String($(".play>script:nth-child(1)").html());
    const url = unescape(
      scpt.split(";")[3].split("(")[1].split(")")[0].replace(/\"/g, "")
    );
    const name: string = String($("title").html());
    info.push({
      name,
      url,
    });
    const result = {
      time: new Date().getTime(),
      data: info,
    };
    return result;
  }
  async getRawHtml() {
    const result = await superagent.get(this.url);
    return result.text;
  }
  getJsonContent(info: InfoResult) {
    const filePath = path.resolve(__dirname, "../data/url.json");
    let fileContent: objJson = {};
    if (fs.existsSync(filePath)) {
      fileContent = JSON.parse(fs.readFileSync(filePath, "utf-8"));
    }
    fileContent[info.time] = info.data;
    fs.writeFileSync(filePath, JSON.stringify(fileContent));
  }
  async initSpiderProcess() {
    const html = await this.getRawHtml();
    const info = this.getJsonInfo(html);
    this.getJsonContent(info);
  }
  constructor() {
    this.initSpiderProcess();
  }
}
const crawler = new Crawler();


  1. 运行命令


npm run dev-t


  1. 查看生成文件的效果


{
  "1610738046569": [
    {
      "name": "《复仇者联盟4:终局之战》HD1080P中字m3u8在线观看-韩剧网",
      "url": "https://wuxian.xueyou-kuyun.com/20190728/16820_302c7858/index.m3u8"
    }
  ],
  "1610738872042": [
    {
      "name": "《钢铁侠2》HD高清m3u8在线观看-韩剧网",
      "url": "https://www.yxlmbbs.com:65/20190920/54uIR9hI/index.m3u8"
    }
  ],
  "1610739069969": [
    {
      "name": "《钢铁侠2》中英特效m3u8在线观看-韩剧网",
      "url": "https://tv.youkutv.cc/2019/11/12/mjkHyHycfh0LyS4r/playlist.m3u8"
    }
  ]
}


准结语


到这里真的结束了吗?

不!

不!

不!

真的没有结束。

我们会看到上面一坨代码,真的很臭~

我们将分别使用组合模式与单例模式将其优化。


优化一:组合模式


组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。


这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。


简言之,就是可以像处理简单元素一样来处理复杂元素。


首先,我们在src文件夹下创建一个combination文件夹,然后在其文件夹下分别在创建两个文件crawler.tsurlAnalyzer.ts


**crawler.ts **


crawler.ts文件的作用主要是处理获取页面内容以及存入文件内。


import superagent from "superagent";
import fs from "fs";
import path from "path";
import UrlAnalyzer from "./urlAnalyzer.ts";
export interface Analyzer {
  analyze: (html: string, filePath: string) => string;
}
class Crowller {
  private filePath = path.resolve(__dirname, "../../data/url.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.analyze(html, this.filePath);
    this.writeFile(fileContent);
  }
  constructor(private analyzer: Analyzer, private url: string) {
    this.initSpiderProcess();
  }
}
const url = "https://www.hanju.run/play/39257-1-1.html";
const analyzer = new UrlAnalyzer();
new Crowller(analyzer, url);


urlAnalyzer.ts


urlAnalyzer.ts文件的作用主要是处理获取页面节点内容的具体逻辑。


import cheerio from "cheerio";
import fs from "fs";
import { Analyzer } from "./crawler.ts";
interface objJson {
  [propName: number]: Info[];
}
interface InfoResult {
  time: number;
  data: Info[];
}
interface Info {
  name: string;
  url: string;
}
export default class UrlAnalyzer implements Analyzer {
  private getJsonInfo(html: string) {
    const $ = cheerio.load(html);
    const info: Info[] = [];
    const scpt: string = String($(".play>script:nth-child(1)").html());
    const url = unescape(
      scpt.split(";")[3].split("(")[1].split(")")[0].replace(/\"/g, "")
    );
    const name: string = String($("title").html());
    info.push({
      name,
      url,
    });
    const result = {
      time: new Date().getTime(),
      data: info,
    };
    return result;
  }
  private getJsonContent(info: InfoResult, filePath: string) {
    let fileContent: objJson = {};
    if (fs.existsSync(filePath)) {
      fileContent = JSON.parse(fs.readFileSync(filePath, "utf-8"));
    }
    fileContent[info.time] = info.data;
    return fileContent;
  }
  public analyze(html: string, filePath: string) {
    const info = this.getJsonInfo(html);
    console.log(info);
    const fileContent = this.getJsonContent(info, filePath);
    return JSON.stringify(fileContent);
  }
}


可以在package.json文件中定义快捷启动命令。


"scripts": {
    "dev-c": "ts-node ./src/combination/crawler.ts"
  },


然后使用npm run dev-c启动即可。


优化二:单例模式


**单例模式(Singleton Pattern)**是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。


应用实例:


  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。


同样,我们在src文件夹下创建一个singleton文件夹,然后在其文件夹下分别在创建两个文件crawler1.tsurlAnalyzer.ts


这两个文件的作用与上文同样,只不过代码书写不一样。


crawler1.ts


import superagent from "superagent";
import fs from "fs";
import path from "path";
import UrlAnalyzer from "./urlAnalyzer.ts";
export interface Analyzer {
  analyze: (html: string, filePath: string) => string;
}
class Crowller {
  private filePath = path.resolve(__dirname, "../../data/url.json");
  async getRawHtml() {
    const result = await superagent.get(this.url);
    return result.text;
  }
  private writeFile(content: string) {
    fs.writeFileSync(this.filePath, content);
  }
  private async initSpiderProcess() {
    const html = await this.getRawHtml();
    const fileContent = this.analyzer.analyze(html, this.filePath);
    this.writeFile(JSON.stringify(fileContent));
  }
  constructor(private analyzer: Analyzer, private url: string) {
    this.initSpiderProcess();
  }
}
const url = "https://www.hanju.run/play/39257-1-1.html";
const analyzer = UrlAnalyzer.getInstance();
new Crowller(analyzer, url);


urlAnalyzer.ts


import cheerio from "cheerio";
import fs from "fs";
import { Analyzer } from "./crawler1.ts";
interface objJson {
  [propName: number]: Info[];
}
interface InfoResult {
  time: number;
  data: Info[];
}
interface Info {
  name: string;
  url: string;
}
export default class UrlAnalyzer implements Analyzer {
  static instance: UrlAnalyzer;
  static getInstance() {
    if (!UrlAnalyzer.instance) {
      UrlAnalyzer.instance = new UrlAnalyzer();
    }
    return UrlAnalyzer.instance;
  }
  private getJsonInfo(html: string) {
    const $ = cheerio.load(html);
    const info: Info[] = [];
    const scpt: string = String($(".play>script:nth-child(1)").html());
    const url = unescape(
      scpt.split(";")[3].split("(")[1].split(")")[0].replace(/\"/g, "")
    );
    const name: string = String($("title").html());
    info.push({
      name,
      url,
    });
    const result = {
      time: new Date().getTime(),
      data: info,
    };
    return result;
  }
  private getJsonContent(info: InfoResult, filePath: string) {
    let fileContent: objJson = {};
    if (fs.existsSync(filePath)) {
      fileContent = JSON.parse(fs.readFileSync(filePath, "utf-8"));
    }
    fileContent[info.time] = info.data;
    return fileContent;
  }
  public analyze(html: string, filePath: string) {
     const info = this.getJsonInfo(html);
     console.log(info);
    const fileContent = this.getJsonContent(info, filePath);
    return JSON.stringify(fileContent);
  }
  private constructor() {}
}


可以在package.json文件中定义快捷启动命令。


"scripts": {
     "dev-s": "ts-node ./src/singleton/crawler1.ts",
  },


然后使用npm run dev-s启动即可。


结语


这下真的结束了,谢谢阅读。希望可以帮到你。


完整源码地址:


https://github.com/maomincoding/TsCrawler



相关文章
|
8月前
|
数据采集 存储 开发者
Python爬虫实战:打造高效数据采集工具
本文将介绍如何利用Python编写一个高效的网络爬虫,实现对特定网站数据的快速抓取与处理,帮助开发者更好地应对大规模数据采集的需求。
|
8月前
|
数据采集 JavaScript 前端开发
实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库
实用工具推荐:适用于 TypeScript 网络爬取的常用爬虫框架与库
|
22天前
|
数据采集 人工智能 自然语言处理
FireCrawl:开源 AI 网络爬虫工具,自动爬取网站及子页面内容,预处理为结构化数据
FireCrawl 是一款开源的 AI 网络爬虫工具,专为处理动态网页内容、自动爬取网站及子页面而设计,支持多种数据提取和输出格式。
141 18
FireCrawl:开源 AI 网络爬虫工具,自动爬取网站及子页面内容,预处理为结构化数据
|
24天前
|
数据采集 JavaScript 前端开发
异步请求在TypeScript网络爬虫中的应用
异步请求在TypeScript网络爬虫中的应用
|
1月前
|
前端开发 JavaScript 开发者
前端项目代码规范工具 (ESLint. Prettier. Stylelint. TypeScript)
前端项目代码规范工具 (ESLint. Prettier. Stylelint. TypeScript)
123 4
|
2月前
|
数据采集 存储 监控
Java爬虫:数据采集的强大工具
在数据驱动的时代,Java爬虫技术凭借其强大的功能和灵活性,成为企业获取市场信息、用户行为及竞争情报的关键工具。本文详细介绍了Java爬虫的工作原理、应用场景、构建方法及其重要性,强调了在合法合规的前提下,如何有效利用Java爬虫技术为企业决策提供支持。
|
8月前
|
数据采集 Web App开发 JavaScript
TypeScript 爬虫实践:选择最适合你的爬虫工具
TypeScript 爬虫实践:选择最适合你的爬虫工具
|
5月前
|
开发框架 前端开发 JavaScript
基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面
基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面
|
6月前
|
JavaScript 安全
TypeScript(十一)泛型工具类型
TypeScript(十一)泛型工具类型
56 0
|
8月前
|
JavaScript 前端开发 开发者
Vue工具和生态系统: Vue.js和TypeScript可以一起使用吗?
【4月更文挑战第18天】Vue.js与TypeScript兼容,官方文档支持在Vue项目中集成TypeScript。TypeScript作为JavaScript超集,提供静态类型检查和面向对象编程,增强代码准确性和健壮性。使用TypeScript能提前发现潜在错误,提升代码可读性,支持接口和泛型,使数据结构和函数更灵活。然而,不是所有Vue插件都兼容TypeScript,可能需额外配置。推荐尝试在Vue项目中使用TypeScript以提升项目质量。
135 0