Список пользователей

1
Админ
Постов: 101
2
Элита
Постов: 34
3
Элита
Постов: 28
4
VIP
Постов: 26
5
Дизайнер
Постов: 25
6
Пользователи
Постов: 25
7
Пользователи
Постов: 24
8
Проверенные
Постов: 21

  • Страница 1 из 1
  • 1
Форумный парсер новостей карточек последних тем UCOZ
Дата: Среда, 22.10.2025, 14:47 | Сообщение # 1 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Парсер это скрипт, который получает данные с другой страницы (в данном случае с форума), извлекает нужные элементы (заголовок темы, картинку, краткий текст) и отображает их в виде карточек.

На Юкозе лучше всего выводить нужную информацию через парсинг или RSS.
Все стандартные модули-информеры UCOZ имеют большие ограничения — другими словами, это практически бесполезно.

Он работает прямо в браузере пользователя , это называется клиентский парсер.

На чём написан: Это JavaScript (ES6+) чистый JS без серверной части,

Работает прямо на странице форума, сайта.

Для кэширования используется localStorage браузера, чтобы не перегружать форум каждый раз.

Особенности моего решения:

Клиентский парсер не требует PHP, базы данных или стороннего хостинга.

Кэширование через localStorage , данные обновляются раз в 30 минут, что снижает нагрузку.

Карточки под дизайн форума , фиксированный размер, название темы, картинка, короткое описание.

Ограничение на количество карточек максимум 8 штук, 4 в ряд

Вы можете изменить настройки вручную: в скрипте количество загружаемых карточек, в CSS дизайн самих карточек.

Вставляйте код на главную страницу вашего каталога файлов или новостей.

Если вам нужны какие-то особенные карточки, напишите ниже, какие именно, чтобы мы сделали их под ваш сайт.

Все настройки (URL форума, количество карточек, кэш) можно вынести в объект конфигурации, меняйте на свои параметры под ваш форум.

Ничего сложного нет , меняйте конфигурацию под свои нужды .

Парсер ссылается на конкретную страницу последних новостей форума.

в ручную меняйте количество карточек ниже смотрим показываю где менять.

Код
    const forumListUrl = '/forum/0-0-1-34';
    const MAX_CARDS = 8;
    const CACHE_KEY = 'forum_cards_cache';
    const CACHE_TIME = 30 * 60 * 1000; // 30 минут


CSS стили

Код
<style>
#custom-forum-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: flex-start;
  width: 100%;
}

.custom-card {
  flex: 0 0 calc(25% - 15px); /* 4 карточки в ряд */
  box-sizing: border-box;
  border-radius: 12px;
  overflow: hidden;
  position: relative;
  background: #1e1e1e;
  color: #fff;
  cursor: pointer;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  text-decoration: none;
}

.custom-card:hover {
  transform: translateY(-8px) scale(1.02);
  box-shadow: 0 15px 25px rgba(0,0,0,0.4);
}

.custom-card-img {
  width: 100%;
  height: 180px;
  overflow: hidden;
  position: relative;
}

.custom-card-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.custom-card:hover .custom-card-img img {
  transform: scale(1.1);
}

.custom-card-title {
  font-weight: bold;
  font-size: 14px;
  padding: 8px 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.custom-card-desc {
  font-size: 12px;
  color: #ccc;
  padding: 0 10px 10px 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-card-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  background: #853b97;
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
</style>


Скрипт

Код
<script>
(async function() {
    const container = document.querySelector('#custom-forum-cards');
    if (!container) return;

    const forumListUrl = '/forum/0-0-1-34';
    const MAX_CARDS = 8;
    const CACHE_KEY = 'forum_cards_cache';
    const CACHE_TIME = 30 * 60 * 1000; // 30 минут

    function renderCards(cards) {
        container.innerHTML = '';
        cards.forEach(parsed => {
            const title = parsed.title || 'Без названия';
            const desc = parsed.text || '';
            const img = parsed.img || 'https://adm-karpovka.ru/wp-content/uploads/2020/10/novosti-1.jpg';

            const card = document.createElement('a');
            card.className = 'custom-card';
            card.href = parsed.href;
            card.target = '_blank';
            card.innerHTML = `
                <div class="custom-card-img">
                    <img src="${img}" alt="${title}">
                    <div class="custom-card-badge">Новое</div>
                </div>
                <div class="custom-card-title">${title}</div>
                <div class="custom-card-desc">${desc}</div>
            `;
            container.appendChild(card);
        });
    }

    // Проверяем кэш
    const cached = localStorage.getItem(CACHE_KEY);
    if (cached) {
        const data = JSON.parse(cached);
        const now = Date.now();
        if (now - data.time < CACHE_TIME) {
            renderCards(data.cards);
            return; // используем кэшированные данные
        }
    }

    async function fetchHTML(url) {
        const res = await fetch(url);
        return await res.text();
    }

    function parseThreadsFromList(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const arr = [];
        const items = doc.querySelectorAll('.threadNametd .threadLink');
        for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) {
            const a = items[i];
            const titleEl = a.querySelector('.ipsType_pagetitle') || a;
            const title = (titleEl && titleEl.textContent || '').trim();
            let href = a.getAttribute('href') || a.href || '';
            if (href && !href.startsWith('http')) {
                href = window.location.origin + (href.startsWith('/') ? href : '/' + href);
            }
            if (href) arr.push({ href, title });
        }
        return arr;
    }

    function parseFirstPost(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText');
        let text = '';
        let img = '';
        if (post) {
            text = post.textContent.trim().substring(0, 50);
            const imgEl = post.querySelector('img');
            if (imgEl) {
                img = imgEl.getAttribute('src') || '';
                if (img && !img.startsWith('http')) {
                    const base = window.location.origin;
                    img = img.startsWith('/') ? (base + img) : (base + '/' + img);
                }
            }
        }
        return { text, img };
    }

    try {
        const listHtml = await fetchHTML(forumListUrl);
        const threads = parseThreadsFromList(listHtml);

        const promises = threads.map(t =>
            fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(e => null)
        );
        const results = (await Promise.all(promises)).filter(Boolean);

        // сохраняем в localStorage
        localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results }));

        renderCards(results);

    } catch (e) {
        console.error('Ошибка:', e);
    }
})();
</script>


Полностью шаблон парсера , куда хотите туда и устанавливайте

Код
<style>
#custom-forum-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  justify-content: flex-start;
  width: 100%;
}

.custom-card {
  flex: 0 0 calc(25% - 15px); /* 4 карточки в ряд */
  box-sizing: border-box;
  border-radius: 12px;
  overflow: hidden;
  position: relative;
  background: #1e1e1e;
  color: #fff;
  cursor: pointer;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  text-decoration: none;
}

.custom-card:hover {
  transform: translateY(-8px) scale(1.02);
  box-shadow: 0 15px 25px rgba(0,0,0,0.4);
}

.custom-card-img {
  width: 100%;
  height: 180px;
  overflow: hidden;
  position: relative;
}

.custom-card-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.5s ease;
}

.custom-card:hover .custom-card-img img {
  transform: scale(1.1);
}

.custom-card-title {
  font-weight: bold;
  font-size: 14px;
  padding: 8px 10px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.custom-card-desc {
  font-size: 12px;
  color: #ccc;
  padding: 0 10px 10px 10px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-card-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  background: #853b97;
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
</style>

<div id="custom-forum-cards"></div>

<script>
(async function() {
    const container = document.querySelector('#custom-forum-cards');
    if (!container) return;

    const forumListUrl = '/forum/0-0-1-34';
    const MAX_CARDS = 8;
    const CACHE_KEY = 'forum_cards_cache';
    const CACHE_TIME = 30 * 60 * 1000; // 30 минут

    function renderCards(cards) {
        container.innerHTML = '';
        cards.forEach(parsed => {
            const title = parsed.title || 'Без названия';
            const desc = parsed.text || '';
            const img = parsed.img || 'https://via.placeholder.com/180x180?text=No+Image';

            const card = document.createElement('a');
            card.className = 'custom-card';
            card.href = parsed.href;
            card.target = '_blank';
            card.innerHTML = `
                <div class="custom-card-img">
                    <img src="${img}" alt="${title}">
                    <div class="custom-card-badge">Новое</div>
                </div>
                <div class="custom-card-title">${title}</div>
                <div class="custom-card-desc">${desc}</div>
            `;
            container.appendChild(card);
        });
    }

    // Проверяем кэш
    const cached = localStorage.getItem(CACHE_KEY);
    if (cached) {
        const data = JSON.parse(cached);
        const now = Date.now();
        if (now - data.time < CACHE_TIME) {
            renderCards(data.cards);
            return; // используем кэшированные данные
        }
    }

    async function fetchHTML(url) {
        const res = await fetch(url);
        return await res.text();
    }

    function parseThreadsFromList(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const arr = [];
        const items = doc.querySelectorAll('.threadNametd .threadLink');
        for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) {
            const a = items[i];
            const titleEl = a.querySelector('.ipsType_pagetitle') || a;
            const title = (titleEl && titleEl.textContent || '').trim();
            let href = a.getAttribute('href') || a.href || '';
            if (href && !href.startsWith('http')) {
                href = window.location.origin + (href.startsWith('/') ? href : '/' + href);
            }
            if (href) arr.push({ href, title });
        }
        return arr;
    }

    function parseFirstPost(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText');
        let text = '';
        let img = '';
        if (post) {
            text = post.textContent.trim().substring(0, 50);
            const imgEl = post.querySelector('img');
            if (imgEl) {
                img = imgEl.getAttribute('src') || '';
                if (img && !img.startsWith('http')) {
                    const base = window.location.origin;
                    img = img.startsWith('/') ? (base + img) : (base + '/' + img);
                }
            }
        }
        return { text, img };
    }

    try {
        const listHtml = await fetchHTML(forumListUrl);
        const threads = parseThreadsFromList(listHtml);

        const promises = threads.map(t =>
            fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(e => null)
        );
        const results = (await Promise.all(promises)).filter(Boolean);

        // сохраняем в localStorage
        localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results }));

        renderCards(results);

    } catch (e) {
        console.error('Ошибка:', e);
    }
})();
</script>


У меня он выглядит так .

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Среда, 22.10.2025, 18:48 | Сообщение # 2 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Можно вставить любую стандартную (дефолтную) картинку на случай, если скрипт не сможет найти изображение в теме.

Код
        cards.forEach(parsed => {
            const title = parsed.title || 'Без названия';
            const desc = parsed.text || '';
            const img = parsed.img || 'https://via.placeholder.com/180x180?text=No+Image';


Можно такую вставить . 2

Код
function renderCards(cards) {
container.innerHTML = '';
cards.forEach(parsed => {
const title = parsed.title || 'Без названия';
const desc = parsed.text || '';
const img = parsed.img || 'https://adm-karpovka.ru/wp-content/uploads/2020/10/novosti-1.jpg';

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
Дата: Четверг, 23.10.2025, 17:43 | Сообщение # 3 | | Написал: Начинающий
Автор темы
Мурчанн не в сети
        Сообщений:101
         Регистрация:20.10.2016

Немного обновил скрипт: теперь при клике на карточку контент открывается прямо в этой же вкладке, без перезагрузки и перехода на новую страницу.

CSS-стили остаются без изменений , внешний вид карточек сохраняется полностью.

Теоретически можно добавить в карточку дополнительную информацию, например, имя автора темы или больше деталей из поста, но это может немного замедлить загрузку карточек.

Код
<script>
(async function() {
  const container = document.querySelector('#custom-forum-cards');
  if (!container) return;

  const forumListUrl = '/forum/0-0-1-34'; // URL раздела форума, откуда брать темы
  const MAX_CARDS = 8;
  const CACHE_KEY = 'forum_cards_cache';
  const CACHE_TIME = 30 * 60 * 1000; // 30 минут

  // Рендер карточек
  function renderCards(cards) {
    container.innerHTML = '';
    cards.forEach(parsed => {
      const title = parsed.title || 'Без названия';
      const desc = parsed.text || '';
      const img = parsed.img || 'https://jordan.moy.su/forumimages/Newsletter-3.png';

      // создаем обычную ссылку
      const card = document.createElement('a');
      card.className = 'custom-card';
      card.href = parsed.href;
      card.target = '_self'; // открывать в этой же вкладке
      card.innerHTML = `
        <div class="custom-card-img">
          <img src="${img}" alt="${title}">
          <div class="custom-card-badge">Новое</div>
        </div>
        <div class="custom-card-title">${title}</div>
        <div class="custom-card-desc">${desc}</div>
      `;
      container.appendChild(card);
    });
  }

  // Получение HTML страницы
  async function fetchHTML(url) {
    const res = await fetch(url);
    return await res.text();
  }

  // Парсим список тем из форума
  function parseThreadsFromList(html) {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const arr = [];
    const items = doc.querySelectorAll('.threadNametd .threadLink');
    for (let i = 0; i < items.length && arr.length < MAX_CARDS; i++) {
      const a = items[i];
      const titleEl = a.querySelector('.ipsType_pagetitle') || a;
      const title = (titleEl && titleEl.textContent || '').trim();
      let href = a.getAttribute('href') || a.href || '';
      if (href && !href.startsWith('http')) {
        href = window.location.origin + (href.startsWith('/') ? href : '/' + href);
      }
      if (href) arr.push({ href, title });
    }
    return arr;
  }

  // Парсим первое сообщение темы
  function parseFirstPost(html) {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const post = doc.querySelector('.post_content, .post_body, .post, .ipsType_richText');
    let text = '';
    let img = '';
    if (post) {
      text = post.textContent.trim().substring(0, 80);
      const imgEl = post.querySelector('img');
      if (imgEl) {
        img = imgEl.getAttribute('src') || '';
        if (img && !img.startsWith('http')) {
          const base = window.location.origin;
          img = img.startsWith('/') ? (base + img) : (base + '/' + img);
        }
      }
    }
    return { text, img };
  }

  // Проверка кэша
  const cached = localStorage.getItem(CACHE_KEY);
  if (cached) {
    const data = JSON.parse(cached);
    const now = Date.now();
    if (now - data.time < CACHE_TIME) {
      renderCards(data.cards);
      return;
    }
  }

  // Основная логика
  try {
    const listHtml = await fetchHTML(forumListUrl);
    const threads = parseThreadsFromList(listHtml);
    const promises = threads.map(t =>
      fetchHTML(t.href).then(html => ({ ...t, ...parseFirstPost(html) })).catch(() => null)
    );
    const results = (await Promise.all(promises)).filter(Boolean);

    // сохраняем кэш
    localStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), cards: results }));

    renderCards(results);
  } catch (e) {
    console.error('Ошибка:', e);
  }
})();
</script>

Мурчанн

Признаюсь, не знаю почему, но глядя на звезды мне всегда хочется мечтать.
  • Страница 1 из 1
  • 1
Поиск: