本文已同步发布在微信公众号:灰灰的Rom笔记
微信扫码获取更多内容:
背景
业余时间又爬了下网站,这回碰到点问题,爬下来的数据有点奇葩。说是 xml 格式吧,又有些奇怪的东西,而且时不时来个\n
,有效数据还被<![CDATA[
和 ]]>
包着在。这里我截取了部分贴出来:(为了方便查看,做了简单的格式化)
dwr.engine._remoteHandleCallback('0','0',"
<?xml version=\"1.0\" encoding=\"utf-8\"?>\n
<title>
<![CDATA[\u5173\u4E8E\u8F6C\u53D1\u201C\u6E56\u5317\u5DE5\u5EFA\u676F\u201D\u7B2C\u516D\u5C4A\u6E56\u5317\u7701\u5927\u5B66\u751F\u7ED3\u6784\u8BBE\u8BA1\u7ADE\u8D5B\u5F81\u96C6\u5FBD\u6807\u7684\u901A\u77E5]]>
</title>\n
<link>
<![CDATA[/news/152473431173875997.html]]>
</link>\n
<category>
<![CDATA[\u6559\u52A1\u5904]]>
</category>\n
<pubDate>2018-04-26 17:10:35</pubDate>\n
<xwbh>152473431173875997</xwbh>\n
<color>null</color>\n
<spanpic>pic</spanpic>\n
如何解析这个数据并不是难事,Jsoup轻松搞定,至于如何使用Jsoup,不是本文重点,这里不做介绍。
Jsoup解析数据时,只能解析到标签,比如上面的 title,通过Jsoup解析出来就是:
<![CDATA[\u5173\u4E8E\u8F6C\u53D1\u201C\u6E56\u5317\u5DE5\u5EFA\u676F\u201D\u7B2C\u516D\u5C4A\u6E56\u5317\u7701\u5927\u5B66\u751F\u7ED3\u6784\u8BBE\u8BA1\u7ADE\u8D5B\u5F81\u96C6\u5FBD\u6807\u7684\u901A\u77E5]]>
前面已经说过了,有效数据是<![CDATA[
和 ]]>
中间夹着的部分,如果就这么几个字串,那通过字符串截取方法也能轻松拿到有效数据。但是上面贴出来的只是局部,实际整个数据是一个list,里面有很多的<![CDATA[
和 ]]>
,如果一个个去截取字串,无疑会很繁琐。
因此我的做法是:先将整个数据进行字符串替换,将<![CDATA[
和 ]]>
替换为空串""
;然后交给Jsoup去解析,这样就可以直接一个循环一步到位了。
字符串替换
基于上面的思路,如何一次性替换掉所有的<![CDATA[
和 ]]>
就成了关键。好在 String 类就有替换方法:replace
和replaceAll
,我们直接拿过来用就可以了。
但是我这里就被这两个方法的名字迷惑了,想着是替换全部的<![CDATA[
和 ]]>
嘛,那replaceAll
在合适不过了。于是就这么用了:
//这里先将上面那些奇怪的编码转换一下
html = AsciiUtils.ascii2Native(html);
//然后替换所有字串
html = html.replaceAll("<![CDATA[", "").replaceAll("]]>", "");
结果这么一写出来,AS 就在提示参数<![CDATA[
有问题:Unclosed character class
。
这下懵了,然后老老实实看了下 replaceAll 的说明:
Replaces each substring of this string that matches the given regular expression with the given replacement.
replaceAll 实现方式:
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
意思很明显,第一个参数 regex 是正则表达式,这个方法的作用就是用 replacement 去替换所有满足 regex的内容。而[
也是正则表达式的关键字符,因此这里没有经过处理直接使用就出现了参数问题。
这里我对正则表达式了解的不多,也就没有深究 replaceAll 方法了,转而看了下 replace 的说明和实现:
/**
* Replaces each substring of this string that matches the literal target
* sequence with the specified literal replacement sequence. The
* replacement proceeds from the beginning of the string to the end, for
* example, replacing "aa" with "b" in the string "aaa" will result in
* "ba" rather than "ab".
*/
public String replace(CharSequence target, CharSequence replacement) {
if (target == null) {
throw new NullPointerException("target == null");
}
if (replacement == null) {
throw new NullPointerException("replacement == null");
}
String replacementStr = replacement.toString();
String targetStr = target.toString();
// Special case when target == "". This is a pretty nonsensical transformation and nobody
// should be hitting this.
//
// See commit 870b23b3febc85 and http://code.google.com/p/android/issues/detail?id=8807
// An empty target is inserted at the start of the string, the end of the string and
// between all characters.
final int len = length();
if (targetStr.isEmpty()) {
// Note that overallocates by |replacement.size()| if |this| is the empty string, but
// that should be a rare case within an already nonsensical case.
StringBuilder sb = new StringBuilder(replacementStr.length() * (len + 2) + len);
sb.append(replacementStr);
for (int i = 0; i < len; ++i) {
sb.append(charAt(i));
sb.append(replacementStr);
}
return sb.toString();
}
// This is the "regular" case.
int lastMatch = 0;
StringBuilder sb = null;
for (;;) {
int currentMatch = indexOf(this, targetStr, lastMatch);
if (currentMatch == -1) {
break;
}
if (sb == null) {
sb = new StringBuilder(len);
}
sb.append(this, lastMatch, currentMatch);
sb.append(replacementStr);
lastMatch = currentMatch + targetStr.length();
}
if (sb != null) {
sb.append(this, lastMatch, len);
return sb.toString();
} else {
return this;
}
}
这里意思就是说,替换每一个满足条件的字串。而且替换过程是从字符串开始到结束单向的,不会出现倒退;比如说:在aaa
里面用b
替换aa
,结果是ba
,而不是ab
。用也就是说 replace 和 replaceAll 不是字面意思,它俩都是替换所有满足条件的内容。
另外,这里replace的实现方式也非常严谨,对target == ""
的情况也进行了处理,处理方式是在原字串首尾和其所有字符之间都添加 replacement。举个栗子:
String str = "123";
str.replace("", "X");
结果是:X1X2X3X
小结
1、replace 和 replaceAll 都是替换所有满足条件的内容,只不过replace的匹配条件是普通的字串,而replaceAll的匹配条件是正则表达式。
2、当要被处理的字符串比较庞大时,replaceAll 的效率总是比 replace 的快一些;当字符串量不大时,二者效率不相上下。
3、当字符串无法确定是否具有转义字符时,而且也不需要转义时,建议使用 replace 函数;否则,使用 replaceAll 函数。