爬取网址中的数据。
下面3个分别是姓、女孩名字、男孩名字的网址。
String familyURLStr = "http://www.baijiaxing.net.cn/";
String girlNameURLStr = "https://wannianli.tianqi.com/qiming/news/16536.html";
String boyNameURLStr = "https://wannianli.tianqi.com/qiming/news/883.html";
如何爬取呢?
1、首先有一个网址
这是一个百家姓的网址。
String familyURLStr = "http://www.baijiaxing.net.cn/";
2、爬取网址中的全部数据
注意注释中的细节。
//爬取全部数据
private static String webUrl(String URLStr) throws IOException {
//先转换为url对象
URL url = new URL(URLStr);
//打开连接
URLConnection conn = url.openConnection();
//利用IO流进行读取
//1.由于url对象提供字节流进行读取,无法读取中文,所以转换为字符流
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
//使用一次read()方法只能读取一个字符
//使用sb把读取出来的字符存起来
StringBuilder sb = new StringBuilder();
int b;
while ((b = isr.read()) != -1) {
sb.append((char)b);
}
return sb.toString();
}
输出结果是以下html这种格式的,由服务器传过来的原本就是html,只不过平时被浏览器解析了而已。
3、提取需要的信息
对我们有用的只有其中的姓氏而已,所以利用正则表达式进行匹配。
①下面是利用正则表达式读取数据的代码:
private static ArrayList<String> getData(String str, String regex) {
ArrayList<String> list = new ArrayList<>();
//转换为正则表达式对象
Pattern pattern = Pattern.compile(regex);
//将字符串与正则表达式进行匹配
Matcher matcher = pattern.matcher(str);
//是否有与正则表达式匹配的值,有则返回true,无则返回false
while (matcher.find()){
//将匹配到的值进行返回
list.add(matcher.group());
}
return list;
}
②正则表达式如何书写?
观察百家姓可以看到都是4个汉字一组,并且后面跟着中文的","或者中文的"。",所以正则表达式可写为
".{4},|。"
输出结果如下:
输出为什么是这种格式?
因为这种情况下,把".{4},"看作一个逻辑,"。"看作一个逻辑,所以应该把,|。括起来,正确的是
.{4}(,|。)
但是又出现一个问题,返回结果我们只想要前面的4个汉字,不想要后面中文的","或者中文的"。",这种情况下有两种解决办法。
(1)这种需求很常见,所以专门提出了一个符号去解决这个问题,就是?=,将匹配结果进行返回时忽略?=后面的东西,但是匹配的时候还是参与的,因此正则表达式可写为
".{4}(?=,|。)"
(2)将正则表达式使用括号进行分组,然后在使用matcher.group()将匹配结果进行返回时,可以选择返回哪些组。
正则表达式可写为
"(.{4})(,|。)"
这种情况下,提取需要的数据的方法就需要额外加上一个参数,就是要返回的组号。
关于组号有一些说明:组号从1开始编,也存在一个特殊的0,表示全部数据。
private static ArrayList<String> getData(String str, String regex, int index ) {
ArrayList<String> list = new ArrayList<>();
//转换为正则表达式对象
Pattern pattern = Pattern.compile(regex);
//将字符串与正则表达式进行匹配
Matcher matcher = pattern.matcher(str);
//是否有与正则表达式匹配的值,有则返回true,无则返回false
while (matcher.find()){
//将匹配到的值进行返回
list.add(matcher.group(index));
}
return list;
}
返回结果:
错误1:在与网址建立连接时抛错:Server returned HTTP response code: 403 for URL,如下图:
解决办法:在建立完连接之后加上
conn.setRequestProperty("User-Agent", "Mozilla/4.76");
//打开连接
URLConnection conn = url.openConnection();
conn.setRequestProperty("User-Agent", "Mozilla/4.76");
如何写女生名字的正则表达式呢?
首先看到2个汉字一组,并且后面跟着中文的"、"或者中文的"。",所以正则表达式可写为
".{2}(?=、|。)"
返回结果如下:
可以看到把一些不是名字的汉字也给返回了,原因就是也符合正则表达式。那怎么解决呢?
把"露怡、琦倩、澜蕾、思琳、"看作一组进行匹配,正则表达式可写为
"(.{2}(、|。)){4}"
返回结果如下:
如何写男生名字的正则表达式呢?
下面是一个男生名字的网址:
100个好听的男孩名字,古风儒雅、洒脱大气的好名字! - 知乎 (zhihu.com)
网址中的数据如下:
如果依旧按照前面的把4个看作一组进行匹配,会把前面的序号也进行匹配,这不是我们想要的,所以要限定为中文。
有一个插件AnyRule,可以搜索常见需求的正则表达式,鼠标右键一下可看到:
打开之后搜索中文的正则表达式:
复制:[\u4E00-\u9FA5]
因此正则表达式写为
"([\\u4E00-\\u9FA5]{2}(、|\\n)){4}"
输出结果一直为空,刚开始以为是正则表达式有问题,后面怀疑知乎是不是有反爬啊。
测试一下,输出爬取的全部数据:
答案确实是这样,知乎将数据处理成密文了,所以无法匹配到与正则表达式相符的数据,导致输出一直为空。
只能换个网址了。
换成这个:【男孩名字】好听的男孩名字大全_男孩独特少见的名字_男孩简单大气的名字_亲子百科_太平洋亲子网 (pcbaby.com.cn)
结果还是有问题,爬取的中文无法解析,是乱码的。
又接连试了好几个网址,都是同样的问题,都怀疑是不是代码有问题了,于是又回去测试了一下姓的爬取,也没有问题啊。
只能不断寻找不乱码的网址,终于找到一个:
男宝宝起名大全 100个好听的男孩名字 - 万年历 (tianqi.com)
输出结果如下:
下面对爬取的数据再次进行处理:
首先对姓进行处理,对于集合中的一个元素来说是有4个姓的,所以现在要拆开。
代码如下:
可以看到有错误提示:
使用强制类型转换无法将char转为String, 那怎么办呢?
解决办法就是使用+将字符和空字符串""进行拼接就可以转换了。
正确的代码如下:
private static ArrayList<String> processData(ArrayList<String> familyNameTempList) {
ArrayList<String> familyNameList = new ArrayList<>();
for (String s : familyNameTempList) {
for (int i = 0; i < s.length(); i++) {
familyNameList.add("" + s.charAt(i));
}
}
return familyNameList;
}
对名字进行处理,代码如下:
//由于男孩和女孩的格式差不多,由一个方法进行处理
private static ArrayList<String> processName(ArrayList<String> nameTempList) {
ArrayList<String> nameList = new ArrayList<>();
for (String s : nameTempList) {
String[] ss = s.split("、");
for (String name : ss) {
nameList.add(name);
}
}
return nameList;
}
姓和名处理之后的结果如下:
下面将姓和名进行拼接,生成姓名,代码如下:
private static HashSet<String> getFullName(ArrayList<String> familyNameList, ArrayList<String> nameList, int number) {
//由于名字不能重复,所以选用HashSet存
HashSet<String> fullNamehs = new HashSet<>();
for (int i = 0; i < number; i++) {
//1.首先随机选取姓和名
//没有使用随机数的方式,而是集合工具类的shuffle()方法
Collections.shuffle(familyNameList);
Collections.shuffle(nameList);
//选取索引0处的元素
String familyName = familyNameList.get(0);
String name = nameList.get(0);
//将姓和名进行拼接并添加到集合中
fullNamehs.add(familyName + name);
}
return fullNamehs;
}
生成指定格式的数据,代码如下:
//生成男生信息
private static HashSet<String> getBoyInfo(HashSet<String> fullBoyNamehs) {
HashSet<String> infohs = new HashSet<>();
//生成年龄
Random r = new Random();
for (String fullName : fullBoyNamehs) {
int age = r.nextInt(10) + 18;
infohs.add(fullName + "-男-" + age);
}
return infohs;
}
//生成女生信息
private static HashSet<String> getGirlInfo(HashSet<String> fullGirlNamehs) {
HashSet<String> infohs = new HashSet<>();
//生成年龄
Random r = new Random();
for (String fullName : fullGirlNamehs) {
int age = r.nextInt(8) + 18;
infohs.add(fullName + "-女-" + age);
}
return infohs;
}
将其写入到外部设备中,代码如下:
//写到外部设备中
private static void writeInfo(HashSet<String> girlInfo, HashSet<String> boyInfo) throws IOException {
ArrayList<String> infoList = new ArrayList<>();
infoList.addAll(girlInfo);
infoList.addAll(boyInfo);
Collections.shuffle(infoList);
BufferedWriter bw = new BufferedWriter(new FileWriter("name.txt"));
for (String info : infoList) {
bw.write(info);
bw.newLine();
}
bw.close();
}
可以看到已写入到文件中:
总结一下全部过程就是:爬取数据--对数据进行处理--写入文件。