# Java 正则和爬虫
# 一、正则表达式
定义:正则表达式是一些用来匹配和处理文本的字符串
正则表达式在线测试 | 菜鸟工具 (runoob.com) (opens new window)
正则表达式学习 以简单的方式学习正则表达式 (github.com) (opens new window)
# 1.1 正则的基础规则
元字符
. 匹配除换行符(\n)以外的任何单个字符 w 匹配字母、数字、下划线、汉字 s 匹配任意空白字符(包括空格、制表符、换页符等) d 匹配数字,匹配单词的开始或结束 ^ 匹配字符串的开始 $ 匹配字符串的结束
1
2
3
4
5
6重复限定符
* 重复0次或更多次 + 重复1次或更多次 ? 重复0次或1次 {n} 重复n次 {n,} 重复n次或更多次 {n,m} 重复n到m次
1
2
3
4
5
6分组和条件或
^(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区间: 比如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
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
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
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
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
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
7s.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