├── .gitignore ├── index.js ├── src └── services │ ├── utils.js │ ├── search.js │ ├── glossary.js │ ├── top.js │ ├── deals.js │ └── catalog.js ├── package.json ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | package-lock.json -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | const catalog = require('./src/services/catalog'); 3 | const deals = require('./src/services/deals'); 4 | const glossary = require('./src/services/glossary'); 5 | const search = require('./src/services/search'); 6 | const top = require('./src/services/top'); 7 | 8 | module.exports = { 9 | catalog, 10 | deals, 11 | glossary, 12 | search, 13 | top, 14 | }; 15 | -------------------------------------------------------------------------------- /src/services/utils.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | exports.getDataFromUrl = async (url) => { 4 | const html = await axios({ 5 | method: 'get', 6 | url: `https://www.gsmarena.com${url}`, 7 | }); 8 | 9 | return html.data; 10 | }; 11 | 12 | exports.getPrice = (text) => { 13 | const value = text.replace(',', '').split(' '); 14 | return { 15 | currency: value[0], 16 | price: parseFloat(value[1]), 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gsmarena-api", 3 | "version": "2.0.5", 4 | "description": "Parse GSMArena and then return as JSON", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/nordmarin/gsmarena-api.git" 9 | }, 10 | "scripts": {}, 11 | "keywords": [ 12 | "gsmarena", 13 | "api" 14 | ], 15 | "author": "Kirill Potapenko (kirill.potap97@gmail.com)", 16 | "license": "MIT", 17 | "dependencies": { 18 | "axios": "^1.2.5", 19 | "cheerio": "^1.0.0-rc.10" 20 | }, 21 | "devDependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /src/services/search.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const { getDataFromUrl } = require('./utils'); 3 | 4 | const search = async (searchValue) => { 5 | const html = await getDataFromUrl(`/results.php3?sQuickSearch=yes&sName=${searchValue}`); 6 | 7 | const $ = cheerio.load(html); 8 | const json = []; 9 | 10 | const devices = $('.makers').find('li'); 11 | devices.each((i, el) => { 12 | const imgBlock = $(el).find('img'); 13 | json.push({ 14 | id: $(el).find('a').attr('href').replace('.php', ''), 15 | name: $(el).find('span').html().split('
') 16 | .join(' '), 17 | img: imgBlock.attr('src'), 18 | description: imgBlock.attr('title'), 19 | }); 20 | }); 21 | 22 | return json; 23 | }; 24 | 25 | module.exports = { 26 | search, 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) nordmarin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/services/glossary.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const { getDataFromUrl } = require('./utils'); 3 | 4 | const get = async () => { 5 | const html = await getDataFromUrl('/glossary.php3'); 6 | 7 | const $ = cheerio.load(html); 8 | const json = []; 9 | 10 | const parentTerms = $('#body').find('.st-text'); 11 | parentTerms.children().each((index, el) => { 12 | if (index % 2 === 0) { 13 | json.push({ letter: $(el).text(), list: [] }); 14 | } else { 15 | const terms = $(el).find('a'); 16 | terms.each((i, ele) => { 17 | const id = $(ele).attr('href').replace('glossary.php3?term=', ''); 18 | const name = $(ele).text(); 19 | 20 | json[Math.floor(index / 2)].list.push({ 21 | id, 22 | name, 23 | }); 24 | }); 25 | } 26 | }); 27 | 28 | return json; 29 | }; 30 | 31 | const getTerm = async (term) => { 32 | const html = await getDataFromUrl(`/glossary.php3?term=${term}`); 33 | 34 | const $ = cheerio.load(html); 35 | const body = $('#body'); 36 | const title = body.find('.review-header .article-hgroup h1').text(); 37 | const text = body.find('.st-text').first().html(); 38 | 39 | return { 40 | title, 41 | html: text, 42 | }; 43 | }; 44 | 45 | module.exports = { 46 | get, 47 | getTerm, 48 | }; 49 | -------------------------------------------------------------------------------- /src/services/top.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const { getDataFromUrl } = require('./utils'); 3 | 4 | const get = async () => { 5 | const html = await getDataFromUrl('/deals.php3'); 6 | 7 | const $ = cheerio.load(html); 8 | const json = []; 9 | 10 | const categories = $('.sidebar.col.left').find('.module.module-rankings.s3'); 11 | categories.each((i, el) => { 12 | const category = $(el).find('h4').text(); 13 | const positions = $(el).find('tr'); 14 | 15 | const ranks = []; 16 | positions.each((ind, ele) => { 17 | const position = $('td[headers=th3a]', ele).text().replace('.', ''); 18 | if (position) { 19 | const name = $('nobr', ele).text(); 20 | const id = $('a', ele).attr('href').replace('.php', ''); 21 | const index = parseInt($('td[headers=th3c]', ele).text().replace(',', ''), 10); 22 | 23 | const element = { 24 | position: parseInt(position, 10), 25 | id, 26 | name, 27 | }; 28 | 29 | if (ind === 0) { 30 | element.dailyHits = index; 31 | } else { 32 | element.favorites = index; 33 | } 34 | ranks.push(element); 35 | } 36 | }); 37 | 38 | json.push({ 39 | category, 40 | list: ranks, 41 | }); 42 | }); 43 | 44 | return json; 45 | }; 46 | 47 | module.exports = { 48 | get, 49 | }; 50 | -------------------------------------------------------------------------------- /src/services/deals.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const { getDataFromUrl, getPrice } = require('./utils'); 3 | 4 | const getDeals = async () => { 5 | const html = await getDataFromUrl('/deals.php3'); 6 | 7 | const $ = cheerio.load(html); 8 | const json = []; 9 | const devices = $('#body').find('.pricecut'); 10 | 11 | devices.each((i, el) => { 12 | const img = $(el).find('.row a img').attr('src'); 13 | const url = $(el).find('.row a.image').attr('href'); 14 | const name = $(el).find('.row .phone div h3').text(); 15 | const id = $(el).find('.row .phone div a').attr('href').replace('.php', ''); 16 | const description = $(el).find('.row .phone p a').text(); 17 | 18 | const price = getPrice($(el).find('.row .phone .deal a.price').text()); 19 | const deal = { 20 | memory: $(el).find('.row .phone .deal a.memory').text(), 21 | storeImg: $(el).find('.row .phone .deal a.store img').attr('src'), 22 | price: price.price, 23 | currency: price.currency, 24 | discount: parseFloat($(el).find('.row .phone .deal a.discount').text()), 25 | }; 26 | 27 | const device = { 28 | id, 29 | img, 30 | url, 31 | name, 32 | description, 33 | deal, 34 | }; 35 | 36 | const historyList = $(el).find('.history .stats'); 37 | const history = []; 38 | 39 | historyList.children().each((index, elem) => { 40 | if (index % 2 === 0) { 41 | history.push({ time: $(elem).text() }); 42 | } else { 43 | const historyPrice = getPrice($(elem).text()); 44 | history[Math.floor(index / 2)].price = historyPrice.price; 45 | history[Math.floor(index / 2)].currency = historyPrice.currency; 46 | } 47 | }); 48 | 49 | device.history = history; 50 | 51 | json.push(device); 52 | }); 53 | 54 | return json; 55 | }; 56 | 57 | module.exports = { 58 | getDeals, 59 | }; 60 | -------------------------------------------------------------------------------- /src/services/catalog.js: -------------------------------------------------------------------------------- 1 | const cheerio = require('cheerio'); 2 | const { getDataFromUrl } = require('./utils'); 3 | 4 | const getBrands = async () => { 5 | const html = await getDataFromUrl('/makers.php3'); 6 | 7 | const $ = cheerio.load(html); 8 | const json = []; 9 | const brands = $('table').find('td'); 10 | 11 | brands.each((i, el) => { 12 | const aBlock = $(el).find('a'); 13 | json.push({ 14 | id: aBlock.attr('href').replace('.php', ''), 15 | name: aBlock.text().replace(' devices', '').replace(/[0-9]/g, ''), 16 | devices: parseInt($(el).find('span').text().replace(' devices', ''), 10), 17 | }); 18 | }); 19 | 20 | return json; 21 | }; 22 | 23 | const getNextPage = ($) => { 24 | const nextPage = $('a.prevnextbutton[title="Next page"]').attr('href'); 25 | if (nextPage) { 26 | return nextPage.replace('.php', ''); 27 | } 28 | return false; 29 | }; 30 | 31 | const getDevices = ($, devicesList) => { 32 | const devices = []; 33 | devicesList.each((i, el) => { 34 | const imgBlock = $(el).find('img'); 35 | devices.push({ 36 | id: $(el).find('a').attr('href').replace('.php', ''), 37 | name: $(el).find('span').text(), 38 | img: imgBlock.attr('src'), 39 | description: imgBlock.attr('title'), 40 | }); 41 | }); 42 | 43 | return devices; 44 | }; 45 | 46 | const getBrand = async (brand) => { 47 | let html = await getDataFromUrl(`/${brand}.php`); 48 | 49 | let $ = cheerio.load(html); 50 | let json = []; 51 | 52 | let devices = getDevices($, $('.makers').find('li')); 53 | json = [...json, ...devices]; 54 | 55 | while (getNextPage($)) { 56 | html = await getDataFromUrl(`/${getNextPage($)}.php`); 57 | $ = cheerio.load(html); 58 | devices = getDevices($, $('.makers').find('li')); 59 | json = [...json, ...devices]; 60 | } 61 | 62 | return json; 63 | }; 64 | 65 | const getDevice = async (device) => { 66 | const html = await getDataFromUrl(`/${device}.php`); 67 | const $ = cheerio.load(html); 68 | 69 | const displaySize = $('span[data-spec=displaysize-hl]').text(); 70 | const displayRes = $('div[data-spec=displayres-hl]').text(); 71 | const cameraPixels = $('.accent-camera').text(); 72 | const videoPixels = $('div[data-spec=videopixels-hl]').text(); 73 | const ramSize = $('.accent-expansion').text(); 74 | const chipset = $('div[data-spec=chipset-hl]').text(); 75 | const batterySize = $('.accent-battery').text(); 76 | const batteryType = $('div[data-spec=battype-hl]').text(); 77 | 78 | const quickSpec = []; 79 | quickSpec.push({ name: 'Display size', value: displaySize }); 80 | quickSpec.push({ name: 'Display resolution', value: displayRes }); 81 | quickSpec.push({ name: 'Camera pixels', value: cameraPixels }); 82 | quickSpec.push({ name: 'Video pixels', value: videoPixels }); 83 | quickSpec.push({ name: 'RAM size', value: ramSize }); 84 | quickSpec.push({ name: 'Chipset', value: chipset }); 85 | quickSpec.push({ name: 'Battery size', value: batterySize }); 86 | quickSpec.push({ name: 'Battery type', value: batteryType }); 87 | 88 | const name = $('.specs-phone-name-title').text(); 89 | const img = $('.specs-photo-main a img').attr('src'); 90 | 91 | const specNode = $('table'); 92 | const detailSpec = []; 93 | 94 | specNode.each((i, el) => { 95 | const specList = []; 96 | const category = $(el).find('th').text(); 97 | const specN = $(el).find('tr'); 98 | 99 | specN.each((index, ele) => { 100 | specList.push({ 101 | name: $('td.ttl', ele).text(), 102 | value: $('td.nfo', ele).text(), 103 | }); 104 | }); 105 | if (category) { 106 | detailSpec.push({ 107 | category, 108 | specifications: specList, 109 | }); 110 | } 111 | }); 112 | 113 | return { 114 | name, 115 | img, 116 | detailSpec, 117 | quickSpec, 118 | }; 119 | }; 120 | 121 | module.exports = { 122 | getBrands, 123 | getBrand, 124 | getDevice, 125 | }; 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GSMArena API (gsmarena.com) 2 | 3 | GSMArena phone specification and finder. This project is still in early development. 4 | 5 | The API basically reads from GSMArena website and results JSON data. 6 | 7 | ## Table of Contents 8 | 9 | * [Implemented Features](#implemented-features) 10 | * [Installation](#installation) 11 | * [Usage](#usage) 12 | * [Contact](#contact) 13 | * [License](#license) 14 | 15 | ## Implemented Features 16 | 17 | - [x] Get all brands 18 | - [x] Get devices by brand 19 | - [x] Get device specification 20 | - [x] Find devices by keyword 21 | - [x] Top of devices 22 | - [x] Hot deals 23 | - [x] Glossary 24 | - [x] Glossary detail 25 | - [ ] Find devices by advanced filters 26 | - [ ] News 27 | - [ ] Reviews 28 | 29 | ## Installation 30 | 31 | ```bash 32 | npm i gsmarena-api 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Import 38 | 39 | ```js 40 | const gsmarena = require('gsmarena-api'); 41 | ``` 42 | 43 | ### Brand list 44 | 45 | ```js 46 | const brands = await gsmarena.catalog.getBrands(); 47 | console.log(brands); 48 | ``` 49 | 50 | ```json 51 | [ 52 | { 53 | "id": "apple-phones-48", 54 | "name": "Apple", 55 | "devices": 98 56 | } 57 | ] 58 | ``` 59 | 60 | ### Device list by brand 61 | 62 | ```js 63 | const devices = await gsmarena.catalog.getBrand('apple-phones-48'); 64 | console.log(devices); 65 | ``` 66 | 67 | ```json 68 | [ 69 | { 70 | "id": "apple_iphone_13_pro_max-11089", 71 | "name": "iPhone 13 Pro Max", 72 | "img": "https://fdn2.gsmarena.com/vv/bigpic/apple-iphone-13-pro-max.jpg", 73 | "description": "Apple iPhone 13 Pro Max smartphone. Announced Sep 2021..." 74 | } 75 | ] 76 | ``` 77 | 78 | ### Device detail 79 | 80 | ```js 81 | const device = await gsmarena.catalog.getDevice('apple_iphone_13_pro_max-11089'); 82 | console.log(device); 83 | ``` 84 | 85 | ```json 86 | { 87 | "name": "Apple iPhone 13 Pro Max", 88 | "img": "https://fdn2.gsmarena.com/vv/bigpic/apple-iphone-13-pro-max.jpg", 89 | "quickSpec": [ 90 | { 91 | "name": "Display size", 92 | "value": "6.7\"" 93 | } 94 | ], 95 | "detailSpec": [ 96 | { 97 | "category": "Network", 98 | "specifications": [ 99 | { 100 | "name": "Technology", 101 | "value": "GSM / CDMA / HSPA / EVDO / LTE / 5G" 102 | } 103 | ] 104 | } 105 | ] 106 | } 107 | ``` 108 | 109 | ### Searching for device 110 | 111 | ```js 112 | const devices = await gsmarena.search.search('casio'); 113 | console.log(devices); 114 | ``` 115 | 116 | ```json 117 | [ 118 | { 119 | "id": "casio_g_zone_ca_201l-5384", 120 | "name": "Casio G'zOne CA-201L", 121 | "img": "https://fdn2.gsmarena.com/vv/bigpic/casio-gzone-ca-201l.jpg", 122 | "description": "Casio G'zOne CA-201L Android smartphone. Announced Mar 2013..." 123 | } 124 | ] 125 | ``` 126 | 127 | ### Top 128 | 129 | ```js 130 | const top = await gsmarena.top.get(); 131 | console.log(top); 132 | ``` 133 | 134 | ```json 135 | [ 136 | { 137 | "category": "Top 10 by daily interest", 138 | "list": [ 139 | { 140 | "position": 1, 141 | "id": "xiaomi_12-11285", 142 | "name": "Xiaomi 12", 143 | "dailyHits": 50330 144 | } 145 | ] 146 | } 147 | ] 148 | ``` 149 | 150 | ### Deals 151 | 152 | ```js 153 | const deals = await gsmarena.deals.getDeals(); 154 | console.log(deals); 155 | ``` 156 | 157 | ```json 158 | [ 159 | { 160 | "id": "oneplus_9-10747", 161 | "img": "https://m.media-amazon.com/images/I/31ICm7rK-hS._SL500_.jpg", 162 | "url": "https://www.amazon.co.uk/dp/B08V1NKHZF?tag=gsmcom-21&linkCode=osi&th=1&psc=1", 163 | "name": "OnePlus 9", 164 | "description": "OnePlus 9 5G (UK) SIM-Free Smartphone with Hasselblad Camera for Mobile - Arctic Sky...", 165 | "deal": { 166 | "memory": "128GB 8GB RAM", 167 | "storeImg": "https://fdn.gsmarena.com/imgroot/static/stores/amazon-uk1.png", 168 | "price": 449.00, 169 | "currency": "£", 170 | "discount": 24.6 171 | }, 172 | "history": [ 173 | { 174 | "time": "Previous", 175 | "price": 479.00, 176 | "currency": "£" 177 | } 178 | ] 179 | } 180 | ] 181 | ``` 182 | 183 | ### Glossary 184 | 185 | ```js 186 | const glossary = await gsmarena.glossary.get(); 187 | console.log(glossary); 188 | ``` 189 | 190 | ```json 191 | [ 192 | { 193 | "letter": "X", 194 | "list": [ 195 | { 196 | "id": "xenon-flash", 197 | "name": "Xenon flash" 198 | } 199 | ] 200 | } 201 | ] 202 | ``` 203 | 204 | ### Glossary detail 205 | 206 | ```js 207 | const term = await gsmarena.glossary.getTerm('xenon-flash'); 208 | console.log(term); 209 | ``` 210 | 211 | ```json 212 | { 213 | "title": "Xenon flash - definition", 214 | "html": "

A xenon flash produces an extremely intense full-spectrum white...

" 215 | } 216 | ``` 217 | 218 | ## Contact 219 | 220 | Created by [@nordmarin](https://t.me/nordmarin) - feel free to contact me! 221 | 222 | ## License 223 | 224 | GSMArena API is MIT licensed. --------------------------------------------------------------------------------