avaScript 的 Rest 和 Spread 操作符自 ES6 引入以来,大大改变了开发者处理数组和对象的方式。这些操作符提供了更简洁、更易读的语法,使代码更易于理解和维护。本文将介绍如何有效地使用 Rest 和 Spread 操作符,并通过示例进行说明。
Rest 操作符(...)允许你将多个元素收集到一个数组或对象中,通常用于函数参数中,将参数列表转换为数组。而 Spread 操作符(...)则用于将数组或对象展开为单个元素,这在复制、合并或传递数组和对象时特别有用。
理解并掌握这些操作符可以大大提升你的 JavaScript 编码水平,使代码更加简洁和富有表现力。
函数参数处理
Rest 操作符在函数定义中非常有用,可以处理不定数量的参数:
function multiply(...numbers) {
return numbers.reduce((product, number) => product * number, 1);
}
console.log(multiply(2, 3, 4)); // 输出: 24
在这个例子中,multiply 函数接受任意数量的参数并进行相乘。Rest 操作符将所有参数收集到 numbers 数组中,使得可以轻松应用 reduce 等数组方法。
数组解构
Rest 操作符还可以在数组解构中使用,将剩余元素收集到一个新数组中:
const [head, ...tail] = ['a', 'b', 'c', 'd'];
console.log(head); // 输出: 'a'
console.log(tail); // 输出: ['b', 'c', 'd']
在这个例子中,数组的第一个元素被赋值给 head,其余元素则被收集到 tail 数组中。
数组合并
使用 Spread 操作符,数组合并变得非常简单:
const fruits = ['apple', 'banana'];
const vegetables = ['carrot', 'potato'];
const food = [...fruits, ...vegetables];
console.log(food); // 输出: ['apple', 'banana', 'carrot', 'potato']
在这个例子中,Spread 操作符将 fruits 和 vegetables 展开为单个元素并合并到 food 数组中。
数组复制
创建数组副本同样很方便:
const numbers = [1, 2, 3];
const numbersCopy = [...numbers];
console.log(numbersCopy); // 输出: [1, 2, 3]
这样创建了一个包含与 numbers 数组相同元素的新数组 numbersCopy,修改 numbersCopy 不会影响 numbers。
对象合并
Spread 操作符还可以用于对象的合并:
const person = { name: 'Alice', age: 25 };
const job = { title: 'developer', company: 'Tech Co.' };
const employee = { ...person, ...job };
console.log(employee); // 输出: { name: 'Alice', age: 25, title: 'developer', company: 'Tech Co.' }
在这个例子中,person 和 job 被合并到 employee 对象中,形成一个新的对象。
对象解构中的 Rest 操作符
Rest 操作符可以在对象解构中使用,收集剩余的属性:
const { title, ...details } = { title: 'Book', author: 'John Doe', year: 2021 };
console.log(title); // 输出: 'Book'
console.log(details); // 输出: { author: 'John Doe', year: 2021 }
这个例子展示了如何提取特定属性(title),同时将剩余属性收集到 details 对象中。
Spread 操作符用于函数参数
在调用接受多个参数的函数时,Spread 操作符可以简化传递数组元素作为参数:
function concatenate(str1, str2, str3) {
return str1 + str2 + str3;
}
const words = ['Hello', ' ', 'World!'];
console.log(concatenate(...words)); // 输出: 'Hello World!'
通过展开 words 数组,每个元素作为独立参数传递给 concatenate 函数。
JavaScript 中的 Rest 和 Spread 操作符是强大的工具,可以极大地增强代码的灵活性和可读性。不论是处理数组、对象还是函数参数,这些操作符都能提供优雅的解决方案。通过掌握这些操作符,你可以编写更简洁、高效的代码,提升整体开发体验。
理解并有效使用 Rest 和 Spread 操作符,不仅可以简化当前项目,还能为未来的挑战做好准备,使你的 JavaScript 代码更易维护和表达。
HTML超文本标记语言是应用于网页端的页面标记语言,它通过在文本中添加标记,浏览器依据标签的不同,按照不同的方式显示内容,组织内容结构。HTML是作为一名前端开发工程师必须精通的语言。
html
HTML有W3C组织维护,目前已经发展到HTML5,大部分的桌面浏览器和几乎全部的移动端浏览器都已经支持了HTML5,但也还有少数的浏览器并不支持。因此学习者对于HTML5之前的HTML版本也需要有了解,特别HTML4,和最常见的HTML4.01。HTML的内容较为简单,掌握起来也十分容易,但编写出优秀的html结构却还是有难度。
HTML标签大致可以分为章节标签、文本标签、组标签、资源标签、表格标签、表单标签,共计一百多个标签,剔除不常用和不推荐使用的标签,还有部分支持度还不高的HTML5标签,也就只剩下七八十个常用标签了。记忆这些标签及其相关属性是精通HTML的第一步。
html标签
自从css开始大量使用之后,HTML中直接嵌套属性的使用已经越来越少。我们这里提到的HTML属性是指HTML全局属性,这些属性用于赋予元素意义和语境。编写过html的同学应该都知道id、class、style、title等常用的HTML属性。
html属性
语义化是指用正确的标签描述页面。HTML语言使用中最难的莫过于使用合适的标签和属性优雅的描述页面。语义化是html超文本标记语言学习的进阶内容,在编写或参考到了大量的网页结构之后,会得到很大的提升。
实体字符是在html语言中,例如空格、引号、大于号、小于号等一些已经被html语言标准所占用的字符,需要用特定的字符组合来表现。html编码过程中你编写了多个空格,但是在最终页面输出时只会有一个空格,这就是因为键盘上输入的空格如果不经过特殊的标签处理,或者未使用实体字符来表示,当你在编码时使用 ;来表示空格,页面上就会按照你的目的显示空格。
html甚至没有被归入编程语言中,而只是作为一种标记语言存在,就是因为它太简单了,学习起来异常容易,但是我们看高手程序员编写的html页面,却总是会惊讶一个标签怎么还会有如此用法。
掌握了上面的四块内容,就可以算是基本掌握了HTML超文本标记语言。
本文仅代表个人观点!欢迎关注!
x1 工具准备
工欲善其事必先利其器,爬取语料的根基便是基于python。
我们基于python3进行开发,主要使用以下几个模块:requests、lxml、json。
简单介绍一个各模块的功能
01|requests
requests是一个Python第三方库,处理URL资源特别方便。它的官方文档上写着大大口号:HTTP for Humans(为人类使用HTTP而生)。相比python自带的urllib使用体验,笔者认为requests的使用体验比urllib高了一个数量级。
我们简单的比较一下:
urllib:
1import urllib2 2import urllib 3 4URL_GET = "https://api.douban.com/v2/event/list" 5#构建请求参数 6params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'}) 7 8#发送请求 9response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params) 10#Response Headers 11print(response.info()) 12#Response Code 13print(response.getcode()) 14#Response Body 15print(response.read()) 复制代码
requests:
1import requests 2 3URL_GET = "https://api.douban.com/v2/event/list" 4#构建请求参数 5params = {'loc':'108288','day_type':'weekend','type':'exhibition'} 6 7#发送请求 8response = requests.get(URL_GET,params=params) 9#Response Headers 10print(response.headers) 11#Response Code 12print(response.status_code) 13#Response Body 14print(response.text)复制代码
我们可以发现,这两种库还是有一些区别的:
1. 参数的构建:urllib需要对参数进行urlencode编码处理,比较麻烦;requests无需额外编码处理,十分简洁。
2. 请求发送:urllib需要额外对url参数进行构造,变为符合要求的形式;requests则简明很多,直接get对应链接与参数。
3. 连接方式:看一下返回数据的头信息的“connection”,使用urllib库时,"connection":"close",说明每次请求结束关掉socket通道,而使用requests库使用了urllib3,多次请求重复使用一个socket,"connection":"keep-alive",说明多次请求使用一个连接,消耗更少的资源
4. 编码方式:requests库的编码方式Accept-Encoding更全,在此不做举例
综上所诉,使用requests更为简明、易懂,极大的方便我们开发。
02|lxml
BeautifulSoup是一个库,而XPath是一种技术,python中最常用的XPath库是lxml。
当我们拿到requests返回的页面后,我们怎么拿到想要的数据呢?这个时候祭出lxml这强大的HTML/XML解析工具。python从不缺解析库,那么我们为什么要在众多库里选择lxml呢?我们选择另一款出名的HTML解析库BeautifulSoup来进行对比。
我们简单的比较一下:
BeautifulSoup:
1from bs4 import BeautifulSoup #导入库 2# 假设html是需要被解析的html 3 4#将html传入BeautifulSoup 的构造方法,得到一个文档的对象 5soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8') 6#查找所有的h4标签 7links = soup.find_all("h4") 复制代码
lxml:
1from lxml import etree 2# 假设html是需要被解析的html 3 4#将html传入etree 的构造方法,得到一个文档的对象 5root = etree.HTML(html) 6#查找所有的h4标签 7links = root.xpath("//h4") 复制代码
我们可以发现,这两种库还是有一些区别的:
1. 解析html: BeautifulSoup的解析方式和JQ的写法类似,API非常人性化,支持css选择器;lxml的语法有一定的学习成本
2. 性能:BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多;而lxml只会局部遍历,另外lxml是用c写的,而BeautifulSoup是用python写的,明显的性能上lxml>>BeautifulSoup。
综上所诉,使用BeautifulSoup更为简明、易用,lxml虽然有一定学习成本,但总体也很简明易懂,最重要的是它基于C编写,速度快很多,对于笔者这种强迫症,自然而然就选lxml啦。
03|json
python自带json库,对于基础的json的处理,自带库完全足够。但是如果你想更偷懒,可以使用第三方json库,常见的有demjson、simplejson。
这两种库,无论是import模块速度,还是编码、解码速度,都是simplejson更胜一筹,再加上兼容性 simplejson 更好。所以大家如果想使用方库,可以使用simplejson。
0x2 确定语料源
将武器准备好之后,接下来就需要确定爬取方向。
以电竞类语料为例,现在我们要爬电竞类相关语料。大家熟悉的电竞平台有企鹅电竞、企鹅电竞和企鹅电竞(斜眼),所以我们以企鹅电竞上直播的游戏作为数据源进行爬取。
我们登陆企鹅电竞官网,进入游戏列表页,可以发现页面上有很多游戏,通过人工去写这些游戏名收益明显不高,于是我们就开始我们爬虫的第一步:游戏列表爬取。
1import requests 2from lxml import etree 3 4# 更新游戏列表 5def _updateGameList(): 6 # 发送HTTP请求时的HEAD信息,用于伪装为浏览器 7 heads = { 8 'Connection': 'Keep-Alive', 9 'Accept': 'text/html, application/xhtml+xml, */*', 10 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3', 11 'Accept-Encoding': 'gzip, deflate', 12 'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' 13 } 14 # 需要爬取的游戏列表页 15 url = 'https://egame.qq.com/gamelist' 16 17 # 不压缩html,最大链接时间为10妙 18 res = requests.get(url, headers=heads, verify=False, timeout=10) 19 # 为防止出错,编码utf-8 20 res.encoding = 'utf-8' 21 # 将html构建为Xpath模式 22 root = etree.HTML(res.content) 23 # 使用Xpath语法,获取游戏名 24 gameList = root.xpath("//ul[@class='livelist-mod']//li//p//text()") 25 # 输出爬到的游戏名 26 print(gameList) 复制代码
当我们拿到这几十个游戏名后,下一步就是对这几十款游戏进行语料爬取,这时候问题就来了,我们要从哪个网站来爬这几十个游戏的攻略呢,taptap?多玩?17173?在对这几个网站进行分析后,发现这些网站仅有一些热门游戏的文章语料,一些冷门或者低热度的游戏,例如“灵魂筹码”、“奇迹:觉醒”、“死神来了”等,很难在这些网站上找到大量文章语料,如图所示:
我们可以发现,“ 奇迹:觉醒”、“灵魂筹码”的文章语料特别少,数量上不符合我们的要求。 那么有没有一个比较通用的资源站,它拥有着无比丰富的文章语料,可以满足我们的需求。
其实静下心来想想,这个资源站我们天天都有用到,那就是百度。我们在百度新闻搜索相关游戏,拿到搜索结果列表,这些列表的链接的网页内容几乎都与搜索结果强相关,这样我们数据源不够丰富的问题便轻松解决了。但是此时出现了一个新的问题,并且是一个比较难解决的问题——如何抓取到任意网页的文章内容?
因为不同的网站都有不同的页面结构,我们无法与预知将会爬到哪个网站的数据,并且我们也不可能针对每一个网站都去写一套爬虫,那样的工作量简直难以想象!但是我们也不能简单粗暴的将页面中的所有文字都爬下来,用那样的语料来进行训练无疑是噩梦!
经过与各个网站斗智斗勇、查询资料与思索之后,终于找到一条比较通用的方案,下面为大家讲一讲笔者的思路。
0x3 任意网站的文章语料爬取
01|提取方法
1)基于Dom树正文提取
2)基于网页分割找正文块
3)基于标记窗的正文提取
4)基于数据挖掘或机器学习
5)基于行块分布函数正文提取
02|提取原理
大家看到这几种是不是都有点疑惑了,它们到底是怎么提取的呢?让笔者慢慢道来。
1)基于Dom树的正文提取:
这一种方法主要是通过比较规范的HTML建立Dom树,然后地柜遍历Dom,比较并识别各种非正文信息,包括广告、链接和非重要节点信息,将非正文信息抽离之后,余下来的自然就是正文信息。
但是这种方法有两个问题
① 特别依赖于HTML的良好结构,如果我们爬取到一个不按W3c规范的编写的网页时,这种方法便不是很适用。
② 树的建立和遍历时间复杂度、空间复杂度都较高,树的遍历方法也因HTML标签会有不同的差异。
2) 基于网页分割找正文块 :
这一种方法是利用HTML标签中的分割线以及一些视觉信息(如文字颜色、字体大小、文字信息等)。
这种方法存在一个问题:
① 不同的网站HTML风格迥异,分割没有办法统一,无法保证通用性。
3) 基于标记窗的正文提取:
先科普一个概念——标记窗,我们将两个标签以及其内部包含的文本合在一起成为一个标记窗(比如 <h1>我是h1</h1> 中的“我是h1”就是标记窗内容),取出标记窗的文字。
这种方法先取文章标题、HTML中所有的标记窗,在对其进行分词。然后计算标题的序列与标记窗文本序列的词语距离L,如果L小于一个阈值,则认为此标记窗内的文本是正文。
这种方法虽然看上去挺好,但其实也是存在问题的:
① 需要对页面中的所有文本进行分词,效率不高。
② 词语距离的阈值难以确定,不同的文章拥有不同的阈值。
4)基于数据挖掘或机器学习
使用大数据进行训练,让机器提取主文本。
这种方法肯定是极好的,但是它需要先有html与正文数据,然后进行训练。我们在此不进行探讨。
5)基于行块分布函数正文提取
对于任意一个网页,它的正文和标签总是杂糅在一起。此方法的核心有亮点:① 正文区的密度;② 行块的长度;一个网页的正文区域肯定是文字信息分布最密集的区域之一,这个区域可能最大(评论信息长、正文较短),所以同时引进行块长度进行判断。
实现思路:
① 我们先将HTML去标签,只留所有正文,同时留下标签取出后的所有空白位置信息,我们称其为Ctext;
② 对每一个Ctext取周围k行(k<5),合起来称为Cblock;
③ 对Cblock去掉所有空白符,其文字总长度称为Clen;
④ 以Ctext为横坐标轴,以各行的Clen为纵轴,建立坐标系。
以这个网页为例: http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm 该网页的正文区域为145行至182行。
由上图可知,正确的文本区域全都是分布函数图上含有最值且连续的一个区域,这个区域往往含有一个骤升点和一个骤降点。因此,网页正文抽取问题转化为了求行块分布函数上的骤升点和骤降点两个边界点,这两个边界点所含的区域包含了当前网页的行块长度最大值并且是连续的。
经过大量实验,证明此方法对于中文网页的正文提取有较高的准确度,此算法的优点在于,行块函数不依赖与HTML代码,与HTML标签无关,实现简单,准确率较高。
主要逻辑代码如下:
1# 假设content为已经拿到的html 2 3# Ctext取周围k行(k<5),定为3 4blocksWidth = 3 5# 每一个Cblock的长度 6Ctext_len = [] 7# Ctext 8lines = content.split('n') 9# 去空格 10for i in range(len(lines)): 11 if lines[i] == ' ' or lines[i] == 'n': 12 lines[i] = '' 13# 计算纵坐标,每一个Ctext的长度 14for i in range(0, len(lines) - blocksWidth): 15 wordsNum = 0 16 for j in range(i, i + blocksWidth): 17 lines[j] = lines[j].replace("\s", "") 18 wordsNum += len(lines[j]) 19 Ctext_len.append(wordsNum) 20# 开始标识 21start = -1 22# 结束标识 23end = -1 24# 是否开始标识 25boolstart = False 26# 是否结束标识 27boolend = False 28# 行块的长度阈值 29max_text_len = 88 30# 文章主内容 31main_text = [] 32# 没有分割出Ctext 33if len(Ctext_len) < 3: 34 return '没有正文' 35for i in range(len(Ctext_len) - 3): 36 # 如果高于这个阈值 37 if(Ctext_len[i] > max_text_len and (not boolstart)): 38 # Cblock下面3个都不为0,认为是正文 39 if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0): 40 boolstart = True 41 start = i 42 continue 43 if (boolstart): 44 # Cblock下面3个中有0,则结束 45 if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0): 46 end = i 47 boolend = True 48 tmp = [] 49 50 # 判断下面还有没有正文 51 if(boolend): 52 for ii in range(start, end + 1): 53 if(len(lines[ii]) < 5): 54 continue 55 tmp.append(lines[ii] + "n") 56 str = "".join(list(tmp)) 57 # 去掉版权信息 58 if ("Copyright" in str or "版权所有" in str): 59 continue 60 main_text.append(str) 61 boolstart = boolend = False 62# 返回主内容 63result = "".join(list(main_text)) 复制代码
0x4 结语
至此我们就可以获取任意内容的文章语料了,但这仅仅是开始,获取到了这些语料后我们还需要在一次进行清洗、分词、词性标注等,才能获得真正可以使用的语料。
*请认真填写需求信息,我们会在24小时内与您取得联系。