目录

一、项目:唐诗可视化

二、技术选型

三、数据库表的设计

四、选型技术的简单使用Demo(预研阶段)

五、实现思路

六、唐诗爬取模块实现

单线程版本:

多线程版本:

线程池:

改进线程池的版本:

七、数据可视化模块实现

RankServlet 处理作者作诗数:

WordServlet提供给前端处理用词统计:

八、实验结果

九、项目测试

十、回味无穷


一、项目:唐诗可视化

项目目的:爬取 古诗文网 唐诗三百首的内容,将爬取到的内容存储到数据库当中,并对数据进行处理分析等,最终将其以图表等形式展示出来的一个 JavaWeb 项目,目的是使用户能更直观快速的去了解古代唐诗。

项目分为两个模块:诗词爬取模块数据可视化模块

项目核心技术:

  • JDBC(数据库操作)
  1. 唐诗数据保存至数据库 。
  2. 页面展示时提取数据库信息。
  • Servlet 的使用
  • HTTP 协议
  • HtmlUtil、ansj_seg 第三方库的使用
  • sha-256 算法
  • 多线程技术
  • jQuery 前后端交互(进行异步来提交更新数据)
  • 软件测试的基本策略和方法

项目设计:

1、获取数据

  • 访问列表页(唐诗三百首大全)来获取页面中唐诗数据;
  • 编写程序模拟客户端向浏览器构建 Http 请求获取 Html 页面数据;
  • 将获取到的列表页 html 数据,保存在 列表页.html 中。

2、分析数据和整理数据

  • 观察 列表页.xml 中的表单,提取每首唐诗页面的子路径 ,保存至 LinkedList 中;
  • 根据每首诗的 url ,获取每首的详情页(诗词页)页面数据,将页面中诗的作者、标题、朝代、诗词正文等提取出来;
  • 计算 sha256(标题+正文),保证数据不重复;
  • 调用分词的第三方库,对内容进行分词;
  • 将数据保存至数据库中。

3、提取数据库中的信息选择合适的图形界面来展示。

  • 唐朝的各个诗人的作诗量(柱状图);
  • 诗人们使用最频繁的词语(词云)。

二、技术选型

1、Maven 来进行项目管理

Maven 就是专门为 Java 项目打造的管理和构建工具,它的主要功能有:

  • 提供了一套标准化的项目结构;
  • 提供了一套标准化的构建流程(编译,测试,打包,发布……);
  • 提供了一套依赖管理机制。

一个使用Maven管理的普通的Java项目,它的目录结构默认如下:

项目的根目录 a-maven-project是项目名,

它有一个项目描述文件pom.xml,存放Java源码的目录是src/main/java,存放资源文件的目录是src/main/resources,存放测试源码的目录是src/test/java,存放测试资源的目录是src/test/resources,最后,所有编译、打包生成的文件都放在target目录里。

2、Java 语言操作数据库(JDBC)

在 pom.xml 中加入相应依赖:

<dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
</dependency>

3、列表页和详情页的请求解析(HtmlUtil)

HtmlUtil 是一款开源的 java 页面分析工具,读取页面后,可以有效的使用HtmlUtil 分析页面上的内容,项目可以模拟浏览器运行(一个没有界面的浏览器),被誉为 java 浏览器的开源实现,同时它的运行速度迅速。

在这里,我们利用 HtmlUtil 第三方库中的相应方法,来进行一个 Html 页面的请求和解析工作。

在 pom.xml 中加入相应依赖:

<dependency>
            <groupId>net.sourceforge.htmlunit</groupId>
            <artifactId>htmlunit</artifactId>
            <version>2.36.0</version>
</dependency>

通过 HtmlUnit 库,我们可以很方便的加载一个完整的 Html 页面而且可以很轻易的模拟各种浏览器,发起对一个网页的请求,并获得相应的页面元素 HtmlPage .

总结:HtmlUnit 说白了就是一个浏览器,这个浏览器是用 Java 写的无界面的浏览器,正因为其没有界面,因此它执行的速度很快,HtmlUnit 还提供了一系列的 API,这些 API 可以提供的功能比较多,如表单的填充,表单的提交,模仿点击链接等等,由于它还内置了 Rhinojs 引擎,因此可以执行Javascript 代码。

4、分词功能(ansj_seg)

展示页面涉及到了根据每一个词的出现频率来进行词图的展示。这里根据每一首诗的标题和内容来进行分词。我们采用第三方类库 ansj_seg 来完成分词功能。

在 pom.xml 中加入相应依赖:

<dependency>
      <groupId>org.ansj</groupId>
      <artifactId>ansj_seg</artifactId>
      <version>5.1.6</version>
</dependency>

5、前端页面展示渲染工具(echarts:enterprise charts,商业级数据图表)

做一个可视化项目:调研有哪些成熟的可视化第三方类库可以使用 echarts

echarts 它是一个开源免费的 javascript 可视化库,本项目中前端页面展示中用到的柱状图和云图皆来源于它。

选择第三方库 echarts 的原因:

  • 开源免费
  • 使用简单,在官网为我们封装了js,只要会引用就会得到完美的展示效果(可以直接在其网站上进行调试,得到自己想要的效果,然后进行下载)
  • 功能丰富,种类繁多

6、前后端交互技术(jQuery)

利用 $.ajax() 发起一个 HTTP 请求,进行前后端数据的交互。

7、响应字符串(fastjson)

控制自己编写的每一个 Servlet 类实现的方法中的每一个返回值(响应数据)都是 JSON 格式的字符串。

在 pom.xml 中加入相应依赖:

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.62</version>
</dependency>

三、数据库表的设计

1、建表

CREATE DATABASE tangshi CHARSET utf8mb4;
use tangshi;
CREATE TABLE t_tangshi(id INT PRIMARY KEY AUTO_INCREMENT,sha256 CHAR(64) NOT NULL UNIQUE,dynasty VARCHAR(20) NOT NULL,title VARCHAR(30) NOT NULL,author VARCHAR(10) NOT NULL,content TEXT NOT NULL,words TEXT NOT NULL
);

为什么要引入 sha-256 ?

使用 sha256 ,为每首诗生成一个唯一标识符,可以(标题 +正文)来计算 sha-256 的值,保证同一首诗不会被重复插入。

查看字符编码: 

四、选型技术的简单使用Demo(预研阶段)

1、使用 HtmlUtil 抓取网页

步骤:

  1. 定义一个 WebClient 客户端,就相当于定义了一个没有界面的浏览器;
  2. 使用 WebClient 客户端从指定 URL 获取 HtmlPage,HtmlPage 中包含目标 URL页面中的所有元素;
  3. 从 Htmlpage 中获取我们需要的指定元素。
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;import java.io.File;
import java.io.IOException;
import java.util.List;public class HtmlUtil {public static void main(String[] args) throws IOException {// 无界面的浏览器(Http客户端)WebClient webClient=new WebClient(BrowserVersion.CHROME);// 关闭了浏览器的js执行引擎,不再执行网页中的js脚本webClient.getOptions().setJavaScriptEnabled(false);// 关闭了浏览器的css;执行引擎,不再执行网页中的css布局webClient.getOptions().setCssEnabled(false);// 请求列表页HtmlPage page=webClient.getPage("https://www.gushiwen.org/gushi/tangshi.aspx");System.out.println(page);// 保存到指定路径File file=new File("唐诗三百首\\列表页.html");file.delete();page.save(new File("唐诗三百首\\列表页.html"));// 如何从 html 中提取我们需要的元素// 1、获取 html 文件中的 body 标签中的内容HtmlElement body=page.getBody();// 2、在 body 标签中的内容当中,获取 div 标签中 class 属性值为 typecont 的元素List<HtmlElement> elements=body.getElementsByAttribute("div","class","typecont");for(HtmlElement element:elements){System.out.println(element);}/*打印结果:(获取到了各个模块)HtmlPage(https://www.gushiwen.org/gushi/tangshi.aspx)@414225167HtmlDivision[<div class="typecont">]                        // 五言绝句HtmlDivision[<div class="typecont">]                        // 七言绝句HtmlDivision[<div class="typecont">]                        // 五言律诗HtmlDivision[<div class="typecont">]                        // 七言律诗HtmlDivision[<div class="typecont">]                        // 五言古诗HtmlDivision[<div class="typecont">]                        // 七言古诗HtmlDivision[<div class="typecont" style="border:0px;">]    // 乐府*/System.out.println("-----------------------------------------------------");// 取第一个模块——> 五言绝句HtmlElement divElement=elements.get(0);// 获取 五言绝句 模块中的各首诗List<HtmlElement> aElements=divElement.getElementsByAttribute("a","target","_blank");for (HtmlElement element:aElements){System.out.println(element);}System.out.println(aElements.size());System.out.println(aElements.get(0).getAttribute("href"));}
}

// 获取各个模块
        HtmlElement body=page.getBody();
        List<HtmlElement> elements=body.getElementsByAttribute(
                "div",
                "class",
                "typecont"
        );

for(HtmlElement element:elements){
            System.out.println(element);
        }

列表页:

// 取第一个模块——> 五言绝句
        HtmlElement divElement=elements.get(0);
        // 获取 五言绝句 模块中的各首诗
        List<HtmlElement> aElements=divElement.getElementsByAttribute(
                "a",
                "target",
                "_blank"
        );
        for (HtmlElement element:aElements){
            System.out.println(element);
        }

System.out.println(aElements.size());
        System.out.println(aElements.get(0).getAttribute("href"));   // 第一个模块的第一首诗的链接

2、详情页+诗集数据获取

详情页:

XPath:

标题:”//div[@class=‘cont’]/h1/text()”                // 表示获取 div 标签中 class 为 cont 的标签其下面的h1标签的内容。

朝代:”//div[@class=‘cont’]/p[@class=‘source’]/[1]/a[1]/text()”         //a[1] 表示该路径下不止一个a标签此处取第一个 a 标签,a[1]/text() 表示获取第一个标签的内容。

作者:”//div[@class=‘cont’]/p[@class=‘source’]/[1]/a[2]/text()”

正文:”//div[@class=‘cont’]/div[@class=‘contson’]”.getTextContent()

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomText;
import com.gargoylesoftware.htmlunit.html.Html;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;import java.io.IOException;public class 详情页下载提取Demo {public static void main(String[] args) throws IOException {try(WebClient webClient=new WebClient(BrowserVersion.CHROME)){webClient.getOptions().setJavaScriptEnabled(false);webClient.getOptions().setCssEnabled(false);String url="https://so.gushiwen.org/shiwenv_45c396367f59.aspx";HtmlPage page=webClient.getPage(url);HtmlElement body=page.getBody();//标题{String xpath="//div[@class='cont']/h1/text()";// 表示获取 div 标签中 class 为 cont 的标签其下面的h1标签的内容。这是通过XPath路径获取信息,更为方便。Object o=body.getByXPath(xpath).get(0);DomText domtext=(DomText)o;// DomText 为节点对象,asText() 为获取内容的文本形式。System.out.println(domtext.asText());}//朝代{String xpath="//div[@class='cont']/p[@class='source']/a[1]/text()";// a[1] 表示该路径下不止一个a标签此处取第一个 a 标签,a[1]/text() 表示获取第一个标签的内容。Object o=body.getByXPath(xpath).get(0);DomText domtext=(DomText)o;System.out.println(domtext.asText());}//作者{String xpath="//div[@class='cont']/p[@class='source']/a[2]/text()";Object o=body.getByXPath(xpath).get(0);DomText domtext=(DomText)o;System.out.println(domtext.asText());}//正文{String xpath="//div[@class='cont']/div[@class='contson']";Object o=body.getByXPath(xpath).get(0);HtmlElement element=(HtmlElement)o;System.out.println(element.getTextContent().trim());}}}
}

3、计算 SHA256 的值

SHA256,基于哈希的加密方法, SHA即安全散列算法(Secure Hash Algorithm), 256指的是哈希值的位数,即256bit。SHA256的特性在于,相同的输入信息通过SHA256的输出值是唯一的,当用SHA256加密的信息中有修改时,即使是很小的修改,得到的结果也会完全不同。

MD5算法的不足:现在看来,MD5已经较老,散列长度通常为128位,随着计算机运算能力提高,找到“碰撞”是可能的。因此,在安全要求高的场合不使用MD5。

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class 求SHA256Demo {public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {MessageDigest messageDigest=MessageDigest.getInstance("SHA-256");String s="你好世界";byte[] bytes=s.getBytes("UTF-8");messageDigest.update(bytes);   byte[] result=messageDigest.digest();System.out.println(result.length);for(byte b:result){System.out.printf("%02x",b);   }}
}

该MessageDigest类为应用程序提供消息摘要算法的功能,如SHA-1或SHA-256。 消息摘要是采用任意大小的数据并输出固定长度散列值的安全单向散列函数。

MessageDigest对象开始初始化。 数据通过它使用update方法进行处理。 在任何时候可以调用reset来重置摘要。 一旦要更新的所有数据都被更新,则应调用其中一个digest方法来完成哈希计算。

对于给定数量的更新,可以调用digest方法一次。 在digest之后,将MessageDigest对象重置为初始化状态。

4、计算分词  分词词性标注规范

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.NlpAnalysis;import java.util.List;public class 分词Demo {public static void main(String[] args) {String sentence="忽如一夜春风来,千树万树梨花开。";// 调用静态方法将要解析的字符串传入并调用 getTerms() 方法返回一个 Term 的集合(一个 Term 就是一个单词)List<Term> termList=NlpAnalysis.parse(sentence).getTerms();for(Term term:termList){System.out.println(term.getNatureStr()+":"+term.getRealName());}}
}

5、将数据插入数据库

import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.activation.DataSource;
import java.sql.*;public class 插入诗词Demo {public static void main(String[] args) throws ClassNotFoundException, SQLException {String 朝代="唐代";String 作者="白居易";String 标题="问刘十九";String 正文="绿蚁新醅酒,红泥小火炉。晚来天欲雪,能饮一杯无?";/*获取 Connection的第一种方法// 1、注册 DriverClass.forName("com.mysql.jdbc.Driver");// 2、通过 DriverManger 获取 ConnectionString url="jdbc:mysql://175.24.50.87/tangshi?useSSL=false&characterEncoding=uft8";Connection connection=DriverManager.getConnection(url,"root","maymay0722may");System.out.println(connection);Statement statement=connection.createStatement();String sql="insert into tangshi(sha256,dynasty,author,content,words) values()";statement.executeUpdate(sql);*//*获取 Connection 的第二种方法通过 DataSource 获取 Connection*/Class.forName("com.mysql.jdbc.Driver");//DataSource dataSource= (DataSource) new MysqlDataSource();   不带有连接池MysqlConnectionPoolDataSource dataSource=new MysqlConnectionPoolDataSource();   //带有连接池(有利于管理)dataSource.setServerName("175.24.50.87");dataSource.setPort(22);dataSource.setUser("root");dataSource.setPassword("maymay0722may");dataSource.setDatabaseName("tangshi");dataSource.setUseSSL(false);dataSource.setCharacterEncoding("UTF-8");try(Connection connection=dataSource.getConnection()){  //拿到连接String sql="insert into tangshi(sha256,dynasty,author,title,content,words) values(?,?,?,?,?,?)";// 占位符try(PreparedStatement statement=connection.prepareStatement(sql)){statement.setString(1,"sha256");statement.setString(2,朝代);statement.setString(3,作者);statement.setString(4,朝代);statement.setString(5,朝代);statement.setString(6,朝代);statement.executeUpdate();    //插入}}}
}

五、实现思路

唐诗爬取模块:

1、请求和解析列表页

  • 列表页中包含了每个详情页的后半部分 url

2、请求和解析详情页

  • 进入到详情页页面可以获取诗词的相关信息(标题,作者,朝代,内容等信息),这里采用 XPath 来获取这些信息。

XPath的简单介绍:

3、计算 sha-256 的值,目的是为了使数据库中不存储同一首诗,不同诗的 sha-256 的值不同(利用标题+内容计算每一首诗的 sha-256的值)

4、计算分词(只选取标题和正文的内容来进行分词,并且长度小于 1 的词不算,标点符号也不算一个词,为 null 也不算一个分词)。由于分词之后的词有可能会重复情况发生,所以要进行统计,存储的时候就是以 key-value 格式存储到 Map 集合当中去的。( key是词,value是词频 )

5、所有数据就绪完毕,将数据插入到数据库

6、循环执行序号 2-6 的操作,直至所有的古诗都已经全部插入到数据库

数据可视化模块:

1、编写两个 Servlet ,一个是 RankServlet(用来从数据库中获取作者和作者相对应的诗词数量),一个是 WordsServlet (用来从数据库中获取每一首诗的分词情况)两个 Servlet 的响应内容都为 JSON 格式的字符串。

2、通过 $.ajax() 发起一个 HTTP 请求,从服务器后端获取相应数据,用来填充 echarts 图表中的相关内容

3、提取数据库中的信息选择合适的图形界面来展示

  • 各个诗人以及其对应的诗词创作数量(柱状图);
  • 根据词的出现频率展示图片(云图)。

六、唐诗爬取模块实现

单线程版本:

所有内容全部由主线程完成,无线程安全问题,但是速度最慢。

多线程版本:

列表页的请求和解析主线程去做,详情页的请求和解析交给线程去做,目的是为了提高效率。

速度增加,列表页的请求和解析整个项目只执行一次,所以放在主线程中去做;

而详情页的请求和解析需要执行320次,在多线程中去做,同理提取诗词信息(标题,作者,内容)、计算 sha256 的值、计算分词、信息插入数据库等步骤放到多线程中去做。

(1)多线程出现的问题:
WebClient,Connection,PreparedStatement ,生成SHA-256的MessageDigest 都不是线程安全的,加锁的话达不到高效率的目的。

(2)解决方案:
让上述这些对象在每一个线程中都有一个自己的对象,这样就不会出现线程安全问题了。

线程池:

采用 Executors.newFixedThreadPool(int) 方式创建一个固定大小的的线程池

(1)出现问题:
线程池可以减少线程创建和销毁的次数,但线程池中的线程不能自己停止,就算所有诗词已经全部放入数据库也不会停止。

(2)出现原因:
因为 JVM 在所有的非后台线程都结束后才会结束,而线程池中的线程是永远不会停止的(每个线程执行完任务后,自己又返回到线程池当中)那么JVM 就不会停止。

(3)解决方法:
用CountDownLatch:在主线程中调用countDownLatch,待所有的诗都成功上传到数据库当中时,显示调用 pool.shutdown(); 方法即可让程序停止执行。

改进线程池的版本:

法一:通过 CountDownLatch

在主线程中建立对象 countDownLatch,并作为参数传给线程:

CountDownLatch countDownLatch=new CountDownLatch(detailUrlList.size());   //传入的参数是诗的个数( 320 )

在每个线程任务结束的时候,加入代码:

countDownLatch.countDown();   //个数减1(最初该对象里面的属性值为 320)

最后在主线程中加入:

countDownLatch.await();    //等待 320 首诗都上传到数据库(一直等到 countDownLatch 对象里面的属性值为 0)
pool.shutdown();           //关闭线程池

法二:通过 Atomic 原子类

在类中定义变量:

private static AtomicInteger successCount=new AtomicInteger(0);   //原子类
private static AtomicInteger failureCount=new AtomicInteger(0);   //原子类

在每个线程(成功插入时)加入代码:

while(successCount.get()+failureCount.get()<detailUrlList.size()){System.out.printf("一共 % 首诗,成功 %d,失败 %d,%d\r",detailUrlList.size(),successCount.get(),failureCount.get());TimeUnit.SECONDS.sleep(1);   // 1秒打印一次
}
System.out.println();
System.out.println("全部下载成功");
pool.shutdown();

七、数据可视化模块实现

使用技术:Servlet,JSON,jQuery,ajax ,echarts

提供一个接口给前端处理作者作诗数的统计

RankServlet 处理作者作诗数:

@WebServlet("/rank")
public class RankServlet extends HttpServlet {protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf-8");String condition = req.getParameter("condition");if (condition == null) {condition = "3";}JSONArray jsonArray = new JSONArray();try (Connection connection = DBUtil.getConnection()) {String sql = "SELECT author, count(*) AS cnt FROM t_tangshi GROUP BY author HAVING cnt >= ? ORDER BY cnt DESC";try (PreparedStatement statement = connection.prepareStatement(sql)) {statement.setString(1, condition);try (ResultSet rs = statement.executeQuery()) {while (rs.next()) {String author = rs.getString("author");int count = rs.getInt("cnt");JSONArray item = new JSONArray();item.add(author);item.add(count);jsonArray.add(item);}resp.getWriter().println(jsonArray.toJSONString());}}} catch (SQLException e) {e.printStackTrace();JSONObject object = new JSONObject();object.put("error", e.getMessage());resp.getWriter().println(object.toJSONString());}}
}

WordServlet提供给前端处理用词统计:

@WebServlet("/words")
public class WordsServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json; charset=utf-8");JSONArray jsonArray = new JSONArray();Map<String,Integer> map = new TreeMap<>();try (Connection connection = DBUtil.getConnection()) {String sql = "SELECT words FROM t_tangshi";try (PreparedStatement statement = connection.prepareStatement(sql)) {try (ResultSet rs = statement.executeQuery()) {while (rs.next()) {String words = rs.getString("words");String word[] = words.split(",");for (int i = 0;i < word.length;i++){if (!map.containsKey(word[i])){map.put(word[i],1);}else {map.put(word[i], map.get(word[i]) + 1);}}}for (Map.Entry<String,Integer> entry : map.entrySet()){JSONArray item = new JSONArray();item.add(entry.getKey());item.add(entry.getValue());jsonArray.add(item);}resp.getWriter().write(jsonArray.toJSONString());}}} catch (SQLException e) {e.printStackTrace();JSONObject object = new JSONObject();object.put("error", e.getMessage());resp.getWriter().println(object.toJSONString());}}}

index.html 添加下载的 js 文件:

    <script src="js/jquery-3.3.1.min.js"></script><script src="js/echarts.min.js"></script><script src="js/echarts-gl.min.js"></script><script src="js/echarts-wordcloud.min.js"></script>

Echarts教程

编写 ajax_and_echarts.js:

利用$.ajax()发起一个HTTP请求,后台收到请求时返回给前端一个index.html文件,这个文件里面的Script标签又会主动向后台发送http请求,得到json格式的数据,js中的代码把数据写到echart中,页面就展示出来了也引入第三方库,js请求交给相对应的servlet去处理(创作数量排行榜用RankServlet()去处理,诗词用词云图用wordServlet()去处理)

$.ajax({method: "get",  // 发起 ajax 请求时,使用什么 http 方法url: "rank?condition=3",   // 请求哪个 urldataType: "json",   // 返回的数据当成什么格式解析success: function (data) {  // 成功后,执行什么方法var names = [];var counts = [];for (var i in data) {names.push(data[i][0]);counts.push(data[i][1]);}console.log(names);console.log(counts);var myChart = echarts.init(document.getElementById('main'));var option = {// 图标的标题title: {text: '作诗量排行'},tooltip: {},legend: {data:['销量']},// 横坐标xAxis: {data: names},yAxis: {},series: [{name: '作诗数',type: 'bar',    // bar 代表柱状图itemStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1,[{offset: 0, color: '#83bff6'},{offset: 0.5, color: '#188df0'},{offset: 1, color: '#188df0'}])},emphasis: {itemStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1,[{offset: 0, color: '#2378f7'},{offset: 0.7, color: '#2378f7'},{offset: 1, color: '#83bff6'}])}},data: counts}]};myChart.setOption(option);}}
);

八、实验结果

index.html:

rank.html:

word.html:

九、项目测试

十、回味无穷

我的项目_唐诗可视化项目相关推荐

  1. 根据经纬度计算范围_遗传算法可视化项目(插曲):关于距离的计算

    今天暂时先不讲遗传算法,我要解决的是TSP问题,具体什么TSP问题之前文章里讲过了,大家可以点一下历史消息或者这里: 遗传算法可视化项目(1):概述 遗传算法可视化项目(2):获取信息 遗传算法可视化 ...

  2. 用遗传算法求3维函数 的最小值_遗传算法可视化项目(4):遗传算法

    昨天讲了一下关于距离的计算,没有看昨天或者之前的文章,点一下历史消息或者这里: 遗传算法可视化项目(1):概述 遗传算法可视化项目(2):获取信息 遗传算法可视化项目(3):创建图的数据结构 遗传算法 ...

  3. 数仓建模 项目_音乐数据项目火力全开,技能双倍提升!

    ↑ 点击上方"尚学堂"关注我们 音乐数据中心数仓综合项目 1项目介绍 音乐数据中心项目是大型企业级综合数仓项目,此项目针对音乐数据进行分析,构建数据仓库,建立用户.机器.内容等主题 ...

  4. python 创意项目_选择创意项目您需要知道的7个步骤

    python 创意项目 There are obvious surface-level positives and negatives to most projects. If you look be ...

  5. 怎么创建python django项目_创建Django项目图文实例详解

    本文实例讲述了创建Django项目的方法.分享给大家供大家参考,具体如下: 创建Django项目 创建一个HelloDjango项目 GitHub地址:https://github.com/liang ...

  6. 项目众包 开源项目_在开源项目之前要问的4个问题

    项目众包 开源项目 在任何公司的开源部门中,最常见的任务之一就是评估内部软件,以查看是否可以很好地回报给社区. 在PayPal执行此任务时,我们发现通过Danese Cooper最初试图审查四个主要问 ...

  7. 控制器如何跳转web-inf下的项目_第一次开发项目感想

    1.大一感想 第一次真正的开发网站,就前两周开始,我大二期间学习了Java,在那时,我就开始考虑以后到底要从事哪方面的职业,我的专业是物联网,说实话,我真的对这方面没兴趣,我为什么对专业没兴趣,我也曾 ...

  8. github上传本地项目_提交本地项目到GitHub

    注册账号这里就不讲了,自己官网注册.(没注册上篇的添加key步骤肯定完成不了) 官网 https://github.com/ Git与Github区别 Git是一个分布式版本控制系统,简单的说其就是一 ...

  9. sqllite开发安卓项目_【兼职项目】预算3万开发无线温度电流传感,2万开发直流电机打磨机控制...

    [个人/团队兼职项目]是小包为大家筛选的酬金预算≤3万元的中小型软硬件兼职项目,适合高级工程师.技术团队服务商竞标,从而赚取"零花钱". 个人/团队兼职项目(酬金≤3万元) 1.摄 ...

最新文章

  1. python 删除list中的第一个元素
  2. Spark 与MapReduce 资源调度方面的简单对比
  3. 程序最小化后释放了很多的内存的原因
  4. Github热榜--《程序员做饭指南》
  5. java远程下载文件到本地_java远程下载文件到本地
  6. 基于VHDL自动售邮票机设计
  7. mailto 附带附件_为什么附带项目如此重要
  8. cli vue 卸载_记录使用@vue/cli搭建Vue3项目完整流程
  9. java中的过滤器与监听器
  10. 解决mysql地区时间错误_mysql time zone时区的错误解决
  11. ”天空之城”的主题曲《君をのせて》
  12. DirectX9 3D 快速上手 1
  13. 税收分类编码_四个要点教你如何又快又准选好编码?会计实操干货
  14. 最新电脑cpu性能排行服务器,至强cpu天梯图2020_intel服务器cpu排行榜2020
  15. html中表格居中对齐
  16. 计算机网络演进,计算机网络演进之路
  17. 第八届蓝桥杯个人赛赛后总结
  18. Python中NaN、nan和NAN的区别及使用方法
  19. 抗去除花指令(一)——花指令基础
  20. 解决PS中:无法将图片存储为Web存储格式,及如何将图片大小修改成10KB的问题

热门文章

  1. JS正则验证输入框姓名只能输入中文和英文
  2. 2021年牛宝宝起名取名,惊艳有诗意的三字女孩名
  3. idea maven子项目图标右下角没有小蓝方块
  4. [转载]刘峰获“区块链60人”2020赋能中国区块链创新人物奖
  5. CNN-PS: CNN-Based Photometric Stereo for General Non-convex Surfaces 2018ECCV
  6. 我们需要培养职业化的工作习惯
  7. hashmap面试题,音视频学习指南来咯,社招面试心得
  8. 多传感器融合的SLAM综述
  9. 2012意大利之行1:从深圳到罗马_我是亲民_新浪博客
  10. [安卓开发] Broadcast 三种广播的使用总结