基于Jsoup实现的简单爬虫

来源:互联网 发布:大学生应知相关法律 编辑:程序博客网 时间:2024/05/17 02:44

Jsoup 概念

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

其实在这之前我解析Html一直都是使用HtmlPaser来做,在我第一次看到Jsoup的时候,我就在思考Jsoup的存在意义,既然已经有htmlPaser为什么还会Jsoup出现。经过自己动手试验了一下,我感觉到了Jsoup的魅力。

  • 首先是更新频率,Htmlpaser早在06年就停止更新了,这十年间互联网技术的变化不可谓不大,htmlpaser或许现在还能勉强使用,但不保证下一刻啊。Jsoup到目前为止还在继续更新,作为一个比Htmlpaser年轻的Html解释器,或许还有很多地方不足,但在持续更新下终究会得到解决。
  • 然后是实现原理, htmlpaser是基于fileter进行解析,有些类似对字符串进行正则匹配的方式进行获取。或许是我对htmlpaser还不够熟悉,在一个Paser对象进行pase一次后该paser对象将不可再使用。然而Jsoup却使用了一套自建的Dom结构,该DOM结构就是一个树形数据结构。相比htmlPaser在使用filter转换一次后将不可再次使用的问题,jsoup完全不存在这个问题,在使用getElement*方法后仅仅是获取其子树或者叶子,对原有的树并未做出任何改变。
  • 然后是使用方法,而如果需要获取其子节点要么使用filter一步到位,要么就只能根据子节点层次遍历。这种不灵活的节点获取方式造成了很多垃圾代码,也使程序变得十分脆弱。然而Jsoup却使用了一套自建的Dom结构,几乎兼容javascript的dom操作方式,即可取值也可设置。更加需要说明的是jsoup有着类似jquery的dom操作方式,我相信现在绝大多数猿们都会使用jquery,这也是最吸引我的一点。

Jsoup的SSL扩展

现在很多站点都是SSL对数据传输进行加密,这也让普通的HttpConnection无法正常的获取该页面的内容,而Jsoup本身也对次没有做出相应的处理,只是留下来了一个粗糙的使用证书配置什么的方法进行解决。想了一下是否可以让Jsoup可以识别所有的SSL加密过的页面,查询了一些资料,发现可以为本地HttpsURLConnection配置一个“万能证书”,其原理是就是:

  • 重置HttpsURLConnection的DefaultHostnameVerifier,使其对任意站点进行验证时都返回true
  • 重置httpsURLConnection的DefaultSSLSocketFactory, 使其生成随机证书

代码实现

爬虫代码示例

package org.hanmeis.common.html;import org.jsoup.nodes.Document;import org.jsoup.nodes.Element;import org.jsoup.select.Elements;import java.io.FileWriter;import java.io.IOException;import java.net.URL;import java.util.LinkedList;import java.util.List;/** * Created by zhao.wu on 2016/12/2. * 该爬虫用于爬去奇书网的玄幻小说类表 */public class QiShuuListSpider {    //用于保存小说信息的列表    static List<NovelDir> novelDirs = new LinkedList<>();    public static void main(String[] args) throws IOException {        //解析过程        URL index = new URL("http://www.qisuu.com/soft/sort02/");        parsePage(index);        //将信息存档        FileWriter writer = new FileWriter("qishu.txt");        for (NovelDir novelDir : novelDirs) {            writer.write(novelDir.toString());        }        writer.close();    }    static void parsePage(URL url){        try {             //使用Jsoup的解析方法进行填装Dom            Document doc = Jsoups.parse(url, 1000);            //获取小说列表            Elements novelList = doc.select(".listBox li");            for (Element element : novelList) {                NovelDir dir = new NovelDir();                //获取小说作者                Element authorElement = element.select(".s a").first();                if(authorElement!=null) {                    dir.setAuthor(authorElement.html());                }                //获取小说描述                Element descriElement = element.select(".u").first();                if(descriElement!=null) {                    dir.setDescription(descriElement.html());                }                //获取标题、目录地址和封面                Element titleElement = element.select("a").last();                if(titleElement!=null) {                    dir.setTitle(titleElement.html());                    dir.setIndexUrl(titleElement.attr("abs:href"));                    Element imageElement = titleElement.select("img").first();                    if(imageElement!=null) {                        dir.setHeadPic(imageElement.attr("src"));                    }                }                System.out.println(dir);                novelDirs.add(dir);            }            //获取下一页的地址,并进行请求            Elements pageDiv = doc.select(".tspage a");            for (Element element : pageDiv) {                if(element.html().equals("下一页")){                    //使用“abs:href"获取该页面的绝对地址                    String path = element.attr("abs:href");                    //由于该站点做了请求频率限制,过快的请求会遭到暂时屏蔽,所以要细水长流的的慢慢请求                    Thread.sleep(2000);                    parsePage(new URL(path));                }            }        } catch (IOException e) {            System.out.println(url);            e.printStackTrace();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * 小说MATE数据对象     */    static class NovelDir{        //封面        private String headPic;        //作者        private String author;        //标题        private String title;        //目录地址        private String indexUrl;        //大概描述        private String description;        //getter, setter toString    }}

SSL扩展代码

package org.hanmeis.common.html;import org.jsoup.Connection;import org.jsoup.helper.HttpConnection;import org.jsoup.nodes.Document;import javax.net.ssl.*;import java.io.IOException;import java.net.URL;import java.security.SecureRandom;import java.security.cert.CertificateException;import java.security.cert.X509Certificate;/** * Created by zhao.wu on 2016/11/29. */public class Jsoups{    static{        try {            //重置HttpsURLConnection的DefaultHostnameVerifier,使其对任意站点进行验证时都返回true            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {                public boolean verify(String hostname, SSLSession session) {                    return true;                }            });            //创建随机证书生成工厂            SSLContext context = SSLContext.getInstance("TLS");            context.init(null, new X509TrustManager[] { new X509TrustManager() {                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {                }                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {                }                public X509Certificate[] getAcceptedIssuers() {                    return new X509Certificate[0];                }            } }, new SecureRandom());            //重置httpsURLConnection的DefaultSSLSocketFactory, 使其生成随机证书            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 使用ssl的方式去获取远程html的dom,      * 该方法在功能上与Jsoup本身的转换工具一样,     * 仅仅是用来告诉代码阅读者这个方法已经对SSL进行了扩展     * @param url 需要转换的页面地址     * @param timeoutMillis 请求超市时间     * @return 该页面的dom树     * @throws IOException 请求异常或者转换异常时抛出     */    public static Document parse(URL url, int timeoutMillis) throws IOException {        Connection con = HttpConnection.connect(url);        con.timeout(timeoutMillis);        return con.get();    }   }
0 0