手写一个java爬虫,获取网页信息。

本文将介绍 1: 网络爬虫的是做什么的? 2: 手动写一个简单的网络爬虫;

1: 网络爬虫是做什么的? 他的主要工作就是 跟据指定的url地址 去发送请求,获得响应, 然后解析响应 , 一方面从响应中查找出想要查找的数据,另一方面从响应中解析出新的URL路径,

然后继续访问,继续解析;继续查找需要的数据和继续解析出新的URL路径 .

这就是网络爬虫主要干的工作. 下面是流程图:
通过上面的流程图 能大概了解到 网络爬虫 干了哪些活 ,根据这些 也就能设计出一个简单的网络爬虫出来.

一个简单的爬虫 必需的功能:

1: 发送请求和获取响应的功能 ;

2: 解析响应的功能 ;

3: 对 过滤出的数据 进行存储 的功能 ;

4: 对解析出来的URL路径 处理的功能 ;

编写后端代码

1、创建一个java工程,添加一下依赖:

<dependencies><dependency><groupId>com.googlecode.juniversalchardet</groupId><artifactId>juniversalchardet</artifactId><version>1.0.3</version></dependency><dependency><groupId>org.kie.modules</groupId><artifactId>org-apache-commons-httpclient</artifactId><version>6.2.0.CR2</version><type>pom</type></dependency><dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.10.3</version></dependency></dependencies>

2、以下是我的java工程的包结构:


2.1、LinkFile接口:

package com.etoak.crawl.link;public interface LinkFilter {public boolean accept(String url);
}

2.2、Link类:两个属性: 一个是存放 已经访问的url集合的set ; 一个是存放待访问url集合的 queue

package com.etoak.crawl.link;import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;/*
*  Link主要功能;
*  1: 存储已经访问过的URL路径 和 待访问的URL 路径;
*
*
* */
public class Links {//已访问的 url 集合  已经访问过的 主要考虑 不能再重复了 使用set来保证不重复;private static Set visitedUrlSet = new HashSet();//待访问的 url 集合  待访问的主要考虑 1:规定访问顺序;2:保证不提供重复的带访问地址;private static LinkedList unVisitedUrlQueue = new LinkedList();//获得已经访问的 URL 数目public static int getVisitedUrlNum() {return visitedUrlSet.size();}//添加到访问过的 URLpublic static void addVisitedUrlSet(String url) {visitedUrlSet.add(url);}//移除访问过的 URLpublic static void removeVisitedUrlSet(String url) {visitedUrlSet.remove(url);}//获得 待访问的 url 集合public static LinkedList getUnVisitedUrlQueue() {return unVisitedUrlQueue;}// 添加到待访问的集合中  保证每个 URL 只被访问一次public static void addUnvisitedUrlQueue(String url) {if (url != null && !url.trim().equals("")  && !visitedUrlSet.contains(url)  && !unVisitedUrlQueue.contains(url)){unVisitedUrlQueue.add(url);}}//删除 待访问的urlpublic static Object removeHeadOfUnVisitedUrlQueue() {return unVisitedUrlQueue.removeFirst();}//判断未访问的 URL 队列中是否为空public static boolean unVisitedUrlQueueIsEmpty() {return unVisitedUrlQueue.isEmpty();}
}

2.3、MyCrawler类:

package com.etoak.crawl.main;import com.etoak.crawl.link.LinkFilter;
import com.etoak.crawl.link.Links;
import com.etoak.crawl.page.Page;
import com.etoak.crawl.page.PageParserTool;
import com.etoak.crawl.page.RequestAndResponseTool;
import com.etoak.crawl.util.FileTool;
import org.jsoup.select.Elements;import java.util.Set;public class MyCrawler {/*** 使用种子初始化 URL 队列** @param seeds 种子 URL* @return*/private void initCrawlerWithSeeds(String[] seeds) {for (int i = 0; i < seeds.length; i++){Links.addUnvisitedUrlQueue(seeds[i]);}}/*** 抓取过程** @param seeds* @return*/public void crawling(String[] seeds) {//初始化 URL 队列initCrawlerWithSeeds(seeds);//定义过滤器,提取以https://www.douban.com/开头的链接LinkFilter filter = new LinkFilter() {public boolean accept(String url) {if (url.startsWith("https://www.douban.com/"))return true;elsereturn false;}};//循环条件:待抓取的链接不空且抓取的网页不多于 1000while (!Links.unVisitedUrlQueueIsEmpty()  && Links.getVisitedUrlNum() <= 1000) {//先从待访问的序列中取出第一个;String visitUrl = (String) Links.removeHeadOfUnVisitedUrlQueue();if (visitUrl == null){continue;}//根据URL得到page;Page page = RequestAndResponseTool.sendRequstAndGetResponse(visitUrl);//对page进行处理: 访问DOM的某个标签Elements es = PageParserTool.select(page,"a");if(!es.isEmpty()){System.out.println("下面将打印所有a标签: ");System.out.println(es);}//将保存文件FileTool.saveToLocal(page);//将已经访问过的链接放入已访问的链接中;Links.addVisitedUrlSet(visitUrl);//得到超链接Set<String> links = PageParserTool.getLinks(page,"img");for (String link : links) {Links.addUnvisitedUrlQueue(link);System.out.println("新增爬取路径: " + link);}}}//main 方法入口public static void main(String[] args) {MyCrawler crawler = new MyCrawler();crawler.crawling(new String[]{"https://www.douban.com/"});}
}

2.4、Page类:保存响应的相关内容 对外提供访问方法;

package com.etoak.crawl.page;import com.etoak.crawl.util.CharsetDetector;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;import java.io.UnsupportedEncodingException;/*
* page
*   1: 保存获取到的响应的相关内容;
* */
public class Page {private byte[] content ;private String html ;  //网页源码字符串private Document doc  ;//网页Dom文档private String charset ;//字符编码private String url ;//url路径private String contentType ;// 内容类型public Page(byte[] content , String url , String contentType){this.content = content ;this.url = url ;this.contentType = contentType ;}public String getCharset() {return charset;}public String getUrl(){return url ;}public String getContentType(){ return contentType ;}public byte[] getContent(){ return content ;}/*** 返回网页的源码字符串** @return 网页的源码字符串*/public String getHtml() {if (html != null) {return html;}if (content == null) {return null;}if(charset==null){charset = CharsetDetector.guessEncoding(content); // 根据内容来猜测 字符编码}try {this.html = new String(content, charset);return html;} catch (UnsupportedEncodingException ex) {ex.printStackTrace();return null;}}/**  得到文档* */public Document getDoc(){if (doc != null) {return doc;}try {this.doc = Jsoup.parse(getHtml(), url);return doc;} catch (Exception ex) {ex.printStackTrace();return null;}}}

2.5、PageParserTool类:提供了 根据选择器来选取元素 属性 等方法 ;

package com.etoak.crawl.page;import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;public class PageParserTool {/* 通过选择器来选取页面的 */public static Elements select(Page page , String cssSelector) {return page.getDoc().select(cssSelector);}/**  通过css选择器来得到指定元素;**  */public static Element select(Page page , String cssSelector, int index) {Elements eles = select(page , cssSelector);int realIndex = index;if (index < 0) {realIndex = eles.size() + index;}return eles.get(realIndex);}/*** 获取满足选择器的元素中的链接 选择器cssSelector必须定位到具体的超链接* 例如我们想抽取id为content的div中的所有超链接,这里* 就要将cssSelector定义为div[id=content] a*  放入set 中 防止重复;* @param cssSelector* @return*/public static  Set<String> getLinks(Page page ,String cssSelector) {Set<String> links  = new HashSet<String>() ;Elements es = select(page , cssSelector);Iterator iterator  = es.iterator();while(iterator.hasNext()) {Element element = (Element) iterator.next();if ( element.hasAttr("href") ) {links.add(element.attr("abs:href"));}else if( element.hasAttr("src") ){links.add(element.attr("abs:src"));}}return links;}/*** 获取网页中满足指定css选择器的所有元素的指定属性的集合* 例如通过getAttrs("img[src]","abs:src")可获取网页中所有图片的链接* @param cssSelector* @param attrName* @return*/public static ArrayList<String> getAttrs(Page page , String cssSelector, String attrName) {ArrayList<String> result = new ArrayList<String>();Elements eles = select(page ,cssSelector);for (Element ele : eles) {if (ele.hasAttr(attrName)) {result.add(ele.attr(attrName));}}return result;}
}

2.6、RequestAndResponseTool类:

package com.etoak.crawl.page;import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;import java.io.IOException;public class RequestAndResponseTool {public static Page  sendRequstAndGetResponse(String url) {Page page = null;// 1.生成 HttpClinet 对象并设置参数HttpClient httpClient = new HttpClient();// 设置 HTTP 连接超时 5shttpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);// 2.生成 GetMethod 对象并设置参数GetMethod getMethod = new GetMethod(url);// 设置 get 请求超时 5sgetMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);// 设置请求重试处理getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());// 3.执行 HTTP GET 请求try {int statusCode = httpClient.executeMethod(getMethod);// 判断访问的状态码if (statusCode != HttpStatus.SC_OK) {System.err.println("Method failed: " + getMethod.getStatusLine());}// 4.处理 HTTP 响应内容byte[] responseBody = getMethod.getResponseBody();// 读取为字节 数组String contentType = getMethod.getResponseHeader("Content-Type").getValue(); // 得到当前返回类型page = new Page(responseBody,url,contentType); //封装成为页面} catch (HttpException e) {// 发生致命的异常,可能是协议不对或者返回的内容有问题System.out.println("Please check your provided http address!");e.printStackTrace();} catch (IOException e) {// 发生网络异常e.printStackTrace();} finally {// 释放连接getMethod.releaseConnection();}return page;}
}

2.7、CharsetDetector类:获取字符编码

package com.etoak.crawl.util;import org.mozilla.universalchardet.UniversalDetector;import java.io.UnsupportedEncodingException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 字符集自动检测** @author hu*/
public class CharsetDetector {//从Nutch借鉴的网页编码检测代码private static final int CHUNK_SIZE = 2000;private static Pattern metaPattern = Pattern.compile("<meta\\s+([^>]*http-equiv=(\"|')?content-type(\"|')?[^>]*)>",Pattern.CASE_INSENSITIVE);private static Pattern charsetPattern = Pattern.compile("charset=\\s*([a-z][_\\-0-9a-z]*)", Pattern.CASE_INSENSITIVE);private static Pattern charsetPatternHTML5 = Pattern.compile("<meta\\s+charset\\s*=\\s*[\"']?([a-z][_\\-0-9a-z]*)[^>]*>",Pattern.CASE_INSENSITIVE);//从Nutch借鉴的网页编码检测代码private static String guessEncodingByNutch(byte[] content) {int length = Math.min(content.length, CHUNK_SIZE);String str = "";try {str = new String(content, "ascii");} catch (UnsupportedEncodingException e) {return null;}Matcher metaMatcher = metaPattern.matcher(str);String encoding = null;if (metaMatcher.find()) {Matcher charsetMatcher = charsetPattern.matcher(metaMatcher.group(1));if (charsetMatcher.find()) {encoding = new String(charsetMatcher.group(1));}}if (encoding == null) {metaMatcher = charsetPatternHTML5.matcher(str);if (metaMatcher.find()) {encoding = new String(metaMatcher.group(1));}}if (encoding == null) {if (length >= 3 && content[0] == (byte) 0xEF&& content[1] == (byte) 0xBB && content[2] == (byte) 0xBF) {encoding = "UTF-8";} else if (length >= 2) {if (content[0] == (byte) 0xFF && content[1] == (byte) 0xFE) {encoding = "UTF-16LE";} else if (content[0] == (byte) 0xFE&& content[1] == (byte) 0xFF) {encoding = "UTF-16BE";}}}return encoding;}/*** 根据字节数组,猜测可能的字符集,如果检测失败,返回utf-8** @param bytes 待检测的字节数组* @return 可能的字符集,如果检测失败,返回utf-8*/public static String guessEncodingByMozilla(byte[] bytes) {String DEFAULT_ENCODING = "UTF-8";UniversalDetector detector = new UniversalDetector(null);detector.handleData(bytes, 0, bytes.length);detector.dataEnd();String encoding = detector.getDetectedCharset();detector.reset();if (encoding == null) {encoding = DEFAULT_ENCODING;}return encoding;}/*** 根据字节数组,猜测可能的字符集,如果检测失败,返回utf-8* @param content 待检测的字节数组* @return 可能的字符集,如果检测失败,返回utf-8*/public static String guessEncoding(byte[] content) {String encoding;try {encoding = guessEncodingByNutch(content);} catch (Exception ex) {return guessEncodingByMozilla(content);}if (encoding == null) {encoding = guessEncodingByMozilla(content);return encoding;} else {return encoding;}}
}

2.8、FileTool类:文件下载类:

package com.etoak.crawl.util;import com.etoak.crawl.page.Page;import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/*  本类主要是 下载那些已经访问过的文件*/
public class FileTool {private static String dirPath;/*** getMethod.getResponseHeader("Content-Type").getValue()* 根据 URL 和网页类型生成需要保存的网页的文件名,去除 URL 中的非文件名字符*/private static String getFileNameByUrl(String url, String contentType) {//去除 http://url = url.substring(7);//text/html 类型if (contentType.indexOf("html") != -1) {url = url.replaceAll("[\\?/:*|<>\"]", "_") + ".html";return url;}//如 application/pdf 类型else {return url.replaceAll("[\\?/:*|<>\"]", "_") + "." +contentType.substring(contentType.lastIndexOf("/") + 1);}}/**  生成目录* */private static void mkdir() {if (dirPath == null) {dirPath = Class.class.getClass().getResource("/").getPath() + "temp\\";}File fileDir = new File(dirPath);if (!fileDir.exists()) {fileDir.mkdir();}}/*** 保存网页字节数组到本地文件,filePath 为要保存的文件的相对地址*/public static void saveToLocal(Page page) {mkdir();String fileName =  getFileNameByUrl(page.getUrl(), page.getContentType()) ;String filePath = dirPath + fileName ;byte[] data = page.getContent();try {//Files.lines(Paths.get("D:\\jd.txt"), StandardCharsets.UTF_8).forEach(System.out::println);DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(filePath)));for (int i = 0; i < data.length; i++) {out.write(data[i]);}out.flush();out.close();System.out.println("文件:"+ fileName + "已经被存储在"+ filePath  );} catch (IOException e) {e.printStackTrace();}}}

2.9、RegexRule类:正则表达式类;

package com.etoak.crawl.util;import java.util.ArrayList;
import java.util.regex.Pattern;/**** @author hu*/
public class RegexRule {public RegexRule(){}public RegexRule(String rule){addRule(rule);}public RegexRule(ArrayList<String> rules){for (String rule : rules) {addRule(rule);}}public boolean isEmpty(){return positive.isEmpty();}private ArrayList<String> positive = new ArrayList<String>();private ArrayList<String> negative = new ArrayList<String>();/*** 添加一个正则规则 正则规则有两种,正正则和反正则 * URL符合正则规则需要满足下面条件: 1.至少能匹配一条正正则 2.不能和任何反正则匹配* 正正则示例:+a.*c是一条正正则,正则的内容为a.*c,起始加号表示正正则* 反正则示例:-a.*c时一条反正则,正则的内容为a.*c,起始减号表示反正则* 如果一个规则的起始字符不为加号且不为减号,则该正则为正正则,正则的内容为自身* 例如a.*c是一条正正则,正则的内容为a.*c* @param rule 正则规则* @return 自身*/public RegexRule addRule(String rule) {if (rule.length() == 0) {return this;}char pn = rule.charAt(0);String realrule = rule.substring(1);if (pn == '+') {addPositive(realrule);} else if (pn == '-') {addNegative(realrule);} else {addPositive(rule);}return this;}/*** 添加一个正正则规则* @param positiveregex* @return 自身*/public RegexRule addPositive(String positiveregex) {positive.add(positiveregex);return this;}/*** 添加一个反正则规则* @param negativeregex* @return 自身*/public RegexRule addNegative(String negativeregex) {negative.add(negativeregex);return this;}/*** 判断输入字符串是否符合正则规则* @param str 输入的字符串* @return 输入字符串是否符合正则规则*/public boolean satisfy(String str) {int state = 0;for (String nregex : negative) {if (Pattern.matches(nregex, str)) {return false;}}int count = 0;for (String pregex : positive) {if (Pattern.matches(pregex, str)) {count++;}}if (count == 0) {return false;} else {return true;}}
}

3、运行启动类MyCrawler:爬取豆瓣网址信息;
运行完成,打印信息:

4、进入爬出信息储存的地址并点开.html文件:
.html文件显示内容:
文章主要参考: 1: 自己动手写网络爬虫;
2: https://github.com/CrawlScript/WebCollector
WebCollector是一个无须配置、便于二次开发的JAVA爬虫框架(内核),它提供精简的的API,只需少量代码即可实现一个功能强大的爬虫。WebCollector-Hadoop是WebCollector的Hadoop版本,支持分布式爬取。

本文主要参照博主三目鸟的文章,感谢分享!;
原文链接:https://www.cnblogs.com/sanmubird/p/7857474.html

手写一个java爬虫,获取网页信息。相关推荐

  1. python从零写一个采集器:获取网页信息

    博客链接 https://uublog.com/article/20170216/python-extarct-html-info/ 前言 获取内容,比较纠结是用BeautifulSoup还是直接用正 ...

  2. Java爬虫获取网页表格数据

    //Java爬虫获取网页表格数据 public class Pachong implements Runnable { public void run() { String Rpt_date = nu ...

  3. Java爬虫获取网页视频,音乐,图片等

    如何使用Java写爬虫来获取网页视频 方法1对于没有反爬虫机制(不多说直接上代码) @SuppressWarnings("all") public String getVideo( ...

  4. 厉害了,自己手写一个Java热加载!

    热加载:在不停止程序运行的情况下,对类(对象)的动态替换. Java ClassLoader 简述 Java中的类从被加载到内存中到卸载出内存为止,一共经历了七个阶段:加载.验证.准备.解析.初始化. ...

  5. 爬虫系列教程二:如何获取网页信息并定位信息所处位置

    在爬虫中如何获取并定位网页的信息 由于我们获取网页的类型的不同,我们希望爬取的信息的定位方法也有很大差别,但总体来说我们想要爬取的网页可以分为静态和动态,下面讲述在不同的情况下如何爬取这些信息: 网页 ...

  6. 用Java手写一个微型下载资源网站

    文章目录 手写一个微型下载资源网站[Java实现用户注册.登陆.下载功能] 一.技术栈 二.流程分析图 注册 登陆 下载 三.案例实现效果 首页 注册 登陆 下载网主页 壁纸下载 书籍下载 影视下载 ...

  7. 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】

    第二季:5值传递和引用传递[Java面试题] 前言 推荐 值传递 说明 题目 24 TransferValue醒脑小练习 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自 ...

  8. Java 手写一个SQL分页

    Java手写一个类似PageHelper的分页SQL 目前分页插件众所周知的莫过于和mybatis完美融合的PageHelper了,简单两行代码就实现了sql分页,配合PageInfo类,将数据总数量 ...

  9. 手写一个获取验证码的接口,超级简单

    手写一个获取验证码的接口,超级简单,觉得有用就试试吧,话不多说代码附上 private static final int VERIFY_CODE_HEIGHT = 25; //验证码高度private ...

  10. list的add方法 ,foreach循环添加map---List.add(map)(通过一个java爬虫案例说明)

    案例:一个java爬虫程序 1.案例说明 做了一个爬取某程的旅游网站的java程序,程序主要爬取安庆酒店的某些相关信息. 材料准备:jsoup-1.8.1.jar(需要此架包的联系博主,有任何问题欢迎 ...

最新文章

  1. C++多线程之间,线程函数启动之后,多线程依赖的启动和线程唤醒操作。
  2. 字节跳动一面:i++ 是线程安全的吗?
  3. 上海将打造“泛在化、融合化、智敏化”智慧城市
  4. 程序员的杂想-不要只做一名程序员
  5. P(Y=y|x;θ)表示什么意思
  6. 设计模式--原型(Prototype)模式
  7. bzoj4589: Hard Nim
  8. TeamViewer免费版和付费版有什么不同
  9. c语言软件电脑下载文件,求C语言头文件下载?
  10. 为什么程序员跟其他人比起来应该喝更多的水
  11. wireshark windows版数据过滤插件安装及使用
  12. 动手设计 CPU(一)—— 各类元件功能表
  13. Linux中fork函数详解(附图解与代码实现)
  14. vncview用法_vnc远程桌面怎么使用(最新vncviewer使用教程)
  15. 前端网页打印插件print.js
  16. 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(下)- BGP
  17. C++为什么需要高精度计算
  18. MATLAB 函数之resample
  19. html春节祝福烟花,《烟花AR》用ARkit带来浪漫情人节和新春祝福
  20. 华科计算机考研复试真题,华科计算机考研复试机试题【含参考代码】

热门文章

  1. 找规律+菊花图 - hdu6090
  2. [USACO09OCT]谷仓里的回声Barn Echoes(hush、STL)
  3. 代码弱鸡竟然在CSDN写烘焙博客
  4. 调查问卷反馈数据收集、分析过程
  5. 通知 | 2021 中国高校计算机大赛 —— 微信大数据挑战赛
  6. vue图形验证码组件
  7. 微信小程序休眠setTimeout
  8. Javascript的交互性
  9. 聊聊什么是自动化测试,什么是自动化测试框架
  10. Ubuntu/Debain下安装微信、QQ等Windows应用(最简便方法)