크롤러에 대해 말하자면, Java와 함께 제공되는 URLConnection을 사용하면 일부 기본 페이지 크롤링 기능을 수행할 수 있지만 리디렉션 처리 및 HTML 태그 제거와 같은 일부 고급 기능의 경우 URLConnection을 사용하는 것만으로는 충분하지 않습니다.
여기서는 타사 jar 패키지인 HttpClient를 사용할 수 있습니다.
다음으로 HttpClient를 사용하여 Baidu로 크롤링되는 데모를 작성합니다.
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
org.apache.commons.httpclient.HttpClient 가져오기;
org.apache.commons.httpclient.HttpStatus 가져오기;
import org.apache.commons.httpclient.methods.GetMethod;
/**
*
* @author CallMeWhy
*
*/
공개 클래스 스파이더 {
개인 정적 HttpClient httpClient = new HttpClient();
/**
* @param 경로
* 대상 페이지 링크
* @return 대상 페이지가 정상적으로 다운로드되었는지 여부를 나타내는 부울 값을 반환합니다.
* @throwsException
* 웹 페이지 스트림을 읽거나 로컬 파일 스트림을 쓸 때 IO 예외
*/
공개 정적 부울 다운로드 페이지(문자열 경로)에서 예외가 발생합니다.
//입력 및 출력 스트림 정의
InputStream 입력 = null;
OutputStream 출력 = null;
// 게시 방법을 가져옵니다.
GetMethod getMethod = new GetMethod(경로);
//실행, 상태 코드 반환
int statusCode = httpClient.executeMethod(getMethod);
// 상태 코드 처리
// 단순화를 위해 반환 값이 200인 상태 코드만 처리됩니다.
if (statusCode == HttpStatus.SC_OK) {
입력 = getMethod.getResponseBodyAsStream();
// URL에서 파일 이름을 가져옵니다.
문자열 파일 이름 = path.substring(path.lastIndexOf('/') + 1)
+ ".html";
// 파일 출력 스트림을 가져옵니다.
출력 = 새로운 FileOutputStream(파일명);
// 파일로 출력
int tempByte = -1;
while ((tempByte = input.read()) > 0) {
출력.쓰기(임시바이트);
}
// 입력 스트림을 닫습니다.
if (입력 != null) {
입력.닫기();
}
// 출력 스트림을 닫습니다.
if (출력 != null) {
출력.닫기();
}
사실을 반환;
}
거짓을 반환;
}
공개 정적 무효 메인(String[] args) {
노력하다 {
// Baidu 홈페이지를 가져와 출력합니다.
Spider.downloadPage("http://www.baidu.com");
} 잡기(예외 e) {
e.printStackTrace();
}
}
}
그러나 이러한 기본 크롤러는 다양한 크롤러의 요구를 충족할 수 없습니다.
먼저 너비 우선 크롤러를 소개하겠습니다.
나는 모든 사람이 너비 우선에 익숙하다고 믿습니다. 간단히 말해서 너비 우선 크롤러는 이와 같은 것을 이해할 수 있습니다.
우리는 인터넷을 초대형 방향 그래프로 생각합니다. 웹 페이지의 모든 링크는 방향이 있는 가장자리이고, 링크가 없는 모든 파일이나 순수 페이지는 그래프의 끝점입니다.
너비 우선 크롤러는 루트 노드에서 시작하여 계층별로 새 노드의 데이터를 크롤링하여 이 방향성 그래프를 크롤링하는 크롤러입니다.
너비 순회 알고리즘은 다음과 같습니다.
(1) 정점 V가 대기열에 추가됩니다.
(2) 큐가 비어 있지 않으면 실행을 계속하고, 그렇지 않으면 알고리즘이 비어 있습니다.
(3) Dequeue하고, 헤드 노드 V를 얻고, 정점 V를 방문하고 V가 방문되었음을 표시합니다.
(4) 정점 V의 첫 번째 인접 정점 열을 찾습니다.
(5) V의 인접한 정점 col을 방문하지 않은 경우 col이 대기열에 추가됩니다.
(6) 계속해서 V의 다른 인접 정점 col을 탐색하고, V의 인접 정점을 모두 방문했다면 (2)단계로 진행한다.
폭 순회 알고리즘에 따르면 위 그림의 순회 순서는 A->B->C->D->E->F->H->G->I이므로 한 층씩 순회하게 됩니다. .
너비 우선 크롤러는 실제로 일련의 시드 노드를 크롤링하는데, 이는 기본적으로 그래프 순회와 동일합니다.
크롤링해야 하는 페이지의 URL을 TODO 테이블에 배치하고 방문한 페이지의 URL을 Visited 테이블에 배치할 수 있습니다.
너비 우선 크롤러의 기본 프로세스는 다음과 같습니다.
(1) 구문 분석된 링크를 Visited 테이블의 링크와 비교합니다. 해당 링크가 Visited 테이블에 존재하지 않으면 방문하지 않은 것입니다.
(2) 링크를 TODO 테이블에 넣습니다.
(3) 처리 후 TODO 테이블에서 링크를 얻어 Visited 테이블에 직접 넣습니다.
(4) 이 링크로 표시되는 웹페이지에 대해 위의 과정을 계속합니다. 등.
다음으로 너비우선 크롤러를 단계별로 생성하겠습니다.
먼저 TODO 테이블을 저장할 데이터 구조를 설계합니다. 선입선출의 필요성을 고려하여 큐를 사용하고 Quere 클래스를 사용자 정의합니다.
java.util.LinkedList 가져오기;
/**
* TODO 테이블을 저장하는 사용자 정의 대기열 클래스
*/
공개 클래스 대기열 {
/**
* 큐를 정의하고 LinkedList를 사용하여 구현
*/
private LinkedList<Object> queue = new LinkedList<Object>() // 큐
/**
* 대기열에 t를 추가합니다.
*/
공공 무효 enQueue(객체 t) {
queue.addLast(t);
}
/**
* 큐에서 첫 번째 항목을 제거하고 반환
*/
공개 객체 deQueue() {
return queue.removeFirst();
}
/**
* 대기열이 비어 있는지 여부를 반환합니다.
*/
공개 부울 isQueueEmpty() {
return queue.isEmpty();
}
/**
* 큐에 t가 포함되어 있는지 확인하고 반환합니다.
*/
공개 부울 값(객체 t) {
return queue.contains(t);
}
/**
* 큐가 비어 있는지 확인하고 반환
*/
공개 부울 비어 있음() {
return queue.isEmpty();
}
}
방문한 URL, 즉 방문 테이블을 기록하려면 데이터 구조도 필요합니다.
이 테이블의 역할을 고려하면, URL에 접근할 때마다 이 데이터 구조에서 먼저 검색됩니다. 현재 URL이 이미 존재하는 경우 해당 URL 작업은 삭제됩니다.
이 데이터 구조는 중복되지 않아야 하고 빠르게 검색할 수 있어야 하므로 HashSet을 저장 대상으로 선택합니다.
요약하면 Visited 테이블과 TODO 테이블을 저장하기 위해 또 다른 SpiderQueue 클래스를 생성합니다.
import java.util.HashSet;
import java.util.Set;
/**
* 방문한 테이블과 방문하지 않은 테이블을 저장하는 사용자 정의 클래스
*/
공개 클래스 SpiderQueue {
/**
* 방문한 URL 컬렉션, 즉 Visited 테이블
*/
private static Set<Object> VisitUrl = new HashSet<>();
/**
* 방문한 URL 대기열에 추가
*/
공개 정적 무효 addVisitedUrl(문자열 URL) {
VisitUrl.add(url);
}
/**
* 방문한 URL 제거
*/
공개 정적 무효 제거VisitedUrl(문자열 URL) {
VisitedUrl.remove(url);
}
/**
* 방문한 URL 수를 가져옵니다.
*/
공개 정적 int getVisitedUrlNum() {
VisitedUrl.size()를 반환합니다.
}
/**
* 방문할 URL의 집합, 즉 unVisited 테이블
*/
개인 정적 대기열 unVisitedUrl = new Queue();
/**
* 방문하지 않은 대기열 가져오기
*/
공개 정적 대기열 getUnVisitedUrl() {
unVisitedUrl을 반환합니다.
}
/**
* 방문하지 않은 unVisitedUrl은 대기열에서 제거됩니다.
*/
공개 정적 개체 unVisitedUrlDeQueue() {
unVisitedUrl.deQueue()를 반환합니다.
}
/**
* unVisitedUrl에 URL을 추가할 때 각 URL이 한 번만 방문되는지 확인하세요.
*/
공개 정적 무효 addUnvisitedUrl(문자열 URL) {
if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)
&& !unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
* 방문하지 않은 URL 대기열이 비어 있는지 확인
*/
공개 정적 부울 unVisitedUrlsEmpty() {
unVisitedUrl.empty()를 반환합니다.
}
}
위는 일부 사용자 정의 클래스를 캡슐화한 것입니다. 다음 단계는 웹 페이지를 다운로드하기 위한 도구 클래스를 정의하는 것입니다.
패키지 컨트롤러;
import java.io.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
공개 클래스 DownTool {
/**
* URL 및 웹페이지 유형을 기준으로 저장할 웹페이지의 파일명을 생성하고, URL에서 파일명 이외의 문자를 제거합니다.
*/
private String getFileNameByUrl(String url, String contentType) {
// "http://" 7개 문자를 제거합니다.
url = url.substring(7);
// 캡쳐된 페이지가 text/html 형식인지 확인
if (contentType.indexOf("html") != -1) {
// URL의 모든 특수 기호를 밑줄로 변환합니다.
url = url.replaceAll("[//?/:*|<>/"]", "_") + ".html";
} 또 다른 {
url = url.replaceAll("[//?/:*|<>/"]", "_") + "."
+ contentType.substring(contentType.lastIndexOf("/") + 1);
}
반환 URL;
}
/**
* 웹 페이지 바이트 배열을 로컬 파일에 저장합니다. filePath는 저장될 파일의 상대 주소입니다.
*/
private void saveToLocal(byte[] data, String filePath) {
노력하다 {
DataOutputStream 출력 = 새 DataOutputStream(새 FileOutputStream(
새 파일(파일 경로)));
for (int i = 0; i < data.length; i++)
out.write(데이터[i]);
out.flush();
종료.닫기();
} 잡기(IOException e) {
e.printStackTrace();
}
}
// URL이 가리키는 웹페이지를 다운로드합니다.
공개 문자열 다운로드파일(문자열 URL) {
문자열 파일 경로 = null;
// 1. HttpClinet 객체 생성 및 매개변수 설정
HttpClient httpClient = 새로운 HttpClient();
//HTTP 연결 시간 제한을 5초로 설정
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
// 2. GetMethod 객체 생성 및 매개변수 설정
GetMethod getMethod = 새로운 GetMethod(url);
//요청 가져오기 시간 초과를 5초로 설정합니다.
getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
//요청 재시도 처리 설정
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
새로운 DefaultHttpMethodRetryHandler());
// 3. GET 요청 실행
노력하다 {
int statusCode = httpClient.executeMethod(getMethod);
// 액세스 상태 코드를 확인합니다.
if (statusCode != HttpStatus.SC_OK) {
System.err.println("메소드 실패: "
+ getMethod.getStatusLine());
파일 경로 = null;
}
// 4. HTTP 응답 콘텐츠 처리
byte[] responseBody = getMethod.getResponseBody(); // 바이트 배열로 읽기
// 웹페이지 URL을 기반으로 저장할 때 파일명을 생성합니다.
filePath = "임시//"
+ getFileNameByUrl(url,
getMethod.getResponseHeader("콘텐츠 유형")
.getValue());
saveToLocal(responseBody, filePath);
} 잡기(HttpException e) {
//치명적인 예외가 발생했습니다. 프로토콜이 잘못되었거나 반환된 내용에 문제가 있을 수 있습니다.
System.out.println("http 주소가 올바른지 확인해주세요.");
e.printStackTrace();
} 잡기(IOException e) {
//네트워크 예외가 발생했습니다.
e.printStackTrace();
} 마지막으로 {
// 연결 해제
getMethod.releaseConnection();
}
파일 경로 반환;
}
}
여기서는 Html 태그를 처리하기 위해 HtmlParserTool 클래스가 필요합니다.
패키지 컨트롤러;
import java.util.HashSet;
import java.util.Set;
org.htmlparser.Node 가져오기;
import org.htmlparser.NodeFilter;
org.htmlparser.Parser 가져오기;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
org.htmlparser.tags.LinkTag 가져오기;
org.htmlparser.util.NodeList 가져오기;
import org.htmlparser.util.ParserException;
모델 가져오기.LinkFilter;
공개 클래스 HtmlParserTool {
// 웹사이트에서 링크를 가져옵니다. 필터는 링크를 필터링하는 데 사용됩니다.
공개 정적 Set<String> extracLinks(String url, LinkFilter 필터) {
Set<String> 링크 = new HashSet<String>();
노력하다 {
파서 파서 = new Parser(url);
파서.setEncoding("gb2312");
// 프레임 태그에서 src 속성을 추출하는 데 사용되는 <frame> 태그를 필터링합니다.
NodeFilter 프레임Filter = 새로운 NodeFilter() {
개인 정적 최종 긴 serialVersionUID = 1L;
@보수
공개 부울 수락(노드 노드) {
if (node.getText().startsWith("frame src=")) {
사실을 반환;
} 또 다른 {
거짓을 반환;
}
}
};
// <a> 태그와 <frame> 태그 필터를 설정하는 OrFilter
OrFilter linkFilter = new OrFilter(new NodeClassFilter(
LinkTag.class), 프레임필터);
// 필터링된 모든 태그를 가져옵니다.
NodeList 목록 = 파서.extractAllNodesThatMatch(linkFilter);
for (int i = 0; i < list.size(); i++) {
노드 태그 = list.elementAt(i);
if (LinkTag의 인스턴스 태그)// <a> 태그
{
LinkTag 링크 = (LinkTag) 태그;
문자열 linkUrl = link.getLink();// URL
if (filter.accept(linkUrl))
link.add(linkUrl);
} else// <프레임> 태그
{
// <frame src="test.html"/>과 같이 프레임에서 src 속성의 링크를 추출합니다.
문자열 프레임 = tag.getText();
int start = 프레임.indexOf("src=");
프레임 = 프레임.하위 문자열(시작);
int end = 프레임.indexOf(" ");
if (끝 == -1)
end = 프레임.indexOf(">");
String FrameUrl = frame.substring(5, end - 1);
if (filter.accept(frameUrl))
link.add(frameUrl);
}
}
} 잡기(ParserException e) {
e.printStackTrace();
}
반환 링크;
}
}
마지막으로 이전 캡슐화 클래스와 함수를 호출하는 크롤러 클래스를 작성해 보겠습니다.
패키지 컨트롤러;
import java.util.Set;
모델 가져오기.LinkFilter;
모델 가져오기.SpiderQueue;
공개 클래스 BfsSpider {
/**
* 시드를 사용하여 URL 대기열 초기화
*/
개인 무효 initCrawlerWithSeeds(String[] 씨앗) {
for (int i = 0; i < 씨앗.길이; i++)
SpiderQueue.addUnvisitedUrl(seeds[i]);
}
//http://www.xxxx.com으로 시작하는 링크를 추출하는 필터를 정의합니다.
공공 무효 크롤링(문자열[] 씨앗) {
LinkFilter 필터 = 새로운 LinkFilter() {
공개 부울 수락(문자열 URL) {
if (url.startsWith("http://www.baidu.com"))
사실을 반환;
또 다른
거짓을 반환;
}
};
//URL 대기열 초기화
initCrawlerWithSeeds(씨앗);
// 루프 조건: 크롤링할 링크가 비어 있지 않고 크롤링된 웹페이지 수가 1000개를 넘지 않습니다.
동안(!SpiderQueue.unVisitedUrlsEmpty()
&& SpiderQueue.getVisitedUrlNum() <= 1000) {
//큐에서 제거된 대기열 헤드 URL
String VisitUrl = (String) SpiderQueue.unVisitedUrlDeQueue();
if (visitUrl == null)
계속하다;
DownTool downLoader = 새로운 DownTool();
// 웹페이지 다운로드
downLoader.downloadFile(visitUrl);
// 이 URL을 방문한 URL에 넣습니다.
SpiderQueue.addVisitedUrl(visitUrl);
//다운로드 웹페이지에서 URL을 추출합니다.
Set<String> link = HtmlParserTool.extracLinks(visitUrl, filter);
// 방문하지 않은 새로운 URL이 대기열에 추가됩니다.
for (문자열 링크 : 링크) {
SpiderQueue.addUnvisitedUrl(링크);
}
}
}
//메인 메소드 진입
공개 정적 무효 메인(String[] args) {
BfsSpider 크롤러 = new BfsSpider();
크롤러.크롤링(new String[] { "http://www.baidu.com" });
}
}
실행한 후 크롤러가 Baidu 웹페이지의 모든 페이지를 크롤링한 것을 확인할 수 있습니다.
위 내용은 HttpClient 툴킷과 너비 크롤러를 사용하여 내용을 크롤링하는 Java의 전체 내용입니다. 조금 더 복잡하므로 신중하게 생각해 보시기 바랍니다.