Spiderman — Java-открытый исходный код для веб-данных
Spiderman2 — новейшая предварительная версия уже доступна! Просто, более высокая производительность, сбор данных с сохранением состояния, распределённая система, поддержка JS-скриптов. Не упустите возможность попробовать! PS: стабильные версии будут обновляться здесь позже.
Spiderman представляет собой инструмент для сбора данных из открытых источников на основе Java. Он способен собирать данные с указанных веб-страниц и извлекать из них полезную информацию.
В Spiderman используются такие технологии, как XPath и регулярные выражения.
Он состоит из двух частей:
Основные характеристики:
Как использовать?
Последние обновления:
Выражение в <parser может инициировать HTTP-запрос для получения содержимого: <parser exp="$Fetcher.get('http://www.baidu.com')".
<target узел добавляет <before узел конфигурации, который работает перед <model узлом. Результаты его работы можно использовать в качестве контекста $before.xxx.
Переработана загрузка данных, поддерживаются различные реализации загрузчиков. Можно настроить собственный загрузчик в файле XML. Официально предоставляются три варианта: на основе HttpClient, на основе WebUnit и на основе Selenium WebDriver. <site downloader="org.eweb4j.spiderman.plugin.util.WebDriverDownloader" или <site downloader="xxx.YourDownloader">
Аналогично пункту 3, переработана обработка модели, теперь поддерживаются разные реализации. Разработчики могут указать свою реализацию в файле XML. На данный момент официально предоставляются две модели: DefaultModelParser и WebDriverModelParser. <before parser="xxx.xxx.xxx.YourModelParser" или <model parser="xxx.YourModelParser".
Другие незначительные обновления, исправления ошибок и т. д.
XPath-получение навыков:
Пример использования Spiderman:
Код для использования Spiderman:
public class TestSpider {
private final Object mutex = new Object();
@Test
public void test() throws Exception {
String err = EWeb4JConfig.start();
if (err != null)
throw new Exception(err);
SpiderListener listener = new SpiderListenerAdaptor(){
public void afterScheduleCancel(){
//调度结束回调
}
/**
* 每次调度执行前回调此方法
* @date 2013-4-1 下午03:33:11
* @param theLastTimeScheduledAt 上一次调度时间
*/
public void beforeEveryScheduleExecute(Date theLastTimeScheduledAt){
System.err.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [LAST_SCHEDULE_AT] ~ ");
System.err.println("at -> " + CommonUtil.formatTime(theLastTimeScheduledAt));
}
public void onFetch(Thread thread, Task task, FetchResult result) {
System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [FETCH] ~ ");
System.out.println("fetch result ->" + result + " from -> " + task.sourceUrl);
}
public void onNewUrls(Thread thread, Task task, Collection<String> newUrls) {
System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [DIG] ~ ");
System.out.println(newUrls);
}
public void onDupRemoval(Thread currentThread, Task task, Collection<Task> validTasks) {
// for (Task t : validTasks){ // System.out.print("[SPIDERMAN] "+CommonUtil.getnowTime("HH:mm:ss")+" [DUPREMOVE] ~ "); // System.out.println(t.url+" from->"+t.sourceUrl); // } } public void onTaskSort(Thread currentThread, Task task, Collection afterSortTasks) { // for (Task t : afterSortTasks){ // System.out.print("[SPIDERMAN]
Обратите внимание, что в тексте запроса присутствуют фрагменты кода, которые не были переведены. Это связано с тем, что они представляют собой программный код, а не текст на естественном языке. ``` +CommonUtil.getNowTime("HH:mm:ss")+ [SORT] ~ ); // System.out.println(t.url+" from->"+t.sourceUrl); // } } public void onNewTasks(Thread thread, Task task, Collection newTasks) { // for (Task t : newTasks){ // System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [NEWTASK] ~ "); // System.out.println(t.sort + ",,,," + t.url+" from->"+t.sourceUrl); // } } public void onTargetPage(Thread thread, Task task, Page page) { // System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [TARGET] ~ "); // System.out.println(page.getUrl()); } public void onInfo(Thread thread, Task task, String info) { System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [INFO] ~ "); System.out.println(info); }
public void onError(Thread thread, Task task, String err, Throwable e) {
System.err.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [ERROR] ~ ");
e.printStackTrace();
}
public void onParse(Thread thread, Task task, List<Map<String, Object>> models) {
final String projectRoot = FileUtil.getTopClassPath(TestSpider.class);
final File dir = new File(projectRoot+"/Data/"+task.site.getName()+"/"+task.target.getName());
try {
if (!dir.exists())
dir.mkdirs();
for (int i = 0; i < models.size(); i++) {
Map<String, Object> map = models.get(i);
String fileName = dir + "/count_" + task.site.counter.getCount() + i;
StringBuilder sb = new StringBuilder();
for (Iterator<Entry<String,Object>> it = map.entrySet().iterator(); it.hasNext();){
Entry<String,Object> e = it.next();
boolean isBlank = false;
if (e.getValue() == null)
isBlank = true;
else if (e.getValue() instanceof String && ((String)e.getValue()).trim().length() == 0)
isBlank = true;
else if (e.getValue() instanceof List && ((ArrayList<?>)e.getValue()).isEmpty())
isBlank = true;
else if (e.getValue() instanceof List && !((ArrayList<?>)e.getValue()).isEmpty()) {
if (((ArrayList<?>)e.getValue()).size() == 1 && String.valueOf(((ArrayList<?>)e.getValue()).get(0)).trim().length() == 0)
isBlank = true;
}
if (isBlank){
if (sb.length() > 0)
sb.append("_");
sb.append(e.getKey());
}
}
String content = CommonUtil.toJson(map);
if (sb.length() > 0)
fileName = fileName + "_no_"+sb.toString()+"_";
File file = new File(fileName+".json");
FileUtil.writeFile(file, content);
System.out.print("[SPIDERMAN] "+CommonUtil.getNowTime("HH:mm:ss")+" [INFO] ~ ");
System.out.println(fileName + " create finished...");
}
} catch (Exception e) {``` Вот перевод текста на русский язык:
e.printStackTrace(); } } };
//Запуск паука
Spiderman.me()
.init(listener)//Инициализация
.startup()//Запуск
.keepStrict("2h");//Время жизни, после истечения времени жизни сразу закрыть
//Запуск паука + планирование перезапуска по расписанию
//Spiderman.me()
//.listen(listener)//Установка слушателя
//.schedule("10s")//Планирование, паук работает 10 секунд
//.delay("2s")//Каждые 10 + 2 секунды перезапуск паука
//.times(3)//Планирование 3 раза
//.startup()//Запуск
//.blocking();//Блокировка до завершения всех планирований
}
}
Рассмотрим подробнее файл конфигурации sample:
Сначала есть файл конфигурации инициализации spiderman.properties, который находится в каталоге #{ClassPath}
#Каталог размещения файлов конфигурации веб-сайтов
website.xml.folder=#{ClassPath}/WebSites
#Хранилище каталога уже посещённых URL-адресов веб-сайтов
website.visited.folder=#{ClassPath}/dbEnv
#Количество попыток повторной попытки при неудачном получении данных через HTTP
http.fetch.retry=3
#Тайм-аут соединения HTTP, поддерживает единицы s секунд m минут h часов d дней, если единица не указана, то это s секунд
http.fetch.timeout=5s
Затем в каталоге #{ClassPath}/WebSites есть файл oschina.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
| Spiderman Java с открытым исходным кодом вертикальный сетевой паук
| Главная страница проекта: https://gitcafe.com/laiweiwei/Spiderman
| Автор: l.weiwei@163.com
| Блог: http://laiweiweihi.iteye.com,http://my.oschina.net/laiweiwei
| QQ: 493781187
| Электронная почта: l.weiwei@163.com
| Создание: 2013-01-08 16:12
| Обновление: 2013-04-10 18:06
-->
<beans>
<!--
| name:имя
| url:начальный URL
| skipStatusCode:установить, какие коды состояния необходимо игнорировать, несколько кодов разделяются запятыми
| userAgent:установить идентификатор паука
| includeHttps:0|1 следует ли извлекать страницы HTTPS
| isDupRemovalStrict:0|1 строго ли удалять повторяющиеся TargetUrl, т.е. если TargetUrl был посещён один раз, он больше не будет посещаться, даже если он повторяется, пока его источник URL отличается, он будет посещён
| isFollowRedirects:0|1 следовать ли редиректам 30X для продолжения извлечения
| reqDelay:{n}s|{n}m|{n}h|n задержка перед каждым запросом
| enable:0|1 включить ли извлечение этого сайта
| charset:набор символов сайта
| schedule:планирование времени, повторное извлечение начального URL каждые {время}
| thread:количество потоков, выделенных этому сайту паука
| waitQueue:пауку ждать {время}, прежде чем запрашивать задачи, когда очередь задач пуста
| timeout:тайм-аут HTTP-запроса
-->
<site name="oschina" includeHttps="1" url="http://www.oschina.net/question?catalog=1&show=&p=1" reqDelay="1s" enable="0" charset="utf-8" schedule="1h" thread="2" waitQueue="10s">
<!--
| Настройка нескольких начальных URL
| name:начальный URL
| url:начальный URL
-->
<!--seeds>
<seed name="" url="" />
</seeds-->
<!--
| Сообщить пауку извлекать только следующие URL хоста, в основном для обработки вторичных или многоуровневых доменных имён
-->
<!--validHosts>
<validHost value="demo.eweb4j.org" />
<validHost value="wwww.eweb4j.org" />
</validHosts-->
<!--
| Заголовок HTTP
<headers>
<header name="" value="" />
</headers>-->
<!--
| Cookie HTTP
<cookies>
<cookie name="" value="" host="" path="" />
</cookies>-->
<!--
| Правила добавления URL в очередь
| policy:стратегия нескольких правил, and | or
-->
<queueRules policy="and">
<!--
| Правило
| type:тип правила, включая regex | equal | start | end | contains все правила могут быть добавлены "!" перед ними для отрицания
| value:значение
-->
<rule type="!regex" value="^.*\.(jpg|png|gif)$" />
</queueRules>
<!--
| Извлекаемые цели
-->
<targets>
<!--
| Ограничить источник целевых URL, обычно это страница канала сайта, например, список новостей в определённой категории
-->
<sourceRules policy="and">
<rule type="regex" value="http://www\.oschina\.net/question\?catalog=1&show=&p=\d+">
<!--
| Определить, как выкопать новые URL на исходной странице
| Этот узел имеет ту же структуру, что и узел <model>, за исключением того, что имя называется не model, а digUrls
-->
<digUrls>
<field name="page_url" isArray="1">
<parsers>
<parser xpath="//div[@class='QuestionList']//ul[@class='pager']//li[@class='page']//a[@href]" attribute="href" /> xpath="//div[@class='QTitle']/h1/text()"/>
</parsers>
</field>
<field name="content">
<parsers>
<parser xpath="//div[@class='Content']//div[@class='detail']" exp="$output($this)" />
<!--attribute 黑名单-->
<parser exp="$Attrs.xml($this).rm('class').rm('style').rm('width').rm('height').rm('usemap').rm('align').rm('border').rm('title').rm('alt').ok()" />
<!--tag 黑名лан,去掉内嵌内容-->
<parser exp="$Tags.xml($this).rm('map').rm('iframe').rm('object').empty().ok()" />
<!--tag 白名单,保留的标签,除此之外都要删除(不删除其他标签内嵌内容)-->
<parser exp="$Tags.xml($this).kp('br').kp('h1').kp('h2').kp('h3').kp('h4').kp('h5').kp('h6').kp('table').kp('th').kp('tr').kp('td').kp('img').kp('p').kp('a').kp('ul').kp('ol').kp('li').kp('td').kp('em').kp('i').kp('u').kp('er').kp('b').kp('strong').ok()" />
<!--其他-->
</parsers>
</field>
<field name="author">
<parsers>
<parser xpath="//div[@class='stat']//a[@target='_blank']/text()"/>
</parsers>
</field>
<field name="tags" isArray="1">
<parsers>
<parser xpath="//div[@class='Tags']//a/text()"/>
</parsers>
</field>
<field name="answers" isArray="1">
<parsers>
<parser xpath="//li[@class='Answer']//div[@class='detail']/text()" />
</parsers>
</field>
</model>
</target>
</targets>
<!--
| 插件
-->
<plugins>
<!--
| enable:是否开启
| name:插件名
| version:插件版本
| desc:插件描述
-->
<plugin enable="1" name="spider_plugin" version="0.0.1" desc="这是一个官方实现的默认插件,实现了所有扩展点。">
<!--
| 每个插件包含了对若干扩展点的实现
-->
<extensions>
<!--
| point:扩展点名它们包括 task_poll, begin, fetch, dig, dup_removal, task_sort, task_push, target, parse, pojo, end
-->
<extension point="task_poll">
<!--
| 扩展点实现类
| type: 如何获取实现类 ,默认通过无参构造器实例化给定的类名,可以设置为ioc,这样就会从EWeb4J的IOC容器里获取
| value: 当时type=ioc的时候填写IOC的bean_id,否则填写完整类名
| sort: 排序,同一个扩展点有多个实现类,这些实现类会以责任链的方式进行执行,因此它们的执行顺序将变得很重要
-->
<impl type="" value="org.eweb4j.spiderman.plugin.impl.TaskPollPointImpl" sort="0"/>
</extension>
<extension point="begin">
<impl type="" value="org.eweb4j.spiderman.plugin.impl.BeginPointImpl" sort="0"/>
</extension>
<extension point="fetch">
<impl type="" value="org.eweb4j.spiderman.plugin.impl.FetchPointImpl" sort="0"/>
</extension>
<extension point="dig">
<impl type="" value="org.eweb4j.spiderman.plugin.impl.DigPointImpl" sort="0"/>
</extension>
<extension point="dup_removal">
<impl type="" value="org.eweb4j.spiderman.plugin.impl.DupRemovalPointImpl" sort="0"/>
</extension> *Расширение точки task_sort:*
*impl type: «» value: «org.eweb4j.spiderman.plugin.impl.TaskSortPointImpl» sort: «0».*
Расширение точки task_push:
impl type: «» value: «org.eweb4j.spiderman.plugin.impl.TaskPushPointImpl» sort: «0».
Расширение точки target:
impl type: «» value: «org.eweb4j.spiderman.plugin.impl.TargetPointImpl» sort: «0».
Расширение точки parse:
impl type: «» value: «org.eweb4j.spiderman.plugin.impl.ParsePointImpl» sort: «0».
Расширение точки end:
impl type: «» value: «org.eweb4j.spiderman.plugin.impl.EndPointImpl» sort: «0».
Провайдеры:
провайдер:
организация name: «CFuture» website: «http://lurencun.com» desc: «Color your future»:
*автор name: «weiwei» website: «http://laiweiweihi.iteye.com | http://my.oschina.net/laiweiwei» email: «l.weiwei@163.com» weibo: «http://weibo.com/weiweimiss» desc: «один IT-парень, который любит свободу, музыку и рисование».*
Плагин:
сайт:
бобы:
плагины:
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.