Java网络爬虫编写
我最初刚学习Python时,对编程基本一窍不通,当时的想法很简单,只是为了学点简单的编程方便自己做点小玩意儿。当时自己编写的第一个应用就是爬虫,写完之后对Python的语法、模块安装、数据结构(虽然Python就不存在什么数据结构)等各种方面都有了入门级的了解。我个人认为,直接从一个项目开始学习比按部就班地按照课程学习会来的快得多,当然这是比较功利的,如果是真的想好好掌握Java这门语言的,应当还是按照课程慢慢学习。这篇文章主要参考了https://blog.csdn.net/weixin_36146275/article/details/54974359。
新建一个Java项目
在vscode中新建一个Java项目,会看到选择项目类型这个下拉框,里面有几种类型:Maven、Spring Boot、Quarkus、MicroProfile。他们的解释如下:
-
Maven:Apache Maven是一个软件项目管理的综合工具。基于项目对象模型(POM)的概念,提供了帮助管理构建、文档、报告、依赖、发布等方法,Maven简化和标准化项目建设过程。Maven的基本原理是采用远程仓库和本地仓库以及一个核心的配置文件pom.xml,pom.xml中定义的jar文件从远程仓库下载到本地仓库,各个项目使用同一个本地仓库的jar,同一个版本的jar只需下载一次,而且避免每个应用都去拷贝jar。同时它采用了现在流行的插件体系架构,所以maven的核心非常的小,只有几兆大小的文件,在执行maven任务时,才会自动下载需要的插件。
-
Spring Boot:是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。Spring的一个最大的目的就是使JAVA EE开发更加容易。JavaEE:全称Java平台企业版(Java Platform Enterprise Edition),是Sun公司为企业级应用推出的标准平台。JavaEE是个大杂烩,包括Applet、EJB、JDBC、JNDI、Servlet、JSP等技术的标准,运行在一个完整的应用服务器上,用来开发大规模、分布式、健壮的网络应用。
-
Quarkus:一个针对OpenJDK HotSpot和GraalVM量身定制的Kubernetes本机Java堆栈,它是从最佳Java库和标准中精制而成的。
-
Eclipse MicroProfile:和Spring类似,是一个Jave EE的微服务框架。
实际上初学者应该迟一些再接触这些框架。这里选择Maven进入下一步,Maven介绍和入门传送门在这里。
Java模块(Jsoup)安装与导入
Jsoup可以用于编写Java网络爬虫。官方介绍:Open source Java HTML parser, with the best of HTML5 DOM methods and CSS selectors, for easy data extraction.
在Maven+vscode的开发环境下中导入外部包非常方便。同样参照Maven介绍和入门。
功能实现
简便起见,将所有的功能都先写在main方法里。
连接目标网页并获取内容
链接目标网页通过Jsoup的connect方法实现,参数为网页的url。Connection 接口还提供一个方法链来解决特殊请求,具体如下:
Document doc = Jsoup.connect("http://example.com";)
.data("query", "Java")
.userAgent("Mozilla")
.cookie("auth", "token")
.timeout(3000)
.post();
最简单的例子(也是此爬虫使用的例子如下):
Document doc = Jsoup.connect("http://news.sohu.com/").get();
这里需要使用到Jsoup的Document类。Document是一个装载html的文档类,它是Jsoup一个非常重要的类。所有的HTML文件都应该被声明为Document类才能被Jsoup进一步处理。
Java的异常处理机制
注意,因为get取得和解析一个HTML文件。如果从该URL获取HTML时发生错误,便会抛出 IOException,应适当处理。处理的方法之一是在定义方法时,将这个异常抛出,如下:
public static void main( String[] args ) throws IOException
这种方法在方法签名中,用于声明该方法可能抛出的异常。还有其他的异常处理机制有:
- try:它里面放置可能引发异常的代码
- catch:后面对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块,可以有多个catch块。
- finally:主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件),异常机制总是保证finally块总是被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者 throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw:用于抛出一个实际的异常,可以单独作为语句使用,抛出一个具体的异常对象。
检索HTML内容
示例如下:
Elements newATags = doc.select("div.main-right").select("div.list16").select("ul").select("li").select("a");
Element类似Python中lxml通过xpath解析得到的“Elements”,在Jsoup中,可以通过select这个更为强大的方法直接获得所有满足检索条件的标签。这些标签被存在属于Elements类的newATags中,可以通过get方法来获取所需索引的Element,也可以通过for进行遍历。除此之外,比较常用的方法是attr(),获得相应属性名的属性值;text()获得标签内的文字。
Java文件读写
Java中最简单的写入文件方法是通过FileWriter类,如下:
public void writeToFile( String str) {
try {
FileWriter out = new FileWriter("foobar.txt", true);
out.write(str);
out.close();
} catch (IOException e) {
}
}
调用这个方法,就可以以附加的形式逐条写入爬取内容。如果不加参数true,默认是false,即每次写入都会覆盖上一次的记录。
整体程序如下:
package lx;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* Hello world!
*
*/
public class MyScrapy
{
public void writeToFile( String str) {
try {
FileWriter out = new FileWriter("foobar.txt", true);
out.write(str);
out.close();
} catch (IOException e) {
}
}
public static void main( String[] args ) throws IOException
{
MyScrapy scrapy = new MyScrapy();
Document doc = Jsoup.connect("http://news.sohu.com/").get();
Elements newATags = doc.select("div.main-right").select("div.list16").select("ul").select("li").select("a");
for (Element element:newATags){
String url = element.attr("href");
if(url.indexOf("//")==0 || url.indexOf("http://www.sohu.com/a/")<0){
continue;
}
String title = element.attr("title");
Document dm = Jsoup.connect(url).get();
Elements em = dm.select("p").select("img");
if(em==null || em.size()<1){
continue;
}
String img = em.get(0).attr("src");
if(img.indexOf("//")==0){
img = img.substring(2);
}
String output = "url: "+url+" title: "+title + " image: "+img;
System.out.println(output);
scrapy.writeToFile(output+'\n');
}
}
}
编译、运行、打包
输入命令mvn clean package
,然后在/target/classes目录下运行命令java lx.MyScrapy
,却发现报错:
Exception in thread "main" java.lang.NoClassDefFoundError: org/jsoup/Jsoup
at lx.MyScrapy.main(MyScrapy.java:29)
Caused by: java.lang.ClassNotFoundException: org.jsoup.Jsoup
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 1 more
而在maven下直接编译运行java文件是可以运行的。经过一番搜索,发现这是因为maven在打包时就不会把外部依赖也打包进去,防止其他人在使用你的jar包时产生依赖冲突。stackoverflow上给出了通过在POM中加入插件项的解决办法,不过在vscode下,有更简单的方式将外部依赖打包到jar包中。
-
首先用maven进行package,生成classes
-
在左侧窗口的JAVA PROJECT中,点击导出到jar文件
-
再运行
java -jar .\scrapy.jar
,此时jar文件可以运行。
总结
通过这个爬虫教程,主要学习以下基础知识:
- Maven+VScode的基本使用方法;
- Maven导入外部依赖包的基础方式;
- Java中异常的处理方法;
- Jsoup包的基本使用方式;
- Java中基础的文件读写操作;
- 在VScode下对Java进行打包的简单方式。