整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

仅使用 01 提示使用 ChatGPT 代码解释器进行网页抓取(分步教程)

论您是否具有编程知识,执行网页抓取似乎是一项复杂且要求苛刻的任务。但是,ChatGPT 和代码解释器插件将为我们节省许多代码行和麻烦,因为它只需一个提示即可在几秒钟内从网页中提取信息。

接下来,我们将通过三个示例看到如何使用 ChatGPT 以简单实用的方式执行网页抓取,所有这些都是一步一步解释的

让我们开始...\

1) 沃尔玛

我们将使用沃尔玛在线商店的“购买所有返校”部分。我在下面提供直接链接:

在返校 - Walmart.com 中购买所有返校产品

在“返校”中购买所有返校商店。购买产品,如 JLab 音频 JBuddies 工作室儿童贴耳式...

www.walmart.com

步骤 1:定义要提取的字段

我们需要定义我们希望提取的信息。这非常重要,因为它将帮助我们以后在 ChatGPT 中构建我们的提示

在这种情况下,我们将抓取产品名称和价格

第 2 步:检查代码

在这里,我们需要定义 1 个产品的代码(作为示例,然后将其输入到 ChatGPT 中)

但在我们这样做之前,请记住以下几点:

要访问 Chrome 中的检查元素功能,如果您使用的是 Windows,则有两个键盘快捷键选项:

a) 按 + 移位 + c

b) 按 + 移位 + i

如果您使用的是 macOS,请使用:

a) alt + Command + i

b) 选项 + 命令 + i

考虑到这一点,我们现在可以检查沃尔玛网站。让我们回顾一下以下部分:

i) 产品名称

在这种情况下,我们需要在代码中找到产品名称进行抓取

让我们复制它,然后将其包含在我们的提示中。要复制 span 标签\,我们将鼠标悬停在该部分上,右键单击,将出现以下内容:

现在我们只是复制它,出于实际目的,我们将保留它方便,以便稍后包含在提示中

任天堂儿童超级马里奥兄弟马里奥世界17“笔记本电脑背包

ii ) 价格

我们将对价格字段执行相同的操作

我们将保留价格字段的复制元素供以后使用

.92

如果您需要从网页中提取更多部分,则应重复我们对产品名称和价格执行的相同步骤

提示:\要在代码区域内快速找到要检查的字段,只需将鼠标放在字段上,单击鼠标右键,即可启用检查选项。

步骤 3:保存 HTML 文件

由于我们将使用代码解释器,因此我们需要向其附加一个文件。因此,我们要做的是将要抓取的页面另存为HTML文件。

返回页面并使用键盘快捷键 Ctrl + S(适用于 Windows 和 macOS)

键盘快捷键:按 Ctrl + s

接下来,将文件以 HTML 格式保存在本地文件夹中

第 4 步:上传 HTML 文件 + 生成提示

现在我们已经在 Web 上定义了要抓取的字段及其代码,让我们在 ChatGPT 中构造提示

如果您尚未激活代码解释器,让我们按照一些说明进行操作。否则,我建议您跳过此部分,直接进入构造提示

i) 设置

ii ) 打开代码解释器

ChatGPT 中激活代码解释器后,让我们上传我们在步骤 3 中保存的 HTML 文件

现在,让我们构建提示,同时考虑产品名称和价格,以及每个部分的代码(如有疑问,请查看步骤 2)

提示:从HTML文件中,提取产品名称和价格,将数据放在表格上并导出为CSV文件

这是一个产品的元素: 任天堂儿童超级马里奥兄弟马里奥世界17“笔记本电脑背包

以下是价格的要素:

.92

如果缺少产品价格,请将该价格保留为空数据

在提示中,我们看到有 04 个部分

在第一段中,我指定我已经加载了一个 HTML 文件,并要求它抓取产品名称和价格。完成此操作后,我请求它将数据导出到CSV文件中

在第二段和第三段中,我向 ChatGPT 提供了产品名称和价格字段的每个相应结构的示例。我们看到每个产品都是一个跨度\标签,价格是一个 div 标签\

在 last 段落中,如果它找到价格的空值,我要求它分配空\数据

请务必牢记此提示,因为即将推出的示例将具有相同的结构,并且只会更改字段及其代码

结果:

下载并打开 CSV\ 文件

最后,我们成功地对产品及其各自的价格进行了网络抓取,然后将其导出为CSV文件,如表格图像所示。请注意,我们用作示例的产品包括在内!

奖金

前面的步骤使我们能够从沃尔玛网站的第一(01)页执行网络抓取。但是,如果我们想从第二个 (02) 页面中提取数据,我们执行与前面相同的步骤,但不要忘记在此新页面中识别产品并将其作为示例包含在提示中

沃尔玛网站上“返校”部分的第02页

i) 产品名称

Minecraft Boys Cliff Goats 图形 T 恤,2 件装,尺码 4–18</跨度>

ii) 价格

.96

就像第一页一样,我们需要将第二(02)页的文件保存为HTML\格式(如果您有任何疑问,请查看步骤03)

提示

从HTML文件中,提取产品和价格的名称,将数据放在表格上并将其导出为CSV文件。

这是一个产品的元素: Minecraft Boys Cliff Goats 图形 T 恤,2 件装,尺码 4–18</跨度>

以下是价格的要素:

.96

如果缺少产品价格,请将该价格保留为空数据

如果您希望将两个表合并为一个,您可以要求 ChatGPT 执行以下操作:

2. 目标

在第二个示例中,我们将从目标网站的手机部分执行网页抓取。我们将直接继续,如果有任何疑问,请参阅沃尔玛的第一个示例的步骤

这是直接链接:

手机 : 目标

购买目标手机,你会喜欢的手机,价格低廉。选择当日送货,开车或订购取货...

www.target.com

步骤 1:让我们确定要提取的字段

a) 产品 b) 品牌 c) 价格

现在,让我们检查每个目标字段的代码级别(查看步骤 2)

用于检查的键盘快捷键:Ctrl + Shift + cWindows) 或 Alt + Command + imacOS)

步骤 2:检查代码

i) 产品

我们找到代码和标签。我们复制并保留代码,以便以后将其合并到 ChatGPT 提示符中(如有疑问,请查看第一个沃尔玛示例的步骤 02)

Tracfone Prepaid Apple iPhone SE 2nd Gen (64GB) CDMA — Black

ii) 品牌

Apple

iii) 价格

9.99

步骤 3:保存 HTML 文件

将要抓取的页面另存为 HTML 文件(查看沃尔玛示例中的步骤 3

第 4 步:上传 HTML 文件 + 生成提示

我们将构造提示,但与前面的示例不同,我们将包括手机品牌字段(请参阅沃尔玛示例的步骤 4)。

加载 HTML 文件并为每个要抓取的字段添加代码(产品名称、品牌和价格)

提示: 从HTML文件中,提取产品名称,品牌,价格,将数据放在表格上并导出为CSV文件。提取所有产品

这是一个产品的元素: Tracfone Prepaid Apple iPhone SE 2nd Gen (64GB) CDMA — Black

以下是品牌的元素: Apple

以下是价格的元素:

9.99

如果缺少产品价格,请将该价格保留为空数据

结果

下载并打开 CSV\ 文件

结果很棒,我们能够从 Target 网站抓取所有数据

3) 亚马逊

在最后一个示例中,我们将对 Kindle 书籍执行网页抓取。看看哪些书最受欢迎,然后使用 ChatGPT 创建具有不同趋势主题的故事可能会很有趣

这是链接:

Amazon.com : 电子书点燃

回到学校 残疾客户支持 关闭到大学诊所 畅销书 客户服务 亚马逊基础知识 音乐...

www.amazon.com

步骤1:让我们确定要提取的字段

a) 产品或标题 b) 作者 c) 价格

步骤 2:检查代码

i) 产品或标题:

我们找到代码和标签。我们复制并保留代码,以便稍后将其合并到 ChatGPT 提示符中(如有疑问,请查看第一个沃尔玛示例的步骤 02)

要检查的键盘快捷键是:Ctrl + Shift + cWindows)或Alt + Command + imacOS)。您可以参考步骤 2 了解更多详情

Lessons in Chemistry: A Novel

ii ) 作者

邦妮·加莫斯

iii) 价格

请注意,对于此示例,我们只会提取价格的整数部分

14.

步骤 3:保存 HTML 文件

我们将要抓取的网页保存为 HTML 文件。为此,我们使用快捷键 Ctrl + S 在我们要保存的页面上。我们不要忘记将文件保存为 HTML 格式(检查沃尔玛示例的步骤 3 中的详细信息)

第 4 步:上传 HTML 文件 + 生成提示

现在,让我们根据我们要从亚马逊网页中提取的字段来构建提示,特别是从他们的 Kindle 书籍部分。在本例中,我们要提取标题、作者和价格。

接下来,我们加载 HTML 文件并添加代码以抓取每个所需的字段(标题、作者和价格\))

提示: 从HTML文件中,提取产品名称、作者和价格,将数据放在表格上并导出为CSV文件。

这是一个产品的元素: Lessons in Chemistry: A Novel

以下是作者的元素: Bonnie Garmus

以下是价格元素: 14.

如果缺少产品价格,请将该价格保留为空数据

让我们看到我们看到的示例中的提示具有相同的结构

结果

我们下载 CSV\ 文件

我们成功了!

总结和建议

  1. 如果我们尝试将URL直接放入ChatGPT,即使激活了代码解释器,它也无法执行网页抓取。出于这个原因,我们下载要在 HTML 中抓取的页面
  2. ChatGPT 最初可能无法识别要提取的字段的标签,并且可能会向我们提供错误的信息。此时,我建议打开另一个聊天并再次运行提示
  3. 我们应该记住,代码解释器使用Python和库,如BeautifulSoup进行网页抓取。
  4. 此方法的目的不是取代传统的网页抓取,但是,它将节省我们的时间和代码行
  5. 我们通过 03 个网页抓取示例在故事中看到的内容既面向从事编程工作的人,也面向在该领域知之甚少或一无所知的人
  6. 有趣的是,我们可以通过网络抓取完成什么,正如我上面提到的,我们可以专注于直销创建 Kindle 书籍,考虑到畅销书籍分析竞争对手的价格跟踪某些产品等等

本完整指南适用于希望使用 ChatGPT 进行网页抓取的替代方案的人。没有必要有先前的编程知识,只需要好奇心和耐心。下个故事见,祝福!

、HTML简介

1.HTML是什么?

HTML:htper text markup language超文本标记(标签)语言

由各种标签组成,用来制作网页,告诉浏览器如何显示页面

2.作用

  • 制作网页,控制网页和内容的显示
  • 插入图片、音乐、视频、动画等多媒体
  • 通过链接来检索信息
  • 使用表单获取用户的信息,实现交互

3.版本

w3c:world wide web consortium万维网联盟,制定web技术相关标准和规范的组织,HTML技术hi由w3c制定的标准

两个版本:HTML4.0.1、HTML5.0-----通常H5

官网:http://www.W3shcool.com.cn

4.扩展名

HTML文档是以.html或.htm结尾

二、HTML文档结构

1.基本结构

1.1简介

  • HTML标签是由尖括号括起来的关键词,如,通常是成对出现的,如<html></html>
  • <html>为根标签,包含: <head>头部和<body>主体部分
  • 头部提供关于网页的相关信息,如标题、文档类型、字符编码、关键字等摘要信息
  • 主体部分提供网页的显示内容,真正显示在页面中的内容
  • 合理地进行缩进
  • 标签名不区分大小写,但是一般要用小写

1.2.开发工具

记事本notepad、sublime、Notepad++、Dreamweaver、VScode、Webstorm等

使用步骤:

  1. 新建文件(cltr+N),然后保存(ctrl+s),指定扩展名为.html
  2. 编写HTML代码
  3. 在浏览器中打开文件

使用技巧:

  • 先保存再写代码,否则代码无颜色提示
  • 创建一个文件夹,用于保存所有的网页内容,将文件夹拖拽到sublime中,便于管理
  • 显示/隐藏侧边栏方式1:查看–>侧边栏–>显示/隐藏侧边栏方式2:ctrl+K紧接着按B
  • 显示多栏方式1:查看–>布局–>列数:2列方式2:Alt+shift+2

1.3浏览器

常见的浏览器:IE浏览器微软、chrome谷歌浏览器、fifirefox火狐、safari苹果

浏览器的作用是读取html文件,并以网页的形式来显示

浏览器不会直接显示html标签,而是使用标签来解释网页的内容

2.标签

2.1标签的组成

一个完整的html标签的组成:

<标签名 属性名="属性值">内容</标签名>

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>标签</title>
	</head>
	<body  bgcolor="red" text="blue">
		html从入门到精通!
    </body>
</html>
12345678910

属性值要用双撇号括起来,一般用双引号

2.2标签的分类

根据标签是否关闭,分为,关闭型和非关闭型

  • 关闭型:有结束标签,即标签成对出现
<html></html>
<head></head>
<title></title>

非关闭型:没有结束标签

<meta>
<br>
<h1>....<h6>

根据标签是否独占一行,分为块级标签和行级标签

块级标签:显示为块状,独占一行

<h1>大家好</h1>
<hr>

行级标签:在行内显示,可与其他内容在同一行显示

<span></span>

2.3注释

注释在浏览器中不会显示,是用来标注解释html语句,但通过查看源代码的方式可以看到

语法:

<--注释内容-->

2.4实体字符

也称为特殊字符,用于显示一些特殊符号,如<>&空格等

语法:

<&实体字符的名称>

2.5文档类型

在html文档的第一行,使用<!DOCTYPE html>

声明HTML文档的类型用来告诉浏览器页面的文档嘞型,用来制定html版本的规范

目前基本上最常用的html5

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	
</body>
</html>
12345678910

三\常用标签

3.1基本标签

  • 块级标签\亲啊后有明显的间隔 |
    | h1…h5 | 标题标签 | 按照h1到h6逐渐变小.块级标签 |
    | | | |
    | | | |


  • 三、常用标签

1.基本标签

1.1 有序列表
ol:ordered listli:list item默认使用阿拉伯数字、从1开始标记,可以通过属性进行修改
· type属性:设置列表的符号标记、取值;数字1(默认)、字母(a或A)、罗马数字(i或I) · start属性:设置起始值,值必须是数字
1.2 无序列表
ul:unodered list
li:list item
默认情况下使用实心圆表作为符号标记,可以通过属性进行修改
· type属性:设置列表的符号标记、取值:disc实心圆(默认)、circle空心圆、square正方形、none不 显示项目符号
1.3 定义列表
dl:definition list
dt:definition title
dd:definition description
1.4 水平线标签
hr:horizontal
常用属性:
· color:颜色

两种方式:

颜色名称:如red、green、blue、white、black、pink、orange等

16进制的RGB表示法:Red、Green、Blue用法:#RRGGBB 每种颜色的取值范值0-255,转换为16 进制00-FF

如: #FF0000 红色 #00FF00绿色 #0000FF蓝色 #FFFFFF白色、#CCCCCC #FF7300桔色

· size:粗细,数值

· width宽度

两种写法:

​ 像素:绝对值(固定值)

​ 百分比:相对值,相对于水平线标签所在父容器宽度的百分比

· align对齐

​ 取值:center居中 left right
1.5图像标签
img:image
常见的图片格式:.jpg .png .gif .bmp
常见的属性:
· src:source指定图片的路径(来源),必选叁数

如果图片与html源代码在同一个文件夹中,可以直接在src中写图片名称即可

习惯上,我们会将多个图片与html代码文档分别放在同一个文件夹project中的不同目录下,此时需要 在src中指定图片的路径为相对路径

路径的分类:

​ · 相对路径

​ 表示: ./当前路径
…/当前位置的上一级文件夹

​ 提示:…/image

​ · alt:当图片无法显示时显示的提示信息

​ · title:当鼠标放到图片上时显示的提示信息

​ · width和 height:设置图片的宽度和高度
默认图片以原始尺寸显示

​ 如果只设置其中一个,则另一个会按比例缩放

​ 如果同时设置宽和高,可能导致图片变形

​ 两种写法:

​ 像素:绝对值(固定值)

​ 百分比:相对值,相对于父容器的尺寸的百分比
2.其他标签

为了更好语义化
3.头部标签
· meta定义网页的摘要信息,如字符编码,关键词,描述,作者等
· title定义网页的标题
· style定义内容css样式
· link引用外部css样式
· script定义或引用脚本
· base定义基础路径
默认以当前页面文件所在的位置为相对路径参照
4.标签嵌套
一个标签中嵌套另外一个标签
标签不能乱嵌套
浏览器渲染后显示的页面代码与编码时有所不同
chrome浏览器提供的开发工具:帮助开发人员查看和调试页面的
如何打开:
· Elements:从浏览器的角度来看页面,浏览器渲染页面时内部的结构
· console:控制台,显示各种警告和错误信息
· network:查看网络请求信息,浏览器向服务器请求了哪些资源,资源大小,
加载资源所消耗的时间

四、超链接
1.简介
使用超链接可以从一个页面跳转到另外一个页面,实现页面之间导航
当鼠标移动到超链接文本或图片时,鼠标箭头会变成一只小手
超链接有三种类型:
普通链接/页面间的链接,跳转到另一个页面 锚链接:链接到锚点(链接到同一个页面的指定位置) 功能链接:实现特殊功能(发邮件,下载)
2.基本用法
使用 标签来创建超链接
语法格式:

常用属性:
href:链接地址或路径,链接地址

world

链接文本或图片

1 2 3 4 5 1 target:链接打开的位置,取值

路径分类:
绝对路径 以根开始的路径
file:///D:/software/b.html https://www.baidu.com/img/bd_logo1.png
相对路径 相对于当前页面文件所在的路径,不是以根开始的路径 ./ 当前路径 …/ 当前位置上一级目录

3.锚链接
3.1简介
点击链接后跳转到某一个页面的指定位置(锚点anchor)
锚链接的分类:
页面内的锚链接 页面间的锚链接
3.2 页面内的锚链接
步骤:

  1. 定义锚点(标记)
  2. 链接锚点
    _self 自身,当前,默认值 _blank新的,空白的 _parent父层框架 _top顶层框架
    目标位置
    1 2 3 4
    1

3.3 页面间的锚链接

4.功能链接
5.URL
5.1 简介
URL:Uniform Resource Locator 统一资源定位器,用来定位资源所在的位置,最常见的就是网址

5.2 组成
一个完整的URL由8个部分组成:
协议:prococol 如 http:超文本传输协议,用来访问WEB网站Hyper text Transfer protocal https:更加安全的协议 SSL安全套接子层 ftp文件传输协议,用来访问服务器上的文件,实现文件的上传和下载File Transfer protocol file:文件协议,用来访问本地文件 主机名hostname服务器地址或服务器Netbios名称,如www.baidu.com ftp://10.255.254.254 端口:port位于主机名的后面,使用冒号进行分隔 不同的协议使用不同的端口,如http使用80端口,https使用的443端口,ftp使用20和21 如果使用的是默认端口,则端口可以省略 如果使用的不是默认端口,则必须指定端口http://59.49.32.213:7070/ 路径:path目标文件所在的路径结构,如:www.baidu.com/img/ 资源resource要访问的目标文件,如bd_logo1.png 查询字符串:query string 也称为参数 在资源后面使用?开头的一组名称/值
链接文本
链接文本
https://www.baidu.com/img/bd_logo1.png?name=tom&age=2&sex=male https://www.w3school.com.cn/html/html_quotation_elements.asp file:///C:/Users/Administrator/Desktop/project/code/09.%E5%B8%B8%E7%94%A8%E6%A0%87%E7%A D%BE3.html http://www.sxgjpx.net/ ftp://10.255.254.253/
1
1
1 2 3
4 5
名称和值之间以=分隔,多个之间用&分隔,如:name=tom&age=2&sex=male 锚点anchor,在资源后面使用#开头的文本,如#6 身份认证authentication,指定身份信息,如:ftp://账户:密码@ftp.bbshh010.com
五、表格
1.简介
表格是一个规则的行列结构,每个表格是由若干行组成,每行由若干个单元格组成
table row column
2.基本结构
2.1 table标签
用来定义表格
常用属性:
border:表格边框 默认为0 width/height:宽度/高度 bordercolor:边框的颜色 align:对齐方式,取值:left(默认) center居中 right居右 bgcolor:背景颜色 background:背景图片 cellspacing间距:单元格与单元格之间的距离 cellpadding边距:单元格中的内容到边界之间的距离
2.2 tr标签
用来定义行:table row
常用属性:
align:水平对齐 取值:left(默认) center right valign垂直对齐 取值:top center bottom bgcolor:背景颜色 background:背景图片
2.3 td标签
用来定义单元格,table data
常用属性:align、valign、bgcolor、background
注意:表格必须是由行组成,行必须由单元格来组成,数据必须放到单元格中
3.合并单元格
合并单元格也称为单元格的跨行跨列
两个属性:
rowspan 设置单元格所跨的行数 colspan 设置单元格所跨的列数
步骤:

  1. 在跨越的单元格中设置rowspan/colspan属性 2. 将被跨越的单元格删除
    必须要保证每行的实际列数是相同的,否则表格可能会出错乱
    4.高级标签
    4.1caption标签
    表格的标题标签
    4.2thead标签
    表格的头部table head
    4.3th标签
    表格的头部标题table head title
    一般用在thead中,设置头部的标题,替代td标签,与td的区别,th中的文本会加粗且居中显示
    4.4tbody标签
    表格的主体table body
    4.5tfoot标签
    表格的底部table foot




六、表单
1.简介
表单是一个包含若干个表单元素的区域,用于获取琐类型的用户数据

表单元素是允许用户在表单输入信息的元素,如文本框、密码框、单选按钮、复选框、下拉列表、按钮等
2.表单结构
2.1表单语法

1

2.2form标签
用来定义表单,可以包含多个表单元素
常用属性:
action:提交数据给谁处理,即处理数据的程序,默认为当前页面 method:提交数据的方式或方法,取值:get(默认),post get和post的区别: get:以查询字符串的形式提交,在地址栏中能看到,长度有限制,不安全 post以表单数据组的形式进行提交,在地址栏中看不到,长度无限制,安全 enctype(encode type)编码类型:提交数据的编码,取值:application/X-www-form-urlencoded(默 认)、multipart/form-data(文件上传)
3.表单元素
大多数的表单元素都是使用 标签来定义的,通过设置属性type来定义不同的表单元素

1

3.1单行文本框
常用属性:
·name名称,很重要,如果没有定义name属性,则该表单元素的数据是无法提交的

·value初始值

·size显示宽度

·maxlength:大字符数,默认是没有限制

·readonly只读:readonly=“readonly”,可简写readonly,即只写属性名

·disabled禁用:disabled=“disabled”, 可简写disabled完全禁用

表单元素被提交的两个条件,1.有name属性2.非disabled

3.2 单选按钮
常用属性:

·name名称:多个radio的name属性必须相同,才能实现互斥(单选)

·value值

·checked:是否被选中,两种状态,选中,未选中 checked=“checked” 简写 checked

3.3 复选框
常用属性与单选按钮radio类似

3.4 文件选择器
常用属性:

·name:名称

·accept设置可选择的文件类型,用来限制上传的文件类型

使用MIME格式字符串对资源类型进行限制

常见的MIME类型:

·纯文本:text/plain text/xml text/html

· 图像:image/png image/jpeg image/gif

4.特殊表单元素

4.1下拉列表

select常用属性:

·name名称

·size行数,同时显示多个选项

·multiple允许同时选择多个

option常用属性:

·value选项值

·selected设置默认选中项

optgroup常用属性:

·label分组的标签

4.2文本域

·name名称

·rows行数

·cols列数


5、其他标签

5.1 label标签

为表单元素提供标签,当选中label标签中的文本内容时会自动将光标切换到与之相关联的表单元素。

常用属性:

·for必须将该属性值设置为与相关联的表单元素的Id属性值相同。

注:几乎所有HTML标签都具有id属性,且id值必须唯一。

5.2 button标签

也表示按钮,与input按钮类似

语法:

1按钮文字或图像

常用属性:

·type按钮的类型,取值: submit(默认)、reset、button

5.3 fieldset和legend标签

fieldset标签,对表单元素进行分组

legend标签,对分组添加标题

七、内嵌框架
1、简介

使用iframe可以在一个页面中引用另一个页面,实现复用、灵活

2、基本用法

语法:

1

常用属性:

· src:引用的页面

· width/height宽度/高度 ,像素或百分比

· frameborder是否显示边框,取值:1(yes) 0(no)—默认

· scrolling是否显示滚动条,取值:yes no auto

· name属性 为框架定义名称

3、在框架中打开链接

1

2

3链接的文本或图像

八、HTML5简介

1、发展

W3C于1992年12月发布了HTML4.0.1标准
W3C于2014年10月发布了HTML5标准

2、特点

· 取消了过时的标签,如font、center等,它们仅具有展示外观的功能

· 增加了一些更具有语义化的标签,如header、footer、aside等

· 增加了一些新功能标签,如canvas、audio、video

· 增加了一些表单控件,如email、date、time、url、search等

· 可以直接在浏览器中绘画(canvas),无需flash

· 增加了本地存储的支持

3、兼容性

http://caniuse.com

提供了各种浏览器版本对HTML5和CSS规范的支持度

九、HTML5新增内容
1、结构相关的标签

用来进行页面结构布局,本身无任何特殊样式,需要使用CSS进行样式设置

· article定义一个独立的内容,完整的文章

· section定义文档的章节、段落

· header文章的头部、页眉、标题

· footer文章的底部、页脚、标注

· aside定义侧边栏

· figure图片区域

· figcaption为图片区域定义标题

· nav定义导航菜单

结构标签只是表明各部分的角色,并无实际的外观样式,与普通div相同

2、语义相关的标签
2.1 mark标签
标注,用来突出显示文本,默认添加黄色背景
2.2 time标签
定义日期和时间,便于搜索引擎智能查找
2.3 details和 summary标签
默认显示summary中的内容,点击后显示details中的内容
注:并不是所有的浏览器都兼容,chrome、opera支持、Firefox、IE浏览器不支持
2.4 meter标签
计数仪,表示度量
常用属性:

· max定义大值,默认为1

· min定义小值,默认为0

· value定义当前值

· high定义限定为高的值

· low定义限定为低的值

· optimum定义佳值

规则:

  1. 如果optimum大于high,则表示值越大越好

当value大于high时为绿色

当value在low与high之间时为黄色

当value小于low时为红色

  1. 如果optimum小于low,则表示值越小越好

当value小于low时为绿色

当value在low与high之间时为黄色

当value大于high时为红色

  1. 当optimum介于low和high之间,则表示值在low和high之间好当value在low与high之间时显示绿色,否则显示黄色

2.5 progress标签
进度条,表示运行中的进度
常用属性:

· value定义当前值

· max定义完成的值

3.表单相关
3.1 新增表单元素
新增以下type类型:

· email接收邮箱

· url接收URL

· tel接收电话号码,目前仅在移动设备上有效

· search搜索文框

· number/range接收数字/数字滑块,包含min,max,step属性

· date/month/week/time/datetime日期时间选择器,兼容性不好

· color颜色拾取

作用:

· 具有格式校验的功能

· 可以与移动设备的键盘相关联

3.2新增表单属性
form标签的属性:

· autocomplete是否启动表单的自动完成功能, 取值:on(默认)、off

· novalidate提交表单时不进行校验,默认会进行表单校验

3.3 新增表单元素的属性
新增表单元素属性:input/select/textarea等

· placeholder提示文字

· required是否必填

· autocomplete是否启用该表单元素的自动完成功能

· autofocus设置初始焦点元素

· pattern使用正则表达式(RegExp后面会讲解),进行数据校验

· list使文本元素具有下拉列表的功能,需要配合datalist和option标签一起使用

· form可以将表单元素写在form标签外面,然后通过该属性关联指定的表单

4、多媒体标签
4.1audio标签
在页面中插入音频,不同的浏览器对音频格式的支持不一样
audio常用属性:

· src音频文件的来源

· controls是否显示控制面板,默认不显示

· autoplay是否自动播放,默认不自动播放

· loop是否循环播放

· muted是否静音

· preload是否预加载,取值:none不预加载、auto预加载(默认)、metadata只加载元数据

如果设置了autoplay属性,则该属性无效

可以结合source标签使用,指定多个音频文,浏览器会检测并使用第一个可用的音频文件

4.2 video标签
在页面中插入视频,不同的浏览器对视频格式的支持不一样
用法与audio标签基本相同,增加属性:

· widht/height视频播放器的宽度/高度

· poster在视频加载前显示的图片

案例1hello.html

<html>
	<body>
		<tiele>HTML技术</tiele>
	</body>
	<body>
		大家好,欢迎学习html技术!
	</body>
</html>1234567

效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h271e4v6-1593240920352)(C:\Users\lenovo\Desktop\新建文件夹\静态网页2\案例\result\案例1.png)]

案例2标签的组成.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>标签</title>
</head>
<body text="blue">
	标签的组成
	<br>
	html从入门到精通!
	<hr>
	<h1>标签的分类</h1>
	<hr>

	<h2>标签的分类</h2>
	<hr>

	<h6>标签的分类</h6>
	<hr>

	<span>哈哈</span>嘿嘿
	
</body>
</html>1234567891011121314151617181920212223

效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jx6zJE1P-1593240920354)(C:\Users\lenovo\Desktop\新建文件夹\静态网页2\案例\result\案例2.png)]

案例3实体字符.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	图书:<<HTML从入门到精通<<
	<hr>

	北京      上海      广州
	<hr>

	在HTML中用<表示<小于号
	<hr>

	“HTML语言” 或 &qout;HTML语言&qout;
	<hr>

	版权所有© 2000-2020 高教培训
	<hr>

	×关闭符号
</body>
</html>123456789101112131415161718192021222324

效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuFLl3hm-1593240920355)(C:\Users\lenovo\Desktop\新建文件夹\静态网页2\案例\result\案例3.png)]

(剩下的下期出)

原文链接:https://blog.csdn.net/WanXuang/article/details/106982782?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160513384519724835852804%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=160513384519724835852804&biz_id=&utm_medium=distribute.pc_search_top_result.none-task-code-2~all~top_position~default-1-106982782-12.nonecase&utm_term=html

作者:WanXuang

出处:从CSDN

践过程中,我们使用Jira做统一的项目管理和缺陷管理。在测试完成之后,测试同学需要发送测试报告或者release notes给项目组成员,虽然Jira自身提供了基于release管理的release notes的自动生成,但其界面不太友好,所以有必要我们做一下二次开发。

一般测试报告的生成,采用word格式(直接套用定义好的模板,填入数据)、html格式或者mardown等格式,其中html和markdown格式非常方便程序的自动生成,我们两种方式都支持。

而在测试报告的发送这一块,可以发送一个链接,可以发送一个附件,也可以在邮件内部链接上报告的内容。如此,html格式最为适合。其中在附件类型选择上,html或者pdf都具有很好的显示效果和跨平台性。此处为了方便存档,我们增加了pdf格式的测试报告的自动生成。

wkhtmltopdf简介

其官网为:https://wkhtmltopdf.org/index.html。

首先我们复制一下官网介绍:

What is it?

wkhtmltopdf and wkhtmltoimage are open source (LGPLv3) command line tools to render HTML into PDF and various image formats using the Qt WebKit rendering engine. These run entirely "headless" and do not require a display or display service.

There is also a C library, if you're into that kind of thing.

简单翻译一下:wkhtmltopdf和wkhtmltoimage是基于开源LGPLv3协议的命令行工具,它使用Qt webkit渲染引擎把HTML渲染为PDF。运行的时候,是“无头”的并不需要显示器或者显示服务。

同时该工具也提供了基于C的库文件。

How do I use it?

  1. Download a precompiled binary or build from source
  2. Create your HTML document that you want to turn into a PDF (or image)
  3. Run your HTML document through the tool.
  4. For example, if I really like the treatment Google has done to their logo today and want to capture it forever as a PDF:
  5. wkhtmltopdf http://google.com google.pdf

再简单翻译一下使用方法:下载二进制文件或者从源文件构建;创建html文件;以google为例进行html到pdf的转换: wkhtmltopdf http://google.com google.pdf即可。

整体看,wkhtmltopdf使用起来很方便,pdf转换效果很理想,安装也简单。其下载地址为:https://wkhtmltopdf.org/downloads.html

定义html模板文件

测试报告的html模板,这里列出一些共性的东西:上线内容、缺陷统计、研发效率等,大家可以结合自身的业务需要进行添加。简单的实现,可以直接做字符串替换,麻烦点的,就可以用thymeleaf等模板文件了:

<html>
 <meta http-equiv="content-type" content="text/html;charset=utf-8">
 <head>
 <style type="text/css">
 body{
 font-size: 9pt;
 }
 table{border-collapse: collapse;width:800px;}
 tr{}
 td{border: 1px solid #0D3349;padding:5px;font-size:9pt;}
 .tr-label{
 background-color: #2D64B3;
 color:#ffffff;
 }
 a{
 text-decoration: none;
 color:#000000;
 }
 </style>
 <title>{{REPORT_TITLE}}</title>
 </head>
 <body>
 <h1>{{REPORT_TITLE}}</h1>
 <hr style="size:1px;"/>
 <p><h2>上线内容:</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">类别</td>
 <td>内容</td>
 </tr>
 {{RELEASE_NOTE}}
 </table>
 <p><h2>缺陷分析 - 根据模块:</h2></h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">模块名称</td>
 <td>个数</td>
 </tr>
 {{ISSUE_COMPONENT}}
 </table>
 <p><h2>缺陷分析 - 根据根据状态:</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">状态</td>
 <td>个数</td>
 </tr>
 {{ISSUE_STATUS}}
 </table>
 <p><h2>缺陷分析 - 根据优先级:</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">优先级</td>
 <td>个数</td>
 </tr>
 {{ISSUE_PRIORITY}}
 </table>
 <p><h2>缺陷分析 - 根据经办人:</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">经办人</td>
 <td>个数</td>
 </tr>
 {{ISSUE_ASSIGNEE}}
 </table>
 <p><h2>缺陷分析 - 根据报告人:</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">报告人</td>
 <td>个数</td>
 </tr>
 {{ISSUE_REPORTER}}
 </table>
 <p><h2>缺陷开发测试效率</h2></p>
 <table>
 <tr class="tr-label">
 <td width="150px">类别</td>
 <td>耗时(小时)</td>
 </tr>
 <tr>
 <td width="150px">开发响应平均耗时</td>
 <td>{{DEV_REACT}}</td>
 </tr>
 <tr>
 <td width="150px">开发处理平均耗时</td>
 <td>{{DEV_PROCESS}}</td>
 </tr>
 <tr>
 <td width="150px">测试响应平均耗时</td>
 <td>{{TEST_REACT}}</td>
 </tr>
 <tr>
 <td width="150px">测试处理平均耗时</td>
 <td>{{TEST_PROCESS}}</td>
 </tr>
 </table>
 </body>
</html>

结果数据的获取

上文提到,我们才用Jira做项目管理,所以测试数据的获取,主要还是使用Jira-client这个三方jar,个别地方,比如缺陷时间这一块,采用直接query数据库的方式实现以提高效率,关键代码如下(注意里面夹杂了mardown格式的):

/**
 * 获取根据jql生成的issue
 * @param jql
 * @return
 * @throws Exception
 */
public List<Issue> getIssues(String jql) throws Exception{
 Iterator<Issue> iterator = jiraClient.searchIssues(jql).iterator();
 List<Issue> list = new ArrayList<>();
 while(iterator.hasNext()){
 list.add(iterator.next());
 }
 return list;
}
/**
 * 生成报告
 * @param title
 * @param jql
 * @return
 * @throws Exception
 */
public String[] generateReport(String title, String jql) throws Exception{
 String[] content = generateReport(getIssues(jql));
 for(int i = 0; i < content.length; i ++){
 content[i] = content[i].replace("{{REPORT_TITLE}}", title);
 }
 return content;
}
/**
 * 测试报告生成pdf
 * @param title
 * @param jql
 * @throws Exception
 */
public File generateReportPdf(String title, String jql) throws Exception{
 String html = generateReport(title, jql)[1];
 LocalDateTime localDate = LocalDateTime.now();
 String date = localDate.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
 String htmlFileName = jiraReportPath + "/" + date + ".html";
 IOUtils.write(html, new FileOutputStream(new File(htmlFileName)));
 String pdfFileName = jiraReportPath + "/" + date + ".pdf";
 String cmd = wkthmltopdfCmd + " " + htmlFileName + " " + pdfFileName;
 Process process = Runtime.getRuntime().exec(cmd);
 process.waitFor();
 return new File(pdfFileName);
}
private int timediffToMinutes(String timeDiff){
 String[] info = timeDiff.split(":");
 return Integer.valueOf(info[0]) * 60 + Integer.valueOf(info[1]);
}
/**
 * 获取issue的开发、测试响应和处理耗时
 * @param issueList
 * @return
 */
private String[] issueReactProcess(List<Issue> issueList){
 List<Integer> devReact = new ArrayList<>();
 List<Integer> devProcess = new ArrayList<>();
 List<Integer> testReact = new ArrayList<>();
 List<Integer> testProcess = new ArrayList<>();
 String[] result = new String[8];
 int maxSingleDevReact = 0;
 int maxSingleDevProcess = 0;
 int maxSingleTestReact = 0;
 int maxSingleTestProcess = 0;
 for(Issue issue : issueList){
 if((issue.getIssueType().getName().equalsIgnoreCase("BUG")
 || issue.getIssueType().getName().equalsIgnoreCase("缺陷")
 || issue.getIssueType().getName().equalsIgnoreCase("故障")) && issue.getStatus().getName().equalsIgnoreCase("测试通过")){//只统计测试通过的
 log.info(">>> 添加需要统计耗时的缺陷:{}", issue.getKey());
 try {
 String sql = "select timediff(changegroup.created,jiraissue.created) from changeitem,changegroup,jiraissue where \n" +
 "changeitem.field='status' and changeitem.groupid in (select id from changegroup where issueid=" + issue.getId() + ") and changeitem.groupid=changegroup.id \n" +
 "and changegroup.issueid=jiraissue.id and changeitem.newString='处理中';";
 int singleDevReact = timediffToMinutes(jiraJdbcTemplate.queryForObject(sql, String.class));
 devReact.add(singleDevReact);
 if(singleDevReact > maxSingleDevReact){
 maxSingleDevReact = singleDevReact;
 try {
 result[4] = "<a href='" + IssueUtils.webUrl(issue) + "' target='_blank'>" + issue.getSummary() + "</a>";
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 sql = "select timediff(changegroup.created,jiraissue.created) from changeitem,changegroup,jiraissue where \n" +
 "changeitem.field='status' and changeitem.groupid in (select id from changegroup where issueid=" + issue.getId() + ") and changeitem.groupid=changegroup.id \n" +
 "and changegroup.issueid=jiraissue.id and changeitem.newString='开发完成';";
 int singleDevProcess = timediffToMinutes(jiraJdbcTemplate.queryForObject(sql, String.class));
 devProcess.add(singleDevProcess - singleDevReact);
 if(singleDevProcess > maxSingleDevProcess){
 maxSingleDevProcess = singleDevProcess;
 try {
 result[5] = "<a href='" + IssueUtils.webUrl(issue) + "' target='_blank'>" + issue.getSummary() + "</a>";
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 sql = "select timediff(changegroup.created,jiraissue.created) from changeitem,changegroup,jiraissue where \n" +
 "changeitem.field='status' and changeitem.groupid in (select id from changegroup where issueid=" + issue.getId() + ") and changeitem.groupid=changegroup.id \n" +
 "and changegroup.issueid=jiraissue.id and changeitem.newString='测试进行中';";
 int singleTestReact = timediffToMinutes(jiraJdbcTemplate.queryForObject(sql, String.class));
 testReact.add(singleTestReact - singleDevProcess);
 if(singleTestReact > maxSingleTestReact){
 maxSingleTestReact = singleTestReact;
 try {
 result[6] = "<a href='" + IssueUtils.webUrl(issue) + "' target='_blank'>" + issue.getSummary() + "</a>";
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 sql = "select timediff(changegroup.created,jiraissue.created) from changeitem,changegroup,jiraissue where \n" +
 "changeitem.field='status' and changeitem.groupid in (select id from changegroup where issueid=" + issue.getId() + ") and changeitem.groupid=changegroup.id \n" +
 "and changegroup.issueid=jiraissue.id and changeitem.newString='测试通过';";
 int singleTestProcess = timediffToMinutes(jiraJdbcTemplate.queryForObject(sql, String.class));
 testProcess.add(singleTestProcess - singleTestReact);
 if(singleTestProcess > maxSingleTestProcess){
 maxSingleTestProcess = singleTestProcess;
 try {
 result[7] = "<a href='" + IssueUtils.webUrl(issue) + "' target='_blank'>" + issue.getSummary() + "</a>";
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 } catch (DataAccessException e) {
 continue;
 }
 }
 }
 result[0] = String.valueOf(generateIssueDuration(devReact));
 result[1] = String.valueOf(generateIssueDuration(devProcess));
 result[2] = String.valueOf(generateIssueDuration(testReact));
 result[3] = String.valueOf(generateIssueDuration(testProcess));
 return result;
}
private double generateIssueDuration(List<Integer> list){
 DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics();
 for(int d : list){
 descriptiveStatistics.addValue(d / 60f);//按小时统计
 }
 double r = 0;
 try{
 r = new BigDecimal(descriptiveStatistics.getMean()).setScale(BigDecimal.ROUND_HALF_UP, 2).doubleValue();
 }catch(Exception e){
 }
 return r;
}
/**
 * 生成报告
 * @param issueList
 * @throws Exception
 */
private String[] generateReport(List<Issue> issueList) throws Exception{
 String[] issueReactProcessResult = issueReactProcess(issueList);
 Map<String, String> ldapUserMap = new HashMap<>();
 for(LdapUserEntity ldapUserEntity : ldapUserService.allUsers()){
 ldapUserMap.put(ldapUserEntity.getUserName(), ldapUserEntity.getDisplayName());
 }
 ldapUserMap.put("TBD", "TBD");
 String content = IOUtils.toString(new ClassPathResource("jira/report.md").getInputStream(), "utf-8");
 String content2 = IOUtils.toString(new ClassPathResource("jira/jira_release_report.html").getInputStream(), "utf-8");
 content2 = content2.replace("{{DEV_REACT}}", issueReactProcessResult[0]);
 content2 = content2.replace("{{DEV_PROCESS}}", issueReactProcessResult[1]);
 content2 = content2.replace("{{TEST_REACT}}", issueReactProcessResult[2]);
 content2 = content2.replace("{{TEST_PROCESS}}", issueReactProcessResult[3]);
 content2 = content2.replace("{{MAX_DEV_REACT}}", issueReactProcessResult[4]);
 content2 = content2.replace("{{MAX_DEV_PROCESS}}", issueReactProcessResult[5]);
 content2 = content2.replace("{{MAX_TEST_REACT}}", issueReactProcessResult[6]);
 content2 = content2.replace("{{MAX_TEST_PROCESS}}", issueReactProcessResult[7]);
 Map<String, List<String>> releaseNote = new HashMap<>();
 Map<String, Integer> issuePriority = new HashMap<>();
 Map<String, Integer> issueAssignee = new HashMap<>();
 Map<String, Integer> issueReporter = new HashMap<>();
 Map<String, Integer> issueStatus = new HashMap<>();
 Map<String, Integer> component = new HashMap<>();
 Comparator<Map.Entry<String, Integer>> comparator = new Comparator<Map.Entry<String, Integer>>() {
 @Override
 public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
 if(o1.getKey().equalsIgnoreCase(o2.getKey())){
 return o2.getValue().compareTo(o1.getValue());
 }
 else{
 return o1.getKey().compareTo(o2.getKey());
 }
 }
 };
 for(Issue issue : issueList){
 //生成release note
 String type = issue.getIssueType().getName();
 if(!releaseNote.keySet().contains(type)){
 releaseNote.put(type, new ArrayList<String>());
 }
 releaseNote.get(type).add(issue.getKey() + ":" + issue.getSummary());
 if("bug".equalsIgnoreCase(type) || "故障".equalsIgnoreCase(type)) {
 //统计priority
 String priority = issue.getPriority().getName();
 if (!issuePriority.keySet().contains(priority)) {
 issuePriority.put(priority, 0);
 }
 issuePriority.put(priority, issuePriority.get(priority) + 1);
 //统计assignee
 String assignee = "TBD";
 try {
 assignee = issue.getAssignee().getName();
 } catch (Exception e) {
 log.error(e.getMessage(), e);
 }
 if (!issueAssignee.keySet().contains(assignee)) {
 issueAssignee.put(assignee, 0);
 }
 issueAssignee.put(assignee, issueAssignee.get(assignee) + 1);
 //统计reporter
 String reporter = "TBD";
 try {
 reporter = issue.getReporter().getName();
 } catch (Exception e) {
 log.error(e.getMessage(), e);
 }
 if (!issueReporter.keySet().contains(reporter)) {
 issueReporter.put(reporter, 0);
 }
 issueReporter.put(reporter, issueReporter.get(reporter) + 1);
 //统计issue status
 String status = issue.getStatus().getName();
 if (!issueStatus.keySet().contains(status)) {
 issueStatus.put(status, 0);
 }
 issueStatus.put(status, issueStatus.get(status) + 1);
 //统计component
 for (net.rcarz.jiraclient.Component c : issue.getComponents()) {
 String cc = c.getName();
 if (!component.keySet().contains(cc)) {
 component.put(cc, 0);
 }
 component.put(cc, component.get(cc) + 1);
 }
 }
 }
 //issuePriority排序
 List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(issuePriority.entrySet());
 Collections.sort(list, comparator);
 StringBuilder sb = new StringBuilder();
 StringBuilder sb2 = new StringBuilder();
 sb.append("| 级别 | 个数 |\n");
 sb.append("|:----|----:|\n");
 for(Map.Entry<String, Integer> entry : list){
 sb.append("| " + entry.getKey() + " | " + entry.getValue() + " |\n");
 sb2.append("<tr><td>" + entry.getKey() + "</td><td>" + entry.getValue() + "</td></tr>");
 }
 content = content.replace("{{ISSUE_PRIORITY}}", sb.toString());
 content2= content2.replace("{{ISSUE_PRIORITY}}", sb2.toString());
 //issueassignee
 list = new ArrayList<Map.Entry<String, Integer>>(issueAssignee.entrySet());
 Collections.sort(list, comparator);
 sb = new StringBuilder();
 sb2 = new StringBuilder();
 sb.append("| 经办人 | 个数 |\n");
 sb.append("|:----|----:|\n");
 for(Map.Entry<String, Integer> entry : list){
 sb.append("| " + entry.getKey() + " | " + entry.getValue() + " |\n");
 sb2.append("<tr><td>" + ldapUserMap.get(entry.getKey()) + "</td><td>" + entry.getValue() + "</td></tr>");
 }
 content = content.replace("{{ISSUE_ASSIGNEE}}", sb.toString());
 content2 = content2.replace("{{ISSUE_ASSIGNEE}}", sb2.toString());
 //issue reporter
 list = new ArrayList<Map.Entry<String, Integer>>(issueReporter.entrySet());
 Collections.sort(list, comparator);
 sb = new StringBuilder();
 sb2 = new StringBuilder();
 sb.append("| 报告人 | 个数 |\n");
 sb.append("|:----|----:|\n");
 for(Map.Entry<String, Integer> entry : list){
 sb.append("| " + entry.getKey() + " | " + entry.getValue() + " |\n");
 sb2.append("<tr><td>" + ldapUserMap.get(entry.getKey()) + "</td><td>" + entry.getValue() + "</td></tr>");
 }
 content = content.replace("{{ISSUE_REPORTER}}", sb.toString());
 content2 = content2.replace("{{ISSUE_REPORTER}}", sb2.toString());
 //issuestatus
 list = new ArrayList<Map.Entry<String, Integer>>(issueStatus.entrySet());
 Collections.sort(list, comparator);
 sb = new StringBuilder();
 sb2 = new StringBuilder();
 sb.append("| 状态 | 个数 |\n");
 sb.append("|:----|----:|\n");
 for(Map.Entry<String, Integer> entry : list){
 sb.append("| " + entry.getKey() + " | " + entry.getValue() + " |\n");
 sb2.append("<tr><td>" + entry.getKey() + "</td><td>" + entry.getValue() + "</td></tr>");
 }
 content = content.replace("{{ISSUE_STATUS}}", sb.toString());
 content2 = content2.replace("{{ISSUE_STATUS}}", sb2.toString());
 //issue component
 list = new ArrayList<Map.Entry<String, Integer>>(component.entrySet());
 Collections.sort(list, comparator);
 sb = new StringBuilder();
 sb2 = new StringBuilder();
 sb.append("| 模块 | 个数 |\n");
 sb.append("|:----|----:|\n");
 for(Map.Entry<String, Integer> entry : list){
 sb.append("| " + entry.getKey() + " | " + entry.getValue() + " |\n");
 sb2.append("<tr><td>" + entry.getKey() + "</td><td>" + entry.getValue() + "</td></tr>");
 }
 content = content.replace("{{ISSUE_COMPONENT}}", sb.toString());
 content2 = content2.replace("{{ISSUE_COMPONENT}}", sb2.toString());
 //release note
 List<Map.Entry<String, List<String>>> list2 = new ArrayList<>(releaseNote.entrySet());
 Collections.sort(list2, new Comparator<Map.Entry<String, List<String>>>() {
 @Override
 public int compare(Map.Entry<String, List<String>> o1, Map.Entry<String, List<String>> o2) {
 return o1.getKey().compareTo(o2.getKey());
 }
 });
 sb = new StringBuilder();
 sb2 = new StringBuilder();
 Comparator<String> comparator2 = new Comparator<String>() {
 @Override
 public int compare(String o1, String o2) {
 return o1.substring(0, o1.indexOf(":")).compareTo(o2.substring(0, o2.indexOf(":")));
 }
 };
 for(Map.Entry<String, List<String>> entry : list2){
 List<String> valueList = entry.getValue();
 Collections.sort(valueList, comparator2);
 for(String v : valueList) {
 sb.append("* [ " + entry.getKey() + " ] " + v + "\n");
 sb2.append("<tr><td>" + entry.getKey() + "</td><td><a href='" + jiraIssueBrowseUrl + v.substring(0, v.indexOf(":")) + "' target='_blank'>" + v + "</a></td></tr>");
 }
 }
 content = content.replace("{{RELEASE_NOTE}}", sb.toString());
 content2 = content2.replace("{{RELEASE_NOTE}}", sb2.toString());
 return new String[]{content, content2};
}
private String jiraBugSummaryProcessUser(String user){
 String[] reporters = user.split(",");
 for(int i = 0; i < reporters.length; i ++){
 reporters[i] = "'" + reporters[i] + "'";
 }
 user = StringUtils.join(reporters, ",");
 return user;
}
/**
 *
 * @param type
 * @param user
 * @return
 */
public List<String> jiraBugSummaryLabel(String type, String user){
 user = jiraBugSummaryProcessUser(user);
 String sql = "";
 if("reporter".equalsIgnoreCase(type) || "depReporter".equalsIgnoreCase(type)){
 sql = "select distinct(date_format(a.created, '%Y%m')) as sdate from jiraissue a where a.reporter in(" + user + ")";
 }
 else if("assignee".equalsIgnoreCase(type) || "depAssignee".equalsIgnoreCase(type)){
 sql = "select distinct(date_format(a.created, '%Y%m')) as sdate from jiraissue a where a.assignee in(" + user + ")";
 }
 List<String> list = jiraJdbcTemplate.queryForList(sql, String.class);
 Collections.sort(list, new Comparator<String>() {
 @Override
 public int compare(String o1, String o2) {
 return o1.compareTo(o2);
 }
 });
 return list;
}
public List<JiraBugSummaryEntity> jiraBugSummary(String type, String user, String dep){
 user = jiraBugSummaryProcessUser(user);
 String sql = "";
 if("reporter".equalsIgnoreCase(type)){
 sql = "select count(1) as `count`, date_format(a.created, '%Y%m') as sdate, a.reporter as `user`";
 sql += " from jiraissue a where a.issuetype in ('10004', '10207') and a.reporter in(" + user + ") group by sdate,user order by sdate";
 }
 else if("assignee".equalsIgnoreCase(type)){
 sql = "select count(1) as `count`, date_format(a.created, '%Y%m') as sdate, a.assignee as `user`";
 sql += " from jiraissue a where a.issuetype in ('10004', '10207') and a.assignee in(" + user + ") group by sdate,user order by sdate";
 }
 else if("depReporter".equalsIgnoreCase(type)){
 sql = "select count(1) as `count`, date_format(a.created, '%Y%m') as sdate, '" + dep + "' as `user`";
 sql += " from jiraissue a where a.issuetype in ('10004', '10207') and a.reporter in(" + user + ") group by sdate order by sdate";
 }
 else if("depAssignee".equalsIgnoreCase(type)){
 sql = "select count(1) as `count`, date_format(a.created, '%Y%m') as sdate, '" + dep + "' as `user`";
 sql += " from jiraissue a where a.issuetype in ('10004', '10207') and a.assignee in(" + user + ") group by sdate order by sdate";
 }
 List<JiraBugSummaryEntity> list = jiraJdbcTemplate.query(sql, new BeanPropertyRowMapper<JiraBugSummaryEntity>(JiraBugSummaryEntity.class));
 return list;
}

都是一些常规用法,就不解释代码了。