正则表达式Regex
和爬虫
- 正则表达式(Regular Expression,简称 Regex)是一种文本匹配的模式,用于对字符串进行搜索、匹配、提取、替换等操作。它广泛应用于字符串处理领域,例如文本解析、验证用户输入、文件内容查找等。
作用:
- 校验字符串是否满足规则
- 在一段文本中查找满足要求的内容
一、正则表达式语法:
正则表达式在字符串方法中的使用:
方法名 | 说明 |
---|---|
public boolean matches(String regex) | 判断字符串是否满足正则表达式的规则 |
public String replaceAll(String regex,String newStr) | 按照正则表达式的规则进行替换 |
public String[] split(String regex) | 按照正则表达式的规则切割字符串 |
方法2、3的演示:
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]); // 输出: 小诗诗 小丹丹 小惠惠
}
}
}
详细示例:
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
——文本匹配器,按照正则表达式的规则去读取字符串,从头开始读取
示例:
// 爬取数据
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
示例代码:
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) |
演示代码:
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当中,默认就是贪婪爬取,如果我们在数量词+ *
的后面加上?
,那么此时就是非贪婪爬取
演示代码:
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、正则内部使用分组:
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、正则外部使用分组:
ublic class RegexDemo09 {
public static void main(String[] args) {
// 捕获分组
/* 需求:
将字符串:我要学学编编编编程程程程程程
替换为:我要学编程
*/
// 把重复的内容替换为单个的
String str = "我要学学编编编编程程程程程程";
String result = str