Java的正则表达式和爬虫

正则表达式Regex和爬虫

  • 正则表达式 (Regular Expression,简称 Regex)是一种文本匹配的模式,用于对字符串进行搜索、匹配、提取、替换等操作。它广泛应用于字符串处理领域,例如文本解析、验证用户输入、文件内容查找等。

作用:

  1. 校验字符串是否满足规则
  2. 在一段文本中查找满足要求的内容

一、正则表达式语法:

正则表达式在字符串方法中的使用:

方法名 说明
public boolean matches(String regex) 判断字符串是否满足正则表达式的规则
public String replaceAll(String regex,String newStr) 按照正则表达式的规则进行替换
public String\[\] split(String regex) 按照正则表达式的规则切割字符串

方法2、3的演示:

java 复制代码
public class RegexDemo07 {
    public static void main(String[] args) {
        // 正则表达式在字符串方法中的使用

        // 有一段字符串:小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠
        // 要求1:把字符串中三个姓名之间的字母替换为vs
        // 要求2:把字符串中的三个姓名切割出来

        String str = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
        String str1 = str.replaceAll("[\\w&&[^_]]+", "VS");
        System.out.println(str1);	// 输出: 小诗诗VS小丹丹VS小惠惠
        // 细节:
        // 方法在底层跟之前一样也会创建文本解析器的对象
        // 然后从头开始去读取字符串中的内容,只要有满足的,那么就用第二个参数去替换

        // 在所指定的文本处分割
        String[] strarr = str.split("[\\w&&[^_]]+");
        for (int i = 0; i < strarr.length; i++) {
            System.out.println(strarr[i]);	// 输出: 小诗诗 小丹丹 小惠惠
        }
    }
}

详细示例:

java 复制代码
public class RegexDemo01 {
    public static void main(String[] args) {
        // 验证手机号码: 13815644862 19748524761
        String regex1 = "1[3-9]\\d{9}";
        // 验证分成三部分:
        // 第一部分: 1 表示手机号码只能以1开头
        // 第二部分: [3-9] 表示手机号码第二位只能是3-9之间的
        // 第三部分: \\d{9} 表示任意数字可以出现9次,也只能出现9次
        System.out.println("13546874556".matches(regex1));  // 输出: true
        System.out.println("12546874556".matches(regex1));  // 输出: false
        System.out.println("62546874556".matches(regex1));  // 输出: false
        System.out.println("125468745565".matches(regex1)); // 输出: false

        // 验证座机号码: 020-2324242 02122442 027-42424 0712-3242434
        String regex2 = "0\\d{2,3}-?\\d{4,9}";
        System.out.println("020-2324242".matches(regex2));  // 输出: true
        System.out.println("02122442".matches(regex2));     // 输出: true
        System.out.println("0712-3242434".matches(regex2)); // 输出: true
        System.out.println("07123-3242434".matches(regex2));// 输出: false

        // 验证邮箱号码:
        // 3232323@qq.com zhangsan@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
        // 细节:
        // 要显示.需要加转义字符------因为.是预定义字符,代表任何字符 \\.
        // .com这类的可以出现一次或两次,这时候可以使用()进行分组,可以在后面规定出现的次数

        String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
        System.out.println("3232323@qq.com".matches(regex3));       // 输出: true
        System.out.println("zhangsan@itcast.cnn".matches(regex3));  // 输出: true
        System.out.println("dlei0009@163.com".matches(regex3));     // 输出: true
        System.out.println("dlei0009@pci.com.cn".matches(regex3));  // 输出: true
        // 在实际的开发中,很少会自己写正则表达式
        // 百度一个类似的,自己改成公司要求的

        // 24小时的正则表达式
        String regex4 = "([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
        System.out.println("23:11:11".matches(regex4)); // 输出: true

        // 身份证号码的简单校验
        String regex5 = "[1-9]\\d{16}(\\d|X|x)";
        // String regex5 = "[1-9]\\d{16}(\\d|[Xx])";
        // String regex5 = "[1-9]\\d{16}(\\d|(?i)x)";
        System.out.println("41080119930228457x".matches(regex5));   // 输出: true
        System.out.println("510801197609022309".matches(regex5));   // 输出: true
        System.out.println("15040119810705387X".matches(regex5));   // 输出: true
        System.out.println("130133197204039024".matches(regex5));   // 输出: true
        System.out.println("430102197606046442".matches(regex5));   // 输出: true

        // 忽略大小写的书写方式
        // 在匹配的时候忽略abc大小写
        String regex6 = "(?i)abc";
        // 忽略(?i)后面的大小写
        System.out.println("------------------------");
        System.out.println("abc".matches(regex6));  // 输出: true
        System.out.println("Abc".matches(regex6));  // 输出: true
        System.out.println("ABC".matches(regex6));  // 输出: true
        // 如果要仅忽略b的大小写
        String regex7 = "a((?i)b)c";
        System.out.println("------------------------");
        System.out.println("abc".matches(regex7));  // 输出: true
        System.out.println("ABc".matches(regex7));  // 输出: false
        System.out.println("aBc".matches(regex7));  // 输出: true

        // 身份证号码的复杂校验
        //前面6位:省份,市区,派出所等信息 第一位不能是0,后面5位是任意数字
        //年的前半段:18 19 20
        //年的后半段:任意数字出现两次
        //月份:01~09 10 11 12
        //日期:01~31
        //后面四位:任意数字出现3次 最后一位可以是数字也可以是大写x或者小写x
        String regex8 = "[1-9]\\d{5}(18|19|20)\\d{2}(0\\d|1[0-2])([0-2]\\d|3[01])\\d{3}(\\d|(?i)x)";
        System.out.println("41080119930228457x".matches(regex8));
        System.out.println("510801197609022309".matches(regex8));
        System.out.println("15040119810705387X".matches(regex8));
        System.out.println("130133197204039024".matches(regex8));
        System.out.println("430102197606046442".matches(regex8));
    }
}

二、爬虫:

  • 本地爬虫
  • 网络爬虫

原理:通过正则表达式从文本中爬取指定的数据。

1、本地爬虫

  • 从一个字符串中爬取指定的数据

需要用到regex包下的两个类Pattern和Matcher

  • Pattern------表示正则表达式
  • Matcher------文本匹配器,按照正则表达式的规则去读取字符串,从头开始读取

示例:

java 复制代码
	// 爬取数据
        String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,"+
                    "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";	// 从这段文本中获取JavaXX的数据

        // 获取正则表达式对象
        // 调用Pattern中的静态方法来创建Pattern对象------------Pattern.compile()
        Pattern p = Pattern.compile("Java\\d{0,2}");

        // 获取文本匹配器对象
        // 调用Pattern中的matcher方法传递所要查询的源文本,返回的类型就是Matcher
        Matcher m = p.matcher(str);
        // m: 文本匹配器对象
        // str: 所要查询的源文本
        // p: 规则
        // 即: m要在str中找到符合p规则的小串

        // 拿着文本匹配器从开头开始读取,寻找是否有满足规则的子串
        // 如果没有,方法就返回false
        // 如果有,则返回true,并在底层记录子串的起始索引和结束索引+1

        // boolean b = m.find();
        // 方法底层会根据find方法记录的索引进行字符串的截取
        // subString(起始索引, 结束索引);包头不包尾
        while(m.find()) {
            String s = m.group();
            System.out.println(s);
        }

注意事项:

  • 文本匹配器每调用一次find()方法,它捕获到的指定格式文本也会搜索下一个
  • 且find()方法的返回值为布尔类型,也可用于判断是否有想要获取的文本
  • 当调用Matcher中的group()方法时,底层会返回截取到的子字符串

2、网络爬虫

  • 从网站中爬取想要的数据

这里准备了一个带随机生成身份证号的网站:https://yapi.pro/mock/583732/getsource

示例代码:

java 复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexDemo03 {
    public static void main(String[] args) throws IOException {
        // 网络爬虫
        // 从网站中爬取想要的数据

        // 创建一个URL对象
        URL url;
        try {
            url = new URL("https://yapi.pro/mock/583732/getsource");
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        // 连接上这个网站------------保证网络是畅通的
        URLConnection conn = url.openConnection();

        // 创建一个对象去读取网站中的数据
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;

        // 获取正则表达式的对象 Pattern
        String regex = "[1-9]\\d{17}";
        Pattern pattern = Pattern.compile(regex);
        // 在读取的时候每次读一整行
        while((line = br.readLine()) != null) {
            // 拿着文本匹配器的对象matcher按照pattern的规则去读取当前的这一行信息
            Matcher matcher = pattern.matcher(line);
            while(matcher.find()) {
                System.out.println(matcher.group());
            }
        }

        br.close();
    }
}

运行后即可爬取这个网站中的身份证号码


3、爬虫的深入:

3.1 带条件的爬取(非捕获分组)
符号 含义 举例
(? : 正则) 获取所有 Java(?:8|11|17)
(? = 正则) 获取前面的部分 Java(?=8|11|17)
(? ! 正则) 获取不是指定内容的前面部分 Java(?!8|11|17)

演示代码:

java 复制代码
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexDemo05 {
    public static void main(String[] args) {
        // 带条件的爬取数据
        String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,"+
                "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
        // 需求1:爬取版本号为8,11,17的Java文本,但是只要Java,不显示版本号。
        // 需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
        // 需求3:爬取除了版本号为8,11,17的Java文本

        // 1.定义正则表达式
        // ?理解为前面的数据Java
        // =表示在Java后面跟随的数据
        // 但是在获取的时候,只能获取前半部分
        String regex1 = "Java(?=8|11|17)";

        // :表示获取整体所有的值
        String regex2 = "Java(?:8|11|17)";
//        String regex2 = "Java(8|11|17)";

        // !表示去除
        String regex3 = "Java(?!8|11|17)";
        Pattern p1 = Pattern.compile(regex3);
        Matcher m1 = p1.matcher(str);

        while(m1.find()) {
            System.out.println(m1.group());
        }
    }
}
3.2贪婪爬取和非贪婪爬取
  • 贪婪爬取:在爬取数据的时候尽可能的多获取数据
  • 非贪婪爬取:在爬取数据的时候尽可能少的获取数据

在Java当中,默认就是贪婪爬取,如果我们在数量词+ *的后面加上?,那么此时就是非贪婪爬取

演示代码:

java 复制代码
public class RegexDemo06 {
    public static void main(String[] args) {
        String str = "Java自从95年问世以来,abbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa" +
                "经历了很多版本,目前企业中用的最多的是Java8和Java11,"+
                "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
        // 贪婪爬取和非贪婪爬取


        // 创建正则表达式对象
        // 贪婪爬取
        Pattern p1 = Pattern.compile("ab+");	// 会在下面输出: abbbbbbbbbb
        // 非贪婪爬取
        Pattern p2 = Pattern.compile("ab+?");	// 会在下面输出: ab
        Matcher m = p2.matcher(str);
        while(m.find()) {
            System.out.println(m.group());
        }

    }
}
3.3分组
  • 分组就是一个小括号
  • 分组是有组号的,也就是序号。
    • 规则一:从1开始,连续不间断
    • 规则二:以左括号为基准,最左边的是第一组,其次为第二组,以此类推(嵌套小括号也是,只看左括号的顺序,在最左边就是第一个)
捕获分组和非捕获分组
  • 捕获分组 :(可以获取每组中的内容反复使用)
    • 后续还要继续使用本组的数据。
    • 正则内部使用:\\+组号
    • 正则外部使用:$组号
  • 非捕获分组 :(分组之后不需要再用本组数据,仅仅是把数据括起来)
    • ++就是上面的三种((?:)(?=)(?!))带条件爬取数据++(若要使用这些分组的数据会报错)

捕获分组的两个代码实例:

1、正则内部使用分组:

java 复制代码
public class RegexDemo08 {
    public static void main(String[] args) {
        // 需求1:判断一个字符串的开始字符和结束字符是否一致?只考虑一个字符
        // 举例:a123a   b456b   17891   &abc&   a123b(false)
        //  \\+组号: 表示把第x组的内容再出来用一次
        String regex1 = "(.).+\\1";
        System.out.println("a123a".matches(regex1));    // true
        System.out.println("b456b".matches(regex1));    // true
        System.out.println("17891".matches(regex1));    // true
        System.out.println("&abc&".matches(regex1));    // true
        System.out.println("a123b".matches(regex1));    // false
        System.out.println("----------------------------------");

        // 需求2:判断一个字符串的开始部分和结束部分是否一致?可以有多个字符
        // 举例:abc123abc   b456b   123789123   &!@abc&!@   abc123abd(false)
        String regex2 = "(.+).+\\1";
        System.out.println("abc123abc".matches(regex2));    // true
        System.out.println("b456b".matches(regex2));    // true
        System.out.println("123789123".matches(regex2));    // true
        System.out.println("&!@abc&!@".matches(regex2));    // true
        System.out.println("abc123abd".matches(regex2));    // false
        System.out.println("----------------------------------");

        // 需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致
        // 举例:aaa123aaa   bbb456bbb   111789111   &&abc&&
        String regex3 = "((.)\\2).+\\1";
        System.out.println("aaa123aaa".matches(regex3));    // true
        System.out.println("bbb456bbb".matches(regex3));    // true
        System.out.println("111789111".matches(regex3));    // true
        System.out.println("&&abc&&".matches(regex3));    // true
        System.out.println("&&abc&*".matches(regex3));    // false
        System.out.println("----------------------------------");

    }
}

2、正则外部使用分组:

java 复制代码
ublic class RegexDemo09 {
    public static void main(String[] args) {
        // 捕获分组
        /* 需求:
            将字符串:我要学学编编编编程程程程程程
            替换为:我要学编程
        */
        // 把重复的内容替换为单个的
        String str = "我要学学编编编编程程程程程程";
        String result = str.replaceAll("(.)\\1+", "$1");
        System.out.println(result);	// 输出: 我要学编程
        // (.) 表示把重复内容的第一个字符看做一组
        // \\1 表示第一个字符再次出现
        // + 至少再次出现一次
        // $1 表示把正则表达式中第一组的内容,再拿出来用
    }
}
相关推荐
Elastic 中国社区官方博客1 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1881 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn2 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su2 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123453 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu3 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠4 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python
旋律4144 小时前
同义方法区别(增加类与删除类)
java·后端
学到老活到老4 小时前
方法执行完了,对象为什么还占着内存?
java·jvm