| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import * as cheerio from "cheerio";
- import { mkdir, writeFile } from "node:fs/promises";
- const BASE = "https://scientia.ru";
- const UA =
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
- const PAGES = [
- "/",
- "/about",
- "/prorock",
- "/magicore",
- "/prorockfeatures",
- "/digger",
- "/nur",
- "/emergency_care",
- "/joint_explorer",
- "/geodesy",
- "/prorock_updates",
- "/personal_data",
- "/education",
- "/scientiajoints",
- "/subscribe",
- "/diggerslope_updates",
- ];
- const clean = (s) => s.replace(/\s+/g, " ").trim();
- function extract(html, path) {
- const $ = cheerio.load(html);
- const meta = {
- title: clean($("title").first().text()),
- description: $('meta[name="description"]').attr("content")?.trim() || "",
- ogImage: $('meta[property="og:image"]').attr("content") || "",
- h1: clean($("h1").first().text()),
- };
- const blocks = [];
- const seenImages = new Set();
- $(".t-rec").each((_, rec) => {
- const $rec = $(rec);
- if ($rec.parents(".t-rec").length) return; // top-level records only
- const block = { type: "", headings: [], texts: [], images: [], links: [], form: null };
- $rec.find("h1,h2,h3,h4,h5,h6").each((__, h) => {
- const t = clean($(h).text());
- if (t) block.headings.push({ level: Number(h.tagName[1]), text: t });
- });
- $rec
- .find(
- ".t-descr, .t-text, .t-uptitle, .t-name, .t-heading, .t-subtitle, .t-section__title, .t-section__descr, .tn-atom",
- )
- .each((__, el) => {
- const $el = $(el);
- if ($el.parents("form").length) return;
- if ($el.find(".tn-atom").length) return; // nested duplicates in zero blocks
- const t = clean($el.text());
- if (t && !block.texts.includes(t)) block.texts.push(t);
- });
- $rec.find("li").each((__, li) => {
- const t = clean($(li).text());
- if (t && !block.texts.some((x) => x.includes(t))) block.texts.push("• " + t);
- });
- $rec.find("img").each((__, img) => {
- const src = ($(img).attr("data-original") || $(img).attr("src") || "").split("?")[0];
- if (src && /tildacdn|scientia\.ru/.test(src) && !seenImages.has(src)) {
- seenImages.add(src);
- block.images.push(src);
- }
- });
- $rec.find('[style*="background-image"]').each((__, el) => {
- const m = ($(el).attr("style") || "").match(/url\(['"]?([^'")]+)['"]?\)/);
- if (m && /tildacdn|scientia\.ru/.test(m[1]) && !seenImages.has(m[1])) {
- seenImages.add(m[1]);
- block.images.push(m[1].split("?")[0]);
- }
- });
- $rec.find("a[href]").each((__, a) => {
- const href = $(a).attr("href") || "";
- if (href && !href.startsWith("#") && !href.startsWith("javascript") && !block.links.includes(href)) {
- block.links.push(href);
- }
- });
- const form = $rec.find("form").first();
- if (form.length) {
- block.form = {
- action: form.attr("action") || "",
- fields: form
- .find("input,textarea,select")
- .map((__, f) => ({
- tag: f.tagName,
- name: $(f).attr("name") || "",
- type: $(f).attr("type") || "",
- placeholder: $(f).attr("placeholder") || "",
- }))
- .get(),
- };
- }
- if (block.headings.length || block.texts.length || block.images.length || block.form) {
- blocks.push(block);
- }
- });
- // Global navigation
- const nav = [];
- $(".t228__list_item a, .tmenu a, nav a, .t396__elem a[href^='/']").each((__, a) => {
- const text = clean($(a).text());
- const href = $(a).attr("href") || "";
- if (text && href && !nav.some((n) => n.href === href)) nav.push({ text, href });
- });
- return { path, meta, nav, blocks };
- }
- async function main() {
- await mkdir(".scratch/raw", { recursive: true });
- await mkdir(".scratch/content", { recursive: true });
- const allImages = new Map();
- for (const path of PAGES) {
- const slug = path === "/" ? "home" : path.replace(/^\//, "");
- process.stdout.write(`fetch ${path} ... `);
- try {
- const res = await fetch(BASE + path, { headers: { "User-Agent": UA } });
- if (!res.ok) {
- console.log(`HTTP ${res.status}`);
- continue;
- }
- const html = await res.text();
- await writeFile(`.scratch/raw/${slug}.html`, html);
- const data = extract(html, path);
- await writeFile(`.scratch/content/${slug}.json`, JSON.stringify(data, null, 2));
- for (const b of data.blocks) for (const img of b.images) allImages.set(img, slug);
- console.log(`${data.blocks.length} blocks, meta: "${data.meta.title}"`);
- } catch (e) {
- console.log(`ERROR ${e.message}`);
- }
- }
- await writeFile(
- ".scratch/images.json",
- JSON.stringify([...allImages.entries()].map(([url, page]) => ({ url, page })), null, 2),
- );
- console.log(`\n${allImages.size} unique images found`);
- }
- main();
|