step by step guide tell you how to build a website like apkmirror

简介: There are many [free apk download](https://idoras.com) websites such as apkmirror, today i will tell you how to build a website like apkmirror, the programming language i used is [node.js](https://nodejs.org/en/), the database i used is [mongodb](https://www.mongodb.com/), search engine used is [ela

logo.png

There are many free apk download websites such as apkmirror, today i will tell you how to build a website like apkmirror, the programming language i used is node.js, the database i used is mongodb, search engine used is elasticsearch, the web framework i use is eggjs.

Part I analysis apk file and save data

The tool we need is appt, you can install it with sudo apt-get install aapt on ubuntu, the command to analysis apk file is aapt dump badging test.apk, as we need execute the command in node.js script so i use execSync to call the command, the code block is below.

const { execSync } = require('child_process');
const fs = require("fs"); 
const md5 = require('md5');
const { sleep, getDir } = require('../util');

const analysis = (output) => {
  const res = {};
  let out = output.toString().replace(/'/g, '');

  out = out.split('\n');

  let tmp = out[0].split(' ');
  res.package = tmp[1].split('=')[1];
  res.versionCode = tmp[2].split('=')[1];
  res.versionName = tmp[3].split('=')[1];

  tmp = out[2].split(':')
  if (tmp[0] == 'sdkVersion') {
    res.sdkVersion = tmp[1];
  }

  tmp = out[3].split(':');
  if (tmp[0] == 'targetSdkVersion') {
    res.targetSdkVersion = tmp[1];
  }

  tmp = out[out.length - 2].split(':');
  if (tmp[0] == 'native-code') {
    res.variant = tmp[1].trim().replace(/ /g, '|');

    tmp = out[out.length - 6].split(':');
    res.screen = tmp[1].trim();

    tmp = out[out.length - 4].split(':');
    res.locales = tmp[1].trim();
  } else {
    res.variant = 'noarch';

    tmp = out[out.length - 5].split(':');
    res.screen = tmp[1].trim();

    tmp = out[out.length - 3].split(':');
    res.locales = tmp[1].trim();
  }
  return res;
}

const filters = ['.', '..'];

const run = async () => {
  while (true) {
    const dirs = fs.readdirSync(getDir('tmp'));
    for (const dir of dirs) {
      if (!filters.includes(dir)) {
        const name = getDir('tmp', dir);
        try {
          const output = execSync(`/usr/bin/aapt dump badging ${name}`);
          const res = analysis(output);
          const stats = fs.statSync(name);
          res.fileSize = stats.size;
          const newName = getDir('apk', `${res.package}_${res.versionCode}_idoras.com.apk`);
          const tasks = [];
          const url = `https://play.google.com/store/apps/details?id=${res.package}`;
          tasks.push({ _id: md5(url), url, host: 'play.google.com', type: 'detail', package: res.package, done: 0 });
          await global.db.collection('tasks').insertMany(tasks, { ordered: false }).catch(() => {});
          const one = await global.db.collection('apk').findOne({ _id: res.package }).catch(() => {});
          if (one) {
            const a = +one.versionCode;
            const b = +res.versionCode;
            if (b > a) {
              await global.db.collection('apk').updateOne({ _id: res.package }, { $set:{ udate: Date.now(), versionCode: res.versionCode, versionName: res.versionName, done: 1 }}).catch(() => {});
            }
          } else {
            await global.db.collection('apk').insertOne({ _id: res.package, versionCode: res.versionCode, versionName: res.versionName, cdate: Date.now(), udate: Date.now(), done: 0, down: 0 }).catch(() => {});
          }
          res._id = `${res.package}_${res.versionCode}`;
          await global.db.collection('variant').insertOne({ ...res, cdate: Date.now() }).catch(() => {});
          fs.renameSync(name, newName);
        } catch (err) {
          console.error(err);
          fs.unlinkSync(name);
        }
      }
    }
    if (dirs.length == 0) {
      console.log('no apk files to analysis');
      await sleep(60000);
    }
  }
};

module.exports = run;

The getDir and sleep is util function, you can find the source code below.

const req = require('request');
const path = require('path');
const os = require('os');
const agent = new require('socks-proxy-agent')('socks5://localhost:1080');

let root = '/data';
const options = {
  headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
  },
  timeout: 30000,
  encoding: null,
};
if (os.platform() == 'win32') {
  options.agent = agent;
  root = 'd:/data';
}
const request = req.defaults(options);

const getDir = (...paths) => {
  paths.unshift(root);
  return path.join(...paths);
};

const sleep = (time) => new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });

const fetch = (url) => new Promise((resolve) => {
    console.log(`down ${url} started`);
    request(url, (err, res, body) => {
      if (res && res.statusCode === 200) {
        console.log(`down ${url} 200`);
        resolve(body);
      } else {
        console.error(`down ${url} ${res && res.statusCode} ${err}`);
        if (res && res.statusCode) {
          resolve(res.statusCode);
        } else {
          // ESOCKETTIMEOUT 超时错误返回600
          resolve(600);
        }
      }
    });
  });

module.exports = { getDir, sleep, fetch };

The main logic is we get basic information from appt, parse the output, get apk id such as com.moonvideo.android.resso, and insert a task to get apk logo and images and brief from play.google.com, i wrote a little crawler framework, each website with one parser bind with the host, every url have a type, each type bind with a function, the play.google.com’s parser code is below.

const { URL } = require('url');
const md5 = require('md5');
const cheerio = require('cheerio');
const slugify = require('slugify');

const resolve = (from, to) => {
  const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
  if (resolvedUrl.protocol === 'resolve:') {
    // `from` is a relative URL.
    const { pathname, search, hash } = resolvedUrl;
    return pathname + search + hash;
  }
  return resolvedUrl.toString();
}

const fns = {

  detail: async (page) => {
    const host = page.host;
    try {
      const $ = cheerio.load(page.con.toString(), { decodeEntities: false });
      const brief = $('div.DWPxHb > span > div:nth-child(1)').html();
      const name = $('h1.AHFaub > span').text();
      const dev = $('div.qQKdcc > span:nth-child(1) > a').text();
      const tag = $('div.qQKdcc > span:nth-child(2) > a').attr('href').split('/')[4];
      const images = [];
      const tasks = [];
      $('button.Q4vdJd > img').each((i, ele) => {
        let url = $(ele).attr('src');
        if (!url || !url.startsWith('https')) {
          url = $(ele).attr('data-src')
        }
        tasks.push({ _id: md5(url), url, type: 'download', host: (new URL(url)).hostname, done: 0 });
        images.push({ _id: md5(url), url });
      });
      const logourl = $('div.xSyT2c > img').attr('src');
      tasks.push({ _id: md5(logourl), url: logourl, type: 'download', host: (new URL(logourl)).hostname, package: page.package, done: 0 });
      const res = await global.db.collection('tasks').insertMany(tasks, { ordered: false }).catch(() => {});
      res && console.log(`${host}-detail insert ${res.insertedCount} from ${tasks.length} tasks`);
      await global.db.collection('apk').updateOne({ _id: page.package }, { $set:{ brief, name, dev, devSlug: slugify(dev), tag, tagSlug: slugify(tag), images, done: 1 } });
      return 1;
    } catch (err) {
      console.error(`${host}-detail parse ${page.url} ${err}`);
      return 4;
    }
  },

  run: (page) => {
    const fn = fns[page.type];
    if (fn) {
      return fn(page);
    }
    console.error(`${page.url} parser not found`);
    return 0;
  }

};

module.exports = fns;

every apk we analysis with aapt will send one request to play.google.com to get it’s name, brief, developer info, tag, images, i will use these data to show on the website.

now we have the data of apk file, so where to find the apk files? You can download them from idoras or apkmirror or apkpure, also you can write node.js script to download them from website such as idoras.

Next we need index these data with elasticsearch so we can use them later, the code is below.

const { sleep } = require('../util');
const { Client } = require('@elastic/elasticsearch');

const getApks = (apks) => {
  const res = [];
  apks.forEach((apk) => {
    if(apk.name) {
      const tmp = {};
      res.push({ index: { _index: 'apk', _type: '_doc', _id: apk._id } });
      tmp.id = apk._id;
      tmp.versionCode = apk.versionCode;
      tmp.versionName = apk.versionName;
      tmp.cdate = apk.cdate;
      tmp.udate = apk.udate;
      tmp.down = apk.down;
      tmp.brief = apk.brief;
      tmp.dev = apk.dev;
      tmp.devSlug = apk.devSlug;
      tmp.name = apk.name;
      tmp.tag = apk.tag;
      res.push(tmp);
    }
  });
  return res;
};

const run = async () => {

  while (true) {
    const esclient = new Client({
      node: 'http://username:password@ip:9200',
      maxRetries: 3,
      requestTimeout: 60000
    });
    const apks = await global.db.collection('apk').find({ done: 1 }).limit(100).toArray().catch((err) => console.error(err));
    const tmp = getApks(apks);
    if (tmp && tmp.length) {
      let res = await esclient.bulk({ refresh: true, body: tmp }).catch((err) => console.error(err));
      if (res) {
        const ulist = apks.map((apk) => ({ updateOne: { filter: { _id: apk._id }, update: { $set: { done: 6 } }, upsert: false } }));
        res = await global.db.collection('apk').bulkWrite(ulist, { ordered: false, w: 1 }).catch(() => {});
        res && console.log(`apk:indexer update ${res.modifiedCount} from ${res.matchedCount}`);
      }
    } else {
      console.log('apk:indexer no apk to index');
      await sleep(60000);
    }
    await esclient.close();
  }
};

module.exports = run;

Part II build website with eggjs

The template engine i use is nunjucks, also i used some eggjs plugins such egg-elasticsearch2

The logic is easy, at home page we query data from elasticsearch, and render data into nunjucks template, basiclly it’s a list page, also i will cache the hot download apks into ctx.app.hots, refresh it every 30 minutes, and show the hot download apks on the right aside, also the html template support responsive design, it will show perfect on mobile system.

'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {

  async home() {
    const { ctx } = this;
    const params = ctx.params;
    const page = parseInt(params.page, 10) || 1;
    if (page < 1) {
      ctx.redirect('/list/1/', 301);
    }
    const start = (page - 1) * ctx.helper.size;
    const query = {
      body: [
        { index: 'apk', type: '_doc' },
        { query: { bool: { must: [{ match_all: {} }, { exists: { field: 'id' } }] } }, highlight: { fields: { name: {} } }, from: start, size: ctx.helper.size, sort: [{ udate: { order: 'desc' } }, '_score' ] },
      ],
    };
    const info = {};
    const res = await ctx.app.elasticsearch.msearch(query);
    ctx.helper.getList(res.responses[0], info);

    let tpage = Math.floor(info.all / ctx.helper.size) + (info.all % ctx.helper.size ? 1 : 0);
    tpage < 1 && (tpage = 1);
    if (page > tpage) {
      if (tpage > 500) {
        ctx.redirect('/list/500/', 301);
      } else {
        ctx.redirect(`/list/${tpage}/`, 301);
      }
    } else if (page > 500) {
      ctx.redirect('/list/500/', 301);
    }
    tpage = tpage > 500 ? 500 : tpage;
    info.pages = ctx.helper.getPages(page, tpage);

    info.url = ctx.request.url;
    if (info.url === '/') {
      info.title = 'Free Apk download online - idoras.com';
      info.keywords = 'app download,apk download, free apk download, idoras, apk downloader, android apk download';
      info.description = 'Free apk download for Android with idoras APK downloader. NoAds, Faster apk downloads and apk file update speed. Best of all, it\'s free';
      info.home = true;
    } else {
      info.title = `Free Apk download - page${page} - idoras.com`;
      info.keywords = `app download,apk download, free apk download, idoras, apk downloader, android apk download,apk list page${page}`;
      info.description = info.list.map(item => item.name).join(',').substring(0, 150);
    }

    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('home.html', { info });
  }

  async version() {
    const { ctx } = this;
    const params = ctx.params;
    const id = params.id;
    const apk = await ctx.model.Apk.findById(id);
    const list = await ctx.model.Variant.find({ package: id });
    list.sort((a, b) => b.versionCode - a.versionCode);
    const info = { apk, list };
    info.variant = list[0] || {};
    info.title = `${apk.name} Free Apk download - idoras.com`;
    info.keywords = `${apk.name} download, app download,apk download, free apk download, idoras, apk downloader, android apk download`;
    info.description = `download ${apk.name} free apks online, it's faster and free online, many version to download`;
    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('version.html', { info });
  }

  async download() {
    const { ctx } = this;
    const params = ctx.params;
    const id = params.id;
    const version = params.version;
    const apk = await ctx.model.Apk.findById(id);
    const variant = await ctx.model.Variant.findById(`${id}_${version}`);
    const list = await ctx.model.Variant.find({ package: id });
    list.sort((a, b) => b.cdate - a.cdate);
    apk.down = apk.down + 1;
    apk.save();
    ctx.app.elasticsearch.update({ index: 'apk', type: '_doc', id, body: { script: 'ctx._source.down += 1', upsert: { down: 1 } } }, () => { });
    const info = { apk, variant, list };
    info.title = `${apk.name} ${apk.versionName} Free Apk download online - idoras.com`;
    info.keywords = `${apk.name} ${apk.versionName} download online, app download,apk download, free apk download, idoras, apk downloader, android apk download`;
    info.description = `download ${apk.name} ${apk.versionName} free apks online, it's faster and free online, many version to download`;
    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('download.html', { info });
  }

  async dev() {
    const { ctx } = this;
    const params = ctx.params;
    const devSlug = params.devSlug;
    const page = parseInt(params.page, 10) || 1;
    if (page < 1) {
      ctx.redirect(`/dev/${devSlug}/1/`, 301);
    }
    const start = (page - 1) * ctx.helper.size;
    const info = { devSlug };
    const query = {
      body: [
        { index: 'apk', type: '_doc' },
        { query: { term: { devSlug } }, from: start, size: ctx.helper.size, sort: [{ udate: { order: 'desc' } }, '_score' ] },
      ],
    };
    const res = await ctx.app.elasticsearch.msearch(query);
    ctx.helper.getList(res.responses[0], info);

    let tpage = Math.floor(info.all / ctx.helper.size) + (info.all % ctx.helper.size ? 1 : 0);
    tpage < 1 && (tpage = 1);
    if (page > tpage) {
      if (tpage > 500) {
        ctx.redirect(`/dev/${devSlug}/500/`, 301);
      } else {
        ctx.redirect(`/dev/${devSlug}/${tpage}/`, 301);
      }
    } else if (page > 500) {
      ctx.redirect(`/dev/${devSlug}/500/`, 301);
    }
    tpage = tpage > 500 ? 500 : tpage;
    info.pages = ctx.helper.getPages(page, tpage);

    info.title = `apk developered by ${devSlug} download - idoras.com`;
    info.keywords = `${devSlug} apks download online, app download,apk download, free apk download, idoras, apk downloader, android apk download`;
    info.description = `download ${devSlug} free apks online, it's faster and free online, many version to download`;
    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('dev.html', { info });
  }

  async tag() {
    const { ctx } = this;
    const params = ctx.params;
    const tag = params.tag;
    const page = parseInt(params.page, 10) || 1;
    if (page < 1) {
      ctx.redirect(`/tag/${tag}/1/`, 301);
    }
    const start = (page - 1) * ctx.helper.size;
    const info = { tag };
    const query = {
      body: [
        { index: 'apk', type: '_doc' },
        { query: { term: { tag } }, from: start, size: ctx.helper.size, sort: [{ udate: { order: 'desc' } }, '_score' ] },
      ],
    };
    const res = await ctx.app.elasticsearch.msearch(query);
    ctx.helper.getList(res.responses[0], info);

    let tpage = Math.floor(info.all / ctx.helper.size) + (info.all % ctx.helper.size ? 1 : 0);
    tpage < 1 && (tpage = 1);
    if (page > tpage) {
      if (tpage > 500) {
        ctx.redirect(`/tag/${tag}/500/`, 301);
      } else {
        ctx.redirect(`/tag/${tag}/${tpage}/`, 301);
      }
    } else if (page > 500) {
      ctx.redirect(`/tag/${tag}/500/`, 301);
    }
    tpage = tpage > 500 ? 500 : tpage;
    info.pages = ctx.helper.getPages(page, tpage);

    info.title = `apk tagged with ${tag} download - idoras.com`;
    info.keywords = `${tag} apks download online, app download,apk download, free apk download, idoras, apk downloader, android apk download`;
    info.description = `download ${tag} free apks online, it's faster and free online, many version to download`;
    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('tag.html', { info });
  }

  async search() {
    const { ctx } = this;
    const params = ctx.params;
    const key = params.key;
    const page = parseInt(params.page, 10) || 1;
    if (page < 1) {
      ctx.redirect(`/search/${key}/1/`, 301);
    }
    const start = (page - 1) * ctx.helper.size;
    const info = { key };
    const query = {
      body: [
        { index: 'apk', type: '_doc' },
      ],
    };
    if (key) {
      query.body.push({ query: { multi_match: { query: key, fields: [ 'name', 'id', 'brief' ] } }, from: start, size: ctx.helper.size, sort: [ '_score' ] });
    } else {
      query.body.push({ query: { bool: { must: [{ match_all: {} }, { exists: { field: 'id' } }] } }, from: start, size: ctx.helper.size, sort: [ '_score' ] });
    }
    const res = await ctx.app.elasticsearch.msearch(query);
    key && res.responses[0].hits.hits.length && ctx.helper.putKey(key);
    ctx.helper.getList(res.responses[0], info);

    let tpage = Math.floor(info.all / ctx.helper.size) + (info.all % ctx.helper.size ? 1 : 0);
    tpage < 1 && (tpage = 1);
    if (page > tpage) {
      if (tpage > 500) {
        ctx.redirect(`/search/${key}/500/`, 301);
      } else {
        ctx.redirect(`/search/${key}/${tpage}/`, 301);
      }
    } else if (page > 500) {
      ctx.redirect(`/search/${key}/500/`, 301);
    }
    tpage = tpage > 500 ? 500 : tpage;
    info.pages = ctx.helper.getPages(page, tpage);

    info.title = `search ${key} apks to download online - idoras.com`;
    info.keywords = `${key} apks download online, app download,apk download, free apk download, idoras, apk downloader, android apk download`;
    info.description = `download ${key} free apks online, it's faster and free online, many version to download`;
    if (!ctx.app.hots) {
      ctx.app.hots = info.hots = await ctx.service.search.hot();
    } else {
      info.hots = ctx.app.hots;
    }
    info.slist = ctx.helper.getKeys();
    await ctx.render('search.html', { info });
  }

  async re() {
    const { ctx } = this;
    const params = ctx.request.body;
    const key = params.key;
    if (key) {
      ctx.redirect(`/search/${encodeURI(key)}/`, 301);
    } else {
      ctx.redirect('/search/', 301);
    }
  }
}

module.exports = HomeController;
'use strict';

// eslint-disable-next-line no-unused-vars
module.exports = (options, app) => {
  return async function mobileMiddleware(ctx, next) {
    const u = ctx.get('user-agent') || '';
    const tmp = {
      trident: u.indexOf('Trident') > -1, // IE内核
      presto: u.indexOf('Presto') > -1, // opera内核
      webKit: u.indexOf('AppleWebKit') > -1, // 苹果、谷歌内核
      gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') === -1, // 火狐内核
      mobile: !!u.match(/AppleWebKit.*Mobile.*/), // 是否为移动终端
      ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
      android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, // android终端或者uc浏览器
      iPhone: u.indexOf('iPhone') > -1, // 是否为iPhone或者QQHD浏览器
      iPad: u.indexOf('iPad') > -1, // 是否iPad
      webApp: u.indexOf('Safari') === -1, // 是否web应该程序,没有头部与底部
      weixin: u.indexOf('MicroMessenger') > -1, // 是否微信 (2015-01-22新增)
      qq: u.match(/\sQQ/i) === ' qq', // 是否QQ
    };
    if (tmp.mobile || tmp.android || tmp.ios || tmp.weixin) {
      ctx.request.mobile = true;
    }
    await next();
  };
};
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{info.title}}</title>
  <meta name="keywords" content="{{info.keywords}}">
  <meta name="description" content="{{info.description}}">
  <meta property="og:site_name" content="idoras.com">
  <meta property="og:title" content="{{info.title}}">
  <meta property="og:type" content="website">
  <link rel="shortcut icon" href="/favicon.ico">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css">
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <link rel="stylesheet" href="/css/material-components-web.min.css">
  <link rel="stylesheet" href="/css/app.css?v=2021080817">
</head>

<body>

  <aside class="mdc-drawer mdc-drawer--dismissible">
    <div class="mdc-drawer__header">
      <h3 class="mdc-drawer__title">IDORAS</h3>
      <h6 class="mdc-drawer__subtitle">download free android apks</h6>
    </div>
    <div class="mdc-drawer__content">
      <nav class="mdc-list">
        <a class="mdc-list-item mdc-list-item--activated" href="/" aria-selected="true">
          <i class="material-icons mdc-list-item__graphic" aria-hidden="true">home</i>
          <span class="mdc-list-item__text">Home</span>
        </a>
      </nav>
    </div>
  </aside>
  <div class="mdc-drawer-app-content">
    <header class="mdc-top-app-bar app-bar" id="app-bar">
      <div class="mdc-top-app-bar__row container">
        <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
          <i href="" class="demo-menu material-icons mdc-top-app-bar__navigation-icon mdc-nav-icon menuLogo"
            style="text-decoration: none;cursor: pointer;">menu</i>
          <a class="mdc-top-app-bar__title" href="/" style="color: #fff;">IDORAS</a>
        </section>
        <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end" role="toolbar">
          <a href="/search/">
            <i class="material-icons icon-white">search</i>
          </a>
        </section>
      </div>
    </header>

    <main class="main-content" id="main-content">
      <div class="mdc-top-app-bar--fixed-adjust container">
        <div class="mdc-layout-grid" style="padding:0">
          <div class="mdc-layout-grid__inner">
            <div
              class="mdc-layout-grid__cell mdc-layout-grid__cell--span-8 mdc-layout-grid__cell--span-12-tablet mdc-layout-grid__cell--span-12-phone">
              {% if ctx.request.mobile %}
              <div class="details-title">
                <!-- Go to www.addthis.com/dashboard to customize your tools --> 
                <div class="addthis_inline_share_toolbox_bm88"></div>
              </div>
              {% endif %}
              <div class="listWidght">
                <form action="/re" style="overflow:visible" method="POST" onsubmit="return key.value!=''" role="search">
                  <input class="search-key" type="text" id="key" name="key" placeholder="Google Play Store or com.android.vending" size="1" style="padding: 5px;" />
                  <button type="submit" class="search mdc-button mdc-button--raised button">
                    <i class="material-icons icon-white">search</i>
                  </button>
                </form>
              </div>
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Latest Upload</h5>
                  </div>
                </div>
                {% for item in info.list %}
                <div>
                  <div class="appRow">
                    <div class="table-row">
                      <div style="width: 56px;" class="table-cell">
                        <img style="width:50px; height:50px;"
                          src="https://img.idoras.com/img/{{item.id}}.png">
                      </div>
                      <div class="table-cell">
                        <div style="padding-top: 4px;">
                          <h5 title="{{item.name}} {{item.versionName}} ({{item.versionCode}}) by {{item.dev}}"
                            class="appRowTitle wrapText marginZero block-on-mobile">
                            <a class="fontBlack" href="/apk/{{item.id}}/">{{item.name}} {{item.versionName}}</a>
                          </h5>
                          <a href="/dev/{{item.devSlug}}/" class="byDeveloper block-on-mobile wrapText">by {{item.dev}}</a>
                          <span style="padding:4px 12px 0 0;" class="colorLightBlack"><span class="udate">{{ctx.helper.fdate(item.udate)}}</span></span>
                        </div>
                      </div>
                      <div style="width: 30px;" class="table-cell">
                        <div class="iconsBox ">
                          <div class="downloadIconPositioning">
                            <a class="downloadLink" href="/apk/{{item.id}}/{{item.versionCode}}/">
                              <i class="material-icons icon-blue" aria-hidden="true">download</i>
                            </a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                {% endfor %}
                <div class="appRow center">
                  <div class="pagination mobile">
                    <div class="wp-pagenavi" role="navigation">
                      {% for page in info.pages %}
                      <a href="/list/{{page.page}}/"
                        class="{%if loop.first%}first{%endif%} {%if loop.last%}last{%endif%} {%if page.current%}current{%endif%}">
                        <span class="mdc-fab__icon">
                          {{page.text}}
                        </span>
                      </a>
                      {% endfor %}
                    </div>
                  </div>
                </div>
              </div>
              <div class="pages">
                
              </div>
            </div>
            <div
              class="mdc-layout-grid__cell mdc-layout-grid__cell--span-4 mdc-layout-grid__cell--span-12-tablet mdc-layout-grid__cell--span-12-phone">
              {% if info.slist.length > 0 %}
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Hot Searches</h5>
                  </div>
                </div>
                <div>
                  <div class="appRow">
                    {% for item in info.slist %}
                    <a href="/search/{{item.ekey}}/" class="mdc-button">
                      <span class="mdc-button__label">{{item.key}}</span>
                    </a>
                    {% endfor %}
                  </div>
                </div>
              </div>
              {% endif %}
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Social Medias</h5>
                  </div>
                </div>
                <div>
                  <div class="appRow">
                    <div class="table-row">
                      <div class="table-cell center">
                        <a href="https://www.facebook.com/profile.php?id=100071468057179" target="_blank">
                          <img style="vertical-align: baseline; height: 24px; width: 24px;" src="/img/facebook.png" class="clickable">
                        </a>
                      </div>
                      <div class="table-cell center">
                        <a href="https://twitter.com/Shaoyang2000" target="_blank">
                          <img style="vertical-align: baseline; height: 24px; width: 24px;" src="/img/twitter.png" class="clickable">
                        </a>
                      </div>
                      <div class="table-cell center">
                        <a href="https://t.me/idoras_official" target="_blank">
                          <img style="vertical-align: baseline; height: 24px; width: 24px;" src="/img/telegram.png" class="clickable">
                        </a>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Popular In Last 24 Hours</h5>
                  </div>
                </div>
                {% for item in info.hots[0].list %}
                <div>
                  <div class="appRow">
                    <div class="table-row">
                      <div style="width: 56px;" class="table-cell">
                        <img style="width:50px; height:50px;"
                          src="https://img.idoras.com/img/{{item.id}}.png">
                      </div>
                      <div class="table-cell">
                        <h5 title="{{item.name}} {{item.versionName}}" class="appRowTitle wrapText marginZero block-on-mobile">
                          <a class="fontBlack" href="/apk/{{item.id}}/">{{item.name}} {{item.versionName}}</a>
                        </h5>
                      </div>
                      <div style="width: 50px; text-align: right;" class="table-cell">
                        <span style="padding-right:12px;" class="colorLightBlack">{{item.down}}</span>
                      </div>
                      <div style="width: 22px;" class="table-cell">
                        <div class="iconsBox one-icon">
                          <div class="downloadIconPositioning">
                            <a class="downloadLink"
                              href="/apk/{{item.id}}/">
                              <i class="material-icons icon-blue" aria-hidden="true">download</i>
                            </a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                {% endfor %}
              </div>
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Popular In Last 7 Days</h5>
                  </div>
                </div>
                {% for item in info.hots[1].list %}
                <div>
                  <div class="appRow">
                    <div class="table-row">
                      <div style="width: 56px;" class="table-cell">
                        <img style="width:50px; height:50px;"
                          src="https://img.idoras.com/img/{{item.id}}.png">
                      </div>
                      <div class="table-cell">
                        <h5 title="{{item.name}} {{item.versionName}}" class="appRowTitle wrapText marginZero block-on-mobile">
                          <a class="fontBlack" href="/apk/{{item.id}}/">{{item.name}} {{item.versionName}}</a>
                        </h5>
                      </div>
                      <div style="width: 50px; text-align: right;" class="table-cell">
                        <span style="padding-right:12px;" class="colorLightBlack">{{item.down}}</span>
                      </div>
                      <div style="width: 22px;" class="table-cell">
                        <div class="iconsBox one-icon">
                          <div class="downloadIconPositioning">
                            <a class="downloadLink"
                              href="/apk/{{item.id}}/">
                              <i class="material-icons icon-blue" aria-hidden="true">download</i>
                            </a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                {% endfor %}
              </div>
              <div class="listWidget">
                <div class="box">
                  <div class="title bread-crumbs">
                    <h5 class="widgetHeader">Popular In Last 30 Days</h5>
                  </div>
                </div>
                {% for item in info.hots[2].list %}
                <div>
                  <div class="appRow">
                    <div class="table-row">
                      <div style="width: 56px;" class="table-cell">
                        <img style="width:50px; height:50px;"
                          src="https://img.idoras.com/img/{{item.id}}.png">
                      </div>
                      <div class="table-cell">
                        <h5 title="{{item.name}} {{item.versionName}}" class="appRowTitle wrapText marginZero block-on-mobile">
                          <a class="fontBlack" href="/apk/{{item.id}}/">{{item.name}} {{item.versionName}}</a>
                        </h5>
                      </div>
                      <div style="width: 50px; text-align: right;" class="table-cell">
                        <span style="padding-right:12px;" class="colorLightBlack">{{item.down}}</span>
                      </div>
                      <div style="width: 22px;" class="table-cell">
                        <div class="iconsBox one-icon">
                          <div class="downloadIconPositioning">
                            <a class="downloadLink"
                              href="/apk/{{item.id}}/">
                              <i class="material-icons icon-blue" aria-hidden="true">download</i>
                            </a>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                {% endfor %}
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="footer">
        <div class="b">
          <p><a href="/about/">About Us</a> | <a href="/contact-us/">Contact Us</a></p>
          <p><a href="/privacy-policy/" rel="nofollow">Privacy Policy</a></p>
          <p><a href="/report-content/">Report abuse</a></p>
          <p><a href="/dmca/" rel="nofollow">DMCA Disclaimer</a></p>
          <p>idoras.com © 2016-2021</p>
        </div>
      </div>
    </main>
  </div>
  <script src="/js/material-components-web.min.js"></script>
  <script src="/js/app.js?v=20210805"></script>
</script>
</body>

</html>

That’s the main code i wrote for the website, leave a comment if you want to know more about my code, at last remember the website address https://idoras.com i built a week ago, and share it with your firends if you find it useful, thanks.

相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。 &nbsp;
相关文章
|
运维 网络安全
运维实用神器-clustershell(实现多台服务器同时控制)
运维实用神器-clustershell(实现多台服务器同时控制)
1106 0
运维实用神器-clustershell(实现多台服务器同时控制)
|
4月前
|
人工智能 前端开发 API
|
4月前
|
存储 监控 测试技术
从检索到回答:RAG 流水线中三个被忽视的故障点
RAG系统看似运行正常,却常存在“静默故障”:检索相关但不相关、LLM自信幻觉、用户反馈未被采集。本文揭示三大缺口,并提出可落地的闭环方案——相关性门控、生成后自评估、全链路Trace追踪、用户行为信号转化,让RAG从“能答”走向“可信”。
240 6
|
5月前
|
人工智能 网络协议 数据中心
你的GPU正在“等米下锅”:RDMA如何修出一条绕过CPU的高速公路?
本文深度解析现代集群网络的底层革命:从冯·诺依曼瓶颈出发,剖析RDMA如何通过零拷贝、内核旁路与硬件卸载突破TCP/IP桎梏;直击其易用性差、连接爆炸、内存注册昂贵等痛点;并揭示Mooncake(面向LLM KV Cache的张量传输引擎)与Infinistore(万卡级分布式KV底座)如何以内存池化、拓扑感知、RC/UD混合等架构创新, bridging hardware power and software simplicity。
832 7
|
5月前
|
机器学习/深度学习 人工智能 搜索推荐
1688新方向丨 读懂1688 AI排序,让产品只给对的人看!
1688搜索生态正由“关键词堆砌”迈向“AI意图洞察”。AI深度识别买家身份、需求与偏好,按文本相关性、商品质量、商家实力、个性化匹配四大维度排序。商家需转向用户价值创造,以优质产品和服务赢得AI精准推荐。
|
6月前
|
人工智能 安全 搜索推荐
智能体来了:从0到1教你三步构建属于你的 AI 数字分身
本文带你从零构建专属AI智能体:解析其自主性本质,详解“骨架—性格—应用”三步搭建法,涵盖决策中枢、记忆系统与行动接口,并强调隐私保护与伦理边界。门槛降低,人人可启程。
2651 1
|
机器学习/深度学习 资源调度 数据可视化
RT-DETR改进策略【损失函数篇】| 将激活函数替换为带有注意力机制的激活函数 ARelu
RT-DETR改进策略【损失函数篇】| 将激活函数替换为带有注意力机制的激活函数 ARelu
484 2
RT-DETR改进策略【损失函数篇】| 将激活函数替换为带有注意力机制的激活函数 ARelu
|
JSON 数据挖掘 API
Lazada商品 API接口,开发者详解与使用指南
Lazada商品API为开发者提供商品信息获取功能,适用于电商应用开发与数据分析。支持获取标题、价格、库存等详细信息,具备实时更新、高并发支持等特点,适用于竞品分析、价格趋势研究、导购应用及客服系统集成。需获取凭证后调用接口,示例代码使用Python实现。
|
机器学习/深度学习 人工智能 大数据
基于深度学习的图像识别技术最新进展####
近年来,深度学习在图像识别领域取得了显著突破,本文综述了该领域的最新研究进展,特别关注卷积神经网络(CNN)的优化与创新、数据集扩充与增强策略、以及模型压缩与加速技术。通过对比分析不同网络结构和训练技巧,揭示了提升图像识别精度与效率的关键因素。此外,探讨了小样本学习、迁移学习在实际应用中的挑战与解决方案,为未来研究方向提供了新的视角和思路。 ####
1482 8