首页 > 技术文章 > Java学习(1)网络爬虫编写

OverloadBachPunk 2021-01-28 21:30 原文

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
这种方法在方法签名中,用于声明该方法可能抛出的异常。还有其他的异常处理机制有:

  1. try:它里面放置可能引发异常的代码
  2. catch:后面对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块,可以有多个catch块。
  3. finally:主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件),异常机制总是保证finally块总是被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者 throw等终止方法的语句,则就不会跳回执行,直接停止。
  4. 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包中。

  1. 首先用maven进行package,生成classes

  2. 在左侧窗口的JAVA PROJECT中,点击导出到jar文件

  3. 再运行java -jar .\scrapy.jar,此时jar文件可以运行。

总结

通过这个爬虫教程,主要学习以下基础知识:

  1. Maven+VScode的基本使用方法;
  2. Maven导入外部依赖包的基础方式;
  3. Java中异常的处理方法;
  4. Jsoup包的基本使用方式;
  5. Java中基础的文件读写操作;
  6. 在VScode下对Java进行打包的简单方式。

推荐阅读