qrtzcode
HomeDocsAPILeaderboardChangelog
Contribute
Docs

qrtzcode

Kumpulan gist & snippet pribadi — anime, AI tools, scraper, dan randomness.

Navigate

HomeDocsAPI Reference

Links

© 2026 qrtz. All snippets welcome.

ESC

Navigate

Links

Manga

manga18

Fitur:

· home, list, completed, manhwaRaw, genre, search, author, artist, detail, chapter · Pagination + filter (latest, alphabet, rating, trending) · Chapter list lengkap (semua chapter per slug) · Rating fix di semua halaman · 20+ User-Agent, retry, proxy support

Note

jan lupa npm install axios cheerio

Creator

qrtz

Language

javascript

Views

5

Copies

9

Base

https://manga18.me

Updated

24 Jun 2026

#manga#scraper

Code

59
manga18.js
const axios = require('axios');
const cheerio = require('cheerio');
const https = require('https');
const { randomInt } = require('crypto');

const USER_AGENTS = [
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
  "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0",
  "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1",
  "Mozilla/5.0 (iPad; CPU OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1",
  "Mozilla/5.0 (Android 14; Mobile; rv:133.0) Gecko/133.0 Firefox/133.0",
  "Mozilla/5.0 (Android 14; Mobile; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Vivaldi/6.9.0.0",
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Vivaldi/6.8.0.0",
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Vivaldi/6.9.0.0",
  "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
  "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0",
];

const BASE = "https://manga18.me";
const MAX_RETRIES = 5;
const RETRY_DELAY = 1000;

class Manga18 {
  constructor(options = {}) {
    this.proxy = options.proxy || process.env.HTTP_PROXY || process.env.https_proxy || null;
    this.timeout = options.timeout || 30000;
    this.agent = new https.Agent({ rejectUnauthorized: false, keepAlive: true });
  }

  _ua() {
    return USER_AGENTS[randomInt(0, USER_AGENTS.length)];
  }

  _headers(referer = BASE + "/") {
    const ua = this._ua();
    return {
      "User-Agent": ua,
      "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
      "Accept-Language": "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7",
      "Accept-Encoding": "gzip, deflate, br",
      "Connection": "keep-alive",
      "Upgrade-Insecure-Requests": "1",
      "Sec-Fetch-Dest": "document",
      "Sec-Fetch-Mode": "navigate",
      "Sec-Fetch-Site": "none",
      "Sec-Fetch-User": "?1",
      "Cache-Control": "max-age=0",
      "Sec-Ch-Ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
      "Sec-Ch-Ua-Mobile": "?0",
      "Sec-Ch-Ua-Platform": '"Windows"',
      "Referer": referer,
      "Origin": BASE,
    };
  }

  _getProxyConfig() {
    if (!this.proxy) return {};
    const url = new URL(this.proxy);
    return {
      proxy: {
        host: url.hostname,
        port: parseInt(url.port, 10),
        auth: url.username && url.password ? { username: url.username, password: url.password } : undefined,
        protocol: url.protocol.replace(":", ""),
      },
    };
  }

  async _fetch(url, referer = BASE + "/", retries = MAX_RETRIES) {
    for (let i = 0; i < retries; i++) {
      try {
        const config = {
          headers: this._headers(referer),
          timeout: this.timeout,
          withCredentials: true,
          httpsAgent: this.agent,
          ...this._getProxyConfig(),
        };
        const res = await axios.get(url, config);
        if (res.status === 403 || res.status === 503) throw new Error(`Blocked (${res.status})`);
        return res.data;
      } catch (e) {
        if (i === retries - 1) throw e;
        const delay = RETRY_DELAY * Math.pow(2, i) + randomInt(0, 500);
        await new Promise(r => setTimeout(r, delay));
      }
    }
  }

  _cleanUrl(url) {
    if (!url) return null;
    if (url.startsWith("//")) return "https:" + url;
    if (url.startsWith("/")) return BASE + url;
    if (url.startsWith("http")) return url;
    return null;
  }

  _extractMangaItems($, container = ".page-item-detail") {
    const items = [];
    $(container).each((i, el) => {
      const $el = $(el);
      const title = $el.find(".item-title a, .title a, h3 a, .manga-title a").first().text().trim();
      const link = $el.find(".item-title a, .title a, h3 a, .manga-title a").first().attr("href");
      const thumbnail = $el.find(".item-thumb img, .thumb img, .post-thumbnail img").first().attr("src") || "";
      
      let rating = null;
      const ratingSelectors = [
        ".item-rate .num",
        ".rating .num",
        ".score .num",
        ".rate .num",
        ".my-rating .num",
        ".item-rate",
        ".rating",
        ".score",
        ".rate",
        ".my-rating",
        ".average",
        ".avg"
      ];
      for (const sel of ratingSelectors) {
        const elRating = $el.find(sel).first();
        if (elRating.length) {
          const text = elRating.text().trim();
          const numMatch = text.match(/([\d.]+)/);
          if (numMatch) {
            rating = parseFloat(numMatch[1]);
            break;
          }
        }
      }
      if (rating === null) {
        const parentText = $el.text().trim();
        const match = parentText.match(/Average\s*([\d.]+)/i) || parentText.match(/([\d.]+)\s*\/\s*5/);
        if (match) rating = parseFloat(match[1]);
      }
      
      const chapters = $el.find(".item-chapters a, .chapter a, .meta-chapter a").last().text().trim() || "";
      const slug = link ? link.replace("/manga/", "").replace("/", "") : "";
      if (title) {
        items.push({
          title,
          slug,
          link: this._cleanUrl(link),
          thumbnail: this._cleanUrl(thumbnail),
          rating,
          latestChapter: chapters,
        });
      }
    });
    return items;
  }

  async _extractDetail(slug) {
    const url = `${BASE}/manga/${slug}`;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);

    const metaDesc = $('meta[name="description"]').attr("content") || "";
    const ogDesc = $('meta[property="og:description"]').attr("content") || "";
    const summary = (metaDesc || ogDesc || "").replace(/\s+/g, " ").trim();

    const title = ($('meta[property="og:title"]').attr("content") || $("h1").first().text().trim() || "")
      .replace(/\s*\|\s*Manga18\.ME$/, "").trim();

    const thumbnail = $('meta[property="og:image"]').attr("content") || "";

    const author = $('.summary-content a[href*="/author/"]').first().text().trim() || "";
    const artist = $('.summary-content a[href*="/artist/"]').first().text().trim() || "";

    let rating = null;
    $(".summary-content").each((i, el) => {
      const text = $(el).text().trim();
      if (text.includes("Average")) {
        const match = text.match(/Average\s*([\d.]+)/i);
        if (match) rating = parseFloat(match[1]);
      }
    });
    if (!rating) {
      const ratingText = $(".item-rate .num, .rating .num, .score .num").first().text().trim();
      if (ratingText) rating = parseFloat(ratingText);
    }

    const genres = [];
    $('.summary-content a[href*="/genre/"]').each((i, el) => {
      const text = $(el).text().trim();
      if (text && !genres.includes(text) && !text.includes("orderby")) genres.push(text);
    });

    let releaseDate = null, updateDate = null;
    $('script[type="application/ld+json"]').each((i, el) => {
      try {
        const data = JSON.parse($(el).html());
        if (data.datePublished) releaseDate = data.datePublished;
        if (data.dateModified) updateDate = data.dateModified;
      } catch(e) {}
    });

    const chapters = [];
    const seen = new Set();
    const targetSlug = `/${slug}/chapter-`;
    $('a[href*="/chapter-"]').each((i, el) => {
      const href = $(el).attr("href");
      const text = $(el).text().trim();
      if (href && href.includes(targetSlug) && !text.includes("Read First") && !text.includes("Read Last")) {
        const num = href.match(/chapter-(\d+)/i)?.[1] || "";
        if (num && !seen.has(num)) {
          seen.add(num);
          chapters.push({
            number: num,
            title: text || `Chapter ${num}`,
            link: this._cleanUrl(href),
          });
        }
      }
    });
    chapters.sort((a, b) => parseInt(a.number) - parseInt(b.number));

    return {
      title,
      thumbnail: this._cleanUrl(thumbnail),
      summary,
      author,
      artist,
      rating,
      genres,
      releaseDate,
      updateDate,
      totalChapters: chapters.length,
      chapters,
    };
  }

  _extractChapterImages($) {
    const images = [];
    $('img[src*=".jpg"], img[src*=".png"], img[src*=".webp"]').each((i, el) => {
      const src = $(el).attr("src");
      if (src && !src.includes("logo") && !src.includes("icon") && !src.includes("banner") && !src.includes("avatar") && !src.includes("thumbnail")) {
        images.push(this._cleanUrl(src));
      }
    });
    const prevChapter = $('a:contains("Prev"), a:contains("Previous")').first().attr("href") || null;
    const nextChapter = $('a:contains("NEXT"), a:contains("Next")').first().attr("href") || null;
    return {
      images: images.filter(Boolean),
      prevChapter: this._cleanUrl(prevChapter),
      nextChapter: this._cleanUrl(nextChapter),
    };
  }

  async _genericList(path, page = 1, orderby = "") {
    let url = BASE + path;
    if (page > 1) url += "/" + page;
    if (orderby) url += (url.includes("?") ? "&" : "?") + "orderby=" + orderby;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    const items = this._extractMangaItems($);
    const total = items.length;
    const hasNext = $(`.pagination a[href*="${path}/${page + 1}"]`).length > 0;
    return {
      creator: "rynaqrtz",
      page: path.replace("/", ""),
      currentPage: page,
      total,
      hasNext,
      orderby: orderby || "default",
      items,
    };
  }

  async home() {
    const html = await this._fetch(BASE + "/");
    const $ = cheerio.load(html);
    const popularItems = this._extractMangaItems($, ".page-item-detail");
    let latestItems = this._extractMangaItems($, ".page-item-detail:not(.popular)");
    if (latestItems.length === 0) latestItems = popularItems;
    return {
      creator: "rynaqrtz",
      page: "home",
      popular: popularItems.slice(0, 24),
      latest: latestItems.slice(0, 24),
    };
  }

  async list(page = 1, orderby = "") {
    return this._genericList("/manga", page, orderby);
  }

  async completed(page = 1, orderby = "") {
    return this._genericList("/completed", page, orderby);
  }

  async manhwaRaw(page = 1, orderby = "") {
    return this._genericList("/manhwa-raw", page, orderby);
  }

  async genre(slug, page = 1, orderby = "") {
    let url = BASE + "/genre/" + slug;
    if (page > 1) url += "/" + page;
    if (orderby) url += (url.includes("?") ? "&" : "?") + "orderby=" + orderby;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    const items = this._extractMangaItems($);
    const total = items.length;
    const hasNext = $(`.pagination a[href*="/genre/${slug}/${page + 1}"]`).length > 0;
    return {
      creator: "rynaqrtz",
      page: "genre",
      genre: slug,
      currentPage: page,
      total,
      hasNext,
      orderby: orderby || "default",
      items,
    };
  }

  async search(query, limit = 20) {
    const url = BASE + "/search?q=" + encodeURIComponent(query);
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    let items = this._extractMangaItems($);
    if (limit > 0) items = items.slice(0, limit);
    return {
      creator: "rynaqrtz",
      page: "search",
      query,
      total: items.length,
      items,
    };
  }

  async author(slug) {
    const url = BASE + "/author/" + slug;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    const name = $("h1").first().text().trim().replace("Author: ", "").trim();
    const items = this._extractMangaItems($);
    return {
      creator: "rynaqrtz",
      page: "author",
      name,
      slug,
      total: items.length,
      items,
    };
  }

  async artist(slug) {
    const url = BASE + "/artist/" + slug;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    const name = $("h1").first().text().trim().replace("Artist: ", "").trim();
    const items = this._extractMangaItems($);
    return {
      creator: "rynaqrtz",
      page: "artist",
      name,
      slug,
      total: items.length,
      items,
    };
  }

  async detail(slug) {
    const data = await this._extractDetail(slug);
    return {
      creator: "rynaqrtz",
      page: "detail",
      slug,
      ...data,
    };
  }

  async chapter(slug, number) {
    const url = BASE + "/manga/" + slug + "/chapter-" + number;
    const html = await this._fetch(url);
    const $ = cheerio.load(html);
    const title = $('meta[property="og:title"]').attr("content") || $("title").text().trim() || "";
    const images = this._extractChapterImages($);
    return {
      creator: "rynaqrtz",
      page: "chapter",
      mangaSlug: slug,
      chapterNumber: number,
      title: title.replace(/\s*\|\s*Manga18\.ME$/, "").trim(),
      ...images,
    };
  }
}

// ---- CLI ----
function runCLI() {
  const args = process.argv.slice(2);
  const cmd = args[0];
  const params = args.slice(1);

  let proxy = null;
  const proxyIdx = params.indexOf('--proxy');
  if (proxyIdx !== -1) {
    proxy = params[proxyIdx + 1];
    params.splice(proxyIdx, 2);
  }

  const scraper = new Manga18({ proxy });

  const commands = {
    home: () => scraper.home(),
    list: () => scraper.list(parseInt(params[0]) || 1, params[1] || ''),
    completed: () => scraper.completed(parseInt(params[0]) || 1, params[1] || ''),
    manhwaRaw: () => scraper.manhwaRaw(parseInt(params[0]) || 1, params[1] || ''),
    genre: () => {
      if (!params[0]) throw new Error('Usage: genre <slug> [page] [orderby]');
      return scraper.genre(params[0], parseInt(params[1]) || 1, params[2] || '');
    },
    search: () => {
      const q = params.join(' ');
      if (!q) throw new Error('Usage: search <query>');
      return scraper.search(q);
    },
    author: () => {
      if (!params[0]) throw new Error('Usage: author <slug>');
      return scraper.author(params[0]);
    },
    artist: () => {
      if (!params[0]) throw new Error('Usage: artist <slug>');
      return scraper.artist(params[0]);
    },
    detail: () => {
      if (!params[0]) throw new Error('Usage: detail <slug>');
      return scraper.detail(params[0]);
    },
    chapter: () => {
      if (!params[0] || !params[1]) throw new Error('Usage: chapter <slug> <number>');
      return scraper.chapter(params[0], params[1]);
    }
  };

  if (!cmd || !commands[cmd]) {
    console.error(`Usage: node manga18.js [${Object.keys(commands).join('|')}] [...args]`);
    console.error('  node manga18.js home');
    console.error('  node manga18.js list [page] [orderby]');
    console.error('  node manga18.js completed [page] [orderby]');
    console.error('  node manga18.js manhwaRaw [page] [orderby]');
    console.error('  node manga18.js genre <slug> [page] [orderby]');
    console.error('  node manga18.js search <query>');
    console.error('  node manga18.js author <slug>');
    console.error('  node manga18.js artist <slug>');
    console.error('  node manga18.js detail <slug>');
    console.error('  node manga18.js chapter <slug> <number>');
    console.error('  node manga18.js --proxy http://user:pass@proxy:port');
    process.exit(1);
  }

  commands[cmd]()
    .then(result => console.log(JSON.stringify(result, null, 2)))
    .catch(err => {
      console.error('[error]', err.message);
      process.exit(1);
    });
}

if (require.main === module) {
  runCLI();
}

module.exports = Manga18;

Rating

—(0)

Gimana snippet ini menurutmu?

</> Embed

Embed ke website / blog

<iframe src="https://qrtzcode.vercel.app/api/embed/manga/manga18" style="width:100%;height:400px;border:none;border-radius:12px;"></iframe>

Related Snippets

mangadistrict

◈ 𝘿𝘼𝙁𝙏𝘼𝙍 𝘾𝙊𝙈𝙈𝘼𝙉𝘿 1. home [page] → Menampilkan manga/anime terbaru dari homepage → Contoh: node mangadistrict.js home → Contoh: node mangadistrict.js home 2 2. search <query> [page] → Mencari manga/anime berdasarkan judul → Contoh: node mangadistrict.js search "one piece" → Contoh: node mangadistrict.js search "naruto" 2 3. series [orderby] [page] → Menampilkan semua series dengan filter urutan → orderby: latest, title, trending, rating, views, new, modified → Default: latest → Contoh: node mangadistrict.js series rating → Contoh: node mangadistrict.js series title 2 4. genre <slug> [orderby] [page] → Filter manga berdasarkan genre → slug: uncensored, action, romance, comedy, dll → orderby: latest, title, trending, rating, views, new, modified → Contoh: node mangadistrict.js genre uncensored → Contoh: node mangadistrict.js genre action rating 2 5. tag <slug> [orderby] [page] → Filter manga berdasarkan tag → slug: tsundere, school-life, yuri, dll → orderby: latest, title, trending, rating, views, new, modified → Contoh: node mangadistrict.js tag tsundere → Contoh: node mangadistrict.js tag "school-life" title 2 6. release <year> [orderby] [page] → Filter manga berdasarkan tahun rilis → year: 2026, 2025, 2024, dll → orderby: latest, title, trending, rating, views, new, modified → Contoh: node mangadistrict.js release 2026 → Contoh: node mangadistrict.js release 2025 rating 2 7. detail <slug|url> → Menampilkan informasi lengkap series + daftar chapter → slug: one-piece, my-brothers-slipped-inside-me-in-the-bathtub-overflow-uncensored → Contoh: node mangadistrict.js detail "one-piece" → Contoh: node mangadistrict.js detail "https://mangadistrict.com/series/one-piece/" 8. chapter <url> → Mengambil gambar chapter manga (auto-detect video) → Jika URL adalah anime, otomatis ambil video stream → Contoh: node mangadistrict.js chapter "https://mangadistrict.com/series/.../chapter-1/" → Contoh: node mangadistrict.js chapter "https://mangadistrict.com/series/animation-.../s1-e1/" 9. episode-video <url> → Memaksa ekstraksi video dari anime (tanpa auto-detect) → Contoh: node mangadistrict.js episode-video "https://mangadistrict.com/series/animation-.../s1-e1/"

13
4