前言
之前一直想要做一个自己的爬虫,然后从nba数据相关的网上【虎扑,腾讯,官网等,要视网站是否支持】爬点数据写数据分析和图形化展示。虽然年轻的时候就实现过这个功能,但是当时直接借用了一个网上现成的jar包,然后在那个基础上写了一个非常简陋的正则表达式来提取数据。这次打算自己用JAVA API写一个爬虫,里面除了能读取HTML或是JSON或是XML,还要能够相应的支持多线程【这个功能将最后完成】。
今天这篇博客重点讲解java中和正则表达式相关的API为后序程序的实现做一个铺垫。后序的实现将在未来的博客3或4或5中展示,也会提供github源码来供大家参考和指教。所以也欢迎大家关注我,以便获得后序的更新。
进阶的正则表达式
关于正则表达式的基本知识请参考我的博客正则表达式 深入浅出1--你的符号我做主【持续更新中
在这里,我需要再补充一些博客1中没有提及但是必须了解的知识。(完整的正则表达式构造子列表请参考JAVA的API,在文章最后有给出传送门)
其它符号
在博客1中,我将最常用的一些符号整理出来,这里再说一些还需要了解的符号。
字符
\xhh
十六进制值为0xhh的字符\uhhhh
十六进制表示为oxhhhh的Unicode字符
字符类
[abc[def]]
相当于合并操作 等价于[abcdef][a-z&&[hij]]
相当于交集操作,等价于[hij][a-z&&[^b-d]]
相当于减操作 等价于[ae-z]
边界匹配符
\b
词的边界,词的边界是指\w和\W之间的位置
,它是一个定位符,并不代表任何具体的字符\B
非词的边界,也就是是\w和\w 以及\W和\W
之间的位置
这里新添的两个定界符有点难理解。我们已知\w是指词字符[a-zA-Z0-9]
,而\W是指[^a-zA-Z0-9]
。这里举个例子来说明b和B都匹配了什么。
假设我们待匹配的字符串为"hello world!\r\n"
如果调用方法String[] result = s.split("\\b");
那么我们会发现输出为["hello","world"," ","! rn"],也就是说我们可以将字符串看成是"_hello_ _world_!\r\n"
其中_代表单词分界处。
那么如果_代表非单词分界处,那么上述句子可以表示成"h_e_l_l_o w_o_r_l_d!_\r_\n"
那么,在大多数情况下,我们都会通过词的边界符来实现获取而不是进行判断。
量词
量词描述了一个模式吸收输入文本的方式。量词总共分为三种,贪婪型、勉强型和占有型。下面分别介绍这三种量词以便更好的构建正则表达式。
贪婪型
贪婪型模式下的正则表达式会发现尽可能多的匹配。也就是说,即便当前情况已经满足了匹配,贪婪模式还是会继续测试下一个字符直至匹配失败为止。一旦匹配失败,就开始回退直至找到合适的匹配。
在默认情况下的一般正则表达式都是贪婪型的。
勉强型
在希望勉强匹配的正则表达式后面加?号即可进行勉强匹配。勉强匹配是指这个量词满足模式所需要的最小字符数就立刻停止。
占有型
目前,这种类型的量词只在JAVA中才可以用。占有型不同于前面两者,它不会进行回溯。我们知道一个正则表达式对应的字符串可能有多种。在贪婪型中,如果下一个匹配失败,则放出已占有字符回溯到上一个满足的情形继续判断。而勉强型则是多占有一个字符继续判断。占有型则不保存这些中间结果。一旦占有则不会释放。
下面举一个例子来说明这些情况:
输入的字符串:<tr>info</tr>
贪婪模式正则表达式:<.*>
返回输出:<tr>info</tr>
勉强模式正则表达式:<.*?>
返回输出:<tr>
占有模式正则表达式:<.*+>
返回输出:<tr>info</tr>
Pattern和Matcher API
在Java中,和正则表达式最息息相关的两个类就是Pattern
和Matcher
了。基本上所有正则表达式的底层实现都是通过Pattern和Matcher来实现的。比如说,我们非常了解的String中的matches方法,实际上也是通过Pattern和Matcher的配合来实现的。
在这篇博客中,我将介绍重点的API,详细的信息请各位自行参考JAVA DOC。
创建正则表达式并判断字符流是否符合。
Pattern pattern = Pattern.compile("正则表达式"); Matcher matcher = pattern.matcher("等待匹配的字符");//这里可以输入任何继承了CharSequence的类 matcher.matches();//返回一个boolean值说明是否匹配
这里需要注意的是,Pattern和Matcher均不允许通过构造器新建一个对象。不仅如此,Pattern类是相对而言线程安全的,而Matcher类不是如此。
Pattern类
构造器
static Pattern compile(String regex) static Pattern compile(String regex,int flag);
第二个构造pattern的方法中多了一个flag参数,这个参数允许我们定义pattern的模式,这里讲几个比较重要的模式:
Pattern.CASE_INSENSITIVE
: 等价于正则表达式中的?i
,是的匹配可以大小写不敏感Pattern.COMMENTS
: 等价于正则表达式中的?x
,会忽略空格符和以#开头到行末的注释Pattern.MULTILINE
: 等价于?m
,使得^和&符号可以匹配一行的始末,而不仅仅是整个字符串的始末
如果希望有多个Pattern,可以输入Pattern.compile("正则表达式",Pattern1 | Pattern2 | Pattern3)
分割
Pattern自己也定义了split方法。它会根据之前compile的正则表达式进行分割并返回String[],limit值是指分割出的String[]的size不会超过这个limit值。
String[] split(CharSequences c,int limit) String[] split(CharSequences c)
分组
在这里还需要在讲解一个分组的概念以便为后面做铺垫。正则表达式的分组是指将一对括号中的表达式划为一个分组。因此形如A(B(CD)(E))
的正则表达式一共有四个分组{A(B(CD)(E)),B(CD)(E),(CD),(E)}
,其中第0个分组默认为完整的正则表达式。
Matcher类
查找
int groupCount() //返回除了第0组的总分组数 boolean find() //从当前下标开始匹配,如果存在满足正则表达式的值,则返回true boolean find(int i)//从下标i开始匹配,如果存在满足正则表达式的值,返回true String group() //返回前一次匹配的第0个分组的内容 String group(int i)//返回前一次匹配的第i个分组的内容 int start() //返回上一次匹配成功的内容的起始下标 int end() //返回上一次匹配成功的内容的终止下标+1
举个栗子
Pattern p = Pattern.compile("<.*?>"); Matcher matcher = p.matcher("<tr>data</tr>"); while(matcher.find()){ System.out.println(matcher.group()); //先后输出<tr> </tr> }
替换
void replaceFirst(String replacement); void replaceAll(String replacement); void appendReplacement(StringBuffer s,String replacement); void appendTail(StringBuffer s);
这里讲一下appendReplacement和appendTail方法。appendReplacement允许开发者在替换的过程中针对将被替换的内容进行一些动态的操作。这个方法将逐步推进的向前替换,并将替换的结果添加到输入的s中。除此以外,对于没有被扫描到的部分,可以通过appendTail方法添加到输入的s中。
例子:
Pattern p = Pattern.compile("hello",Pattern.CASE_INSENSITIVE); Matcher m = p.matcher("hello world one hello world 2"); StringBuffer s = new StringBuffer(); while(m.find()){ m.appendReplacement(s,m.group().toUpperCase()); System.out.println(s); } m.appendTail(s); System.out.println(s);
输出为:
小结
其实正则表达式的适用范围非常广,除了和CharSequence配合使用之外,还可以和JAVA I/O如Scanner类,InputReader类等联合使用。核心还是在于Pattern类和Matcher类的组合使用。