我学会了,正则表达式

简介: 爬虫是**非常的**的强大,相信不少朋友都有所耳闻,它帮助我们更快地“获得”我们所要关键数据。那么,它怎么知道我们要需要什么内容?它又是如何工作的?在这篇文章里,我们一起来看看。

正则概念

RegExp 它是用于处理字符串的规则对象,用于匹配(test)和捕获(exec|match)。

注意,“ 匹配”得到的值为(true|false),而“ 捕获”得到的值是data。

正则组成

  • 元字符:常用元字符、特殊元字符、普通元字符
  • 修饰符:i m g

常用元字符

符号 语义 含义
* 0-N 0 至 任意
+ 1-N 1 至 任意
0-1 0 或者 1
{n} N 指定为 N 个
{n, m} N-M 指定为 N 至 M个
{n,} N-任意 指定为 N 至 任意个

特殊元字符

符号 语义 含义
\ 转义 普通字符->特殊字符->普通字符串
. 除 换行符 之外的任意普通字符 匹配 非\n之外的所有普通字符
^ 起始字符 以哪个元字符为起始
$ 结束字符 以哪个元字符为结束
\n 换行符 换行
\d 0-9 任意数字
\D 非0-9 非任意数字
\s 空白符 空格符、制表符、换页符等
\S 非空白符 非空格符、制表符、换页符等
\w 0-9 a-Z _ 0-9 a-Z _ 中任意一个
\W 非 0-9 a-Z _ 不为 0-9 a-Z _中任意一个
\t 制表符 tab键:四个空格
x y 表示 x 或者 y 中任意一个字符
[] 其中 [xyz] 表示 x y z 中任意一个字符
[^] 非其中 1 表示 非 x y z 中任意一个字符
[a-z] 其中范围 [a-z] 表示 a到z中任意一个字符,还可以0-9 a-Z
2 非其中范围 2 表示非 a到z中任意一个字符
() 分组 分组符号
(?:) 只匹配不捕获
(?=) 正向预查
(?!) 负向预查

普通元字符

符号 语义 含义
abcd 字符串abcd 常见的普通字符串

修饰符

console.dir(RegExp) // 去控制台看它的原型

符号 语义 含义 使用
i ignoreCase 忽略大小写 /T/i.test('test') // true
m multiline 匹配多行 /t/m.test('e \r\n t') // true
g global 全局匹配 /T/g.test('tes\r\nT') // true

正则使用

两种创建正则表达式的方式

/1/img // /1/gim
new RegExp("1", "img") // /1/gim

/\d/img // /\d/img
new RegExp("\\d", "img") // /\d/gim
正则表达式中的元字符需要由 字符串变量拼接而成时,就不要使用字面量 /1/img的方式,可以使用 new RegExp("1", 'img')的方式。
let a = "abc", b = "efg"
// 元字符需要由`字符串变量`拼接而成的
new RegExp(a + b).test('abcefg') // true

// !不要使用这种方式哟
/ab/.test("abcefg") // false

匹配

/1/.test('1234567') // true

/[0-9]/.test('1234567') // true

/[a-z]/.test('abcd') // true


/./.test("\n") // false
/./.test('a')// true

/^19$/.test('19')// true
/^19$/.test('619')// false
/^19$/.test('196')// false

捕获

/1/.exec('123132') // ["1", index: 0, input: "123132", groups: undefined]


// 匹配身份证:前六位 省市县、中间八位年月日、最后四位:前两位是所在地公安局编码,倒数第二位 奇男偶女,倒数第一位是0-9 或者 x,x表示的是10。
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(\d|x)$/
reg.exec('421126199607171957') 
// ["421126199607171957", "421126", "1996", "07", "17", "5", "7", index: 0, input: "421126199607171957", groups: undefined]

正则技巧

正则RegExp.prototype上的 test 和 exec 都能完成实现捕获。
字符串String.prototyp 上的 replace、match、split 也能实现捕获。

捕获的前提

正则和字符串要进行匹配,如果不匹配,捕获的结果就为null

let str = "test2019test2020test2021"
let reg = /\d+/
console.log(reg.lastIndex) // 0
console.log(reg.exec(str)) // ["2019", index: 4, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex) // 0

// 基于exec实现的正则捕获
// 1. 捕获到的结果为null 或者为一个数组
// 2. 为数组时
//         -  第一项: 本次捕获到的内容
//         - 其余项: 对于小分组本次单独捕获的内容
//        - index:  当前捕获内容在字符串中的起始索引
//        - input:  原始字符串
// 3. 每次执行exec只能捕获一个符合正则规则的,那么默认情况下执行一百遍,获取到的结果都是一样的
//        - 正则捕获的懒惰性:默认捕获第一个
// 4. reg.lastIndex 当前正则的下一次匹配的起始索引位置 
//        - 惰性捕获的原因:默认情况下lastIndex的值不会被修改,每一次都是从字符串的开始位置找,于是找到的永远只是第一个,默认情况下lastIndex是只读的,`无法修改`


reg = /\d+/g
console.log(reg.lastIndex)// 0
console.log(reg.exec(str))// ["2019", index: 4, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex)// 8
console.log(reg.exec(str))// ["2020", index: 12, input: "test2019test2020test2021", groups: undefined]
console.log(reg.lastIndex)// 16

//  5. 使用全局匹配时,就能消除正则捕获的懒惰行了,所以解决方法就是全局匹配符·`g`
//         - 全部都捕获到之后,再匹配会返回null,并且lastIndex还会重置为0。
//      - 这就会形成了一个循环,再匹配就是重新开始匹配了。

实现一个匹配全部内容execAll

通过exec方法加全局匹配符g来实现execAll。

~function() {
  function execAll (str) {
    if (!this.global) {
      console.error('未使用全局匹配符 g,只能匹配第一个')
      return this.exec(str)
    }
    let result = []
    let current = this.exec(str)
    while (current) {
      result.push(current[0])
      current = this.exec(str)
    }
    
    return result.length ? null : result
  }
  RegExp.prototype.execAll = execAll
}();

let str = "test2019test2020test2021"
let reg = /\d+/g
reg.execAll(str) // ["2019", "2020", "2021"]

字符串的match方法等价楼上实现的execAll,并且execAll的实现也是模仿字符串的match来实现的,必须配合全局修饰符g来使用。

let str = "test2019test2020test2021"
let reg = /\d+/g
str.match(reg)  // ["2019", "2020", "2021"]
str.matchAll(reg) // 返回的是一个迭代器,迭代器中的每一次next()的结果是每一次reg.exec的结果,并且必须配合修饰符g来使用。

test的捕获

let str = '{2021}年{4}月{5}日'
let reg = /\{(\d+)\}/g

console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2021
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 4
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 5
console.log(reg.test(str)) // false
console.log(RegExp.$1) // 5
console.log(reg.test(str)) // true
console.log(RegExp.$1) // 2021

test 捕获注意

和exec一样,test也能实现捕获,只不过它是通过Reg.Exp.$1等等特殊变量来获取每次匹配到的内容,$1表示第一个分组中的内容,RegExp.$1 - RegExp.$9 获取当前本次正则匹配后,第一个到第九个分组的信息。
。如果没有分组的话,就如下图所示,通过$& 或者lastMatch来获取匹配到的内容。

正则贪婪

正则又懒惰又贪婪,懒惰用 g,贪婪用 ?

正则匹配的贪婪性

使用 + 等量词 ,默认情况下,正则捕获的时候,获取的是按照当前正则所匹配的最长结果。
let str = "test2019test2020test2021"
let reg = /\d+/g
console.log(str.match(reg)) // ["2019", "2020", "2021"]

消除正则的贪婪性

在量词后面使用 ? ,正则捕获时,获取的是按照当前正则所匹配的最短结果
let str = "test2019test2020test2021"
let reg = /\d+?/g
console.log(str.match(reg)) //  ["2", "0", "1", "9", "2", "0", "2", "0", "2", "0", "2", "1"]

?在正则中的五种作用

  • 问号左边是非量词元字符:本身代表量词元字符,左边的字符要出现 0 至 1 次
  • 问号左边是量词元字符:取消捕获时候的贪婪性
  • (?:):只匹配不捕获
  • (?=):正向预查 (只匹配不捕获),必须条件符合
  • (?!):负向预查 (只匹配不捕获),必须条件不符合

字符串的replace

全部替换

let str = "test2019test2020test2021"
let reg = /test/g
console.log(str.replace(reg, '测试')) // 测试2019测试2020测试2021

替换时使用反向引用

let str = "2021-04-05"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/

str.replace(reg, '$1年$2月$3日')
console.log(str.replace(reg, '$1年$2月$3日')) // 2021年04月05日
console.log(str.replace(reg, (...args) => {
  console.log('args', args)  //  ["2021-04-05", "2021", "04", "05", 0, "2021-04-05"]
  const [big , $1, $2, $3] = args
   
  return $1 + '年' + $2 + '月' + $3 + '日'
})) // 2021年04月05日


// 看下面replace方法,实际上和exec的返回结果几乎一样
let str = "2021-04-05"
let reg = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
str.replace(reg, (...args) => {
  console.log('args', args) //  ["2021-04-05", "2021", "04", "05", 0, "2021-04-05"]
})
reg.exec(str) // ["2021-04-05", "2021", "04", "05", index: 0, input: "2021-04-05", groups: undefined]

首字符大写

let str = "what are you how are you who are you"
let reg = /\b([a-zA-Z])[a-zA-Z]*\b/g
console.log(str.replace(reg, (...args) => {
  let [big, $1] = args
  $1 = $1.toUpperCase()
  big = big.substring(1)
  return $1+ big
})) // What Are You How Are You Who Are You

获取字符串中出现次数最多的字符

{
  let str = "whatareyouhowareyouwhoareyou"
  // 分割再排序和重组
  str = str.split('').sort((a, b) => a.localeCompare(b)).join("") // "aaaaeeehhhooooorrrtuuuwwwyyy"
  // 会匹配出现连续1次以上的字符,默认贪婪模式
  let reg = /([a-zA-Z])\1+/g
  let result = str.match(reg) // ["aaaa", "eee", "hhh", "ooooo", "rrr", "uuu", "www", "yyy"]
  console.log(result.sort((a, b) => b.length - a.length)) // ["ooooo", "aaaa", "eee", "hhh", "rrr", "uuu", "www", "yyy"]
}

{
  let str = "whatareyouhowareyouwhoareyou"
  // 分割再排序和重组
  str = str.split('').sort((a, b) => a.localeCompare(b)).join("") // "aaaaeeehhhooooorrrtuuuwwwyyy"
  let max = 0,res = [], flag = false;
  // 自定义排序,从最大找到最小
  for (let i = str.length; i > 0; i --) {
      let reg = new RegExp("([a-zA-Z])\\1{"+(i-1)+"}", 'g')
      str.replace(reg, (content, $1) => {
          res.push($1)
          max = i
          flag = true
      })
      if (flag) break;
  }

  console.log('出现次数最多的字符是 '+ res, '出现次数是' + max)
}

时间字符格式化


~function () {
  // formatTime: 时间字符串的格式化
  function formatTime (template) {
    // 首先获取时间字符串中的年月日等信息
    let timeAry = this.match(/\d+/g)
    template = template || "{0}年{1}月{2}日 {3}时{4}分{5}秒"
    template = template.replace(/\{(\d+)\}/g, (content, $1) => {
      let time = timeAry[$1] || "00"
      time.length < 2 ? time = "0" + time : null
      return time
    })
    
    return template
  }
  
  function queryURLParams () {}
  
  // 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
  ["formatTime", "queryURLParams"].forEach(item => {
    // String.prototype["formatTime"] = eval("formatTime");
    String.prototype[item] = eval(item);
  })
}()

let str = "2021-04-05 23:36:2"
console.log(str.formatTime()) // 2021年04月05日 23时36分02秒
console.log(str.formatTime("{1}月{2}日")) // 04月05日
console.log(str.formatTime("{1}/{2} {3}:{4}")) // 04/05 23:36

URL参数格式化


~function () {
  function formatTime (template) {
  }
  
  // URL参数格式化
  function queryURLParams () {
    let obj = {}
    this.replace(/([^?=&#]+)=([^?=&#]+)/g, (...[, $1, $2]) => {
          obj[$1] = $2
    })
    
    this.replace(/#([^?=&#]+)/g, (...[, $1]) => {
      obj['HASH'] = $1
    })
    
    
    return obj
  }
  
  // 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
  ["formatTime", "queryURLParams"].forEach(item => {
    // String.prototype["formatTime"] = eval("formatTime");
    String.prototype[item] = eval(item);
  })
}()

let str = "http://www.hao6.website:888/?id=999&name=zs#video"
console.log(str.queryURLParams()) // {id: "999", name: "zs", HASH: "video"}

大数字千分位格式化


~function () {
  function formatTime (template) {
  }
  
  function queryURLParams () {
  }
  
  function millimeter () {
      let result = ''
      result = this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => {
        return content + ','
      })

      return result
  }
  
  // 扩展到内置类 String.prototype上,这种方式是循环往原型上扩展方法
  ["formatTime", "queryURLParams", "millimeter"].forEach(item => {
    // String.prototype["formatTime"] = eval("formatTime");
    String.prototype[item] = eval(item);
  })
}()

let str = "1234193949"
console.log(str.millimeter()) // 1,234,193,949

爬虫

通过网络请求获取你想要的内容字符串,然后通过以上讲述的正则技巧来捕获你需要的那内容,之后保存起来,需要用的时候,再读取、运算,最后经过自定义的筛选,最终渲染出你想要的结果。


  1. xyz
  2. a-z
目录
相关文章
|
1月前
正则表达式
正则表达式
66 36
|
6月前
|
索引 Python
正则表达式详解
正则表达式详解
|
数据库
几种常用的正则表达式
几种常用的正则表达式
108 0
|
人工智能 数据安全/隐私保护
一些常用的正则表达式
今天在写一些输入验证的时候用到了正则表达式,现在就回顾一下我们常用的正则表达式,对于正则表达式的写法很多种,看个人的习惯了,我的写法也许不是很好,但可以满足基本需求。
110 0
什么是正则表达式?
什么是正则表达式?
100 0
|
机器学习/深度学习 JavaScript
详解 正则表达式
详解 正则表达式
详解 正则表达式
|
Windows
正则表达式汇总
常用正则表达式
194 0
|
机器学习/深度学习 Windows JavaScript