# Java 正则和爬虫

# 一、正则表达式

定义:正则表达式是一些用来匹配和处理文本的字符串

正则表达式在线测试 | 菜鸟工具 (runoob.com) (opens new window)

正则表达式学习 以简单的方式学习正则表达式 (github.com) (opens new window)

# 1.1 正则的基础规则

  1. 元字符

    . 匹配除换行符(\n)以外的任何单个字符
    w 匹配字母、数字、下划线、汉字
    s 匹配任意空白字符(包括空格、制表符、换页符等)
    d 匹配数字,匹配单词的开始或结束
    ^ 匹配字符串的开始
    $ 匹配字符串的结束
    
    1
    2
    3
    4
    5
    6
  2. 重复限定符

    * 重复0次或更多次
    + 重复1次或更多次
    ? 重复0次或1次
    {n} 重复n次
    {n,} 重复n次或更多次
    {n,m} 重复n到m次
    
    1
    2
    3
    4
    5
    6
  3. 分组和条件或 ^(130|131|132)d{8}$ 分组就是() 或就是|

    这里可以获取多个组规则的数据,比如抓取手机号、座机号、邮箱、400电话等

    (手机号)|(座机号)|(邮箱)|(400电话)

    **分组规则:**是以左括号为基准的 (\d+)(\d+)(\d+) #每个小括号就是一组,分别为 (第一组)(第二组)(第三组) (\d+(\d+))(\d+) #分别为 (第一组(第二组))(第三组)

    捕获分组(后续还用本组数据,例如\\组号$组号)

    非捕获分组(后续不适用,仅把数据括起来,无法使用组号):

    非捕获分组 含义 举例: str = "Java Java8 Java11 Java17";
    (?:正则) 获取所有 Java(?:8|11|17) // Java8 Java11 Java17
    (?=正则) 获取前面部分 Java(?=8|11|17)// Java Java Java
    (?!正则) 获取不是指定内容的前面部分 Java(?!8|11|17) // Java

    例子

    public static void main(String[] args) throws Exception {
            /*
             * 需求1:判断一个字符串开始字符和结束字符是否一致
             * 比如: a123a  &abc&  a123b(不一致)
             */
            // \\组号:表示把第几组的内容拿出来再用一次
            String regex = "(.).+\\1";
            System.out.println("a123a".matches(regex)); // true
            System.out.println("&abc&".matches(regex)); // true
            System.out.println("a123b".matches(regex)); // false
            
            /*
             * 需求2:判断一个字符串开始部分和结束部分是否一致
             * 比如: ax123ax  &1xx4561xx&  axx123bxs(不一致)
             */
            // \\组号:表示把第几组的内容拿出来再用一次
            String regex = "(.+).+\\1";
            System.out.println("ax123ax".matches(regex));     // true
            System.out.println("&1xx4561xx&".matches(regex)); // true
            System.out.println("axx123bxs".matches(regex));   // false
            
            /*
             * 需求3:判断一个字符串开始部分和结束部分是否一致? 开始部分内部每个字符也要一致
             * 比如: www123www  &&x4561xx&&  axx123bax(不一致)
             */
            //String regex = "(.)\\1*"; 正则解释:
            //(.):表示首字符看作一组
            //\\1:表示把第1组的内容拿出来再用一次
            //*:作用于\\1,表示后面重复的内容出现0次或多次
            String regex = "((.)\\2*).+\\1";
            System.out.println("www123www".matches(regex));   // true
            System.out.println("&&x4561xx&&".matches(regex)); // true
            System.out.println("axx123bax".matches(regex));   // false
            
            
            /*
             * 需求3:将字符串:我要学学编编编编编编程程程程程程程程程程
             *       替换为:我要学编程
             */
            String str = "我要学学编编编编编编程程程程程程程程程程";
            //(.):表示任意的第一个字符
            //\\1+:\\1表示第一组再次使用, +表示至少1次
            //$1:表示正则表达式中的第一组内容
            String s = str.replaceAll("(.)\\1+", "$1");
            System.out.println(s);
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
  4. 区间: 比如0-9里任意 [0-9] a-z/A-Z里任意 [a-zA-Z] bai123里任意 [bai123]

# 1.2 常见正则例子

#1.匹配号码段为130/131/132的11位手机号
^(130|131|132)d{8}$  
#2.匹配网址url
(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] 
#3.邮箱正则 baijq@aliyun.com baijq@tsinghua.edu.cn
\w+@[\w&&[^_]]{2,6}(\.[a-zA-Z]{2,3}){1,2})
#4.简易身份证号码
[1-9]\d{16}(\d|X|x)
#5.400电话正则
400-?[1-9]\d{2}-?[1-9]\d{3}
#6.24小时时间正则 10:23:56 23:12:17
([01]\d|2[0-3]):[0-5]\d:[0-5]\d  或者  ([01]\d|2[0-3])(:[0-5]\d){2}
1
2
3
4
5
6
7
8
9
10
11
12

# 1.3 Java里基础使用

/**
     * regex正则匹配的最基础使用方式
     * <p>
     * Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
     * 因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
     * <p>
     * 要求:找出里面所有的JavaXX
     */
private static void baseRegex() {
    String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,\n" +
        "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

    //1.获取正则表达式对象
    Pattern p = Pattern.compile("Java\\d{0,2}");
    //2.获取文本匹配器对象
    Matcher m = p.matcher(str);

    //3.循环获取符合规则的子串
    while (m.find()) {
        String s = m.group();
        System.out.println(s);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

读取文件里的txt文档,提取符合规则的数据写入文件

//读取文本文档为String
private static String readFile(String pathName) throws IOException {
    StringBuffer sb = new StringBuffer();
    try (BufferedReader br = new BufferedReader(new FileReader(pathName))) {
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line).append("\r\n");
        }
        System.out.println("读取文件完成");
    }
    return sb.toString();
}
//写入文件
private static void writeFile(String pathName, String data) throws IOException {
    //文件不存在的话新建,存在覆盖
    File file = new File(pathName);
    file.createNewFile();
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
        bw.write(data);
        bw.flush();
        System.out.println("文件写入完成");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 1.4 有条件匹配

  • 获取版本号为8,11,17的Java文本,只要Java不显示版本号 ((?i)Java)(?=8|11|17)
  • 获取版本号为8,11,17的Java文本,正确结果为:Java8 Java11 Java17 ((?i)Java)(?:8|11|17) 或者 ((?i)Java)(8|11|17)
  • 获取除了版本号为8,11,17的Java文本 ((?i)Java)(?!8|11|17)
public static void main(String[] args) throws Exception {
    String s = "java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和JAva11," +
            "因为这两个是长期支持版本,下一个长期支持版本是JAVa17,相信在未来不久JAVA17也会逐渐登上历史舞台";
    //`?=` ?表示前面的数据, =表示后面要跟随的数据,但是在获取的时候只获取前面的部分
    //`(?i)` 表示忽略大小写
    String regex1 = "((?i)Java)(?=8|11|17)";
    String regex2 = "((?i)Java)(8|11|17)";
    String regex2 = "((?i)Java)(?!8|11|17)";
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(s);
    while (m.find()) {
        System.out.println(m.group());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.5 贪婪爬取和非贪婪爬取

比如有段文字 str = "哈哈,abbbbbbbbbbbbbbaaaaaaaaaaa";

正则为 ab+

爬取结果为:

  • Java中默认是贪婪爬取:abbbbbbbbbbbbbb
  • Java改为非贪婪爬取ab+?:ab

# 二、爬虫

读取网络里的资源

/**
     * 获取网络资源数据,爬取身份证数据
     * 网站:<a href="https://qichezhan.cn/sfz/">爬取身份证数据的网站</a>
     */
private static void readUrl() throws IOException {
    URL url = new URL("https://qichezhan.cn/sfz/");
    URLConnection conn = url.openConnection();
    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    String line;
    String regex = "[1-9]\\d{17}";
    Pattern p = Pattern.compile(regex);
    while ((line = br.readLine()) != null) {
        Matcher m = p.matcher(line);
        while (m.find()) {
            System.out.println(m.group());
        }
    }
    br.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

小白/爬虫:SpringBoot + Scheduled + jsoup 定时爬取网页元素 (gitee.com) (opens new window)

# 三、正则替换

  • s.replaceAll

    //Java API里的方法
    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }
    
    String str = "要替换的文本串"String res = str.replaceAll("正则", "xxx"); // 把str里满足正则的串替换为xxx,结果为res 可以用在日志脱敏等场景
    
    1
    2
    3
    4
    5
    6
    7
  • s.split

    //Java API里的方法
    public String[] split(String regex) {
        return split(regex, 0);
    }
    
    String str = "要切aaa割的bbbb文本串";
    String[] res = str.split("[\\w&&[^_]]+"); // 把str里满足正则的串作为切割的刀,返回切割后的字符串数组
    System.out.println(Arrays.toString(res)); //[要切, 割的, 文本串]
    
    1
    2
    3
    4
    5
    6
    7
    8