Говоря о сканерах, использование URLConnection, поставляемого с Java, позволяет реализовать некоторые базовые функции сканирования страниц, но для некоторых более сложных функций, таких как обработка перенаправления и удаление HTML-тегов, простого использования URLConnection недостаточно.
Здесь мы можем использовать сторонний jar-пакет HttpClient.
Далее мы используем HttpClient, чтобы просто написать демо-версию, которая сканируется в Baidu:
импортировать java.io.FileOutputStream;
импортировать java.io.InputStream;
импортировать java.io.OutputStream;
импортировать org.apache.commons.httpclient.HttpClient;
импортировать org.apache.commons.httpclient.HttpStatus;
импортировать org.apache.commons.httpclient.methods.GetMethod;
/**
*
* @author CallMeWhy
*
*/
общественный класс Паук {
частный статический HttpClient httpClient = новый HttpClient();
/**
* @param путь
* Ссылка на целевую страницу
* @return Возвращает логическое значение, указывающее, нормально ли загружается целевая страница.
* @throwsException
* Исключение ввода-вывода при чтении потока веб-страницы или записи локального потока файлов.
*/
public static boolean downloadPage(String path) выдает исключение {
//Определяем потоки ввода и вывода
Входной поток ввода = ноль;
Выход OutputStream = null;
// Получаем метод сообщения
GetMethod getMethod = новый GetMethod(путь);
//Выполнить, вернуть код состояния
int statusCode = httpClient.executeMethod(getMethod);
// Обрабатываем код состояния
// Для простоты обрабатывается только код состояния с возвращаемым значением 200
если (statusCode == HttpStatus.SC_OK) {
ввод = getMethod.getResponseBodyAsStream();
// Получаем имя файла по URL
Строковое имя файла = path.substring(path.lastIndexOf('/') + 1)
+ ".html";
// Получаем поток вывода файла
вывод = новый FileOutputStream (имя файла);
// Вывод в файл
интервал tempByte = -1;
while ((tempByte = input.read()) > 0) {
вывод.запись(tempByte);
}
// Закрываем входной поток
если (вход!= ноль) {
ввод.закрыть();
}
// Закрываем выходной поток
если (выход!= ноль) {
вывод.закрыть();
}
вернуть истину;
}
вернуть ложь;
}
public static void main(String[] args) {
пытаться {
// Захват домашней страницы Baidu и вывод
Spider.downloadPage("http://www.baidu.com");
} catch (Исключение е) {
е.printStackTrace();
}
}
}
Однако такой базовый сканер не может удовлетворить потребности различных сканеров.
Давайте сначала представим сканер, работающий в ширину.
Я думаю, что каждый знаком с принципом «сначала в ширину». Проще говоря, вы можете понять такие сканеры, работающие в ширину.
Мы думаем об Интернете как о сверхбольшом ориентированном графе. Каждая ссылка на веб-странице — это направленное ребро, а каждый файл или чистая страница без ссылки — это конечная точка графа.
Поисковый робот в ширину — это такой сканер. Он сканирует этот ориентированный граф, начиная с корневого узла и слой за слоем извлекая данные новых узлов.
Алгоритм обхода ширины следующий:
(1) Вершина V помещается в очередь.
(2) Продолжить выполнение, когда очередь не пуста, в противном случае алгоритм пуст.
(3) Вывести из очереди, получить головной узел V, посетить вершину V и отметить, что V был посещен.
(4) Найдите первую смежную вершину col вершины V.
(5) Если соседняя вершина col вершины V не была посещена, то col помещается в очередь.
(6) Продолжайте поиск других смежных вершин col V и переходите к шагу (5). Если все соседние вершины V посещены, переходите к шагу (2).
В соответствии с алгоритмом обхода по ширине, порядок обхода приведенного выше изображения следующий: A->B->C->D->E->F->H->G->I, так что оно проходится слой за слоем. .
Сканер в ширину фактически сканирует ряд начальных узлов, что по сути аналогично обходу графа.
Мы можем поместить URL-адреса страниц, которые необходимо сканировать, в таблицу TODO, а посещенные страницы — в таблицу «Посещенные»:
Основной процесс сканирования в ширину заключается в следующем:
(1) Сравните проанализированную ссылку со ссылкой в таблице «Посещенные». Если ссылка не существует в таблице «Посещенные», это означает, что она не была посещена.
(2) Поместите ссылку в таблицу TODO.
(3) После обработки получите ссылку из таблицы TODO и поместите ее непосредственно в таблицу «Посещенные».
(4) Продолжите описанный выше процесс для веб-страницы, представленной по этой ссылке. И так далее.
Далее мы шаг за шагом создадим сканер в ширину.
Сначала спроектируйте структуру данных для хранения таблицы TODO. Учитывая необходимость принципа «первым пришел — первым обслужен», мы используем очередь и настраиваем класс Quere:
импортировать java.util.LinkedList;
/**
* Пользовательский класс очереди для сохранения таблицы TODO.
*/
общественный класс Очередь {
/**
* Определите очередь и реализуйте ее с помощью LinkedList.
*/
частная очередь LinkedList<Object> = новый LinkedList<Object>(); // Очередь
/**
* Добавить t в очередь
*/
общественная недействительность enQueue (Объект t) {
очередь.addLast(т);
}
/**
* Удалить первый элемент из очереди и вернуть его
*/
публичный объект deQueue() {
вернуть очередь.removeFirst();
}
/**
* Возвращает, пуста ли очередь
*/
общедоступное логическое значение isQueueEmpty() {
вернуть очередь.isEmpty();
}
/**
* Определить и вернуть информацию о том, содержит ли очередь t
*/
public boolean contians(Object t) {
вернуть очередь.содержит(т);
}
/**
* Определить и вернуть информацию о том, пуста ли очередь
*/
общедоступное логическое значение пусто () {
вернуть очередь.isEmpty();
}
}
Также необходима структура данных для записи посещенных URL-адресов, а именно таблица «Посещенные».
Учитывая роль этой таблицы, при каждом доступе к URL-адресу сначала выполняется его поиск в этой структуре данных. Если текущий URL-адрес уже существует, задача URL-адреса отменяется.
Эта структура данных не должна дублироваться и ее можно быстро найти, поэтому для хранения выбран HashSet.
Подводя итог, мы создаем еще один класс SpiderQueue для сохранения таблиц Visited и TODO:
импортировать java.util.HashSet;
импортировать java.util.Set;
/**
* Пользовательский класс для сохранения посещенных и непосещенных таблиц.
*/
общественный класс SpiderQueue {
/**
* Коллекция посещенных URL-адресов, а именно таблица «Посещенные».
*/
частный статический Set<Object> visitUrl = new HashSet<>();
/**
* Добавить в очередь посещенных URL-адресов.
*/
public static void addVisitedUrl(String url) {
посетилUrl.add(url);
}
/**
* Удаление посещенных URL-адресов.
*/
public static void removeVisitedUrl(String url) {
посетилUrl.remove(url);
}
/**
* Получить количество посещенных URL-адресов
*/
public static int getVisitedUrlNum() {
вернуть VisitUrl.size();
}
/**
* Коллекция URL-адресов, которые необходимо посетить, то есть таблица «Непосещенные».
*/
частная статическая очередь unVisitedUrl = новая очередь();
/**
* Получить очередь «Непосещенные»
*/
общедоступная статическая очередь getUnVisitedUrl() {
вернуть unVisitedUrl;
}
/**
* Непосещенный unVisitedUrl удаляется из очереди.
*/
публичный статический объект unVisitedUrlDeQueue() {
вернуть unVisitedUrl.deQueue();
}
/**
* Убедитесь, что каждый URL-адрес посещается только один раз при добавлении URL-адреса в unVisitedUrl.
*/
public static void addUnvisitedUrl(String url) {
if (url != null && !url.trim().equals("") && !visitedUrl.contains(url)
&& !unVisitedUrl.contians(url))
unVisitedUrl.enQueue(url);
}
/**
* Определить, пуста ли очередь непосещенных URL-адресов.
*/
public static boolean unVisitedUrlsEmpty() {
вернуть unVisitedUrl.empty();
}
}
Вышеуказанное представляет собой инкапсуляцию некоторых пользовательских классов. Следующим шагом является определение класса инструмента для загрузки веб-страниц. Мы определяем его как класс DownTool:
контроллер пакета;
импортировать java.io.*;
импортировать org.apache.commons.httpclient.*;
импортировать org.apache.commons.httpclient.methods.*;
импортировать org.apache.commons.httpclient.params.*;
общественный класс DownTool {
/**
* Создайте имя файла веб-страницы, которая будет сохранена, на основе URL-адреса и типа веб-страницы, а также удалите символы, не относящиеся к имени файла, в URL-адресе.
*/
частная строка getFileNameByUrl (String url, String contentType) {
// Удаляем семь символов "http://"
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 (байтовые данные [], String filePath) {
пытаться {
DataOutputStream out = новый DataOutputStream(новый FileOutputStream(
новый файл(путь к файлу)));
for (int i = 0; i < data.length; i++)
out.write(данные [я]);
out.flush();
выход.закрыть();
} catch (IOException e) {
е.printStackTrace();
}
}
// Загрузите веб-страницу, на которую указывает URL-адрес
public String downloadFile (String url) {
Строка filePath = 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);
// Определяем код статуса доступа
если (statusCode!= HttpStatus.SC_OK) {
System.err.println("Метод не выполнен: "
+ getMethod.getStatusLine());
путь к файлу = ноль;
}
// 4. Обработка содержимого HTTP-ответа
byte[] responseBody = getMethod.getResponseBody() // Чтение как массив байтов;
// Генерируем имя файла при сохранении на основе URL-адреса веб-страницы
filePath = "температура//"
+ getFileNameByUrl(url,
getMethod.getResponseHeader("Тип контента")
.getValue());
saveToLocal (responseBody, filePath);
} поймать (HttpException е) {
//Происходит фатальное исключение. Возможно, протокол неверен или что-то не так с возвращаемым содержимым.
System.out.println("Пожалуйста, проверьте правильность вашего http-адреса");
е.printStackTrace();
} catch (IOException e) {
//Происходит сетевое исключение
е.printStackTrace();
} окончательно {
// Освобождаем соединение
getMethod.releaseConnection();
}
вернуть путь к файлу;
}
}
Здесь нам нужен класс HtmlParserTool для обработки тегов Html:
контроллер пакета;
импортировать java.util.HashSet;
импортировать java.util.Set;
импортировать org.htmlparser.Node;
импортировать org.htmlparser.NodeFilter;
импортировать org.htmlparser.Parser;
импортировать org.htmlparser.filters.NodeClassFilter;
импортировать org.htmlparser.filters.OrFilter;
импортировать org.htmlparser.tags.LinkTag;
импортировать org.htmlparser.util.NodeList;
импортировать org.htmlparser.util.ParserException;
импортировать модель.LinkFilter;
общественный класс HtmlParserTool {
// Получаем ссылки на сайте, для фильтрации ссылок используется фильтр
public static Set<String> extracLinks(String url, фильтр LinkFilter) {
Ссылки Set<String> = новый HashSet<String>();
пытаться {
Парсер парсер = новый парсер (url);
parser.setEncoding("gb2312");
// Фильтрация тега <frame>, используемого для извлечения атрибута src в теге кадра
NodeFilterframeFilter = новый NodeFilter() {
частный статический окончательный длинный серийныйVersionUID = 1L;
@Override
public boolean Accept(Node node) {
if (node.getText().startsWith("frame src=")) {
вернуть истину;
} еще {
вернуть ложь;
}
}
};
// OrFilter для установки тега фильтра <a> и тега <frame>
OrFilter linkFilter = новый OrFilter(новый NodeClassFilter(
LinkTag.class),frameFilter);
// Получаем все отфильтрованные теги
Список NodeList = parser.extractAllNodesThatMatch(linkFilter);
for (int i = 0; i < list.size(); i++) {
Тег узла = list.elementAt(i);
if (tag instanceof LinkTag)// тег <a>
{
ссылка LinkTag = тег (LinkTag);
Строка linkUrl = link.getLink();// URL
если (filter.accept(linkUrl))
ссылки.добавить(linkUrl);
} else// тег <frame>
{
// Извлекаем ссылку на атрибут src в кадре, например <frame src="test.html"/>
Строковый фрейм = tag.getText();
int start =frame.indexOf("src=");
кадр = кадр.подстрока(начало);
int end =frame.indexOf(" ");
если (конец == -1)
конец = Frame.indexOf(">");
СтрокаframeUrl =frame.substring(5, конец - 1);
если (filter.accept(frameUrl))
link.add(frameUrl);
}
}
} catch (ParserException e) {
е.printStackTrace();
}
обратные ссылки;
}
}
Наконец, давайте напишем класс сканера для вызова предыдущего класса и функции инкапсуляции:
контроллер пакета;
импортировать java.util.Set;
импортировать модель.LinkFilter;
импортировать модель.SpiderQueue;
общественный класс BfsSpider {
/**
* Инициализировать очередь URL-адресов, используя начальное число.
*/
Private void initCrawlerWithSeeds (семена String []) {
for (int i = 0; i <seeds.length; i++)
SpiderQueue.addUnvisitedUrl(семена [i]);
}
//Определяем фильтр для извлечения ссылок, начинающихся с http://www.xxxx.com
public void сканирование (семена String[]) {
Фильтр LinkFilter = новый LinkFilter() {
public boolean Accept (String url) {
если (url.startsWith("http://www.baidu.com"))
вернуть истину;
еще
вернуть ложь;
}
};
//Инициализируем очередь URL
initCrawlerWithSeeds (семена);
// Условие цикла: сканируемая ссылка не пуста и количество просканированных веб-страниц не более 1000
в то время как (!SpiderQueue.unVisitedUrlsEmpty()
&& SpiderQueue.getVisitedUrlNum() <= 1000) {
//URL-адрес заголовка очереди удален из очереди
Строка visitUrl = (String) SpiderQueue.unVisitedUrlDeQueue();
если (visitUrl == ноль)
продолжать;
DownTool downLoader = новый DownTool();
// Загрузка веб-страницы
downLoader.downloadFile(visitUrl);
// Помещаем этот URL в посещаемый URL
SpiderQueue.addVisitedUrl(visitUrl);
//Извлекаем URL-адрес с веб-страницы загрузки
Set<String>links = HtmlParserTool.extracLinks(visitUrl, filter);
// Новые непосещенные URL-адреса ставятся в очередь
for (Строковая ссылка: ссылки) {
SpiderQueue.addUnvisitedUrl (ссылка);
}
}
}
//запись основного метода
public static void main(String[] args) {
Сканер BfsSpider = новый BfsSpider();
Crawler.crawling(new String[] { "http://www.baidu.com" });
}
}
После запуска вы увидите, что сканер просканировал все страницы веб-страницы Baidu:
Выше приведено все содержимое Java с использованием набора инструментов HttpClient и сканера ширины для сканирования содержимого. Это немного сложнее, поэтому друзья должны подумать об этом тщательно. Надеюсь, это может быть полезно для всех.