#java #алгоритм #jsoup
Есть метод, который считает хедеры для таблицы
private List headers(String html)
{
Document doc = Jsoup.parse(html);
ArrayList result = new ArrayList<>();
Elements header;
Element firstThead = doc.select("thead").first();
Elements trOfFirstThead = firstThead.children();
for (Element tr : firstThead.children())
{
Elements select = tr.select("th");
for (Element th : select)
{
String s = th.attributes().get("rowspan");
if (!s.isEmpty() && s.equals(String.valueOf(trOfFirstThead.size())))
{
result.add(th.text());
}
}
}
header = trOfFirstThead.last().children();
for (Element element : header)
{
if (element.tag().getName().equals(tag_th))
{
result.add(element.text());
}
}
return result;
}
Суть метода такова - на вход поступает таблица, у которой есть раздел thead и из
него необходимо получить хедеры в виде коллекции строк. Если хедеры расположены в несколько
рядов, то выбирается нижний ряд, и по нему берутся названия.
Данный алгоритм работает для таблиц, представленых под номерами 1, 2, и 3 ( см. вложение).
Но для таблицы типа 4 хедеры находятся не правильно.
Требуемая коллекция :
h4 h10 h11 h12 h6 h7.
При работе алгоритма получается следующая коллекция :
h4 h7 h10 h11 h12.
Прошу помочь советом/алгоритмом, как можно реализовать нужное поведение.
P.S. исходный код таблиц.
-
Таблица 1
h1
h2
h3
1
2
3
4
5
6
7
8
9
-
Таблица 2
h4
h5
h1
h2
h3
1
2
3
4
5
6
7
8
9
-
Таблица 3
h4
h5
h1
h2
1
2
3
4
5
6
7
8
9
-
Таблица 4
h4
h5
h7
h1
h2
h8
h6
h10
h11
h12
1
2
3
4
5
6
7
8
9
10
11
12
Ответы
Ответ 1
По-моему, решением будет реализация в каком-то объеме прописанного в HTML5 алгоритма построения таблицы, благо обработка там простая. Вот этот код выдает нужный результат на ваших примерах: static class TableHeader { private String[][] cells; private int y_height = 0; private int x_width = 0; public TableHeader( int rows, int columns, Element thead ) { cells = new String[rows][columns]; parseTHead( thead ); } private void ensureCapacity( int rows, int columns ) { if ( rows <= cells.length && columns <= cells[0].length ) return; int nRows = Math.max( cells.length, rows ); int nColumns = Math.max( cells[0].length, columns ); String[][] newCells = new String[nRows][nColumns]; for ( int row = 0; row < cells.length; row++ ) { System.arraycopy(cells[row], 0, newCells[row], 0, cells[row].length ); } cells = newCells; } private void fill( String cellValue, int row, int col, int rowspan, int colspan ) { ensureCapacity( row + rowspan, col + colspan ); for ( int r = 0; r < rowspan; r++ ) { for ( int c = 0; c < colspan; c++ ) { cells[row + r][col + c] = cellValue; } } } private int cellSpan( Element th, String attrName ) { String attrValue = th.attr( attrName ); int result = 1; if ( attrValue.isEmpty() ) return result; try { result = Integer.parseInt( attrValue ); } catch ( NumberFormatException ex ) { /*ignore*/ }; return result; } // http://www.w3.org/TR/html5/tabular-data.html#algorithm-for-processing-row-groups private void parseTHead( Element thead ) { //int y_start = y_height; // #1 int y_current = 0; final Elements rows = thead.children().select( "tr" ); final int rowsNumber = rows.size(); ensureCapacity(rowsNumber, x_width); for ( Element tr : rows ) { // #2 //http://www.w3.org/TR/html5/tabular-data.html#algorithm-for-processing-rows if ( y_height == y_current ) { y_height += 1; } int x_current = 0; //TODO: Run the algorithm for growing 'downward-growing cells'. for ( Element currentCell : tr.children().select( "td, th" ) ) { //6. While xcurrent is less than xwidth and the slot with coordinate (xcurrent, ycurrent) // already has a cell assigned to it, increase xcurrent by 1. while ( x_current < x_width && cells[y_current][x_current] != null ) x_current += 1; if ( x_current == x_width ) { x_width += 1; //# 7 } int colspan = cellSpan( currentCell, "colspan" ); //#8 int rowspan = cellSpan( currentCell, "rowspan" ); //#9 if (colspan == 0) colspan = 1; //TODO: 10. If rowspan is zero and the table element's Document is not set to quirks mode, // then let 'cell grows downward' be true, and set rowspan to 1. // Otherwise, let cell grows downward be false. //FIXME: не позволяем rowspan создавать больше строк, чем есть// как этот вопрос решен в стандарте? rowspan = Math.min( rowsNumber - y_current, rowspan ); if ( x_width < x_current + colspan ) x_width = x_current + colspan; if ( y_height < y_current + rowspan ) y_height = y_current + rowspan; // TODO: If any of the slots involved already had a cell covering them, // then this is a table model error. // Those slots now have two cells overlapping. fill( currentCell.text(), y_current, x_current, rowspan, colspan ); // #13 // TODO: If 'cell grows downward' is true, then add the tuple // {c, xcurrent, colspan} to the list of 'downward-growing cells'. x_current += colspan; //#15 } y_current += 1; } } public List lastRow() { return Arrays.stream( cells[y_height - 1]).limit( x_width ).collect( Collectors.toList()); } } private static List headers3(String html) { Document doc = Jsoup.parse(html); Element firstThead = doc.select("thead").first(); TableHeader header = new TableHeader(10, 10, firstThead); return header.lastRow(); } В реализации не обрабатывается случай с rowspan="0", вроде как все манипуляции с шириной и высотой можно закинуть в fill, и ни на чем, кроме ваших примеров я ее не проверял. В качестве бонуса, такой подход позволяет легко получить полный заголовок столбца. upd: есть очевидная проблема со случаем, когда y_current + rowspan превышает количество , в результате fill создает лишние ряды, чего в браузере не наблюдается. С colspan наверняка та же ситуация. Пока просто ограничил rowspan сверху, но я явно чего-то не понимаю в стандарте.
воскресенье, 15 марта 2020 г.
Парсинг html с помощью Jsoup
#android #jsoup #синтаксический_анализ
Есть html строка: Нужно получить адрес изображения:http://www.postfactum.ks.ua/wp-content/uploads/2015/06/DSC_0320_1-300x163.jpg. Пытаюсь парсить: Document doc = Jsoup.parse(image_url); if (doc.select("img[src]").size() > 0) { Element image = doc.select("img[src]").get(0); } else { image_url = "null"; } Ошибок нету, но строка пуста. В чем может быть проблема?Ответы
Ответ 1
Во-первых вам нужно использовать метод first() или last() в зависимости от порядка елемента. В вашем случае нужно использовать метод first(). Element image = doc.select("img[src]").first();. Во-вторых вы не указали атрибут "src": image_url = image.attr("src");. Полный код: Document doc = Jsoup.parse(mage_url); if (doc.select("img[src]").size() > 0) { Element image = doc.select("img[src]").first(); image_url = image.attr("src"); } else { image_url = "null"; }пятница, 13 марта 2020 г.
Jsoup. Парсинг элемента класс которого начинается на
#java #html #jsoup #парсер
Есть элементы подобные этому:Бывает либо trRandom, либо trRandomChild, а в остальных значениях меняются только лишь цифры. Как выбрать элемент класс которого начинается на trRandom средствами библиотеки Jsoup? Можно конечно написать костыли, но не хотелось бы, если есть более деликатный способ решения задачи. Пробовал код ниже, не помогает. select("[^trRandom]"); select("div[^trRandom]"); Остальные способы кидают исключения. Буду благодарен любой подсказке, спасибо! Ответы
Ответ 1
Ваш код: select("[^trRandom]"); select("div[^trRandom]"); Производит поиск по названию атрибута, который начинается на trRandom, а вам нужно по значению атрибута class по такому принципу: [attr^=valPrefix]. Вот так: select("[class^=trRandom]"); select("div[class^=trRandom]"); Подробней: http://jsoup.org/apidocs/org/jsoup/select/Selector.htmlвоскресенье, 8 марта 2020 г.
JSOUP заменить тег
#java #android #html #jsoup #парсер
Всем привет. Есть необходимость с помощью jsoup заменить тег наОтветы
Ответ 1
Оправляем в гугл строку jsoup rename element Идём по первой ссылке Видим ответ: String html = "fsdfsdfsdfdasdasd"; Document doc = Jsoup.parse(html); Elements elements = doc.select("font"); // rename all 'font'-tags to 'span'-tags, will also keep attributs etc. elements.tagName("span"); System.out.println(doc.html());понедельник, 3 февраля 2020 г.
Java jsoup подгрузить страницу
#java #парсер #jsoup
Часть страницы которую парсим скрыта. Что бы она подгрузилась надо кликнуть по блоку. Вот код этого блокаЕщё 1195 комментариев ::beforeКакой команда jsoup нужна что бы он прокликивал по этому блоку после подгрузки страницыОтветы
Ответ 1
Для того, чтобы кликнуть что-либо на странице, необходимо использовать библиотеку selenium. С помощью этой библиотеки Вы сможете кликнуть по блоку, далее достать html-содержание страницы и уже его распарсить jsoup'омОтвет 2
Такой команды нет. Jsoup предназначен только для парсинга HTML-данных, полученных после загрузки страницы/файла. Для выполнения javascript вам необходимо использовать web-браузер. Однако если данные присутствуют на самой странице, но скрыты css-стилями, то их можно извлечь стандартными методами Jsoup.Ответ 3
Воспользуйтесь библиотекой htmlunit. Бывает, если слишком много javascript, библиотека не справится, тогда берите Selenium.воскресенье, 12 января 2020 г.
Многопоточный парсинг страниц
#java #многопоточность #jsoup
Для онлайн-игры пишу прогу, которая позволит получать полезные данные. Есть два метода, которые возвращают стоимость конкретного оружия на рынке для одной страны и для всех стран (74) вместе. public void getCheapestWeapon(Weapon weapon) { //long result = 0; MapcountryAndId = allCountries.getCountryAndId(); for (Map.Entry item : countryAndId.entrySet()) { //long start = System.currentTimeMillis(); getCheapestWeapon(item.getKey(), weapon); //long finish = System.currentTimeMillis(); //System.out.println(finish - start); //result += finish - start; } //System.out.println(result); } public void getCheapestWeapon(String country, Weapon weapon) { long start = System.currentTimeMillis(); String link = MARKET_LINK + allCountries.getCountryId(country) + "/" + weapon.getPlaceOnMarket() + "/" + weapon.getQuality() + "/" + ADDITIONAL_LINK; try { Document page = Jsoup.connect(link) .userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36") .cookies(cookies) .timeout(1000) .get(); Elements table = page.getElementsByClass("price_sorted"); Element row = table.select("tr").first(); Elements columns = row.getElementsByTag("td"); Element priceCell = columns.get(3); String priceStr = priceCell.text(); double price = Double.parseDouble(priceStr.substring(0, priceStr.length() - 4)); System.out.println(country + " - " + price); } catch (IOException e) { e.printStackTrace(); } //long finish = System.currentTimeMillis(); //System.out.println(finish - start); } Проблема в том, что при получении данных для всех стран, время на парсинг составляет в среднем 16-18 секунд что, согласитесь, очень много. Пробовал в первом методе засунуть getCheapestWeapon(item.getKey(), weapon) в отдельный поток. Ничего хорошего это не дало. Как можно реализовать одновременный парсинг всех 74-х страниц? Ответы
Ответ 1
В текущем виде (результаты работы getCheapestWeapon просто выводятся на консоль) проще всего использовать стандартный ExecutorService public void getCheapestWeapon(Weapon weapon) { final int THREADS = 4; ExecutorService pool = Executors.newFixedThreadPool( THREADS ); MapcountryAndId = allCountries.getCountryAndId(); for (Map.Entry item : countryAndId.entrySet()) { pool.execute( () -> getCheapestWeapon( item.getKey(), weapon ) ); } pool.shutdown(); } Если операция не разовая, то нужно вынести создание и остановку сервиса из метода. Т.к. основная задержка, скорее всего из-за сети (74 запроса, даже если пакет идет от клиента до сервера 50мс, съедят 7 секунд), число процессов в сервисе может превышать количество ядер (подберите экспериментально). В качестве альтернативы, попробуйте получать у сервера больше данных меньшим количеством запросов, например убрав фильтр по странам, если вам нужна самая низкая цена. пятница, 10 января 2020 г.
С помощью Jsoup спарсить данные из определенной родительской ветки
#java #android #парсер #jsoup
При парсинге страницы в Андроид приложение (пользую последнюю версию либ от Jsoup.org), выбираю только дочерние элементы(Выбираются все элементы, которые на схеме выделены розовым), а можно как нибудь выбрать сначала дочерние элементы с первого родителя, а потом только со второго и т.д.Как это примерно реализовать?Ответы
Ответ 1
Очевидное решение с выбором родительских элементов и последующим поиском детей в них. public static void main(String[] args) { String html = "" + "" + "Try jsoup " + "" + "" + "" + "" + "child1.1
" + "child1.2
" + "child1.3
" + "" + "" + "child2.1
" + "child2.2
" + "child2.3
" + "" + "" + "child3.1
" + "child3.2
" + "child3.3
" + "" + "" + "" + ""; Document doc = Jsoup.parse( html ); Elements parents = doc.select( "div" ); // выбор родителей System.out.printf( // можно выбрать элемент по индексу "parent[%d]: %s#%s%n", 2, parents.get( 2 ).tagName(), parents.get( 2 ).id() ); for ( Element parent : parents ) { System.out.printf( "parent: %s#%s%n", parent.tagName(), parent.id() ); for ( Element child : parent.select( "p" ) ) { // выбор внутри родителя System.out.println( child.text() ); } } } Выведет: parent[2]: div#parent3 parent: div#parent1 child1.1 child1.2 child1.3 parent: div#parent2 child2.1 child2.2 child2.3 parent: div#parent3 child3.1 child3.2 child3.3 parent: div#parent4 Используя псевдоселектор :has(selector), можно выбрать только те, в которых есть: Elements parents = doc.select( "div:has(p)" ); Тогда в выдачу не попадет #parent4.
суббота, 4 января 2020 г.
Как сохранить html страницу при парсинге
#java #android #jsoup #парсер
Хочу сделать возможность сохранять нужную страницу с интернета в html, чтобы потом данные парсить офлайн именно с этой страницы, которую сохранил на устройстве. Пользуюсь библиотекой Jsoup. Помогите пожалуйста, как это можно осуществить.Ответы
Ответ 1
Всё просто. Загружаете страницу. Получаете её в виде HTML кода методом getOutherHtml() класса Element, от коего наследуется, в т.ч. и класс Document. Т.е. вы можете сделать что-то типа String html = Jsoup.connect("http://example.com/").get().getOutherHtml(); Теперь сохраняйте любым способом. В файл, БД, SharedPreferences, как угодно.Ответ 2
Без использования Jsoup: class DownloadHtmlFromSiteToFile { public static void main(String[] args) { int count; byte[] buff = new byte[64]; InputStream is = null; OutputStream out = null; try { is = new URL("http://google.com").openStream(); out = new FileOutputStream("D:/google.html"); while ((count = is.read(buff)) != -1) { out.write(buff, 0, count); } } catch (IOException e){} }Как сделать авторизацию на сайте в android приложении с помощью Jsoup
#java #android #post #авторизация #jsoup
Большая просьба не кидать камнями а написать по делу либо скинуть ссылку на уже готовые гайды по созданию авторизации на сайте. Перерыл весь гугл - ничего рабочего так и не нашел(Руки не из заднего места) сам по себе начинающий кодер на java, но структуру кода понимаю(Что, где и от чего зависит). Нужен готовый пример с рабочим пост запросом, единственное из того что нашел хоть как то давало признаки жизни. try { Connection.Response res1 = Jsoup.connect("http://site.ru") .data("login","admin") .data("passwd","admin") .data("send","Войти") .method(Connection.Method.POST) .execute(); Map loginCoockies = res1.cookies(); System.out.println(loginCoockies); Document doc = Jsoup.connect("http://site.ru") .cookies(loginCoockies) .get(); System.out.println(doc); } catch (IOException e) { System.out.println(e); } return null; После запуска выдает: {PHPSESSID=t7idbm5jkr40ial25brvju72n2} и html код страницы. Точнее после каждого нажатия на кнопку выдает PHPSESSID=(Всегда разный). Страница html кода показывает что я так и не был авторизован.Ответы
Ответ 1
Разобрался сам, авторизация проходит успешно, проверка тоже. Скачал HTTP Analyzer и посмотрел какие данные отправляются POST запросом и куда. Добавил в запрос недостающих данных, и все заработало )суббота, 21 декабря 2019 г.
Парсинг всего сайта с помощью Jsoup
#java #jsoup #парсер
Осваиваю парсинг сайтов, в связи с чем возник вопрос, на который никак не могу найти ответ. Задача стоит следующая: прошерстить все страницы сайта, и посчитать, сколько раз там встречается заданное слово. На данный момент я разобрался, как сделать вышесказанное только на одной странице. Как можно организовать автоматический переход со страницы на страницу, чтобы пропарсить полностью весь сайт?Ответы
Ответ 1
Задачу можно разбить на несколько этапов: Получаем содержимое страницы и выполняем нужный анализ. Document doc = Jsoup.connect("http://www.site.com/").get(); processDoc(doc); Находим на текущей странице все ссылки с этим же доменом и добавляем их в очередь, предварительно отфильтровав те ссылки, которые уже были посещены. Elements links = doc.select("a[href]"); for (Element link : links) { String absLink = link.attr("abs:href"); if (absLink.contains("site.com") && notYetVisited(absLink)) addToQueue(absLink); } Пока очередь еще не пуста, извлекаем очередную ссылку и переходим шагу 1. Если сайт большой, то необходимо предусмотреть хранение очереди ссылок и списка посещенных ссылок в базе данных. Вот подробный пример с решением похожей задачи.воскресенье, 1 декабря 2019 г.
Парсинг сайта в java с помощью jsoup
#java #парсер #jsoup
Решил написать простенькую телепрограмму с использованием jsoup. Задача более чем скучная, но для меня новая ибо раньше с парсерами не работал. Прошу у Вас помощи не в написание кода, а совета как работать с парсером, читал статьи, но они рассказывают только про заголовки и т.п., а вот как например спарсить расписание с первого канала на странице. И может быть вы считаете, что jsoup полная лажа посоветуйте другой.Ответы
Ответ 1
Ну, а в чём проблема? Загрузите документ. Снавигируйте к элементу, содержащему программы. Тем же способом найдите нужные элементы данных. Какие именно HTML-атрибуты вам нужны, устанавливается внимательным чтением исходника сайта, который вы собираетесь распарсить. На сайте даже есть простейший пример (без навигации, просто все ссылки).Ответ 2
Есть 2 типа html/xml парсеров: SAX парсер - парсит в потоковом режиме, на вход подается поток html/xml, в определенных местах срабатывают т.н. хэндлеры, то есть перехватчики, которые говорят "сейчас парсер наткнулся на такой-то элемент". В хэндлер обычно прогер вставляет свой код и делает свое дело DOM парсер - засовывается весь источник, на выходе получаем дерево - иногда довольно сложное. jsoup это разновидность DOM парсера, так что весь вопрос в том, чтобы правильно спозиционироваться в дереве полученном после парсинга - или выражаясь языком модели DOM в нодах. Это описываетс документацией jsoup API в пакете org.jsoup.nodes. Заодно нелишним будет почитать про DOM это сразу направит мозги в нужном направлении. Удачи.Ответ 3
Я это делаю так. Тоже первый раз :) Пока не разобрался как красиво выводить то, что спарсил :( package sm.play.sportlife.ua; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import android.app.Activity; import android.app.ProgressDialog; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; public class MainActivity extends Activity { private static final String TAG = MainActivity.class.getSimpleName(); public static int dayOfTheWeek = 0; // благодоря этому классу мы будет разбирать данные на куски public Elements time, currentday; // то в чем будем хранить данные public ArrayListtimeList = new ArrayList (); public ArrayList dayEventList = new ArrayList (); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /** Запрос к нашему отдельному поток на выборку данных */ new GetDataThread().execute(); dayOfTheWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 1; if (dayOfTheWeek == 0) { dayOfTheWeek = 7; } Log.d(TAG, "День недели :" + dayOfTheWeek); } /** * А вот и внутрений класс который делает запросы в отдельном потоке */ public class GetDataThread extends AsyncTask { private TableRow row; private TableLayout inflate; private TextView txtcol1, txtcol2; private String eventNames; // private ProgressDialog prog; /** * Метод выполняющий запрос в фоне, в версиях выше 4 андроида, запросы в * главном потоке выполнять нельзя, поэтому все что вам нужно выполнять * - выносите в отдельный поток */ @Override protected String doInBackground(String... arg) { String myURL = "http://www.sportlife.ua/ru/services/schedule/14875"; // класс который захватывает страницу Document doc; try { // определяем откуда будем скачивать данные doc = Jsoup .connect(myURL) .userAgent( "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36") .get(); // задаем с какого места парсить /** * Выбираем содержимое рассписания по индексу столбца * td:eq(Индекс) */ // Время занятий int k = 0; String link = "#shedule-content tr:gt(0) " + "td:eq(" + k + ")"; time = doc.select(link); link = "#shedule-content tr:gt(0) " + "td:eq(" + dayOfTheWeek + ")"; currentday = doc.select(link); /** * Чистим наши ArrayList для того что бы заполнить и в цикле * захватываем все данные какие есть на странице */ timeList.clear(); dayEventList.clear(); for (Element times : time) { if (times.className().equals("time-col")) { timeList.add(times.text()); // Записываем в ArrayList // время занятий } } /** * Для каждого event currentday из записываем в аррей лист * события дня */ for (Element event : currentday) { if (event.hasText() == true) { Elements mEvents = Jsoup.parse(event.html()).select( ".event-item-body"); /** Может быть несколько занятий на одно и тоже время */ int i = 0; do { Element textEvent = mEvents.get(i); String tmpString = textEvent.text(); if (eventNames == null) { eventNames = tmpString + "\n"; } else { eventNames = eventNames + tmpString + "\n"; } i++; } while (i < mEvents.size()); /** События заносим в список */ dayEventList.add(eventNames); eventNames = ""; } else // dayEventList.add(titles.text()); dayEventList.add(""); } } catch (IOException e) { e.printStackTrace(); } return null; } @Override protected void onPreExecute() { // prog = new ProgressDialog(MainActivity.this); // prog.setMessage("Соединяемся..."); // prog.show(); } @Override protected void onPostExecute(String result) { /** ФОРМИРУЕМ ТАБЛИЦУ */ inflate = (TableLayout) MainActivity.this .findViewById(R.id.mytable); for (int i = 0, j = 0; i < timeList.size() || j < dayEventList.size();) { row = new TableRow(MainActivity.this); txtcol1 = new TextView(MainActivity.this); if (timeList.size() > i) { if ((timeList.get(i) != null)) { txtcol1.setText(timeList.get(i)); txtcol1.setBackgroundResource(R.drawable.shape_rec); // txtcol1.setTextColor(Color.rgb(245, 245, 220)); // txtcol1.setBackgroundColor(Color.rgb(0, 0, 0)); i++; } } else { txtcol1.setText(""); } row.addView(txtcol1); txtcol2 = new TextView(MainActivity.this); if ((dayEventList.size() > j)) { if (dayEventList.get(j) != null) { txtcol2.setText(dayEventList.get(j)); txtcol2.setBackgroundResource(R.drawable.shape_rec); // txtcol2.setMaxLines(20); j++; } } else { txtcol2.setText(""); } this.row.addView(txtcol2); inflate.addView(row); } /** КОНЕЦ ФОРМИРОВАНИЯ ТАБЛИЦЫ */ // super.onPostExecute(result); // prog.dismiss(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * A placeholder fragment containing a simple view. */ public static class PlaceholderFragment extends Fragment { public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); return rootView; } } } четверг, 11 июля 2019 г.
Проблема с пост запросом. JSoup
http://net.citycheb.ru/ - Это сайт с электронным журналом для школы. Я пытаюсь авторизироваться, чтобы сделать клиент. Использую JSoup:
import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document;
import java.io.IOException;
class Auth { public static String auth(String login, String password) { try { Document tempodoc = Jsoup.connect("http://net.citycheb.ru").get(); String VER = tempodoc.select("input[name=\"VER\"]").first().toString(); String LT = tempodoc.select("input[name=\"LT\"]").first().toString();
Connection conn = Jsoup .connect("http://net.citycheb.ru/asp/postlogin.asp"); conn.data("VER", VER); conn.data("PW2", ""); conn.data("LT", LT); conn.data("LoginType", "1"); conn.data("ECardID", ""); conn.data("CID", "2"); conn.data("SID", ""); conn.data("PID", "-1"); conn.data("CN", "4"); conn.data("SFT", "2"); conn.data("SCID", "69"); conn.data("UN", login); conn.data("CN", password);
conn.method(Connection.Method.POST); conn.referrer("http://net.citycheb.ru/"); Connection.Response resp = conn.execute(); System.out.println("statusCode: " + resp.statusCode()); System.out.println("response: " + resp.toString());
Document doc = conn.url("http://net.citycheb.ru/asp/Announce/ViewAnnouncements.asp").get();
return doc.select("div[style=\"font-family: Arial, Helvetica, sans-serif; color:#FFFFFF; font-weight: bold;\"]").text(); }
catch (IOException e) { e.printStackTrace(); } return null; } }
В пост запросе меняются VER, PW2, LT, SID параметры, их нужно парсить с главной страницы (по крайней мере я так думаю). Но на самом деле не знаю точно, как строить пост запрос для этого сайта.
В итоге ничего кроме StatusCode : 200 полезного не выводит. Данная система построена на asp.net, как видно по запросам, а так же я думаю что и логин, и пароль предварительно шифруются, но я никаких других скриптов при авторизации через Chrome Development Tools не заметил, хотя может и пользовался неправильно.Ответ
Отсутствует связующее звено между первым запросом и в вторым. JSoup автоматически не запоминает сессии и куки, не хранит данные откуда перешел пользователь. И еще нужно добавлять user-agent для полного понимания.
Вот пример на основе другого сайта:
import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.Map;
import org.jsoup.Connection; import org.jsoup.Connection.Method; import org.jsoup.Jsoup; import org.jsoup.nodes.Document;
public class Rutracker {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException { String urlogin; String urpassword; String userAgent = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36";
Connection.Response res = Jsoup.connect("http://login.rutracker.org/forum/login.php").userAgent(userAgent) .data("login_username", urlogin, "login_password", urpassword, "login", "%E2%F5%EE%E4") .method(Method.POST).execute(); //Третий параметр отлавливал фидлером
MaploginCookies = res.cookies();
Document doc3 = Jsoup.connect("http://rutracker.org/forum/index.php").userAgent(userAgent).cookies(loginCookies).get(); System.out.println(doc3.getElementsByClass("logged-in-as-uname")); }
}вторник, 25 июня 2019 г.
Поиск необходмых заголовков
Есть метод, который считает хедеры для таблицы
private Listheaders(String html) { Document doc = Jsoup.parse(html); ArrayList result = new ArrayList<>(); Elements header; Element firstThead = doc.select("thead").first(); Elements trOfFirstThead = firstThead.children(); for (Element tr : firstThead.children()) { Elements select = tr.select("th"); for (Element th : select) { String s = th.attributes().get("rowspan"); if (!s.isEmpty() && s.equals(String.valueOf(trOfFirstThead.size()))) { result.add(th.text()); } } } header = trOfFirstThead.last().children();
for (Element element : header) { if (element.tag().getName().equals(tag_th)) { result.add(element.text()); } } return result; }
Суть метода такова - на вход поступает таблица, у которой есть раздел thead и из него необходимо получить хедеры в виде коллекции строк. Если хедеры расположены в несколько рядов, то выбирается нижний ряд, и по нему берутся названия.
Данный алгоритм работает для таблиц, представленых под номерами 1, 2, и 3 ( см. вложение). Но для таблицы типа 4 хедеры находятся не правильно.
Требуемая коллекция :
h4 h10 h11 h12 h6 h7
При работе алгоритма получается следующая коллекция :
h4 h7 h10 h11 h12
Прошу помочь советом/алгоритмом, как можно реализовать нужное поведение.
P.S. исходный код таблиц.
Таблица 1
h1 h2 h3 1 2 3 4 5 6 7 8 9 Таблица 2
h4 h5 h1 h2 h3 1 2 3 4 5 6 7 8 9 Таблица 3
h4 h5 h1 h2 1 2 3 4 5 6 7 8 9 Таблица 4
h4 h5 h7 h1 h2 h8 h6 h10 h11 h12 1 2 3 4 5 6 7 8 9 10 11 12
Ответ
По-моему, решением будет реализация в каком-то объеме прописанного в HTML5 алгоритма построения таблицы, благо обработка там простая. Вот этот код выдает нужный результат на ваших примерах:
static class TableHeader { private String[][] cells; private int y_height = 0; private int x_width = 0;
public TableHeader( int rows, int columns, Element thead ) { cells = new String[rows][columns];
parseTHead( thead ); }
private void ensureCapacity( int rows, int columns ) { if ( rows <= cells.length && columns <= cells[0].length ) return;
int nRows = Math.max( cells.length, rows ); int nColumns = Math.max( cells[0].length, columns );
String[][] newCells = new String[nRows][nColumns]; for ( int row = 0; row < cells.length; row++ ) { System.arraycopy(cells[row], 0, newCells[row], 0, cells[row].length ); }
cells = newCells; }
private void fill( String cellValue, int row, int col, int rowspan, int colspan ) { ensureCapacity( row + rowspan, col + colspan ); for ( int r = 0; r < rowspan; r++ ) { for ( int c = 0; c < colspan; c++ ) { cells[row + r][col + c] = cellValue; } } }
private int cellSpan( Element th, String attrName ) { String attrValue = th.attr( attrName ); int result = 1; if ( attrValue.isEmpty() ) return result; try { result = Integer.parseInt( attrValue ); } catch ( NumberFormatException ex ) { /*ignore*/ }; return result; }
// http://www.w3.org/TR/html5/tabular-data.html#algorithm-for-processing-row-groups private void parseTHead( Element thead ) { //int y_start = y_height; // #1 int y_current = 0; final Elements rows = thead.children().select( "tr" ); final int rowsNumber = rows.size(); ensureCapacity(rowsNumber, x_width); for ( Element tr : rows ) { // #2 //http://www.w3.org/TR/html5/tabular-data.html#algorithm-for-processing-rows if ( y_height == y_current ) { y_height += 1; } int x_current = 0; //TODO: Run the algorithm for growing 'downward-growing cells'. for ( Element currentCell : tr.children().select( "td, th" ) ) { //6. While xcurrent is less than xwidth and the slot with coordinate (xcurrent, ycurrent) // already has a cell assigned to it, increase xcurrent by 1. while ( x_current < x_width && cells[y_current][x_current] != null ) x_current += 1; if ( x_current == x_width ) { x_width += 1; //# 7 } int colspan = cellSpan( currentCell, "colspan" ); //#8 int rowspan = cellSpan( currentCell, "rowspan" ); //#9 if (colspan == 0) colspan = 1; //TODO: 10. If rowspan is zero and the table element's Document is not set to quirks mode, // then let 'cell grows downward' be true, and set rowspan to 1. // Otherwise, let cell grows downward be false. //FIXME: не позволяем rowspan создавать больше строк, чем есть// как этот вопрос решен в стандарте? rowspan = Math.min( rowsNumber - y_current, rowspan ); if ( x_width < x_current + colspan ) x_width = x_current + colspan; if ( y_height < y_current + rowspan ) y_height = y_current + rowspan; // TODO: If any of the slots involved already had a cell covering them, // then this is a table model error. // Those slots now have two cells overlapping. fill( currentCell.text(), y_current, x_current, rowspan, colspan ); // #13 // TODO: If 'cell grows downward' is true, then add the tuple // {c, xcurrent, colspan} to the list of 'downward-growing cells'. x_current += colspan; //#15 } y_current += 1; } }
public ListlastRow() { return Arrays.stream( cells[y_height - 1]).limit( x_width ).collect( Collectors.toList()); } }
private static Listheaders3(String html) { Document doc = Jsoup.parse(html);
Element firstThead = doc.select("thead").first();
TableHeader header = new TableHeader(10, 10, firstThead);
return header.lastRow(); }
В реализации не обрабатывается случай с rowspan="0", вроде как все манипуляции с шириной и высотой можно закинуть в fill, и ни на чем, кроме ваших примеров я ее не проверял. В качестве бонуса, такой подход позволяет легко получить полный заголовок столбца.
upd: есть очевидная проблема со случаем, когда y_current + rowspan превышает количество, в результате fill создает лишние ряды, чего в браузере не наблюдается. С colspan наверняка та же ситуация. Пока просто ограничил rowspan сверху, но я явно чего-то не понимаю в стандарте. пятница, 21 июня 2019 г.
Выборка расписания с сайта - Java
Пытаюсь сделать приложение, которое будет парсить страницу с расписанием автобуса и выводить его.
Суть в том, чтобы расписание бралось первой остановки и если автобус будет не раньше, чем текущее время. То есть, если сейчас время 18:10, то надо вывести все оставшееся расписание с 18:10.
http://mybuses.ru/moscow/bus/734/
В идеале был бы показ только 3 автобусов, которые ближе всего к текущему времени.
class main extends AsyncTask{
String Bus1,Sys_time,wtf;
@Override protected Void doInBackground(Void... params) {
String table=""; String time; Integer first,second; final Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis()); Date date = cal.getTime(); Integer mHour = date.getHours(); Integer mMinute = date.getMinutes(); if(mMinute<10){ mMinute=date.getMinutes()+10; } String curr_time=mHour.toString()+":"+mMinute.toString();
Document doc; try { doc = Jsoup.connect("http://mybuses.ru/moscow/bus/734/").get(); Element table_site=doc.select("table").get(0); Element rows1=table_site.select("tr").get(1);
IteratorrowIterator=table_site.select("tr").iterator(); rowIterator.next(); Integer str1,str2,str3,str4;
while (rowIterator.hasNext()) { Element e = doc.select("tr").get(2); String e_1=e.attr("class"); str1=e_1.indexOf("-"); str2=e_1.indexOf(":"); str3=e_1.indexOf("-"); str4 = e_1.indexOf(":"); first = Integer.parseInt(e_1.substring(str1+1, e_1.indexOf(str1+2))); second = Integer.parseInt(e_1.substring(str2+1, e_1.indexOf(str2+2)));
if (first>=mHour && second>=mMinute) { Element rows=table_site.select("tr").get(1); Element record1=rows.select("td").get(1); Iteratortime1=record1.select("td").iterator(); wtf = time1.next().text(); } else { wtf="1232132132"; } } Element record2=rows1.select("td").get(6); Iterator time2=record2.select("td").iterator();
table="Автобус прибудет на Ст. Солнечная в "+wtf+" (через n минут)
Автобус прибудет на ГМС3 в "+time2.next().text(); } catch (IOException e) { e.printStackTrace(); }
Bus1=table; Sys_time=curr_time;
return null;
}
@Override protected void onPostExecute(Void result) { super.onPostExecute(result);
textView.setText("Текущее время: "+Sys_time+"
Расписание автобусов:
"+Bus1); } }
В этом коде ошибка:
Caused by: java.lang.StringIndexOutOfBoundsException: length=41; regionStart=23; regionLength=-24
в строке
first = Integer.parseInt(e_1.substring(str1+1, e_1.indexOf(str1+2)));
Как ее можно исправить? В моей задумке было получить время из названия класса на сайте, т.к. в названии класса указано время первой остановки sh2 columnIsVisible 0-05:26-01-0002-0001, где 05:26 - время. Попытался вырезать это время и сравнить с текущим.Ответ
Исключение вам английским языком говорит что вы вышли за границы строки.
А теперь попробуем разобрать фрагмент вашего кода.
str1 = e_1.indexOf("-"); ... ... first = Integer.parseInt(e_1.substring(str1+1, e_1.indexOf(str1+2)));
в строке e_1 строка для парсинга. в целочисленной переменной str1 индекс символа - в переменную first хотим получить целое число, в которое преобразуется строка из двух символов после символа -
Тут все понятно. А вот дальше начинается интересное.
e_1.indexOf(str1+2) - тут вы пытаетесь найти индекс вхождения в исходную строку символа, который является суммой чисел от индекса str1+2. Т.е. собственно нелогичная и некорректная операция. Думаю что вполне можно заменить это просто нв str1+3. +3 - потому что конечный индекс не включается в результат операции.
Т.е. код должен быть.
first = Integer.parseInt(e_1.substring(str1+1, str1+3));
Аналогично и для получения переменной secondсуббота, 15 июня 2019 г.
Проблема при парсинге сайта гуггл переводчика. Jsoup
Изначально я пытался пропарсить сайт translate.ru, все было хорошо, но при парсинге translate.google.ru вылетает ошибка.
Exception in thread "main" org.jsoup.HttpStatusException: HTTP error fetching URL. Status=403, URL=https://translate.google.ru/ at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:598) at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:548) at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:235) at org.jsoup.helper.HttpConnection.get(HttpConnection.java:224) at http.RequestDemo.main(RequestDemo.java:14)
А вот вот весь код программы
import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements;
public class RequestDemo { public static void main(String[] args) throws IOException{ Document doc = Jsoup.connect("https://translate.google.ru/").get(); //System.out.println(doc.html()); Elements metaElements = doc.select("span"); //System.out.println(metaElements); } }Ответ
Проблема решена. Для того что бы все работало надо поключаться через userAgent("/Здесь передаем название браузера/")
среда, 5 июня 2019 г.
Парсинг html с помощью Jsoup
Есть html строка:
Нужно получить адрес изображения:http://www.postfactum.ks.ua/wp-content/uploads/2015/06/DSC_0320_1-300x163.jpg. Пытаюсь парсить:
Document doc = Jsoup.parse(image_url);
if (doc.select("img[src]").size() > 0) { Element image = doc.select("img[src]").get(0); } else { image_url = "null"; }
Ошибок нету, но строка пуста. В чем может быть проблема?Ответ
Во-первых вам нужно использовать метод first() или last() в зависимости от порядка елемента. В вашем случае нужно использовать метод first(). Element image = doc.select("img[src]").first();. Во-вторых вы не указали атрибут "src": image_url = image.attr("src");. Полный код:
Document doc = Jsoup.parse(mage_url); if (doc.select("img[src]").size() > 0) { Element image = doc.select("img[src]").first(); image_url = image.attr("src"); } else { image_url = "null"; }понедельник, 3 июня 2019 г.
Как сохранить html страницу при парсинге
Хочу сделать возможность сохранять нужную страницу с интернета в html, чтобы потом данные парсить офлайн именно с этой страницы, которую сохранил на устройстве. Пользуюсь библиотекой Jsoup. Помогите пожалуйста, как это можно осуществить.
Ответ
Всё просто.
Загружаете страницу. Получаете её в виде HTML кода методом getOutherHtml() класса Element, от коего наследуется, в т.ч. и класс Document. Т.е. вы можете сделать что-то типа
String html = Jsoup.connect("http://example.com/").get().getOutherHtml();
Теперь сохраняйте любым способом. В файл, БД, SharedPreferences, как угодно.суббота, 1 июня 2019 г.
Jsoup. Парсинг элемента класс которого начинается на
Есть элементы подобные этому:
Бывает либо trRandom, либо trRandomChild, а в остальных значениях меняются только лишь цифры. Как выбрать элемент класс которого начинается на trRandom средствами библиотеки Jsoup? Можно конечно написать костыли, но не хотелось бы, если есть более деликатный способ решения задачи.
Пробовал код ниже, не помогает.
select("[^trRandom]"); select("div[^trRandom]");
Остальные способы кидают исключения. Буду благодарен любой подсказке, спасибо!Ответ
Ваш код:
select("[^trRandom]"); select("div[^trRandom]");
Производит поиск по названию атрибута, который начинается на trRandom, а вам нужно по значению атрибута class по такому принципу: [attr^=valPrefix]
Вот так: select("[class^=trRandom]"); select("div[class^=trRandom]");
Подробней: http://jsoup.org/apidocs/org/jsoup/select/Selector.htmlвоскресенье, 7 апреля 2019 г.
Java jsoup подгрузить страницу
Часть страницы которую парсим скрыта. Что бы она подгрузилась надо кликнуть по блоку. Вот код этого блока
Ещё комментариев ::before
Какой команда jsoup нужна что бы он прокликивал по этому блоку после подгрузки страницыОтвет
Для того, чтобы кликнуть что-либо на странице, необходимо использовать библиотеку selenium. С помощью этой библиотеки Вы сможете кликнуть по блоку, далее достать html-содержание страницы и уже его распарсить jsoup'ом
четверг, 7 марта 2019 г.
Многопоточный парсинг страниц
Для онлайн-игры пишу прогу, которая позволит получать полезные данные. Есть два метода, которые возвращают стоимость конкретного оружия на рынке для одной страны и для всех стран (74) вместе.
public void getCheapestWeapon(Weapon weapon) { //long result = 0; MapcountryAndId = allCountries.getCountryAndId(); for (Map.Entry item : countryAndId.entrySet()) { //long start = System.currentTimeMillis(); getCheapestWeapon(item.getKey(), weapon); //long finish = System.currentTimeMillis(); //System.out.println(finish - start); //result += finish - start; } //System.out.println(result); }
public void getCheapestWeapon(String country, Weapon weapon) { long start = System.currentTimeMillis(); String link = MARKET_LINK + allCountries.getCountryId(country) + "/" + weapon.getPlaceOnMarket() + "/" + weapon.getQuality() + "/" + ADDITIONAL_LINK; try { Document page = Jsoup.connect(link) .userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36") .cookies(cookies) .timeout(1000) .get(); Elements table = page.getElementsByClass("price_sorted"); Element row = table.select("tr").first(); Elements columns = row.getElementsByTag("td"); Element priceCell = columns.get(3); String priceStr = priceCell.text(); double price = Double.parseDouble(priceStr.substring(0, priceStr.length() - 4)); System.out.println(country + " - " + price); } catch (IOException e) { e.printStackTrace(); } //long finish = System.currentTimeMillis(); //System.out.println(finish - start); }
Проблема в том, что при получении данных для всех стран, время на парсинг составляет в среднем 16-18 секунд что, согласитесь, очень много. Пробовал в первом методе засунуть getCheapestWeapon(item.getKey(), weapon) в отдельный поток. Ничего хорошего это не дало. Как можно реализовать одновременный парсинг всех 74-х страниц?Ответ
В текущем виде (результаты работы getCheapestWeapon просто выводятся на консоль) проще всего использовать стандартный ExecutorService
public void getCheapestWeapon(Weapon weapon) { final int THREADS = 4; ExecutorService pool = Executors.newFixedThreadPool( THREADS );
MapcountryAndId = allCountries.getCountryAndId(); for (Map.Entry item : countryAndId.entrySet()) { pool.execute( () -> getCheapestWeapon( item.getKey(), weapon ) ); }
pool.shutdown(); }
Если операция не разовая, то нужно вынести создание и остановку сервиса из метода. Т.к. основная задержка, скорее всего из-за сети (74 запроса, даже если пакет идет от клиента до сервера 50мс, съедят 7 секунд), число процессов в сервисе может превышать количество ядер (подберите экспериментально).
В качестве альтернативы, попробуйте получать у сервера больше данных меньшим количеством запросов, например убрав фильтр по странам, если вам нужна самая низкая цена.Подписаться на: Сообщения (Atom)