简单介绍一下:
老王是个新人,心血来潮想用java试试写爬虫,完全零基础,搜了很多教程,往往因为作者水平太高,不能一下子理解大佬代码中的深意,并且有些看似很简单的东西,对于我这种菜鸟来说,其实是很难解决的错误或者是异常。故,在稍有心得后,写下此篇。从最基础开始。一步一步,从小菜鸟,成为稍微大一点的菜鸟,给初学者带来一点启示。
如果只需要全部的代码,请直接拉至最后
如果转载,请注明出处:https://blog.csdn.net/qq_37893828/article/details/88133686 ——会飞的王浩然
如有疏漏之处,欢迎留言指出
往期列表:更多详细解释请从第一弹翻阅
- java爬虫入门第一弹——从百度首页开始(欢迎浏览)
- java爬虫入门第二弹——通过URL下载图片(以下载百度logo为例)(欢迎浏览)
- java爬虫入门第三弹——正则表达式简单应用(抓取豆瓣读书信息并以文本文件输出)(欢迎浏览)
这一节我们的目标
根据下载到的网页源代码即HTML页面代码,获取相应的文字图片等信息,我们以豆瓣读书为例
java_17">从最基础开始,我首先假设你什么都不会,只会一点点的C语言和java编程
- 正则表达式的简单作用:
- 正则表达式定义了字符串的模式,可以用来搜索、编辑和处理文本。
- 举个小栗子: 给一串字符串:"…1243253565adfGDse24456585…",假如有这样一个字符串,很多很多的数字(省略号表示还有很多数字),我们知道这串字符串里面有几个连续的字母,具体有几个不清楚,字母是什么也不清楚,但是我们想找到这几个字母,我们该怎么办?
- 在不知道正则表达式之前,我们是这样做的:,遍历字符串,对字符串中的每一个字符,判断它的ASCII码的值是不是在65-90或者97-122之间 (因为字母有大写小写之分) ,这样解决起来还不算麻烦,也就做个判断,每个都做个遍历即可
- 但是如果字符串修改为:其中有很多很多的数字,也有很多很多的字母,这些字母既有不连续的一个字母,也有接连两个,接连三个、四个、五个都是字母,我们只要其中连续三个都是字母的子字符串,这时候该怎么解决?也可以通过遍历,但是遍历的次数一下子翻了很多倍,代码长,运算多,何况很多复杂的问题并不仅仅是数字跟字母。这时候我们要怎么做呢?答案就是通过正则表达式匹配 。
- 请看下面的代码:这是一个正则表达式简单应用的例子
java">public static void main(String[] args){
// TODO Auto-generated method stub
//定义我们原始字符串
String ssrString="121412252364636abCDE87897sfsrDF98778";
//定义正则表达式模板
String patternString="([a-z,A-Z]+)";
//进行匹配
Matcher matcher=Pattern.compile(patternString).matcher(ssrString);
//表示输出所有符合条件的子字符串
while(matcher.find()) {
System.out.println(matcher.group());
}
}
-
输出结果为:
-
在字符串ssrString中含有两个全部由字母组成的子串,我们通过正则表达式将其找出来只需要简单的一行代码:“Matcher matcher=Pattern.compile(patternString).matcher(ssrString);”
-
patternString是我们定义的正则表达式形式它的含义是:“[a-z,A-Z]”表示匹配英文大小写字母,“+”表示匹配的数量是一个或者多个。(更多语法参看上面的菜鸟教程)
-
Pattern:一个Pattern对象表示一个正则表达式经编译后的表现模式。
-
patternString 这个字符串中的内容即为我们所定义的正则表达式模板
-
Pattern.compile 的含义是:将给定的正则表达式编译并赋予Pattern类
-
Matcher:一个Matcher对象是一个状态机器,它依据Pattern对象作为匹配模式对字符串展开匹配检查;它的参数是要进行匹配的字符串。
-
上面一行代码拆开来看即为:
java"> Pattern pattern=Pattern.compile(patternString);
Matcher matcher=pattern.matcher(ssrString);
- 第二步: 下载豆瓣读书首页源代码,并对该源代码首页进行分析。
- 我们现在浏览器中打开豆瓣读书,我们希望将豆瓣读书首页上的"最受关注图书榜"中的图书和作者给保存到文本文件中去,无论我们什么时候需要这个排行榜的内容,只需要运行一下代码即可得知,而不需要再自己记录。
- 右键网页空白处,选择查看网页源代码,在2246行,我们找到“最受关注排行榜”字样
-
查看豆瓣读书首页,发现"最受关注图书榜"下面的模块是“豆瓣书店”
在代码中我们发现豆瓣书店也在最受关注排行榜的后面,而我们要爬取的图书名和作者名信息就在2246行和2264行中间,我们需要的是对这中间的代码进行分析,为了避免无关的干扰,我们可以在下载了“豆瓣读书”首页源代码之后,对这串字符串进行分割。让它以“最受关注图书排行榜”开始,“豆瓣书店”为结束;
java">
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String urlString = "https://book.douban.com/";
//将第一弹内容打包成页面下载函数
String doubanBookString = downloadHtml(urlString);
//String类自带的获取子串的方法。
//表示从获取"最受关注排行榜"这个子串的开始位置一直到结束的子串
doubanBookString=doubanBookString.substring(doubanBookString.indexOf("最受关注图书榜"));
//表示获取从头到"豆瓣书店"这个子串的开始位置的子串
doubanBookString=doubanBookString.substring(0,doubanBookString.indexOf("豆瓣书店"));
}
- 继续对这串分割后的字符串进行分析,我们发现每一个<img>标签都对应一张排行榜里面一本图书的图片,而且img标签里面还有这本书的名字。而每一个class=“author” 的<p>标签,里面都有对应的图书的作者名称。发现了这个规律,那么接下来的操作就是显而易见的了。
- 首先构造能够匹配到<img>标签和“作者”的正则表达式
java"> //构造匹配<img>标签的正则表达式
String bookNamePatternString="<img(.+?)>";
//构造匹配class="author"的<p>标签
String bookAuthorPatternString="author(.+?)</p>";
两个表达式分别匹配到的结果为:
- 第三步: 对第二步中匹配到的书名和作者的两个子串继续进行匹配,以便获得书名和作者名称,并将其写入book.txt中,一个完整的步骤如下所示:
java"> public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//构造文件输出流
PrintStream output=new PrintStream(new File("book.txt"));
// 豆瓣读书的链接
String urlString = "https://book.douban.com/";
// 下载豆瓣读书的页面并且以字符串形式进行存储
String doubanBookString = downloadHtml(urlString);
// 获取"最受关注排行榜"这个子串的开始位置一直到结束的子串
doubanBookString = doubanBookString.substring(doubanBookString.indexOf("最受关注图书榜"));
// 获取从头到"豆瓣书店"这个子串的开始位置的子串
doubanBookString = doubanBookString.substring(0, doubanBookString.indexOf("豆瓣书店"));
Matcher matcher = null;
// 构造匹配中文的字符串
String chinesePatternString = "[\\u4E00-\\u9FA5]+";
// 构造匹配<img>标签的正则表达式
String bookNamePatternString = "<img(.+?)>";
String bookNameString = null;
//获得<img>标签中的内容
matcher = Pattern.compile(bookNamePatternString).matcher(doubanBookString);
if (matcher.find())
bookNameString = matcher.group();
//获得<img>标签中的中文即书名
matcher = Pattern.compile(chinesePatternString).matcher(bookNameString);
if (matcher.find())
//将书名写入book.txt中
output.println("书名:《"+matcher.group()+"》");
// 构造匹配class="author"的<p>标签的正则表达式
String bookAuthorPatternString = "author(.+?)</p>";
String bookAuthorString = null;
//获得含有作者名字的子字符串
matcher = Pattern.compile(bookAuthorPatternString).matcher(doubanBookString);
if (matcher.find())
bookAuthorString = matcher.group();
//构造匹配作者名称的正则表达式
bookAuthorPatternString="作者(.+?)(<)";
matcher = Pattern.compile(bookAuthorPatternString).matcher(bookAuthorString);
if (matcher.find()) {
//由于匹配到的作者名称会在最后多一个字符"<"所以我们要去掉这个字符
output.println(matcher.group().substring(0,matcher.group().length()-1));
}
}
- 得到的结果为:
- 这里只保存了一本书的书名和作者名,如果需要保存全部的书名和作者名,我们可以使用字符串列表,将书名和作者名字符串列表中到后面统一输出。具体请参考全部代码
全部代码
java">import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
// 网页下载函数
public static String downloadHtml(String urlString) throws IOException {
// 构造URL对象
URL url = new URL(urlString);
// 连接到URL
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
// 创建数据流
// 获取数据,并以字节的形式放在缓冲区
InputStream inputStream = httpURLConnection.getInputStream();
// 使用指定字符集将字节流转换到字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
// 将字符流放入缓存里,如果缓存满了,就读入内存,这个类存在的作用是为了提高读的效率
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
// 从字符流中读取数据
String lineString;
String htmlString = "";
while ((lineString = bufferedReader.readLine()) != null) {
htmlString += lineString;
}
return htmlString;
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 构造文件输出流
PrintStream output = new PrintStream(new File("book.txt"));
// 构建书本名称列表
ArrayList<String> bookNameList = new ArrayList<String>();
// 构建作者名字列表
ArrayList<String> authorNameList = new ArrayList<String>();
// 豆瓣读书的链接
String urlString = "https://book.douban.com/";
// 下载豆瓣读书的页面并且以字符串形式进行存储
String doubanBookString = downloadHtml(urlString);
// 获取"最受关注排行榜"这个子串的开始位置一直到结束的子串
doubanBookString = doubanBookString.substring(doubanBookString.indexOf("最受关注图书榜"));
// 获取从头到"豆瓣书店"这个子串的开始位置的子串
doubanBookString = doubanBookString.substring(0, doubanBookString.indexOf("豆瓣书店"));
Matcher matcher = null;
// 构造匹配中文的字符串
String chinesePatternString = "[\\u4E00-\\u9FA5]+";
// 构造匹配<img>标签的正则表达式
String bookNamePatternString = "<img(.+?)>";
String arrString = null;
// 获得<img>标签中的内容
matcher = Pattern.compile(bookNamePatternString).matcher(doubanBookString);
while (matcher.find()) {
arrString = matcher.group();
// 获得<img>标签中的中文即书名
Matcher matcher1 = Pattern.compile(chinesePatternString).matcher(arrString);
if (matcher1.find())
// 将书名写入book.txt中
bookNameList.add("书名:《" + matcher1.group() + "》");
}
// 构造匹配class="author"的<p>标签的正则表达式
String bookAuthorPatternString = "author(.+?)</p>";
// 获得含有作者名字的子字符串
matcher = Pattern.compile(bookAuthorPatternString).matcher(doubanBookString);
while (matcher.find()) {
arrString = matcher.group();
// 构造匹配作者名称的正则表达式
bookAuthorPatternString = "作者(.+?)(<)";
Matcher matcher1 = Pattern.compile(bookAuthorPatternString).matcher(arrString);
if (matcher1.find()) {
// 由于匹配到的作者名称会在最后多一个字符"<"所以我们要去掉这个字符
authorNameList.add(matcher1.group().substring(0, matcher1.group().length() - 1));
}
}
for(int i=0;i<bookNameList.size();i++) {
output.println(bookNameList.get(i));
output.println(authorNameList.get(i));
}
}
}