레이블이 httpclient인 게시물을 표시합니다. 모든 게시물 표시
레이블이 httpclient인 게시물을 표시합니다. 모든 게시물 표시

2017년 1월 20일 금요일

web crawler with java 설명

web crawler with java 에 대한 설명

이전에 만든 소스 코드는 아래 링크에 있습니다.
http://swlock.blogspot.com/2017/01/web-crawler-with-java.html
여기에서는 내용중 중요한 부분을 설명을 하도록 하겠습니다.

jericho htmlparser 에서 나오는 로그 막기

jericho 를 사용하다 보면 가끔씩 경고 형태의 로그들이 나오는데 안나오도록 막는 방법은 아래와 같습니다.

  Config.LoggerProvider=LoggerProvider.DISABLED;

구현 코드 main() 함수에 존재합니다.

apache httpclient 에서 나오는 로그 막기

http client에서도 불필요한 로그가 나오는 경우가 있습니다. 이럴때에는 아래와 같이 합니다.

System.setProperty("org.apache.commons.logging.Log",
 "org.apache.commons.logging.impl.NoOpLog"); 
 

apache.commons.cli 로 command line parameter 처리하기

jar 파일은 https://commons.apache.org/proper/commons-cli/ 여기에서 받을 수 있습니다.
이건 command line 툴 만들때 굉장히 편리한 도구 입니다.
전체 코드에서 아래 부분입니다.

Options options = new Options();

   Option savepath = new Option("s", "savepath", true, "input save folder file path");
   savepath.setRequired(true);
   options.addOption(savepath);

   Option url = new Option("u", "url", true, "url ex) http://www.daum.net");
   url.setRequired(true);
   options.addOption(url);

   Option depth = new Option("d", "depth", true, "max depth");
   depth.setRequired(false);
   options.addOption(depth);

   Option changehostdepth = new Option("c", "changehostdepth", true, "change host depth");
   changehostdepth.setRequired(false);
   options.addOption(changehostdepth);

   CommandLineParser parser = new DefaultParser();
   HelpFormatter formatter = new HelpFormatter();
   CommandLine cmd;

   try {
    cmd = parser.parse(options, args);
   } catch (ParseException e) {
    System.out.println(e.getMessage());
    formatter.printHelp("Webcrawler", options);
    System.exit(1);
    return;
   }

   String saveFilePath = cmd.getOptionValue("savepath");
   String urlPath = cmd.getOptionValue("url");
   String depthParam = cmd.getOptionValue("depth");
   if(depthParam==null || depthParam.isEmpty()) depthParam = "2";
   String changehostdepthdepthParam = cmd.getOptionValue("changehostdepth");

Options 라는 클래스가 있고 여기에 각각의 Option을 Options.addOption(Option) 메소드를 이용해서 추가하는 방법입니다.
Option의 생성자 인자는 아래와 같습니다.

org.apache.commons.cli.Option.Option(String opt, String longOpt, boolean hasArg, String description)
opt:옵션 이름
longOpt:긴 옵션 이름
hasArg:옵션 뒤에 인자를 가지는지 여부
description:옵션의 설명
그리고 아래 사용한 setRequired() 메소드는 필수 여부를 나타냅니다. 필수 옵션의 경우 인자를 입력하지 않으면 오류가 발생하게 됩니다.
savepath.setRequired(true);

옵션을 모두 만들었으면 파서를 해야 사용할 수 있습니다.
그리고 helpformatter를 이용해서 파싱시 오류가 발생하면 뭐가 문제인지 출력하도록 합니다.

CommandLineParser parser = new DefaultParser();
HelpFormatter formatter = new HelpFormatter();
CommandLine cmd;

try {
 cmd = parser.parse(options, args);
} catch (ParseException e) {
 System.out.println(e.getMessage());
 formatter.printHelp("Webcrawler", options);
 System.exit(1);
 return;
}

위의 내용을 정리해보자면 아래와 같습니다.

필수 : Option("s", "savepath", true, "input save folder file path");
필수 : Option("u", "url", true, "url ex) http://www.daum.net");
옵션 : Option("d", "depth", true, "max depth");
옵션 : Option("c", "changehostdepth", true, "change host depth");

그리고 그냥 실행시키면 아래와 같이 출력됩니다.

Missing required options: s, u
usage: Webcrawler
 -c,--changehostdepth <arg>   change host depth
 -d,--depth <arg>             max depth
 -s,--savepath <arg>          input save folder file path
 -u,--url <arg>               url ex) http://www.daum.net

필수 옵션을 안넣으면 빠졌다고 알려주고 사용법은 짧은 옵션은 -, 긴 옵션은 -- 를 추가해서 커멘드를 내리면 됩니다.

다음으로 만약 필수 옵션이 아닌경우 디폴트값을 처리해야 합니다.
getOptionValue() 메소드를 사용합니다. 그러면 입력한 데이터가 없으면 null 이 돌아오게 되되는데 그걸 처리하는 부분이 아래 코드 입니다.

String saveFilePath = cmd.getOptionValue("savepath");
String urlPath = cmd.getOptionValue("url");
String depthParam = cmd.getOptionValue("depth");
if(depthParam==null || depthParam.isEmpty()) depthParam = "2";
String changehostdepthdepthParam = cmd.getOptionValue("changehostdepth");
if(changehostdepthdepthParam==null || changehostdepthdepthParam.isEmpty()) changehostdepthdepthParam = "1";

아래는 실행 가능한 코드를 다시 만들었습니다.

package prj.dish;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class OptionsTest {
 public static void main(String[] args) {
  Options options = new Options();

  Option depth = new Option("d", "depth", true, "max depth");
  depth.setRequired(false);
  options.addOption(depth);

  Option changehostdepth = new Option("c", "changehostdepth", true, "change host depth");
  changehostdepth.setRequired(false);
  options.addOption(changehostdepth);

  CommandLineParser parser = new DefaultParser();
  HelpFormatter formatter = new HelpFormatter();
  CommandLine cmd;

  try {
   cmd = parser.parse(options, args);
  } catch (ParseException e) {
   System.out.println(e.getMessage());
   formatter.printHelp("Test", options);
   System.exit(1);
   return;
  }

  String depthParam = cmd.getOptionValue("depth");
  if(depthParam==null) depthParam = "ld empty";
  
  String shortdepthParam = cmd.getOptionValue("d");
  if(shortdepthParam==null) shortdepthParam = "sd empty";
  
  String changehostdepthdepthParam = cmd.getOptionValue("changehostdepth");
  if(changehostdepthdepthParam==null) changehostdepthdepthParam = "lc empty";
  
  System.out.println("depth:"+depthParam);
  System.out.println("d:"+shortdepthParam);
  System.out.println("changehostdepth:"+changehostdepthdepthParam);
 }

}

인자가 없을때 실행 결과 코드
depth:ld empty
d:sd empty
changehostdepth:lc empty



지금까지 main코드에 대한 설명은 대충 마무리가 된것 같습니다.

재귀 호출로 접속하기

main에서 남은 코드는 아래 내용입니다. 가장 중요한 run 메소드가 남아있습니다.
인자는 시작 하고자 하는 http의 url 주소가 됩니다.

   Webcrawler crawler;
   crawler = new Webcrawler();
   crawler.run(urlPath);

run은 내부적으로 connect 메소드를 호출하고 connect는 재귀 호출 방식으로 connect를 호출하도록 되어 있습니다.
다음은 코드를 극단적으로 정리한 코드입니다.

private void run(String string) {
 host = string;
 connect( host, "/", 0, 0);
}
private void connect(String lasturl, String addurl, int depth, int hostchange) {
 ...
 if( maxDepth <= depth ){
  return;
 }
 ...
 lasturl = calcNextUrl(lasturl, addurl);
 newurl = getHttp(lasturl);
 source=new Source(getString());

 ...
 List <Element> elements = source.getAllElements("a");

 for(int i = 0 ; i < elements.size(); i++){
  ...
  String href = ele.getAttributeValue("href");
  ...
  connect(newurl,href,depth+1,hostchange+hostchanged);
 }
}

calcNextUrl() 메소드를 이용해서 다음 접속해야하는 주소를 구한뒤 getHttp로 접속해서 page를 얻어옵니다. 그런후 Source로 파싱을 해서 이중에 A tag href 로 되어 있는 주소를 검색해서 다시 connect 메소드를 호출합니다.
connect->connect->connect->connect->... 이런 식으로 계속 들어가다보면 방문해야 하는 page가 너무 늘어나기 때문에 리턴하는 조건은 재귀로 들어갈때 마다 depth의 인자를 증가시켜서 정해놓은 depth만큼만 들어가도록 하였습니다.

jericho html parser 이용하기

new Source를 사용하는 부분은 jericho html parser를 사용하는데 아래 링크를 확인해보면 좀 더 쉽게 이해할 수 있으리라 생각됩니다.
http://swlock.blogspot.com/2017/01/jericho-htmlparser.html

다음 url 주소 계산하기

얻은 주소로부터 다음 접속해야하는 url 계산은 calcNextUrl메소드를 이용하게 되는데 아래 링크 설명으로 확인하면 됩니다.
http://swlock.blogspot.com/2016/12/httpclient-page.html

접속한 페이지 다시 접속하지 않기

간단한 방법은 방문한 페이지를 저장해놓고 이름이 같으면 접속하지 않게 구현도 가능한데 여기에서는 Set이라는 자료 구조를 사용하였습니다.
Java에는 HashSet이라고 있습니다. set은 집합이라고 생각하면 됩니다. 집합이라 넣기전에 들어있는 내용을 확인가능한데 contains로 들어있는지 확인하고 add로 넣으면 됩니다. 만약 방문한 페이지라면 해당 주소는 들어있을 겁니다.

HashSet<String> visited = new HashSet<String>();

if( !visited.contains(lasturl) ){
 visited.add(lasturl);
}else{
 System.out.println("visited !");
 return;
}

여기까지 간단하게나마 설명을 마치도록 하겠습니다.
2017.1월 어느날.....



2017년 1월 15일 일요일

Exception in thread "main" java.lang.RuntimeException: Stub! 발생이유

HttpClient 를 사용중 아래 와 같은 오류가 발생할때 대처법

Exception in thread "main" java.lang.RuntimeException: Stub!
at org.apache.http.impl.client.AbstractHttpClient.<init>(AbstractHttpClient.java:5)
at org.apache.http.impl.client.DefaultHttpClient.<init>(DefaultHttpClient.java:7)
at com.xxx.xxx.xxx.xxx.execute(xxx.java:76)
at com.xxx.xxx.xxx.xxx.main(xxx.java:126)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

코드는 아래와 같이 사용하였습니다.
발생 원인은 android studio에서 httpclient를 사용하였었는데요. 그리고는 android 단말에서 실행 시킨게 아니라 java main에 연결하여 java native형태로 실행시켜서 발생한 문제입니다. 아무래도 httpclient android용이 android없이 동작할때 제대로 동작이 안되도록 되어있어서 이러한 현상이 발생하였습니다.
해결책은 test code를 android가 정상적으로 돌 수있는 환경을 만들어서 앱 내에서 테스트 할수있도록 변경하였습니다.

샘플코드
public class GetHttpWithAHttpClient {
...

    public boolean execute(String addr) {
        boolean retval = true;
        errorCode = ERROR_CODE_NOERROR;

        //CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpClient httpclient = new DefaultHttpClient();
        try {
            HttpGet httpGet = new HttpGet(addr);
            HttpResponse response1 = httpclient.execute(httpGet);

            try {
                System.out.println(response1.getStatusLine());
                HttpEntity entity1 = response1.getEntity();
                // do something useful with the response body 
               // and ensure it is fully consumed
                entity1.getContent().read(htmlByte);
            } finally {
                //response1.close();            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
            errorCode = ERROR_CODE_HTTP_EXCEPTION;
            retval = false;
        } catch (IOException e) {
            e.printStackTrace();
            errorCode = ERROR_CODE_HTTP_EXCEPTION;
            retval = false;
        } finally {
            //try {
                //httpclient.close();
            //} catch (IOException e) {
            //    e.printStackTrace();
            //}
        }
        return retval;
    }

    public final static void main(String[] args) throws Exception {
        System.out.println("test");
        GetHttpWithAHttpClient http = new GetHttpWithAHttpClient();
        http.execute("http://www.daum.net");
    }
}











2017년 1월 12일 목요일

web crawler with java

web crawler with java


https://ko.wikipedia.org/wiki/%EC%9B%B9_%ED%81%AC%EB%A1%A4%EB%9F%AC
웹 크롤러(web crawler)는 조직적, 자동화된 방법으로 월드 와이드 웹을 탐색하는 컴퓨터 프로그램입니다.. 웹 크롤러에 대한 다른 용어로는 앤트(ants), 자동 인덱서(automatic indexers), 봇(bots), 웜(worms), 웹 스파이더(web spider), 웹 로봇(web robot) 등이 있습니다.


java로 만든 일단 소스를 아래 공유합니다.
동작은 정해진 http주소를 받아서 web page를 가져와서 파일로 저장하는 소스 입니다. depth를 정해서 html페이지내에 여러개의 링크가 있으면 각 링크들을 방문해서 저장을 하게 됩니다.
결국 웹 페이지를 가져와 저장하는 부분밖에는 없지만 가져온 페이지를 파싱해서 필요한 데이터를 추출하고 분석하는 부분은 용도에 따라 다를 수 있으니 이용자의 몫으로 남겨 두겠습니다.
사용라이브러리는 아래와 같습니다.
httpclient 4.5
commons-cli-1.3.1.jar
jericho-html-3.4.jar

설명은 나중에 추가로 작성하도록 하겠습니다.

소스
package prj.dish;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.zip.GZIPInputStream;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import net.htmlparser.jericho.Config;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.LoggerProvider;
import net.htmlparser.jericho.Source;

public class Webcrawler {
 private int maxDepth = 1;
 private int maxHostChange = 1;
 private String savePath;
 private String host;
 boolean DOMAIN_CHANGE = true;
 byte[] htmlByte = null;
 HashSet<String> visited = new HashSet<String>();
 CloseableHttpClient httpclient = HttpClients.createDefault();


 public static void main(String[] args) {
  System.out.println("Welcome !! Webcrawler");
  Config.LoggerProvider=LoggerProvider.DISABLED;
  System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog"); 
  if(args.length >= 1){
   Options options = new Options();

   Option savepath = new Option("s", "savepath", true, "input save folder file path");
   savepath.setRequired(true);
   options.addOption(savepath);

   Option url = new Option("u", "url", true, "url ex) http://www.daum.net");
   url.setRequired(true);
   options.addOption(url);

   Option depth = new Option("d", "depth", true, "max depth");
   depth.setRequired(false);
   options.addOption(depth);

   Option changehostdepth = new Option("c", "changehostdepth", true, "change host depth");
   changehostdepth.setRequired(false);
   options.addOption(changehostdepth);

   CommandLineParser parser = new DefaultParser();
   HelpFormatter formatter = new HelpFormatter();
   CommandLine cmd;

   try {
    cmd = parser.parse(options, args);
   } catch (ParseException e) {
    System.out.println(e.getMessage());
    formatter.printHelp("Webcrawler", options);
    System.exit(1);
    return;
   }

   String saveFilePath = cmd.getOptionValue("savepath");
   String urlPath = cmd.getOptionValue("url");
   String depthParam = cmd.getOptionValue("depth");
   if(depthParam==null || depthParam.isEmpty()) depthParam = "2";
   String changehostdepthdepthParam = cmd.getOptionValue("changehostdepth");
   if(changehostdepthdepthParam==null || changehostdepthdepthParam.isEmpty()) changehostdepthdepthParam = "1";
   System.out.println(urlPath);
   Webcrawler crawler;
   crawler = new Webcrawler();
   crawler.setSavePath(saveFilePath);
   crawler.setMaxDepth(Integer.valueOf(depthParam));
   crawler.setMaxHostChange(Integer.valueOf(changehostdepthdepthParam));
   crawler.run(urlPath);
  }
  System.out.println("End Webcrawler");
 }

 private void run(String string) {
  host = string;
  connect( host, "/", 0, 0);
 }
 public String getString() {
  try {
   return new String(htmlByte, "UTF-8");
  } catch (UnsupportedEncodingException e) {
   e.printStackTrace();
  }
  return null;
 }
 private String getHttp(String url) throws IOException, URISyntaxException{
  String ret=null;
  try {
   HttpGet httpGet = new HttpGet(url);
   HttpClientContext context = HttpClientContext.create();
   httpGet.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
   CloseableHttpResponse response = httpclient.execute(httpGet,context);
   try {
    System.out.println(response.getStatusLine());
    HttpEntity entity = response.getEntity();

    Header contentEncoding = response.getFirstHeader("Content-Encoding");
    if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
     System.out.println("gziped");
     htmlByte = inputStreamToByte( new GZIPInputStream(entity.getContent()));
    }else {
     htmlByte = inputStreamToByte(entity.getContent());
    }

    HttpHost target = context.getTargetHost();
    List<URI> redirectLocations = context.getRedirectLocations();
    URI location = URIUtils.resolve(httpGet.getURI(), target, redirectLocations);
    System.out.println("Final HTTP location: " + location.toASCIIString());
    ret = location.toASCIIString();
   } finally {
    response.close();
   }
  } finally {
   //httpclient.close();
  }
  return ret;
 }
 private byte[] inputStreamToByte(InputStream in)
 {
  final int BUF_SIZE = 1024;
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  byte[] buffer = new byte[BUF_SIZE];
  try {
   int length;
   while ((length = in.read(buffer)) != -1) out.write(buffer, 0, length);
  } catch (IOException e) {
   e.printStackTrace();
   return null;
  }
  return out.toByteArray();
 }
 private void connect(String lasturl, String addurl, int depth, int hostchange) {
  Source source = null;
  String newurl = null;
  int hostchanged = 0;

  if(addurl.startsWith("http://") || addurl.startsWith("https://")){
   hostchanged = 1;
   if( maxHostChange <= hostchange+hostchanged) return;
  }
  if( maxDepth <= depth ){
   return;
  }
  try {
   //if(DOMAIN_CHANGE){
   lasturl = calcNextUrl(lasturl, addurl);
   //}else{
   //lasturl = urlChg(host, lasturl, addurl);
   //}
   System.out.println("Get:["+depth+"]:"+lasturl);
   if( !visited.contains(lasturl) ){
    visited.add(lasturl);
   }else{
    System.out.println("visited !");
    return;
   }
   //source=new Source(new URL(lasturl));
   newurl = getHttp(lasturl);
   //fileSave(savePath + changeFileName(lasturl)+".htm",getString());
   fileSave(savePath + changeFileName(lasturl)+".htm",htmlByte);
   source=new Source(getString());

  } catch (Exception e) {
   e.printStackTrace();
   return;
  }
  //System.out.println(source.getRenderer().toString());
  List <Element> elements = source.getAllElements("a");
  System.out.println("Len:("+htmlByte.length+"), A tag("+elements.size()+")");
  for(int i = 0 ; i < elements.size(); i++){
   Element ele = elements.get(i);
   String href = ele.getAttributeValue("href");
   if(href==null || href.isEmpty()) continue;
   if(!DOMAIN_CHANGE){
    if(href.startsWith("http://") || href.startsWith("https://")){
     continue;
    }
   }
   if(href.startsWith("javascript:")){
    continue;
   }else if(href.contains("#")){
    continue;
   }else if(href.startsWith("<")){
    continue;
   }
   connect(newurl,href,depth+1,hostchange+hostchanged);
  }
 }

 private void fileSave(String name, byte[] htmlByte) {
  FileOutputStream stream = null;
  try{
   stream = new FileOutputStream(name);
   stream.write(htmlByte);
  } catch (Exception e) {
  } finally {
   try {
    stream.close();
   } catch (IOException e) {
   }
  }
 }

 private String changeFileName(String lasturl) {
  lasturl=lasturl.replace('?', '_');
  lasturl=lasturl.replace('*', '_');
  lasturl=lasturl.replace('%', '_');
  lasturl=lasturl.replace('.', '_');
  lasturl=lasturl.replace('/', '_');
  lasturl=lasturl.replace('\\', '_');
  lasturl=lasturl.replace('\"', '_');
  lasturl=lasturl.replace('\'', '_');
  lasturl=lasturl.replace('|', '_');
  lasturl=lasturl.replace('+', '_');
  lasturl=lasturl.replace('-', '_');
  lasturl=lasturl.replace(':', '_');
  return lasturl;
 }

 private void setMaxDepth(int i) {
  maxDepth = i;
 }

 private void setMaxHostChange(int i) {
  maxHostChange = i;
 }

 private void setSavePath(String string) {
  savePath = string;
  if(!savePath.endsWith("/")) savePath=savePath+"/";
  createDirectoryIfNeeded(string);
  String timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm").format(new Date());
  savePath=savePath+timeStamp;
  createDirectoryIfNeeded(savePath);
  if(!savePath.endsWith("/")) savePath=savePath+"/";
 }

 private void createDirectoryIfNeeded(String directoryName)
 {
  File theDir = new File(directoryName); 
  if (!theDir.exists())
   theDir.mkdirs();
 }

 private void Webcrawler() {
 }

 public static String calcNextUrl(String thisurl, String add)
 {
  System.out.println("This:["+thisurl + "]Add:["+add+"]");
  URI thisuri = URI.create(thisurl);
  String data = thisuri.getScheme() + "://" + thisuri.getHost();
  if(thisuri.getPort()!=-1) data=":"+thisuri.getPort();
  if(add.startsWith("/")) data=data+add;
  else if(add.startsWith("http")) data=add;
  else {
   data=thisurl;
   if(data.endsWith("/")) data=data+add;
   else data=data+"/"+add;
  }

  URI returi = URI.create(data);
  returi = returi.normalize();
  if( !returi.toString().startsWith("http") ){
   System.out.println("Error");
  }
  return returi.toString();
 }

 public static void fileSave(String name,String data)
 {
  try {
   File newTextFile = new File(name);
   FileWriter fw = new FileWriter(newTextFile);
   fw.write(data);
   fw.close();
  } catch (IOException iox) {
   iox.printStackTrace();
  }
 }
}


실행할때 아래와 같이 옵션을 주어야 합니다.
-u 는 접속시도하는 url이 됩니다.
-s 는 저장할 폴더 이름입니다.
-d 이건 최대 html의 depth를 의미합니다. 숫자가 크면 링크를 많이 타고 갑니다. 따라서 적당하게 적습니다.
-c 링크를 타고 갈때 다른 site가 나올수 있는데 몇번이나 허용할지 최대 허용 갯수를 의미합니다.

-u http://www.daum.net -s save -d 2 -c 1

실행화면
Welcome !! Webcrawler
http://www.daum.net
This:[http://www.daum.net]Add:[/]
Get:[0]:http://www.daum.net/
HTTP/1.1 200 OK
Final HTTP location: http://www.daum.net/
Len:(154748), A tag(170)
This:[http://www.daum.net/]Add:[/doc/top_accessibility.html]
Get:[1]:http://www.daum.net/doc/top_accessibility.html
HTTP/1.1 200 OK
Final HTTP location: http://www.daum.net/doc/top_accessibility.html
Len:(28899), A tag(40)
End Webcrawler

코드 설명
http://swlock.blogspot.com/2017/01/web-crawler-with-java_20.html