├── .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. --------------------------------------------------------------------------------