
Tradeinn (tradeinn.com) scraper
Deprecated
Pricing
Pay per usage
Go to Store


Tradeinn (tradeinn.com) scraper
Deprecated
Scrapes products titles, prices, images and availability. Does NOT scrape product details.
0.0 (0)
Pricing
Pay per usage
1
Monthly users
1
Last modified
3 years ago
Dockerfile
1FROM apify/actor-node:16
2
3COPY package.json ./
4
5RUN npm --quiet set progress=false \
6 && npm install --only=prod --no-optional
7
8COPY . ./
INPUT_SCHEMA.json
1{
2 "title": "Tradeinn (tradeinn.com) scraper",
3 "description": "Scrapes products titles, prices, images and availability. Does NOT scrape product details.",
4 "type": "object",
5 "schemaVersion": 1,
6 "properties": {
7 "mode": {
8 "title": "Mode",
9 "description": "",
10 "type": "string",
11 "editor": "select",
12 "default": "TEST",
13 "prefill": "TEST",
14 "enum": [
15 "TEST",
16 "CURATED",
17 "FULL"
18 ],
19 "enumTitles": [
20 "TEST",
21 "CURATED",
22 "FULL"
23 ]
24 },
25 "debug": {
26 "title": "Debug",
27 "description": "Debug mode prints more logs, disables concurrency and other optimizations.",
28 "type": "boolean",
29 "editor": "checkbox",
30 "default": false
31 },
32 "shop": {
33 "title": "Shop",
34 "description": "",
35 "type": "string",
36 "editor": "select",
37 "default": "BIKEINN",
38 "prefill": "BIKEINN",
39 "enum": [
40 "BIKEINN",
41 "DIVEINN",
42 "SNOWINN",
43 "TREKKINN",
44 "SMASHINN",
45 "SWIMINN",
46 "WAVEINN",
47 "MOTARDINN",
48 "OUTETINN",
49 "RUNNERINN",
50 "GOALINN",
51 "DRESSINN",
52 "TRAININN",
53 "XTREMEINN",
54 "KIDINN",
55 "TECHINN",
56 "BRICOINN"
57 ],
58 "enumTitles": [
59 "BIKEINN",
60 "DIVEINN",
61 "SNOWINN",
62 "TREKKINN",
63 "SMASHINN",
64 "SWIMINN",
65 "WAVEINN",
66 "MOTARDINN",
67 "OUTETINN",
68 "RUNNERINN",
69 "GOALINN",
70 "DRESSINN",
71 "TRAININN",
72 "XTREMEINN",
73 "KIDINN",
74 "TECHINN",
75 "BRICOINN"
76 ]
77 },
78 "APIFY_DONT_STORE_IN_DATASET": {
79 "sectionCaption": "Advanced",
80 "sectionDescription": "Advanced options, use only if you know what you're doing.",
81 "title": "Don't store in dataset",
82 "description": "If set to true, the actor will not store the results in the default dataset. Useful when using alternative storage, like own database",
83 "type": "boolean",
84 "default": false,
85 "editor": "checkbox"
86 },
87 "PG_CONNECTION_STRING_NORMALIZED": {
88 "title": "Postgres connection string for normalized data",
89 "description": "If set, actor will store normalized data in Postgres database in PG_DATA_TABLE and PG_DATA_PRICE_TABLE tables",
90 "type": "string",
91 "editor": "textfield"
92 },
93 "PG_DATA_TABLE": {
94 "title": "Postgres table name for product data",
95 "description": "Table name for storing product name, url, image, ...",
96 "type": "string",
97 "editor": "textfield"
98 },
99 "PG_DATA_PRICE_TABLE": {
100 "title": "Postgres table name for price data",
101 "description": "Table name for storing price, original price, stock status, ...",
102 "type": "string",
103 "editor": "textfield"
104 }
105 },
106 "required": [
107 "mode",
108 "shop"
109 ]
110}
apify.json
1{
2 "name": "tradeinn-tradeinn-com-scraper",
3 "version": "0.1",
4 "buildTag": "latest",
5 "env": null,
6 "defaultRunOptions": {
7 "build": "latest",
8 "timeoutSecs": 3600,
9 "memoryMbytes": 1024
10 }
11}
main.js
1/**
2 * Dev notes
3 * ===
4 *
5 * `https://www.tradeinn.com/bikeinn/en/${marca}/${id_marca}/m`
6 * - m - landing, no sorting
7 * - nm - all products, has sorting
8 *
9 * Spanish in a nutshell:
10 * marca = brand
11 * familia = family
12 * tienda = shop
13 * fecha_descatalogado = date_discontinued
14 * talla = size
15 * sostenible = sustainable
16 * tengo_tallas_traducidas = have_translated_sizes
17 * num_productes_actius = num_active_products
18 * baja = discontinued
19 * trobat = found
20 * pais = country
21 * enlace = link
22 * entrega = delivery
23 * divisa = currency
24 *
25 * TODO
26 * - v360
27 * - v180
28 * - v90
29 * - v30
30 *
31 * - brut
32 * - stock_reservat
33 * - desc_brand
34 * - pmp
35 * */
36
37// noinspection JSNonASCIINames,NonAsciiCharacters
38
39import { Actor } from "apify3";
40import { BasicCrawler, createBasicRouter } from "crawlee";
41import fetch from "node-fetch";
42import { init, save } from "./_utils/common.js";
43
44var MODE;
45
46(function (MODE) {
47 MODE["TEST"] = "TEST";
48 MODE["CURATED"] = "CURATED";
49 MODE["FULL"] = "FULL";
50})(MODE || (MODE = {}));
51var LABEL;
52
53(function (LABEL) {
54 LABEL["INDEX"] = "INDEX";
55 LABEL["PRODUCTS"] = "PRODUCTS";
56})(LABEL || (LABEL = {}));
57const BASE_URL = `https://www.tradeinn.com`;
58const PER_PAGE = 48;
59
60var SHOP;
61
62// TODO: Dry
63(function (SHOP) {
64 SHOP["BIKEINN"] = "BIKEINN";
65 SHOP["DIVEINN"] = "DIVEINN";
66 SHOP["SNOWINN"] = "SNOWINN";
67 SHOP["TREKKINN"] = "TREKKINN";
68 SHOP["SMASHINN"] = "SMASHINN";
69 SHOP["SWIMINN"] = "SWIMINN";
70 SHOP["WAVEINN"] = "WAVEINN";
71 SHOP["MOTARDINN"] = "MOTARDINN";
72 SHOP["OUTETINN"] = "OUTETINN";
73 SHOP["RUNNERINN"] = "RUNNERINN";
74 SHOP["GOALINN"] = "GOALINN";
75 SHOP["DRESSINN"] = "DRESSINN";
76 SHOP["TRAININN"] = "TRAININN";
77 SHOP["XTREMEINN"] = "XTREMEINN";
78 SHOP["KIDINN"] = "KIDINN";
79 SHOP["TECHINN"] = "TECHINN";
80 SHOP["BRICOINN"] = "BRICOINN";
81})(SHOP || (SHOP = {}));
82const SHOPS = {
83 DIVEINN: 1,
84 SNOWINN: 2,
85 TREKKINN: 3,
86 BIKEINN: 4,
87 SMASHINN: 5,
88 SWIMINN: 6,
89 WAVEINN: 7,
90 MOTARDINN: 8,
91 OUTETINN: 9,
92 RUNNERINN: 10,
93 GOALINN: 11,
94 DRESSINN: 12,
95 TRAININN: 13,
96 XTREMEINN: 14,
97 KIDINN: 15,
98 TECHINN: 16,
99 BRICOINN: 17,
100};
101
102const COUNTRIES = {
103 Albania: 1,
104 Algeria: 2,
105 "American Samoa": 3,
106 Andorra: 4,
107 Angola: 5,
108 Anguilla: 6,
109 Antigua: 7,
110 Argentina: 8,
111 Armenia: 9,
112 Aruba: 10,
113 Australia: 11,
114 Austria: 12,
115 Azerbaijan: 13,
116 Bahamas: 14,
117 Bahrain: 15,
118 Bangladesh: 16,
119 Barbados: 17,
120 Belarus: 18,
121 Belgium: 19,
122 Belize: 20,
123 Benin: 21,
124 Bermuda: 22,
125 Bhutan: 23,
126 Bolivia: 24,
127 Bonaire: 25,
128 "Bosnia and Herzegovina": 26,
129 Botswana: 27,
130 Brasil: 229,
131 Brunei: 29,
132 Bulgaria: 30,
133 "Burkina Faso": 31,
134 Burundi: 32,
135 Cambodia: 33,
136 Cameroon: 34,
137 Canada: 35,
138 "Cape Verde": 37,
139 "Cayman Islands": 38,
140 "Central African Republic": 39,
141 Chad: 40,
142 Chile: 41,
143 "China, People's Republic": 42,
144 Colombia: 43,
145 Comoros: 44,
146 Congo: 45,
147 "Congo, The Democratic Republic": 46,
148 "Cook Islands": 47,
149 "Costa Rica": 48,
150 "Cote d'Ivoire": 49,
151 Croatia: 50,
152 Cuba: 51,
153 Curacao: 52,
154 Cyprus: 53,
155 "Czech Republic, The": 54,
156 Denmark: 55,
157 Djibouti: 56,
158 Dominica: 57,
159 "Dominican Republic": 58,
160 Ecuador: 59,
161 Egypt: 60,
162 "El Salvador": 61,
163 "Equatorial Guinea": 62,
164 Eritrea: 63,
165 Estonia: 64,
166 Ethiopia: 65,
167 "Falkland Islands": 66,
168 "Faroe Islands": 67,
169 Fiji: 68,
170 Finland: 69,
171 France: 70,
172 "France - Corse": 228,
173 "France - Guadeloupe": 81,
174 "France - Martinique": 128,
175 "France - Reunion": 162,
176 "France - Tahiti": 194,
177 "French Guiana": 71,
178 Gabon: 72,
179 Gambia: 73,
180 Georgia: 74,
181 Germany: 75,
182 Ghana: 76,
183 Gibraltar: 77,
184 Greece: 78,
185 Greenland: 79,
186 Grenada: 80,
187 Guam: 82,
188 Guatemala: 83,
189 "Guinea Republic": 85,
190 "Guinea-Bissau": 86,
191 "Guyana (British)": 87,
192 Haiti: 88,
193 Honduras: 89,
194 "Hong Kong": 90,
195 Hungary: 91,
196 Iceland: 92,
197 India: 93,
198 Indonesia: 94,
199 "Iran, Islamic Republic of": 95,
200 "Ireland, Republic Of": 96,
201 Israel: 97,
202 Italy: 98,
203 Jamaica: 99,
204 Japan: 100,
205 Jordan: 102,
206 Kazakhstan: 103,
207 Kenya: 104,
208 Kiribati: 105,
209 "Korea, Republic Of": 107,
210 Kuwait: 108,
211 Kyrgyzstan: 109,
212 "Lao People's Democratic Republ": 110,
213 Latvia: 111,
214 Lebanon: 112,
215 Lesotho: 113,
216 Liberia: 114,
217 Libya: 115,
218 Liechtenstein: 116,
219 Lithuania: 117,
220 Luxembourg: 118,
221 Macau: 119,
222 "Macedonia, Republic of (FYROM)": 120,
223 Madagascar: 121,
224 Malawi: 122,
225 Malaysia: 123,
226 Maldives: 124,
227 Mali: 125,
228 Malta: 126,
229 "Marshall Islands": 127,
230 Mauritania: 129,
231 Mauritius: 130,
232 Mexico: 131,
233 "Moldova, Republic Of": 132,
234 Monaco: 133,
235 Mongolia: 134,
236 Montenegro: 227,
237 Montserrat: 135,
238 Morocco: 136,
239 Mozambique: 137,
240 Myanmar: 138,
241 Namibia: 139,
242 "Nauru, Republic Of": 140,
243 Nepal: 141,
244 "Netherlands, The": 142,
245 Nevis: 143,
246 "New Caledonia": 144,
247 "New Zealand": 145,
248 Nicaragua: 146,
249 Niger: 147,
250 Nigeria: 148,
251 Niue: 149,
252 Norway: 150,
253 Oman: 151,
254 Pakistan: 152,
255 Panama: 153,
256 "Papua New Guinea": 154,
257 Paraguay: 155,
258 Peru: 156,
259 "Philippines, The": 157,
260 Poland: 158,
261 Portugal: 159,
262 "Portugal (Madeira)": 224,
263 "Puerto Rico": 160,
264 Qatar: 161,
265 Romania: 163,
266 "Russian Federation, The": 164,
267 Rwanda: 165,
268 Saipan: 166,
269 Samoa: 167,
270 "San Marino": 226,
271 "Sao Tome and Principe": 168,
272 "Saudi Arabia": 169,
273 Senegal: 170,
274 Serbia: 225,
275 Seychelles: 171,
276 "Sierra Leone": 172,
277 Singapore: 173,
278 Slovakia: 174,
279 Slovenia: 175,
280 "Solomon Islands": 176,
281 Somalia: 177,
282 "Somaliland, Rep of (North Soma": 178,
283 "South Africa": 179,
284 Spain: 180,
285 "Spain (Canary Islands,Ceuta,Melilla)": 223,
286 "Sri Lanka": 181,
287 "St. Barthelemy": 182,
288 "St. Eustatius": 183,
289 "St. Kitts": 184,
290 "St. Lucia": 185,
291 "St. Maarten": 186,
292 "St. Vincent": 187,
293 Sudan: 188,
294 Suriname: 189,
295 Swaziland: 190,
296 Sweden: 191,
297 Switzerland: 192,
298 Syria: 193,
299 Taiwan: 195,
300 Tajikistan: 196,
301 Tanzania: 197,
302 Thailand: 198,
303 Togo: 199,
304 Tonga: 200,
305 "Trinidad and Tobago": 201,
306 Tunisia: 222,
307 Turkey: 202,
308 Turkmenistan: 203,
309 "Turks and Caicos Islands": 204,
310 Tuvalu: 205,
311 Uganda: 206,
312 Ukraine: 207,
313 "United Arab Emirates": 208,
314 "United Kingdom": 209,
315 "United Kingdom (Guernsey)": 84,
316 "United Kingdom (Jersey)": 101,
317 "United States Of America": 210,
318 Uruguay: 211,
319 Uzbekistan: 212,
320 Vanuatu: 213,
321 Vietnam: 215,
322 "Virgin Islands (British)": 216,
323 "Virgin Islands (US)": 217,
324 Yemen: 218,
325 Zambia: 220,
326 Zimbabwe: 221,
327};
328
329// [...$('.imagenesLogos .marca_line')].reduce((acc, x) => { acc[x.querySelector('img').alt] = x.id; return acc}, {})
330const BRANDS = {
331 "+8000": `586`,
332 "100percent": `1275`,
333 "10bar": `1110`,
334 "226ERS": `1360`,
335 "2Toms": `5436`,
336 "2XU": `1283`,
337 "3M": `1362`,
338 "3t": `8734`,
339 "4arm Strong": `3934`,
340 "4F": `7566`,
341 "4iiii": `3426`,
342 "5 five": `8109`,
343 "8 C Plus": `2190`,
344 "9transport": `7829`,
345 Abloc: `8537`,
346 "Absolute Black": `4878`,
347 ABUS: `1363`,
348 Acid: `8077`,
349 Acimut: `7786`,
350 Acme: `4541`,
351 "Action Outdoor": `2017`,
352 adidas: `263`,
353 "Adidas Badminton": `5356`,
354 "adidas originals": `646`,
355 AEE: `368`,
356 Aftershokz: `1120`,
357 Afton: `3381`,
358 Agfa: `5490`,
359 AGU: `6478`,
360 Aifeit: `7033`,
361 "Air Relax": `5962`,
362 Airbone: `4473`,
363 Airman: `5734`,
364 "Airn Outdoor": `9681`,
365 Airofit: `8544`,
366 Airpop: `6916`,
367 Airwings: `5160`,
368 Alhonga: `7739`,
369 "All Mountain Style": `7516`,
370 "All Sins 18k": `9061`,
371 Allmatters: `8891`,
372 "Alpcross Components": `4884`,
373 Alpenheat: `439`,
374 "Alpha Industries": `6700`,
375 Alpina: `2379`,
376 Alpine: `5736`,
377 Alpinestars: `335`,
378 "Alpinestars Bicycle": `9606`,
379 Alpitude: `7737`,
380 "Altec Lansing": `4483`,
381 Alé: `1697`,
382 Amazfit: `4414`,
383 "American Classic": `3016`,
384 Amiaud: `4862`,
385 Amix: `4486`,
386 Amkov: `7563`,
387 Amlsport: `1020`,
388 Amplifi: `3679`,
389 "An Lun": `5282`,
390 "Ana Maria Lajusticia": `2874`,
391 Andrys: `7787`,
392 "Angelina Calzino": `8526`,
393 Anlen: `5281`,
394 Ansmann: `1112`,
395 Aphex: `7482`,
396 Apple: `4321`,
397 Approx: `5893`,
398 Apurna: `8735`,
399 Aqua2go: `7113`,
400 Aquafeel: `7310`,
401 Aquas: `2265`,
402 "Arch Max": `1235`,
403 "Arc’teryx": `1222`,
404 Arena: `11`,
405 Arexons: `10440`,
406 "Argon 18": `6061`,
407 Ariete: `1723`,
408 Arisun: `4970`,
409 Armada: `418`,
410 "Armor-x": `1293`,
411 Artein: `10439`,
412 Artsana: `6812`,
413 Arundel: `8534`,
414 Arva: `276`,
415 Ashima: `2896`,
416 Asics: `284`,
417 Asista: `3609`,
418 Askoll: `8514`,
419 "Ass Savers": `3670`,
420 Assos: `13`,
421 Astvte: `4158`,
422 Asus: `4382`,
423 Así: `8889`,
424 Atala: `4903`,
425 "ATC LTD TA": `4937`,
426 Atipick: `2613`,
427 Atv: `8547`,
428 Auvray: `4978`,
429 Avento: `3622`,
430 Avid: `16`,
431 "Awesome Maps": `7367`,
432 AXA: `3664`,
433 Axxios: `7405`,
434 "B&W": `5432`,
435 "B-Race": `7791`,
436 "B-urban": `7792`,
437 Babolat: `18`,
438 "Back On Track": `8738`,
439 Barbieri: `3687`,
440 Barfly: `4789`,
441 Barts: `2467`,
442 Basil: `2699`,
443 BBB: `483`,
444 Beal: `3430`,
445 Bear: `5430`,
446 "Bearing Cw": `7788`,
447 Beeline: `7118`,
448 Beeloom: `9598`,
449 Bell: `286`,
450 "Bell Italy": `7789`,
451 "Bella Aurora": `1874`,
452 Bellelli: `2045`,
453 Bematrix: `8802`,
454 Bend36: `3904`,
455 Benlee: `1644`,
456 Berghaus: `24`,
457 Bergner: `8144`,
458 Bering: `1338`,
459 Bern: `25`,
460 Beryl: `7119`,
461 "Best Divers": `26`,
462 Beto: `3773`,
463 Bettr: `6980`,
464 Beurer: `5520`,
465 "Bh Fitness": `7120`,
466 "Bicis Esteve": `7032`,
467 Bicisupport: `3890`,
468 "Bicycle Line": `1958`,
469 Biemme: `8936`,
470 "Bike Ahead": `9185`,
471 "Bike Fashion": `4476`,
472 "Bike Hand": `2897`,
473 "Bike Workx": `4803`,
474 "Bike Yoke": `8739`,
475 Bike7: `8741`,
476 Bikecare: `2773`,
477 Bikefinder: `9576`,
478 Bikefun: `3396`,
479 Bikeinn: `1272`,
480 Bikeribbon: `2601`,
481 "Bikers Dream": `5163`,
482 "Bikers Own": `4749`,
483 Bikeshield: `7510`,
484 Biknd: `7455`,
485 Billow: `5904`,
486 Bimanan: `9841`,
487 Bimpair: `5437`,
488 Biofreeze: `6905`,
489 Biona: `6981`,
490 Bioracer: `5647`,
491 "Biotech Usa": `9831`,
492 Biotex: `3932`,
493 Biotherm: `4054`,
494 Biotonik: `7887`,
495 Birzman: `7504`,
496 Bistark: `7790`,
497 "Black Bearing": `8542`,
498 "Black Cat Tire": `7738`,
499 "Black Diamond": `643`,
500 "Black Inc": `5008`,
501 Blackburn: `283`,
502 Blackspire: `7503`,
503 Blindsave: `8768`,
504 Bliz: `5486`,
505 Blossum: `4672`,
506 Blub: `4828`,
507 "Blueball Sport": `4304`,
508 Bluegrass: `875`,
509 "BnB Rack": `3921`,
510 Bobike: `534`,
511 "Body Glide": `1510`,
512 Bodygood: `9001`,
513 Bolle: `2550`,
514 Bombshell: `8743`,
515 Bombtrack: `4904`,
516 Bompar: `3891`,
517 "Bone Collection": `8558`,
518 Bonin: `2913`,
519 Bont: `3816`,
520 Bookman: `8935`,
521 Born: `1536`,
522 "Born Fruits": `1938`,
523 "Born Living Yoga": `6778`,
524 Bosch: `4477`,
525 "Boston Golf": `9277`,
526 Bottecchia: `8853`,
527 Box: `8744`,
528 Br: `6850`,
529 Brakco: `7035`,
530 Brandit: `6794`,
531 Braun: `5470`,
532 Breezer: `5277`,
533 Bresser: `6088`,
534 Brigmton: `5524`,
535 Briko: `466`,
536 "Britax Römer": `3546`,
537 "Brooks England": `1701`,
538 Brose: `8996`,
539 Broyx: `4849`,
540 Brunox: `4478`,
541 Bryton: `896`,
542 Bta: `10458`,
543 "Bub-up": `8745`,
544 Buchel: `4479`,
545 Buddyswim: `3194`,
546 "Buff ®": `252`,
547 "Build Your Brand": `10328`,
548 Bulls: `9200`,
549 Burgtec: `4933`,
550 Burley: `4653`,
551 Burton: `3647`,
552 "Busch&Muller": `4686`,
553 Buzzrack: `6234`,
554 "Bv Sport": `1344`,
555 Byte: `7471`,
556 Cairn: `3035`,
557 Camelbak: `36`,
558 Campagnolo: `37`,
559 Campingaz: `2175`,
560 "Cane Creek": `8292`,
561 Cannondale: `1242`,
562 Canyon: `4851`,
563 Capgo: `7762`,
564 Casall: `1282`,
565 Castelli: `497`,
566 Cateye: `40`,
567 Catlike: `41`,
568 "Cayler & Sons": `8649`,
569 Cebe: `2442`,
570 Cecotec: `4998`,
571 Cegasa: `6645`,
572 Celly: `2591`,
573 Cep: `1627`,
574 Ceramicspeed: `3702`,
575 "Cerda Group": `4926`,
576 Challenge: `3785`,
577 "Chamois Butt´r": `4532`,
578 Chaoyang: `3407`,
579 "Charge Sports Drinks": `8349`,
580 Chase: `8771`,
581 Chiba: `5164`,
582 Chicco: `6649`,
583 Chimpanzee: `1679`,
584 "Chris King": `4936`,
585 Chrome: `5003`,
586 Cinelli: `528`,
587 Cinq: `3876`,
588 Citadel: `5648`,
589 Citybug: `2346`,
590 "Cl Brakes": `7449`,
591 Classic: `5377`,
592 Clever: `4779`,
593 Clicgear: `8772`,
594 Clif: `1339`,
595 "Climbing Technology": `2578`,
596 Clinique: `3998`,
597 Clipon: `9171`,
598 Closca: `5646`,
599 Closethegap: `4885`,
600 "Club Ride": `4485`,
601 CMP: `640`,
602 cnSPOKE: `5283`,
603 Coas: `6747`,
604 Cobags: `7129`,
605 Cocoon: `9120`,
606 "Codex-U": `4363`,
607 Cofra: `6659`,
608 "Color Baby": `6832`,
609 Coluer: `6005`,
610 Columbia: `347`,
611 Columbus: `594`,
612 Compeed: `4194`,
613 Compex: `617`,
614 Composite: `8548`,
615 Compressport: `47`,
616 Conor: `8082`,
617 Consumo: `5058`,
618 Contact: `5529`,
619 Contec: `8969`,
620 Continental: `48`,
621 Controltech: `5950`,
622 Coolbox: `4334`,
623 Coospo: `10357`,
624 Copa: `3074`,
625 Coros: `4487`,
626 Corratec: `2235`,
627 Corsurf: `9454`,
628 "Cosmo Connected": `4719`,
629 CPA: `2594`,
630 Craft: `50`,
631 Craghoppers: `3078`,
632 Crane: `3780`,
633 Crankbrothers: `282`,
634 Cratoni: `3524`,
635 "Crazy Safety": `1943`,
636 "Crep Protect": `2859`,
637 Cressi: `51`,
638 Croozer: `3084`,
639 Crosscall: `4401`,
640 Crud: `5964`,
641 CST: `4720`,
642 Cube: `52`,
643 Cutered: `10039`,
644 Cybex: `3836`,
645 Cycl: `7132`,
646 "Cycling Ceramic": `3447`,
647 Cyclo: `2703`,
648 Cycloc: `5965`,
649 Cycology: `7922`,
650 "D-Light": `3889`,
651 Daewoo: `3193`,
652 Dahon: `384`,
653 Dainese: `346`,
654 Dakar: `7728`,
655 Dakine: `54`,
656 Damartsport: `5644`,
657 Dane: `4540`,
658 Dare2B: `2945`,
659 Darevie: `4902`,
660 Darkpads: `5429`,
661 Dashel: `6324`,
662 "Dcu Tecnologic": `5863`,
663 Decleor: `4055`,
664 Deda: `3674`,
665 Deerhunter: `6002`,
666 Deestone: `2899`,
667 Defeet: `2914`,
668 Deli: `2900`,
669 "Delta Cycle": `3432`,
670 Denver: `5743`,
671 Deportium: `7484`,
672 Dexshell: `8970`,
673 Dickies: `7008`,
674 Difi: `6067`,
675 "Dirt Freak": `7847`,
676 Disney: `2568`,
677 "Dkn Technology": `9599`,
678 Dmr: `8540`,
679 DMT: `597`,
680 Dom: `3802`,
681 Dosun: `2918`,
682 Douchebags: `613`,
683 "Dr Cool": `2139`,
684 "Dr Senst": `6258`,
685 "Dr. Organic": `6726`,
686 Drasanvi: `7473`,
687 Drift: `4467`,
688 "Drop Shot": `372`,
689 Drwheeler: `6909`,
690 "Ds Covers": `7435`,
691 "DT Swiss": `2173`,
692 Duracell: `2962`,
693 "Dutch Perfect": `2595`,
694 DVO: `6704`,
695 Dyedbro: `4934`,
696 Dynafit: `65`,
697 "E-Bike Vision": `4618`,
698 "E-thirteen": `5154`,
699 Eafit: `9094`,
700 Eassun: `887`,
701 Easton: `2034`,
702 Eastpak: `411`,
703 Easycamp: `3275`,
704 Easypix: `2282`,
705 EBC: `7500`,
706 Ebon: `4886`,
707 Echowell: `2901`,
708 Ecoflow: `6928`,
709 "Econic One": `7044`,
710 Ecoon: `9439`,
711 Edelrid: `281`,
712 Edm: `8072`,
713 "Effetto Mariposa": `9031`,
714 Eines: `9397`,
715 "El Gallo": `4837`,
716 Elevn: `8654`,
717 Elite: `69`,
718 Elix: `7890`,
719 Eloa: `6983`,
720 Eltin: `2954`,
721 Elvedes: `7793`,
722 Emhome: `7685`,
723 Enduro: `7501`,
724 "Enduro Bearings": `8639`,
725 Eneloop: `6262`,
726 Energizer: `1475`,
727 "Energy Sistem": `4402`,
728 Enervit: `4725`,
729 "Enforma Socks": `4986`,
730 Enve: `8774`,
731 Eolos: `1346`,
732 Eovolt: `8761`,
733 Epitact: `8775`,
734 Epoch: `3888`,
735 Epsealon: `1480`,
736 Ergon: `3015`,
737 Ergotec: `4621`,
738 Erima: `8621`,
739 Errea: `8625`,
740 Esge: `4722`,
741 ESIgrips: `4793`,
742 Esperia: `8852`,
743 Etixx: `3752`,
744 Etxeondo: `644`,
745 Eucerin: `3986`,
746 Everactive: `9330`,
747 Eveready: `2963`,
748 Evergy: `9428`,
749 "Evo Bikes": `8801`,
750 Evoc: `7494`,
751 Evolve: `8655`,
752 "Exa Form": `7411`,
753 Exal: `4656`,
754 "Excellent Houseware": `8140`,
755 Excess: `8776`,
756 "Exposure Lights": `4787`,
757 Exustar: `2385`,
758 "F-Lite": `4689`,
759 F100: `4687`,
760 "Fabio Quartararo": `2848`,
761 Fabric: `1996`,
762 "Fabrica De Juguetes Chicos": `6959`,
763 Fahrer: `4628`,
764 Falke: `1148`,
765 "Far&Near": `3678`,
766 Fashy: `7309`,
767 Fasi: `4939`,
768 "Fast Forward": `9076`,
769 Fastrider: `7673`,
770 Favero: `7001`,
771 Favour: `6112`,
772 FDP: `7897`,
773 Federal: `8778`,
774 Feedback: `3019`,
775 "Feelfree Gear": `3376`,
776 Fenix: `2395`,
777 Ferrino: `73`,
778 FFWD: `4742`,
779 Fidlock: `3762`,
780 "Fil Safe": `5431`,
781 Fila: `1533`,
782 Finis: `261`,
783 "Finish Line": `2605`,
784 Finna: `4276`,
785 First: `3902`,
786 "First Bike": `2372`,
787 "Fischer Bikes": `6004`,
788 "Fisio Xtreme": `9284`,
789 Fisiocrem: `581`,
790 Fitbit: `1613`,
791 "Fitfiu Fitness": `9105`,
792 "Fitness Tech": `8998`,
793 "Five Ten": `2367`,
794 Fixplus: `3132`,
795 Fizik: `302`,
796 Flashmer: `1306`,
797 Flectr: `3147`,
798 Flexall: `6906`,
799 Flexir: `7462`,
800 FLM: `2781`,
801 "Fly Racing": `7453`,
802 Focus: `8907`,
803 Force: `7036`,
804 "Force Xv": `5637`,
805 Formigli: `7741`,
806 Formula: `3871`,
807 Forward: `8782`,
808 Fox: `3052`,
809 "Fox Rage": `5940`,
810 Foxman: `2177`,
811 Fpd: `8804`,
812 FSA: `2050`,
813 Fuji: `988`,
814 "Fuji-toki": `7773`,
815 Fulcrum: `76`,
816 FullGas: `3782`,
817 "Fun Bike": `8359`,
818 Funken: `9307`,
819 Funkita: `609`,
820 "Funky Trunks": `610`,
821 Fytter: `573`,
822 "G-kos": `7794`,
823 "G-Star": `1914`,
824 Gaadi: `4629`,
825 Gaiam: `1913`,
826 Galfer: `2704`,
827 Garcia: `6006`,
828 Garibaldi: `1518`,
829 "Gear Aid": `3386`,
830 Geco: `3906`,
831 Gembird: `6504`,
832 Gen: `7525`,
833 "Genuine Innovations": `3826`,
834 Geosmina: `10040`,
835 GES: `2902`,
836 GHOST: `4854`,
837 Gigabyte: `4425`,
838 Gios: `6727`,
839 Gipiemme: `9790`,
840 Giro: `81`,
841 Gist: `4726`,
842 Givi: `6706`,
843 Givova: `7492`,
844 Giyo: `2919`,
845 Gladiatorfit: `9797`,
846 Globber: `8617`,
847 Globus: `6480`,
848 "Go System": `3139`,
849 "Goal Zero": `5447`,
850 "Gold Nutrition": `6737`,
851 Goldfren: `9809`,
852 Goobay: `5894`,
853 Goodyear: `1645`,
854 GoPro: `84`,
855 "GORE® Wear": `3158`,
856 "Gorilla Sports": `9615`,
857 "Gorilla Tape": `7759`,
858 Gp: `8205`,
859 "Gp Batteries": `3848`,
860 Graff: `5588`,
861 "Granite Design": `6722`,
862 "Green Cell": `9564`,
863 Gregory: `1010`,
864 Gridinlux: `9671`,
865 Grifone: `1276`,
866 GripGrab: `2275`,
867 Grundens: `6386`,
868 GT: `3393`,
869 GTR: `4993`,
870 GU: `1284`,
871 "Gu Energy": `9239`,
872 Guardian: `1564`,
873 Guee: `7499`,
874 Gurpil: `3313`,
875 "Guy Harvey": `471`,
876 Gymstick: `5454`,
877 H4u: `9824`,
878 Haberland: `4690`,
879 HAD: `4657`,
880 Haeger: `6356`,
881 Haglöfs: `371`,
882 Haibike: `4622`,
883 Hamax: `3548`,
884 Handlz: `4994`,
885 Handup: `5457`,
886 Hannah: `2055`,
887 Hape: `7164`,
888 "Hapo-g": `7795`,
889 Harbinger: `7551`,
890 Haro: `2719`,
891 "Hart Hunting": `3684`,
892 Hartex: `8382`,
893 "Hawaiian Tropic": `5075`,
894 Head: `88`,
895 "Head Bike": `9820`,
896 "Head Swimming": `3705`,
897 Headgy: `4979`,
898 Hebie: `4610`,
899 Hedkayse: `8511`,
900 Heidenau: `4630`,
901 Held: `500`,
902 Helite: `8510`,
903 Hellfire: `2783`,
904 "Helly Hansen": `90`,
905 Hergo: `3893`,
906 Herrmans: `4611`,
907 Herschel: `3002`,
908 Hibros: `7892`,
909 Hilx: `7785`,
910 Hiplok: `2761`,
911 Hirzl: `4887`,
912 Hivital: `8339`,
913 HJC: `503`,
914 "Ho Soccer": `2052`,
915 Hock: `4631`,
916 Hollis: `92`,
917 Homcom: `7700`,
918 Honor: `4341`,
919 Hoopoe: `4815`,
920 Hope: `7498`,
921 Horn: `4724`,
922 "Hotspot Design": `2076`,
923 Hovding: `7169`,
924 Hsn: `8560`,
925 HT: `3316`,
926 "Htp Design": `7468`,
927 Huawei: `3180`,
928 "Huck Norris": `9165`,
929 Hummel: `5860`,
930 Hutchinson: `93`,
931 HydraKnight: `4804`,
932 Hydroponic: `2222`,
933 "Hygen Spray": `7893`,
934 Hyperice: `9087`,
935 Ibera: `2197`,
936 Ibis: `7883`,
937 Icebreaker: `1670`,
938 Icepeak: `6802`,
939 IceToolz: `5570`,
940 "Id Match": `8324`,
941 Igpsport: `9577`,
942 Ihealth: `2398`,
943 Iluv: `1916`,
944 Impac: `4658`,
945 "Impala Rollers": `4830`,
946 Imperial: `4554`,
947 "Inca Hair Jewellery": `6963`,
948 "Industry Nine": `4794`,
949 Infini: `4693`,
950 "Infinitii Ramps": `8947`,
951 Infinity: `2359`,
952 "Inno Bike": `4694`,
953 Innova: `2903`,
954 "Innova Nutrition": `5019`,
955 Inpeak: `10036`,
956 Insight: `8646`,
957 Inspyre: `8971`,
958 Insta360: `5504`,
959 Intenso: `2062`,
960 "Interphone Cellularline": `1115`,
961 Intova: `98`,
962 Inxide: `6720`,
963 ION: `1946`,
964 Iprotec: `1732`,
965 "iQ-Company": `316`,
966 "Iron-ic": `7030`,
967 ISB: `3766`,
968 Isbjörn: `1211`,
969 ISM: `2021`,
970 Iswari: `9840`,
971 ITM: `599`,
972 Ixcor: `9745`,
973 Ixon: `2455`,
974 iXS: `2456`,
975 Izas: `510`,
976 "Izumi Chain": `4256`,
977 "J.Juan": `3894`,
978 Jagwire: `2051`,
979 Jako: `8622`,
980 Janod: `5339`,
981 Jata: `6359`,
982 JCOOL: `4899`,
983 JeansTrack: `3959`,
984 Jeep: `7697`,
985 "Jetblack Cycling": `7099`,
986 Joby: `3961`,
987 "JOE´S": `2546`,
988 "John Smith": `1736`,
989 Joluvi: `4684`,
990 Joma: `607`,
991 "Jopa Mx": `10557`,
992 JRC: `463`,
993 Jucad: `9245`,
994 "Juice Lubes": `4786`,
995 Julbo: `369`,
996 JVC: `4557`,
997 "K-Edge": `1617`,
998 "K-Swiss": `445`,
999 "K-up": `8631`,
1000 Kalas: `9746`,
1001 Kali: `332`,
1002 "Kali Protectives": `4315`,
1003 Kalloy: `3928`,
1004 Kanebo: `4258`,
1005 Kappa: `1681`,
1006 Kaps: `9182`,
1007 "Kari Traa": `7077`,
1008 Kariban: `8623`,
1009 Karpos: `867`,
1010 Kask: `1232`,
1011 Kayak: `5171`,
1012 Kazam: `3125`,
1013 KCNC: `3651`,
1014 Keboo: `9590`,
1015 Ked: `8609`,
1016 Keen: `102`,
1017 Keep: `490`,
1018 Kelme: `2329`,
1019 Kempa: `1337`,
1020 Kenda: `2036`,
1021 Kengine: `7037`,
1022 Kenny: `6891`,
1023 "Keto Protein": `9828`,
1024 "Kids Licensing": `5359`,
1025 Kidzamo: `8065`,
1026 Kilpi: `1562`,
1027 Kimood: `8637`,
1028 "Kind Shock": `2863`,
1029 Kinetic: `3818`,
1030 "Klan-E": `4504`,
1031 KLICKfix: `3754`,
1032 Klim: `4819`,
1033 Klower: `7889`,
1034 Klättermusen: `6806`,
1035 KMC: `2706`,
1036 Knog: `430`,
1037 Kodak: `4412`,
1038 Kody: `4839`,
1039 Kokua: `2548`,
1040 "KOM Cycling": `5448`,
1041 Kong: `353`,
1042 KOO: `3201`,
1043 "Kookie Cat": `6984`,
1044 Korda: `8911`,
1045 Koss: `4559`,
1046 Kovix: `5718`,
1047 Krafwin: `1114`,
1048 Krf: `3256`,
1049 Kroon: `8706`,
1050 Kruskis: `400`,
1051 Kryptonite: `2720`,
1052 "KS Tools": `6143`,
1053 KSIX: `1351`,
1054 "KT Tape": `4310`,
1055 Ktm: `8314`,
1056 Kujo: `5722`,
1057 Kynay: `8980`,
1058 Kyrocream: `1023`,
1059 "L-twoo": `10332`,
1060 L2s: `8916`,
1061 "La Sportiva": `482`,
1062 Lacd: `7832`,
1063 "Lacomed Sport": `7796`,
1064 Lacoste: `645`,
1065 Lactojoy: `8550`,
1066 Lafuma: `305`,
1067 Laica: `7189`,
1068 Lake: `1464`,
1069 Lalizas: `401`,
1070 Lancaster: `4159`,
1071 "Lasalle Sports": `7467`,
1072 Lavina: `7894`,
1073 Lazer: `1937`,
1074 "Le Coq Sportif": `2407`,
1075 Leatt: `1281`,
1076 "Led Lenser": `107`,
1077 "Legend Ebikes": `9326`,
1078 Legnano: `7027`,
1079 Lelumia: `8535`,
1080 Lemond: `1019`,
1081 Lenz: `628`,
1082 "Leonardi Racing": `8708`,
1083 Leone1947: `4734`,
1084 Leotec: `4667`,
1085 Lezyne: `110`,
1086 LG: `4342`,
1087 Lidergrip: `9393`,
1088 LifeSystems: `1662`,
1089 Lifeventure: `1663`,
1090 Limar: `112`,
1091 Linka: `5280`,
1092 Livall: `2927`,
1093 Livecell: `9843`,
1094 Lizard: `3094`,
1095 "Lizard Skins": `8918`,
1096 Lockbox: `6892`,
1097 Loeffler: `2866`,
1098 Lof: `1737`,
1099 Logan: `4889`,
1100 Lolë: `3444`,
1101 Lonsdale: `1646`,
1102 Look: `114`,
1103 Louri: `8610`,
1104 Luft: `5373`,
1105 Luma: `971`,
1106 Lumineo: `8168`,
1107 Luminox: `2049`,
1108 "Lumos Helmet": `8513`,
1109 Lupine: `280`,
1110 "M-Wave": `4751`,
1111 "Mac In A Sac": `6008`,
1112 Mach1: `8052`,
1113 Macna: `2088`,
1114 Macron: `8620`,
1115 Mader: `9363`,
1116 Madform: `1343`,
1117 Madwave: `2383`,
1118 "Mag-Lite": `621`,
1119 Magene: `7880`,
1120 "Magic Shine": `3781`,
1121 Magped: `8920`,
1122 Magura: `2204`,
1123 Mako: `545`,
1124 Mammut: `120`,
1125 Manitou: `8564`,
1126 "Marc Marquez": `976`,
1127 Marmot: `122`,
1128 Marshguard: `8536`,
1129 Maruni: `8281`,
1130 Marzocchi: `3633`,
1131 MASSI: `2144`,
1132 "Master Lock": `1580`,
1133 "Matcha & Co": `9836`,
1134 Matt: `520`,
1135 Maurten: `7906`,
1136 Mavic: `124`,
1137 "Max Protein": `9848`,
1138 Maxcom: `2063`,
1139 Maxell: `1038`,
1140 Maxim: `7495`,
1141 Maxxis: `125`,
1142 "MB Wear": `5147`,
1143 Mbm: `2718`,
1144 "Mc David": `3797`,
1145 McNett: `126`,
1146 Mebaline: `2886`,
1147 Medisana: `3181`,
1148 Megamo: `2937`,
1149 Meilan: `9580`,
1150 Melon: `3525`,
1151 Menabo: `2236`,
1152 "Mercury Equipment": `3051`,
1153 "Merlin Bike Care": `4251`,
1154 Messingschlager: `4613`,
1155 MET: `129`,
1156 Metalsub: `2094`,
1157 Mfi: `8973`,
1158 Miche: `2145`,
1159 Michelin: `264`,
1160 MicroSHIFT: `4867`,
1161 Midland: `130`,
1162 Mighty: `5284`,
1163 "Mijnen Pieper": `4640`,
1164 Mikasa: `2465`,
1165 "Miles Wide": `4783`,
1166 Milkit: `4946`,
1167 Minoura: `535`,
1168 Mission: `1574`,
1169 "Mister Tee": `8627`,
1170 Mitas: `2596`,
1171 Mizuno: `132`,
1172 Mks: `6387`,
1173 Mobilis: `4337`,
1174 Mobius: `3788`,
1175 Momabikes: `3621`,
1176 Momum: `8532`,
1177 Mondaine: `2948`,
1178 "Monkeys Sauce": `4840`,
1179 Montane: `1055`,
1180 Montbell: `5317`,
1181 Montura: `4746`,
1182 Moon: `2904`,
1183 Mooquer: `9387`,
1184 "Moose Soft-goods": `4260`,
1185 "Morgan Blue": `3182`,
1186 Mosconi: `2710`,
1187 Mota: `8178`,
1188 Motip: `7696`,
1189 Motorex: `6689`,
1190 Motorola: `2180`,
1191 Mounty: `7766`,
1192 "Mr. Wolf": `8608`,
1193 "Mr.control": `7727`,
1194 MRP: `3784`,
1195 MSC: `2864`,
1196 "MTB Hopper": `7434`,
1197 "Muc Off": `1971`,
1198 Mueller: `394`,
1199 "Mund Socks": `2264`,
1200 Musto: `619`,
1201 Muvi: `648`,
1202 Muvit: `2920`,
1203 "Muvit Io": `8682`,
1204 Myflash: `7210`,
1205 Mykronoz: `2928`,
1206 Myn: `9750`,
1207 MyWay: `2929`,
1208 Nalini: `5947`,
1209 "Named Sport": `3710`,
1210 Nathan: `962`,
1211 Natruly: `6985`,
1212 "Natural Fit": `8255`,
1213 Naturalshine: `137`,
1214 "Natures Bounty": `6725`,
1215 Naturtierra: `9832`,
1216 Navali: `3895`,
1217 "NC-17": `7511`,
1218 Nebbia: `6047`,
1219 "Nebo Tools": `1733`,
1220 Neco: `4805`,
1221 Neparo: `6271`,
1222 Net: `2039`,
1223 "New Balance": `1312`,
1224 "New Era": `2891`,
1225 "New Looxs": `2373`,
1226 Newline: `5300`,
1227 Newton: `880`,
1228 Nexgim: `9780`,
1229 Nextorch: `977`,
1230 Nfun: `2911`,
1231 Nike: `365`,
1232 "Nike Swim": `2770`,
1233 Nilox: `554`,
1234 Nimo: `8135`,
1235 Niner: `3908`,
1236 "Nite Ize": `1906`,
1237 "Nite Rider": `4741`,
1238 Nokia: `3406`,
1239 Nonbak: `2769`,
1240 Nooyah: `4900`,
1241 Norco: `4697`,
1242 Northwave: `139`,
1243 Novatec: `5319`,
1244 Nox: `140`,
1245 Nrc: `10467`,
1246 NRG: `7798`,
1247 "Nu Swimrun": `4975`,
1248 Nutcase: `1573`,
1249 Nutrinovex: `8509`,
1250 Nutrisport: `1028`,
1251 Nuvo: `3931`,
1252 "O-stand": `6388`,
1253 Oakley: `142`,
1254 "Ocean Sunglasses": `2302`,
1255 Oceanarium: `5443`,
1256 Oceanic: `144`,
1257 Ocun: `1289`,
1258 Odeclas: `1513`,
1259 ODI: `3783`,
1260 Odlo: `145`,
1261 Odyssey: `8795`,
1262 Oem: `7220`,
1263 OJ: `2363`,
1264 Oko: `8943`,
1265 Olive: `4817`,
1266 Ology: `2142`,
1267 One: `7769`,
1268 "One Industries": `3808`,
1269 Oneal: `141`,
1270 OnGuard: `1917`,
1271 "Only Play": `7458`,
1272 "Onn Style": `2608`,
1273 Onza: `7497`,
1274 Oppo: `4981`,
1275 "Orange Mud": `5865`,
1276 "Orange Seal": `4831`,
1277 Orbegozo: `5481`,
1278 Orca: `5933`,
1279 Orcatorch: `1581`,
1280 "Oregon Scientific": `150`,
1281 Oreka: `4841`,
1282 Orontas: `4157`,
1283 Ortlieb: `1225`,
1284 Osprey: `883`,
1285 Otso: `4846`,
1286 Ottolock: `6409`,
1287 "Out Of": `6927`,
1288 "Outdoor Research": `982`,
1289 Outwell: `3280`,
1290 Overade: `5320`,
1291 Overstims: `6795`,
1292 Oxford: `6693`,
1293 Oxypro: `9000`,
1294 "O´neill": `926`,
1295 "P.A.C.": `4698`,
1296 "Pacific Socks": `8695`,
1297 Paingone: `6172`,
1298 Paleobull: `9822`,
1299 Palomar: `7229`,
1300 Panaracer: `536`,
1301 Panasonic: `3318`,
1302 Pangea: `7459`,
1303 Panzer: `9096`,
1304 "Panzer Glass": `4962`,
1305 "Park Tool": `3828`,
1306 Pax: `4879`,
1307 Peak: `8633`,
1308 "Peak Performance": `367`,
1309 "Pearl Izumi": `537`,
1310 "Peaty´s": `4932`,
1311 "Pedal Plate": `5869`,
1312 "Pedro´s": `3020`,
1313 Pelagic: `1008`,
1314 Pembree: `9281`,
1315 "Pepe Jeans": `1588`,
1316 "Perfect Nutrition": `8995`,
1317 Peruzzo: `2041`,
1318 "Petrol Industries": `3699`,
1319 "Petunia Pickle Bottom": `4777`,
1320 Philips: `2065`,
1321 Phorm: `4154`,
1322 Pieces: `7422`,
1323 Pieich: `3076`,
1324 Pilo: `4494`,
1325 Pinarello: `8699`,
1326 Pintyplus: `7237`,
1327 Pioneer: `4570`,
1328 Pirelli: `3606`,
1329 Pissei: `5438`,
1330 Pivo: `7062`,
1331 Platinet: `7239`,
1332 Pletscher: `4699`,
1333 PNI: `6989`,
1334 PNK: `7755`,
1335 POC: `159`,
1336 Point: `4614`,
1337 "Point 65": `4735`,
1338 Pokal: `3930`,
1339 Polar: `160`,
1340 "Polar Gran Fondo": `1229`,
1341 "Polaris Bikewear": `1743`,
1342 Polaroid: `3185`,
1343 "Polaroid Eyewear": `1739`,
1344 Polini: `5453`,
1345 Polisport: `3547`,
1346 "Position One": `8924`,
1347 "POV-Case": `634`,
1348 "Power Plate": `8940`,
1349 Powerbar: `163`,
1350 Powergym: `1481`,
1351 Powershot: `4871`,
1352 Praxis: `3144`,
1353 "Predio Son Quint": `9851`,
1354 "Pride Racing": `8656`,
1355 Prince: `167`,
1356 PRO: `442`,
1357 "Pro Action": `1629`,
1358 "Pro Feet": `7764`,
1359 "Pro Line": `4700`,
1360 Proact: `8794`,
1361 Procell: `2879`,
1362 "Profile Design": `6740`,
1363 Progress: `3410`,
1364 Progrip: `7799`,
1365 Prologo: `170`,
1366 Promax: `4752`,
1367 Proquimia: `5458`,
1368 Protap: `9308`,
1369 "Protein Gastronomy": `8363`,
1370 Protella: `9830`,
1371 Protest: `550`,
1372 Proviz: `2188`,
1373 Prowheel: `4890`,
1374 PTN: `7761`,
1375 Pulifren: `7899`,
1376 Pulseroll: `8825`,
1377 Puma: `514`,
1378 Pure2improve: `8409`,
1379 Purepower: `9170`,
1380 Puressentiel: `3978`,
1381 Puro: `2921`,
1382 "Push Bars": `2880`,
1383 "Pyc Chain": `7470`,
1384 Pygomic: `7074`,
1385 "Q36.5": `3380`,
1386 Qardio: `2917`,
1387 Qbike: `7895`,
1388 QM: `1684`,
1389 Qplay: `7444`,
1390 "QU-AX": `3682`,
1391 Quarq: `2999`,
1392 Quaxar: `4901`,
1393 "Quick Media Electronic": `4359`,
1394 Quiksilver: `894`,
1395 Quoc: `8538`,
1396 "R-evenge": `647`,
1397 "r.s.p": `6739`,
1398 R2: `6410`,
1399 "Race Face": `3658`,
1400 "Race One": `2712`,
1401 Racer: `8350`,
1402 Racingbros: `7508`,
1403 Rad: `9166`,
1404 "Radio Bikes": `9303`,
1405 Raidlight: `2909`,
1406 Rainlegs: `4701`,
1407 Raleigh: `5000`,
1408 "Ram Mounts": `3391`,
1409 Ras: `2320`,
1410 Raskullz: `5867`,
1411 Rassine: `9578`,
1412 Ravemen: `10037`,
1413 "Raw Elements": `8933`,
1414 Rayovac: `6277`,
1415 "RDX Sports": `2697`,
1416 "Re-Fuel": `2931`,
1417 Realme: `4972`,
1418 "Recon Instrument": `10468`,
1419 Record: `4995`,
1420 "Red Bull Spect": `4985`,
1421 "Red Chili": `2424`,
1422 Reebok: `664`,
1423 Reelight: `4702`,
1424 "Reflectiv Spray": `7252`,
1425 Regatta: `2973`,
1426 "Rehab Medic": `5335`,
1427 Rehband: `3387`,
1428 Reich: `4715`,
1429 Reid: `2375`,
1430 Reisenthel: `4704`,
1431 Relber: `8605`,
1432 "Rendiment Race": `5451`,
1433 "Rene Furterer": `3968`,
1434 Renthal: `3675`,
1435 Repente: `6390`,
1436 Replay: `3656`,
1437 Reserve: `9388`,
1438 Resolvbike: `8910`,
1439 Respro: `5439`,
1440 Restrap: `8407`,
1441 Reusch: `477`,
1442 "Reverse Components": `3775`,
1443 Revoloop: `4845`,
1444 Rfr: `8076`,
1445 "Rfx Care": `8808`,
1446 "rh+": `1356`,
1447 Riday: `4499`,
1448 "Ride Concepts": `4935`,
1449 Ridefyl: `9327`,
1450 Ridewill: `4660`,
1451 Ridewrap: `7939`,
1452 Ridley: `6416`,
1453 Riff: `1118`,
1454 Rinat: `642`,
1455 "Rip Curl": `615`,
1456 Ritchey: `173`,
1457 "Rixen&Kaul": `4661`,
1458 Robens: `3429`,
1459 "Rock Experience": `5589`,
1460 "Rock Tape": `8273`,
1461 RockShox: `2035`,
1462 Roeckl: `859`,
1463 Rogelli: `7483`,
1464 Rohloff: `4662`,
1465 Roland: `4615`,
1466 Rondo: `4905`,
1467 Roodol: `9767`,
1468 Rosaura: `9888`,
1469 Rossignol: `259`,
1470 Roswheel: `7885`,
1471 Roto: `5168`,
1472 Rotor: `3172`,
1473 Rowenta: `5476`,
1474 Rox: `5440`,
1475 Roxy: `895`,
1476 Royalcycle: `7038`,
1477 RPM: `3897`,
1478 RRP: `7505`,
1479 RS7: `1624`,
1480 RST: `5472`,
1481 Rtech: `3639`,
1482 Rucanor: `1221`,
1483 "Rude Health": `1712`,
1484 "Rudy Project": `176`,
1485 Rukka: `318`,
1486 Runtastic: `598`,
1487 Ryder: `4663`,
1488 RymeBikes: `1944`,
1489 "S-ride": `7039`,
1490 Saccon: `3898`,
1491 "Safe Max": `2804`,
1492 "Safe Sea": `1511`,
1493 Saft: `8206`,
1494 Safta: `4314`,
1495 Sahmurai: `8533`,
1496 Sahoo: `7884`,
1497 Sailfish: `865`,
1498 Salewa: `178`,
1499 Salice: `2015`,
1500 "Salt&stone": `9497`,
1501 Salter: `460`,
1502 Saltstick: `1685`,
1503 "Salty Crew": `4534`,
1504 Salvimar: `869`,
1505 Samox: `5318`,
1506 Samsung: `2947`,
1507 Sanitas: `8348`,
1508 "Sanity Derm": `7891`,
1509 Sanon: `9823`,
1510 "Santa Cruz Bikes": `9301`,
1511 "Santa Madre": `8822`,
1512 Santini: `1230`,
1513 Sapience: `5951`,
1514 Sapim: `4922`,
1515 Sapo: `7800`,
1516 Saris: `3070`,
1517 Satori: `5322`,
1518 "SAXX Underwear": `2758`,
1519 Sbk: `9437`,
1520 SBS: `4360`,
1521 Schwalbe: `182`,
1522 Schwarz: `4617`,
1523 SCICON: `8711`,
1524 Scott: `1745`,
1525 "Scuba Gifts": `6412`,
1526 Scylla: `9013`,
1527 "SDG Components": `4833`,
1528 "SE Bikes": `5278`,
1529 "Sea To Summit": `392`,
1530 SEAC: `186`,
1531 Seachoice: `1425`,
1532 Sealskinz: `1735`,
1533 SeaSucker: `3636`,
1534 Seeland: `5749`,
1535 Seland: `8934`,
1536 Select: `8629`,
1537 "Selle Bassano": `3875`,
1538 "Selle Italia": `191`,
1539 "Selle Montegrappa": `7797`,
1540 "Selle Royal": `1477`,
1541 "Selle San Marco": `278`,
1542 "Selle San Remo": `9014`,
1543 "Selle SMP": `1942`,
1544 Sena: `2234`,
1545 Sencillo: `10466`,
1546 Senda: `7753`,
1547 Servivita: `9834`,
1548 Setlaz: `3899`,
1549 Seven: `3021`,
1550 "SH+": `2905`,
1551 Shapeheart: `6389`,
1552 Shimano: `193`,
1553 "Shock Absorber": `2444`,
1554 "Shock Doctor": `2876`,
1555 Shot: `2561`,
1556 "Shot Race Gear": `8657`,
1557 Shotgun: `4827`,
1558 Shun: `4806`,
1559 Sidas: `2201`,
1560 Sidi: `194`,
1561 "Sierra Climbing": `7833`,
1562 Sigalsub: `1461`,
1563 Sigeyi: `9575`,
1564 Sigma: `196`,
1565 Sikendiet: `9837`,
1566 Silca: `8219`,
1567 Silic1: `4807`,
1568 Silva: `197`,
1569 Silvano: `8752`,
1570 "Singing Rock": `1241`,
1571 Sinner: `3044`,
1572 Sinter: `8978`,
1573 Siren: `6947`,
1574 Siroko: `8696`,
1575 SIS: `1053`,
1576 Sisley: `4005`,
1577 "Six Union": `7040`,
1578 Sixpro: `9754`,
1579 Sixs: `2579`,
1580 Sixsixone: `7502`,
1581 Skateflash: `3689`,
1582 SKF: `4770`,
1583 Skins: `198`,
1584 SKS: `2054`,
1585 Slc: `8075`,
1586 Slime: `3825`,
1587 "Slug Plug": `9263`,
1588 Smart: `2716`,
1589 Smartek: `8747`,
1590 Smartgyro: `5663`,
1591 Smartshake: `2397`,
1592 Smartwool: `3042`,
1593 Smith: `201`,
1594 Smoove: `4782`,
1595 "Snap Climbing": `3420`,
1596 Sockguy: `7558`,
1597 Sockla: `4346`,
1598 Soehnle: `6195`,
1599 Softee: `4897`,
1600 Sogo: `8748`,
1601 Solaray: `7059`,
1602 Solgar: `6724`,
1603 Soll: `434`,
1604 Sony: `574`,
1605 Soudal: `3388`,
1606 Soundmoovz: `8689`,
1607 Source: `4182`,
1608 "SP Connect": `3829`,
1609 Spalding: `443`,
1610 Sparraw: `7712`,
1611 Spatzwear: `8796`,
1612 SPC: `2695`,
1613 "Specialites TA": `4866`,
1614 Specialized: `4929`,
1615 Speedo: `207`,
1616 Speedplay: `208`,
1617 Speedsleev: `4778`,
1618 Spektrum: `8508`,
1619 Spetton: `209`,
1620 Spich: `9292`,
1621 Spidi: `427`,
1622 Spinergy: `7879`,
1623 Spiro: `8797`,
1624 Spiuk: `433`,
1625 Spokey: `9401`,
1626 "Sport HG": `1234`,
1627 Sportful: `211`,
1628 "Sporti France": `8640`,
1629 Sportlast: `2545`,
1630 Sportourer: `279`,
1631 Sprayke: `7801`,
1632 SPY: `2030`,
1633 Spyder: `415`,
1634 Spypoint: `8439`,
1635 SQlab: `4990`,
1636 Squire: `2955`,
1637 "Squirt Cycling Products": `4880`,
1638 "SR Suntour": `3530`,
1639 Sram: `212`,
1640 "Stages Cycling": `3434`,
1641 Stance: `2971`,
1642 "Stans No Tubes": `4156`,
1643 "Star Blubike": `5963`,
1644 Starter: `6767`,
1645 Steadyrack: `4759`,
1646 Stealth: `8976`,
1647 Stique: `8611`,
1648 Stronglight: `3589`,
1649 Stucchi: `8857`,
1650 "Sun Ringle": `8975`,
1651 "Sun Zapper": `9504`,
1652 Sunny: `4842`,
1653 SunRace: `3048`,
1654 Sunstech: `2350`,
1655 Suomy: `1106`,
1656 Supacaz: `3694`,
1657 "Super B": `2978`,
1658 Superdry: `1577`,
1659 "Superior Bikes": `7745`,
1660 Superthings: `8882`,
1661 Sural: `2189`,
1662 "Surbikes Premium Socks": `4791`,
1663 Swede: `8380`,
1664 Swimovate: `216`,
1665 SwissStop: `1465`,
1666 Swisstone: `4579`,
1667 Syncros: `8884`,
1668 Synpowell: `4808`,
1669 Sytech: `7357`,
1670 "T-One": `2907`,
1671 Tactic: `4462`,
1672 Tacx: `429`,
1673 Tailwind: `9002`,
1674 Talamex: `5460`,
1675 "Tall Order": `8972`,
1676 Tange: `5375`,
1677 Tangent: `6692`,
1678 Tanita: `1484`,
1679 Tannus: `4636`,
1680 Target: `9015`,
1681 Tatonka: `3759`,
1682 Taurus: `5552`,
1683 Taya: `4883`,
1684 Taymory: `1678`,
1685 "Tech Light": `3134`,
1686 Technoline: `6040`,
1687 Technomousse: `8703`,
1688 Techwell: `7888`,
1689 Tecnomar: `221`,
1690 Tecnovita: `7275`,
1691 Tecxus: `7677`,
1692 Tektro: `3900`,
1693 Tenzing: `6987`,
1694 Ternua: `2194`,
1695 "Terry Fisio": `8906`,
1696 Texenergy: `7277`,
1697 Texlock: `7278`,
1698 "Tfa Dostmann": `6041`,
1699 TFHPC: `3907`,
1700 "The Beam": `8974`,
1701 "The Capsoul": `9825`,
1702 "The North Face": `224`,
1703 TheBeam: `3824`,
1704 TheraBand: `1692`,
1705 Theragun: `4976`,
1706 "Therm-ic": `225`,
1707 Thermowave: `9005`,
1708 Thinkrider: `7828`,
1709 Thirtytwo: `8521`,
1710 Thomson: `3336`,
1711 Thor: `3698`,
1712 Thuasne: `8648`,
1713 Thule: `538`,
1714 Tifosi: `480`,
1715 "Tiger Balm": `8663`,
1716 Tigra: `2933`,
1717 Tile: `3720`,
1718 Time: `499`,
1719 "Tip Top": `3896`,
1720 Titici: `8697`,
1721 Tkx: `8858`,
1722 Toad: `8512`,
1723 "Toei Animation": `5841`,
1724 Togs: `4881`,
1725 "Toimsa Bikes": `8881`,
1726 Tols: `5148`,
1727 Toorx: `7849`,
1728 Topaz: `9555`,
1729 Topeak: `3446`,
1730 Torch: `7285`,
1731 Torhans: `9757`,
1732 Torq: `4790`,
1733 Tortue: `8799`,
1734 "Total Bmx": `8928`,
1735 Totto: `6707`,
1736 "Totum Sport": `3435`,
1737 TouchCam: `2293`,
1738 "Tout Terrain": `3912`,
1739 Tovatec: `1086`,
1740 "Trail Gator": `4762`,
1741 Trangoworld: `228`,
1742 Transfil: `3883`,
1743 TranzX: `4769`,
1744 "Travel Blue": `1919`,
1745 Treefrog: `3787`,
1746 Trelock: `229`,
1747 Tremblay: `8644`,
1748 Trespass: `1544`,
1749 Triggerpoint: `7554`,
1750 Trigon: `579`,
1751 Trimm: `10356`,
1752 Trinca: `8556`,
1753 Trislide: `2339`,
1754 Tristar: `5554`,
1755 Triswim: `2340`,
1756 "Troy Lee Designs": `2016`,
1757 "True Utility": `1734`,
1758 Trumpf: `5376`,
1759 Truvativ: `231`,
1760 Tubolight: `4673`,
1761 Tubolito: `3798`,
1762 Tubus: `3361`,
1763 "Tucano Urbano": `8506`,
1764 Tufo: `2597`,
1765 Tunturi: `8707`,
1766 Turbo: `268`,
1767 Tusa: `232`,
1768 "Twenty One": `9670`,
1769 TwoNav: `233`,
1770 TYR: `507`,
1771 "Tyre Yogurt": `9302`,
1772 TyreKey: `4882`,
1773 Ubike: `9351`,
1774 Uco: `1920`,
1775 Uebler: `7409`,
1776 Ufesa: `5477`,
1777 UFO: `3190`,
1778 Ufor: `4784`,
1779 Uhlsport: `627`,
1780 Ulac: `7886`,
1781 ULD: `3872`,
1782 Uller: `4249`,
1783 "Ultimate Performance": `2212`,
1784 Ultrabreathe: `3821`,
1785 Ultraspire: `2468`,
1786 Umbro: `1721`,
1787 Umit: `8859`,
1788 Unex: `4996`,
1789 Union: `4705`,
1790 Unior: `4666`,
1791 Unix: `4763`,
1792 UNO: `4809`,
1793 UNPASS: `5433`,
1794 "Urban Classics": `8837`,
1795 "Urban Iki": `3585`,
1796 "Urban Motion": `8543`,
1797 "Urban Proof": `2915`,
1798 Urbanbiker: `7923`,
1799 Urge: `6048`,
1800 Ursus: `3874`,
1801 USE: `4785`,
1802 USWE: `3935`,
1803 Uvex: `542`,
1804 UYN: `5330`,
1805 "V8 Concepts": `8927`,
1806 Valvul: `9309`,
1807 "Van Allen": `6770`,
1808 VAR: `2209`,
1809 Varta: `5513`,
1810 VAUDE: `241`,
1811 Vavert: `9093`,
1812 VDO: `3827`,
1813 "VEE Rubber": `3884`,
1814 Velco: `5157`,
1815 Velo: `2757`,
1816 "Velo Orange": `8926`,
1817 Veloflex: `2598`,
1818 Velosock: `2376`,
1819 VeloToze: `4792`,
1820 Velox: `3885`,
1821 Venerate: `7811`,
1822 Venom: `8565`,
1823 Ventura: `1994`,
1824 Venzo: `2908`,
1825 Verbatim: `4443`,
1826 VHS: `7783`,
1827 Vicma: `3886`,
1828 "Victory Endurance": `4767`,
1829 Vidaxl: `8484`,
1830 Vincita: `8705`,
1831 Vinnic: `7294`,
1832 Viro: `7758`,
1833 Vision: `370`,
1834 "Vital Bike": `8562`,
1835 "Vital Gym": `8561`,
1836 Vittoria: `2042`,
1837 Vivasport: `7029`,
1838 VK: `3901`,
1839 Vnoise: `5434`,
1840 Volcom: `1534`,
1841 VP: `3887`,
1842 "Vp-components": `4894`,
1843 VR46: `3672`,
1844 Vredestein: `1015`,
1845 Vuelta: `9319`,
1846 Vueltausa: `7765`,
1847 Vulcanet: `5435`,
1848 Vulkan: `8999`,
1849 W2W: `1080`,
1850 Wag: `8084`,
1851 "Wag Bike Bicycle Components": `9764`,
1852 Wahoo: `1660`,
1853 Walio: `9574`,
1854 Wanda: `7041`,
1855 Wantalis: `7296`,
1856 "Warner Bros": `5850`,
1857 Wasp: `3562`,
1858 Watteam: `3706`,
1859 "WD-40": `2343`,
1860 Wd40: `8225`,
1861 Weefine: `2247`,
1862 WeeRide: `7107`,
1863 Weider: `1304`,
1864 Weldtite: `2717`,
1865 Wellgo: `4992`,
1866 Wens: `7903`,
1867 Wera: `6219`,
1868 Weslo: `8860`,
1869 Wheels: `7509`,
1870 "Wheels Manufacturing": `4788`,
1871 "White Lightning": `5715`,
1872 Widek: `4708`,
1873 Wiener: `4941`,
1874 "Wild Republic": `5446`,
1875 Wildcountry: `876`,
1876 Willworx: `4942`,
1877 Wilson: `245`,
1878 "Win It": `9562`,
1879 "Wind X-Treme": `1290`,
1880 Winora: `4638`,
1881 Wippermann: `3590`,
1882 Withings: `4945`,
1883 Woho: `3096`,
1884 Wolfpack: `4781`,
1885 Woomax: `5851`,
1886 Wowow: `4709`,
1887 Woxter: `5661`,
1888 WRC: `8083`,
1889 WTB: `562`,
1890 Wueps: `8482`,
1891 "X-adventurer": `3641`,
1892 "X-BIONIC": `270`,
1893 "X-Sauce": `2884`,
1894 "X-SOCKS": `247`,
1895 "Xavier Mor": `8457`,
1896 Xcel: `927`,
1897 Xena: `9074`,
1898 Xerama: `4895`,
1899 Xiaomi: `2696`,
1900 XLAB: `1731`,
1901 XLC: `3500`,
1902 Xpedo: `3663`,
1903 Xplova: `3186`,
1904 "Xq Max": `8430`,
1905 Yaban: `4810`,
1906 Yamaha: `5159`,
1907 Ybn: `9320`,
1908 Yeah: `8861`,
1909 Yessbmx: `8798`,
1910 Yeti: `5426`,
1911 Yoko: `8641`,
1912 Yokozuna: `6497`,
1913 Youin: `7860`,
1914 YST: `7740`,
1915 Zamst: `570`,
1916 "Zan Headgear": `4279`,
1917 Zanchetta: `7802`,
1918 Zasdar: `8704`,
1919 Zefal: `626`,
1920 Zeray: `7896`,
1921 Zerod: `366`,
1922 ZeroFlats: `1583`,
1923 Ziener: `3029`,
1924 Zipp: `250`,
1925 Zoggs: `380`,
1926 Zone3: `1062`,
1927 Zoom: `374`,
1928 Zoot: `251`,
1929 ZVG: `4711`,
1930 Zycle: `4727`,
1931};
1932
1933function slugize(str) {
1934 // taken directly from tradeinn js
1935 str = str.replace(/à/gi, `a`);
1936 str = str.replace(/á/gi, `a`);
1937 str = str.replace(/â/gi, `a`);
1938 str = str.replace(/ã/gi, `a`);
1939 str = str.replace(/ä/gi, `a`);
1940 str = str.replace(/å/gi, `a`);
1941 str = str.replace(/ä/gi, `a`);
1942 str = str.replace(/à/gi, `a`);
1943 str = str.replace(/á/gi, `a`);
1944 str = str.replace(/â/gi, `a`);
1945 str = str.replace(/ã/gi, `a`);
1946 str = str.replace(/ä/gi, `a`);
1947 str = str.replace(/å/gi, `a`);
1948 str = str.replace(/ä/gi, `a`);
1949 str = str.replace(/ò/gi, `o`);
1950 str = str.replace(/ó/gi, `o`);
1951 str = str.replace(/ô/gi, `o`);
1952 str = str.replace(/õ/gi, `o`);
1953 str = str.replace(/ö/gi, `o`);
1954 str = str.replace(/ø/gi, `o`);
1955 str = str.replace(/ò/gi, `o`);
1956 str = str.replace(/ó/gi, `o`);
1957 str = str.replace(/ô/gi, `o`);
1958 str = str.replace(/õ/gi, `o`);
1959 str = str.replace(/ö/gi, `o`);
1960 str = str.replace(/ø/gi, `o`);
1961 str = str.replace(/€/gi, `e`);
1962 str = str.replace(/è/gi, `e`);
1963 str = str.replace(/é/gi, `e`);
1964 str = str.replace(/ê/gi, `e`);
1965 str = str.replace(/ë/gi, `e`);
1966 str = str.replace(/è/gi, `e`);
1967 str = str.replace(/é/gi, `e`);
1968 str = str.replace(/ê/gi, `e`);
1969 str = str.replace(/ë/gi, `e`);
1970 str = str.replace(/ç/gi, `c`);
1971 str = str.replace(/ç/gi, `c`);
1972 str = str.replace(/ì/gi, `i`);
1973 str = str.replace(/í/gi, `i`);
1974 str = str.replace(/î/gi, `i`);
1975 str = str.replace(/ï/gi, `i`);
1976 str = str.replace(/ì/gi, `i`);
1977 str = str.replace(/í/gi, `i`);
1978 str = str.replace(/î/gi, `i`);
1979 str = str.replace(/ï/gi, `i`);
1980 str = str.replace(/ù/gi, `u`);
1981 str = str.replace(/ú/gi, `u`);
1982 str = str.replace(/û/gi, `u`);
1983 str = str.replace(/ü/gi, `u`);
1984 str = str.replace(/ù/gi, `u`);
1985 str = str.replace(/ú/gi, `u`);
1986 str = str.replace(/û/gi, `u`);
1987 str = str.replace(/ü/gi, `u`);
1988 str = str.replace(/ÿ/gi, `y`);
1989 str = str.replace(/ñ/gi, `n`);
1990 str = str.replace(/ñ/gi, `n`);
1991 str = str.replace(/\//gi, `-`);
1992 str = str.replace(/´/gi, ``);
1993 str = str.replace(/`/gi, `-`);
1994 str = str.replace(/&/gi, `-`);
1995 str = str.replace(/¬/gi, `-`);
1996 str = str.replace(/º/gi, `-`);
1997 str = str.replace(/!/gi, `-`);
1998 str = str.replace(/¡/gi, `-`);
1999 str = str.replace(/¿/gi, `-`);
2000 str = str.replace(/\?/gi, `-`);
2001 str = str.replace(/\+/gi, `-`);
2002 str = str.replace(/\(/gi, `-`);
2003 str = str.replace(/\)/gi, `-`);
2004 str = str.replace(/·/gi, `-`);
2005 str = str.replace(/’/gi, `-`);
2006 str = str.replace(/ª/gi, `-`);
2007 str = str.replace(/™/gi, `-`);
2008 str = str.replace(/®/gi, `-`);
2009 str = str.replace(/"/gi, `-`);
2010 str = str.replace(/°/gi, `-`);
2011 str = str.replace(/µ/gi, `u`);
2012 str = str.replace(/«/gi, `-`);
2013 str = str.replace(/»/gi, `-`);
2014 str = str.replace(/±/gi, `-`);
2015 str = str.replace(/²/gi, `-`);
2016 str = str.replace(/>/gi, `-`);
2017 str = str.replace(/</gi, `-`);
2018 str = str.replace(/ä/gi, `a`);
2019 str = str.replace(/ë/gi, `e`);
2020 str = str.replace(/ï/gi, `i`);
2021 str = str.replace(/ö/gi, `o`);
2022 str = str.replace(/ü/gi, `u`);
2023 str = str.replace(/¨/gi, `-`);
2024 str = str.replace(/ª/gi, `-`);
2025 str = str.replace(/½/gi, `-`);
2026 str = str.replace(/©/gi, `-`);
2027 str = str.replace(`/™/gi`, ``);
2028 str = str.replace(/“/gi, `-`);
2029 str = str.replace(/”/gi, `-`);
2030 str = str.replace(/æ/gi, `-`);
2031 str = str.replace(/©/gi, `-`);
2032 str = str.replace(/³/gi, `-`);
2033 str = str.replace(/¶/gi, `-`);
2034 str = str.replace(/¤/gi, `-`);
2035 str = str.replace(/¦/gi, `-`);
2036 str = str.replace(/æ/gi, `-`);
2037 str = str.replace(/¥/gi, `-`);
2038 str = str.replace(/ÿ/gi, `y`);
2039 str = str.replace(/§/gi, `-`);
2040 str = str.replace(/œ/gi, `-`);
2041 str = str.replace(/ß/gi, `-`);
2042 str = str.replace(/œ/gi, `-`);
2043 str = str.replace(/ð/gi, `-`);
2044 str = str.replace(/¹/gi, `-`);
2045 str = str.replace(/ý/gi, `y`);
2046 str = str.replace(/¼/gi, `-`);
2047 str = str.replace(/¾/gi, `-`);
2048 str = str.replace(/£/gi, `-`);
2049 str = str.replace(/þ/gi, `-`);
2050 str = str.replace(/ /gi, `-`);
2051 str = str.replace(/%/gi, ``);
2052 str = str.replace(//gi, ``); // eslint-disable-line no-control-regex
2053 return str.toLowerCase();
2054}
2055
2056function makeImageUrl(id, title) {
2057 // @ts-ignore
2058 const bucket = parseInt(id / 10000);
2059 return [
2060 BASE_URL,
2061 `/m/`,
2062 bucket.toString(),
2063 `/`,
2064 id,
2065 `/`, // TODO: Higher res
2066 slugize(title),
2067 `.jpg`,
2068 ].join(``);
2069}
2070
2071function makeApiBody(params) {
2072 return params
2073 .map((x) =>
2074 x
2075 .toString() // can be number
2076 // TODO: Replace with proper encoding, but beware that we can't encode all characters!
2077 .replace(/;/g, `%3B`)
2078 .replace(/:/g, `%3A`)
2079 .replace(/,/g, `%2C`)
2080 .replace(/&/g, `%26`)
2081 .replace(/\[]/g, `%5B%5D`)
2082 .replace(/=/g, `%3D`)
2083 .replace(/@/g, `%40`)
2084 )
2085 .map((x) => encodeURI(`vars[]=`) + x)
2086 .concat([`texto_search=`])
2087 .join(`&`);
2088}
2089
2090async function makeApiCall({
2091 shopId = SHOPS.BIKEINN,
2092 brandId, // 212: SRAM, 159: POC, ...
2093 perPage = PER_PAGE, // TODO: Make it work with different per pages
2094 cursor = 0,
2095}) {
2096 const body = makeApiBody([
2097 `id_tienda=${shopId}@fecha_descatalogado=[now/d-*]`,
2098 `atributos_e=5091,6017`, // FIXME: Explain
2099 [
2100 // Lot of following probably not needed // TODO: Replace when introducing test suite
2101 `model.eng`,
2102 `id_marca`,
2103 `sostenible`,
2104 `productes.talla2`,
2105 `productes.talla_usa`,
2106 `productes.talla_jp`,
2107 `productes.talla_uk`,
2108 `tres_sesenta`,
2109 `atributos_padre.atributos.id_atribut_valor`,
2110 `productes.v360`,
2111 `productes.v180`,
2112 `productes.v90`,
2113 `productes.v30`,
2114 `productes.exist`,
2115 `productes.stock_reservat`,
2116 `productes.pmp`,
2117 `productes.id_producte`,
2118 `productes.color`,
2119 `productes.referencia`,
2120 `productes.brut`,
2121 `productes.desc_brand`,
2122 `image_created`,
2123 `id_modelo`,
2124 `familias.eng`,
2125 `familias.eng`,
2126 `familias.id_familia`,
2127 `familias.subfamilias.eng`,
2128 `familias.subfamilias.eng`,
2129 `familias.subfamilias.id_tienda`,
2130 `familias.subfamilias.id_subfamilia`,
2131 `productes.talla`,
2132 `productes.baja`,
2133 `productes.rec`,
2134 `precio_win_54`,
2135 `productes.sellers.id_seller`,
2136 `productes.sellers.precios_paises.precio`,
2137 `productes.sellers.precios_paises.id_pais`,
2138 `fecha_descatalogado`,
2139 `marca`,
2140 ].join(`;`),
2141 `precio_win_54;asc`,
2142 perPage,
2143 `productos`,
2144 `search`,
2145 `marca=${brandId}`,
2146 cursor,
2147 ]);
2148
2149 const resRaw = await fetch(
2150 `https://www.tradeinn.com/index.php` +
2151 `?action=get_info_elastic_listado` +
2152 `&id_tienda=${shopId}` + // TODO: Selectable
2153 `&idioma=eng`,
2154 {
2155 method: `POST`,
2156 headers: {
2157 // From my testing, the following header are required
2158 Accept: `application/json, text/javascript, */*; q=0.01`,
2159 "Accept-Language": `en-US,en`,
2160 "Content-Type": `application/x-www-form-urlencoded; charset=UTF-8`,
2161 Cookie: `id_pais=${COUNTRIES.Germany};`, // required to get `item.precio_win`
2162 },
2163 body,
2164 }
2165 );
2166
2167 const resJson = await resRaw.json();
2168 return resJson;
2169}
2170
2171async function enqueueInitial(mode, crawler, shop) {
2172 if (mode === MODE.FULL) {
2173 await crawler.addRequests([
2174 {
2175 userData: { label: LABEL.INDEX },
2176 uniqueKey: LABEL.INDEX,
2177 url: `https://dummy.com`,
2178 },
2179 ]);
2180 } else if (mode === MODE.TEST) {
2181 const query = {
2182 shopId: SHOPS[shop],
2183 brandId: BRANDS.POC,
2184 };
2185 await crawler.addRequests([
2186 {
2187 userData: { label: LABEL.PRODUCTS, query },
2188 uniqueKey: JSON.stringify(query),
2189 url: `https://dummy.com`,
2190 },
2191 ]);
2192 } else if (mode === MODE.CURATED) {
2193 const brands = [
2194 BRANDS.POC,
2195 BRANDS.Sram,
2196 BRANDS[`3M`],
2197 BRANDS.Assos,
2198 BRANDS.BBB,
2199 BRANDS[`Bike Workx`],
2200 BRANDS[`Bike Yoke`],
2201 BRANDS[`Black Diamond`],
2202 BRANDS.Blackburn,
2203 BRANDS.Bombtrack,
2204 BRANDS.Camelbak,
2205 BRANDS.Chrome,
2206 BRANDS.Crankbrothers,
2207 BRANDS.Dakine,
2208 BRANDS.Douchebags,
2209 BRANDS[`DT Swiss`],
2210 BRANDS.Dynafit,
2211 BRANDS[`E-thirteen`],
2212 BRANDS.Ergon,
2213 BRANDS.Evoc,
2214 BRANDS[`F-Lite`],
2215 BRANDS.Fidlock,
2216 BRANDS[`Finish Line`],
2217 BRANDS[`Five Ten`],
2218 BRANDS.Fizik,
2219 BRANDS.Flashmer,
2220 BRANDS.Fox,
2221 BRANDS.Fuji,
2222 BRANDS.Fulcrum,
2223 BRANDS.FullGas,
2224 BRANDS[`G-Star`],
2225 BRANDS.Giro,
2226 BRANDS.GoPro,
2227 BRANDS.Graff,
2228 BRANDS.Hope,
2229 BRANDS.HT,
2230 BRANDS.Icebreaker,
2231 BRANDS.iXS,
2232 BRANDS.Kask,
2233 BRANDS.Leatt,
2234 BRANDS.Lezyne,
2235 BRANDS[`Lizard Skins`],
2236 BRANDS.Magura,
2237 BRANDS.Maxxis,
2238 BRANDS.MET,
2239 BRANDS.Michelin,
2240 BRANDS[`Muc Off`],
2241 BRANDS.Nike,
2242 BRANDS.Niner,
2243 BRANDS.Oakley,
2244 BRANDS.Ortlieb,
2245 BRANDS.Osprey,
2246 BRANDS[`O´neill`],
2247 BRANDS[`Park Tool`],
2248 BRANDS.POC,
2249 BRANDS[`Race Face`],
2250 BRANDS.Ritchey,
2251 BRANDS.RockShox,
2252 BRANDS.Schwalbe,
2253 BRANDS.Scott,
2254 BRANDS[`Sea To Summit`],
2255 BRANDS[`Shock Doctor`],
2256 BRANDS.Smith,
2257 BRANDS.Specialized,
2258 BRANDS.SQlab,
2259 BRANDS[`SR Suntour`],
2260 BRANDS.Sram,
2261 BRANDS.SunRace,
2262 BRANDS[`Super B`],
2263 BRANDS[`Superior Bikes`],
2264 BRANDS[`The North Face`],
2265 BRANDS.Thule,
2266 BRANDS.Topeak,
2267 BRANDS.TranzX,
2268 BRANDS[`Troy Lee Designs`],
2269 BRANDS.Tubolito,
2270 BRANDS.Tufo,
2271 BRANDS.Vittoria,
2272 BRANDS.Wahoo,
2273 BRANDS.Wolfpack,
2274 BRANDS.WTB,
2275 BRANDS.Xpedo,
2276 BRANDS.Zefal,
2277 BRANDS.Zipp,
2278 ];
2279 for (const brandId of brands) {
2280 const query = { shopId: SHOPS[shop], brandId };
2281 await crawler.addRequests([
2282 {
2283 userData: { label: LABEL.PRODUCTS, query },
2284 uniqueKey: JSON.stringify(query),
2285 url: `https://dummy.com`,
2286 },
2287 ]);
2288 }
2289 }
2290}
2291
2292const router = createBasicRouter();
2293
2294router.addHandler(LABEL.INDEX, async ({ request, $ }) => {
2295 // FIXME
2296});
2297
2298router.addHandler(LABEL.PRODUCTS, async ({ request, crawler }) => {
2299 console.log(`[handleCategory]`, JSON.stringify(request.userData));
2300
2301 const res = await makeApiCall(request.userData.query);
2302
2303 const total = res.total.value;
2304 console.log(`[handleCategory] total`, total);
2305 if (!request.userData.query.cursor) {
2306 // not present or 0
2307 for (let i = 0; i <= total; i += PER_PAGE) {
2308 // skip first page, that is already handled
2309 const query = { ...request.userData.query, cursor: i };
2310 console.log(`[handleCategory] enqueuing pages`, JSON.stringify(query)); // FIXME
2311 void crawler.addRequests([
2312 {
2313 userData: { label: LABEL.PRODUCTS, query },
2314 uniqueKey: JSON.stringify(query),
2315 url: `https://dummy.com`,
2316 },
2317 ]);
2318 }
2319 }
2320
2321 const items = res.id_modelos;
2322
2323 const products = [];
2324 for (const item of items) {
2325 // ID
2326 const pid = item.id_modelo;
2327
2328 // Title & URL
2329 const title =
2330 item.marca + ` ` + (item.nombre_modelo_eng ?? item.nombre_modelo);
2331 const url = `https://www.tradeinn.com/bikeinn/en/${slugize(
2332 title
2333 )}/${pid}/p`;
2334
2335 // Prices
2336 let currentPrice = item.precio_win;
2337 if (!currentPrice) {
2338 for (const product of item.productes) {
2339 const discontinued = product.baja; // 0 = available, 1 = discontinued
2340 if (discontinued !== `0`) continue;
2341 if (!product.sellers?.length) continue;
2342 if (!product.sellers[0]) continue;
2343 let seller = product.sellers.find((x) => x.id_seller === 1); // Seller #1 has preffered price, taken from TradeInn FE code
2344 if (!seller) seller = product.sellers[0]; // TODO: Maybe find one with best price?
2345 currentPrice = seller.precio_producte;
2346 if (currentPrice) break;
2347 }
2348 }
2349 if (!currentPrice) {
2350 console.log(`[handleCategory] currentPrice not found`);
2351 debugger;
2352 }
2353
2354 const originalPrice = parseFloat(item.productes?.[0]?.rec);
2355 if (!originalPrice) {
2356 console.log(`[handleCategory] originalPrice not found`);
2357 }
2358
2359 // // Inbox
2360 // const products = item["productes"] ?? item["_source"]["productes"];
2361 // // TODO: Explain
2362 // const categories = item["familias"] ?? item["_source"]["familias"];
2363 // data[i_mc]["_source"]["precio_win_"+info_pais["id_pais"]];
2364 // data[i_mc]["_source"]["model"]["eng"];
2365
2366 // Other
2367 const _category = item.familias
2368 .map((x) => `${x.eng} > ${x.subfamilias.eng}`)
2369 .join(` | `);
2370 const img = makeImageUrl(pid, title);
2371
2372 // Derived
2373 const discount = originalPrice
2374 ? Math.round((1 - currentPrice / originalPrice) * 100)
2375 : undefined;
2376
2377 const discountAbs = originalPrice
2378 ? originalPrice - currentPrice
2379 : undefined;
2380
2381 const product = {
2382 pid,
2383 url,
2384 name: title,
2385 img,
2386 currentPrice,
2387 originalPrice, // string
2388 currency: `EUR`,
2389
2390 // Derived
2391 _discount: discount,
2392 };
2393
2394 // console.log("⬇⬇⬇")
2395 // console.log(JSON.stringify(product, null, 2))
2396 // console.log("⬆⬆⬆")
2397 products.push(product);
2398 }
2399 await save(products);
2400});
2401
2402void Actor.main(async () => {
2403 const input = await Actor.getInput();
2404 const {
2405 debug = false,
2406 mode = MODE.CURATED, // FIXME: Full
2407 shop = SHOP.BIKEINN,
2408 ...rest
2409 } = input ?? {};
2410 await init({ actorNameOverride: `tradeinn-com` }, rest);
2411 const crawler = new BasicCrawler({
2412 useSessionPool: true,
2413 requestHandler: router,
2414 });
2415 await enqueueInitial(mode, crawler, shop);
2416 await crawler.run();
2417});
package.json
1{
2 "name": "tradeinn-tradeinn-com-scraper",
3 "description": "Scrapes products titles, prices, images and availability. Does NOT scrape product details.",
4 "type": "module",
5 "scripts": {
6 "start": "node ./main.js",
7 "push-to-apify-platform": "npx apify push"
8 },
9 "dependencies": {
10 "apify3": "npm:apify@^3.0.2",
11 "crawlee": "*",
12 "node-fetch": "*",
13 "pg": "*",
14 "pg-connection-string": "*",
15 "dotenv": "*",
16 "find-config": "*",
17 "@elastic/elasticsearch": "*",
18 "filenamify": "*"
19 },
20 "apify": {
21 "title": "Tradeinn (tradeinn.com) scraper",
22 "description": "Scrapes products titles, prices, images and availability. Does NOT scrape product details.",
23 "isPublic": true,
24 "isDeprecated": false,
25 "isAnonymouslyRunnable": true,
26 "notice": "",
27 "pictureUrl": "",
28 "seoTitle": "",
29 "seoDescription": "",
30 "categories": [
31 "ECOMMERCE"
32 ]
33 }
34}
.actor/logo.png
_utils/common.js
1import { createHash } from 'crypto'
2import os from "os"
3import path from "path"
4// eslint-disable-next-line @apify/apify-actor/no-forbidden-node-internals
5import fs from "fs"
6import pg from "pg"
7import pgConnectionString from 'pg-connection-string'
8import { config } from 'dotenv'
9import findConfig from "find-config"
10import { Client as ElasticClient } from "@elastic/elasticsearch"
11import filenamify from 'filenamify'
12import { Dataset } from 'crawlee'
13
14config({ path: findConfig(`.env`) })
15
16const elasticIndexName = `actors-monorepo-shops`
17
18const globalLogsProps = {
19 __NODE_STARTED: new Date().toISOString(),
20}
21
22let actorName
23let pgClient
24let pgClientNormalized
25let elasticClient
26export async function init ({ actorNameOverride }, restInput) {
27 parseEnvFromInput(restInput)
28
29 if (os.platform() === `darwin`) {
30 const filePath = process.argv[1] // ~/Projects/apify-actors-monorepo/actors/foo.ts
31 const basename = path.basename(filePath) // foo.ts
32 actorName = actorNameOverride ?? basename.split(`.`)[0] // foo
33 const gitBranch = fs.readFileSync(path.join(process.cwd(), `..`, `.git/HEAD`), `utf8`)
34 .split(` `)[1]
35 .trim()
36 .replace(`refs/heads/`, ``)
37 const gitCommit = fs.readFileSync(path.join(process.cwd(), `..`, `.git/refs/heads/${gitBranch}`), `utf8`)
38 const gitCommitShort = gitCommit.substring(0, 7)
39 globalLogsProps.__GIT_COMMIT = gitCommitShort
40 }
41
42 if (process.env.APIFY_IS_AT_HOME) {
43 actorName = actorNameOverride ?? process.env.APIFY_ACTOR_ID // Name would be better, but it's not in ENV
44 }
45
46 /* ELASTIC */
47 /* ======= */
48 if (process.env.ELASTIC_CLOUD_ID) {
49 elasticClient = new ElasticClient({
50 cloud: { id: process.env.ELASTIC_CLOUD_ID },
51 auth: { apiKey: process.env.ELASTIC_CLOUD_API_KEY },
52 })
53
54 // const mapping = await elasticClient.indices.getMapping({ index: actorName })
55
56 // eslint-disable-next-line no-inner-declarations
57 async function enforceIndexMapping () {
58 const doesIndexExist = await elasticClient.indices.exists({ index: elasticIndexName })
59 if (!doesIndexExist) await elasticClient.indices.create({ index: elasticIndexName })
60 await elasticClient.indices.putMapping({
61 index: elasticIndexName,
62 body: {
63 properties: {
64 _discount: { type: `float` },
65 originalPrice: { type: `float` },
66 currentPrice: { type: `float` },
67 },
68 },
69 })
70 }
71
72 try {
73 await enforceIndexMapping()
74 } catch (err) {
75 if (err.message.includes(`cannot be changed from type`)) {
76 console.log(`Elastic index ${elasticIndexName} already exists with incorrect mappings. As existing mapping cannot be changed, index will be deleted and recreated.`)
77 await elasticClient.indices.delete({ index: elasticIndexName })
78 await enforceIndexMapping()
79 }
80 }
81 }
82
83 /* POSTGRESQL */
84 /* ========== */
85 if (process.env.PG_CONNECTION_STRING) {
86 const pgConfig = pgConnectionString(process.env.PG_CONNECTION_STRING)
87 // const pgPool = new pg.Pool(pgConfig)
88
89 pgClient = new pg.Client(pgConfig)
90 await pgClient.connect()
91
92 // Check if table exists and have proper columns
93 const { rows: tables } = await pgClient.query(`
94 SELECT table_name
95 FROM information_schema.tables
96 WHERE table_schema = 'public'
97 `)
98
99 // eslint-disable-next-line camelcase
100 const tableExists = tables.some(({ table_name }) => table_name === process.env.PG_DATA_TABLE)
101 if (!tableExists) {
102 throw new Error(`Table ${process.env.PG_DATA_TABLE} does not exist in database ${pgConfig.database}`)
103 }
104
105 // TODO: Handle pgClient closing
106 }
107
108 if (process.env.PG_CONNECTION_STRING_NORMALIZED) {
109 const pgConfig = pgConnectionString(process.env.PG_CONNECTION_STRING_NORMALIZED)
110
111 pgClientNormalized = new pg.Client(pgConfig)
112 await pgClientNormalized.connect()
113
114 // Check if table exists and have proper columns
115 const { rows: tables } = await pgClientNormalized.query(`
116 SELECT table_name
117 FROM information_schema.tables
118 WHERE table_schema = 'public'
119 `)
120
121 // eslint-disable-next-line camelcase
122 const tableMainExists = tables.some(({ table_name }) => table_name === process.env.PG_DATA_TABLE)
123 // eslint-disable-next-line camelcase
124 const tablePricesExists = tables.some(({ table_name }) => table_name === process.env.PG_DATA_PRICE_TABLE)
125 if (!tableMainExists) throw new Error(`Table ${process.env.PG_DATA_TABLE} does not exist in database ${pgConfig.database}`)
126 if (!tablePricesExists) throw new Error(`Table ${process.env.PG_DATA_PRICE_TABLE} does not exist in database ${pgConfig.database}`)
127
128 // TODO: Handle pgClient closing
129 }
130}
131
132// inspired by @drobnikj
133// TODO: Similar, but less obfuscated for easier debugging
134export const createUniqueKeyFromUrl = (url) => {
135 const hash = createHash(`sha256`)
136 const cleanUrl = url.split(`://`)[1] // Remove protocol
137 hash.update(cleanUrl)
138 return hash.digest(`hex`)
139}
140
141/**
142 *
143 * @param {Date} datetime
144 * @return {Promise<void>}
145 */
146export const sleepUntil = async (datetime) => {
147 const now = new Date()
148 const difference = datetime - now
149 if (difference > 0) {
150 return new Promise((resolve) => {
151 setTimeout(resolve, difference)
152 })
153 }
154 return Promise.resolve()
155}
156
157export function parsePrice (string) {
158 let amount, currency
159 const noText = string.replace(/[^\d,.]/g, ``)
160 const decimals = noText.match(/([,.])(\d{2})$/)
161 if (decimals) {
162 const decimalSeparator = decimals[1]
163 // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
164 const decimalAmount = decimals[2]
165 amount = parseInt(noText.split(decimalSeparator)[0])
166 } {
167 const justNumbers = noText.replace(/[,.]/g, ``)
168 amount = parseInt(justNumbers)
169 }
170 return { amount, currency }
171}
172
173export function toNumberOrNull (str) {
174 // TODO: Handle better, but only after adding test
175 if (str === undefined) return null
176 if (str === null) return null
177 if (str === ``) return null
178 const num = Number(str)
179 if (Number.isNaN(num)) return null
180 return num
181}
182
183export async function save (objs) {
184 if (!Array.isArray(objs)) objs = [objs]
185 if (objs.length === 0) return
186
187 const objsExtended = objs.map((obj) => {
188 const objExtended = {
189 ...obj,
190 actorName,
191 ...globalLogsProps,
192 // __NODE_VERSION: global.process.versions.node,
193 // __NODE_UPTIME: global.process.uptime().toFixed(2), // seconds, 2 decimals
194 }
195 // if run on Apify
196 if (process.env.APIFY_IS_AT_HOME) {
197 objExtended.__APIFY_ACTOR_ID = process.env.APIFY_ACTOR_ID
198 objExtended.__APIFY_ACTOR_RUN_ID = process.env.APIFY_ACTOR_RUN_ID
199 objExtended.__APIFY_ACTOR_BUILD_ID = process.env.APIFY_ACTOR_BUILD_ID
200 objExtended.__APIFY_ACTOR_BUILD_NUMBER = process.env.APIFY_ACTOR_BUILD_NUMBER
201 objExtended.__APIFY_ACTOR_TASK_ID = process.env.APIFY_ACTOR_TASK_ID
202 if (!process.env.APIFY_DONT_STORE_IN_DATASET) void Dataset.pushData(obj)
203 }
204 return objExtended
205 })
206 // if runs on local machine (MacOS)
207 if (os.platform() === `darwin`) {
208 const cwd = process.cwd() // ~/Projects/apify-actors-monorepo/actors
209 const storageDir = path.join(cwd, `${actorName}.storage`) // ~/Projects/apify-actors-monorepo/actors/foo.storage
210 if (!fs.existsSync(storageDir)) fs.mkdirSync(storageDir)
211 const dataDir = path.join(storageDir, `data`) // ~/Projects/apify-actors-monorepo/actors/foo.storage/data
212 if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir)
213 for (const objExtended of objsExtended) {
214 const id = objExtended.id ?? objExtended.pid // ?? uuidv4()
215 const fileName = `${filenamify(id)}.json`
216 const dataFilePath = path.join(dataDir, fileName) // ~/Projects/apify-actors-monorepo/actors/foo.storage/data/foo.json
217 fs.writeFileSync(dataFilePath, JSON.stringify(objExtended, null, 2))
218 }
219 }
220
221 if (pgClient) {
222 const objsPg = objs.map((obj) => ({
223 ...obj,
224 // TODO: This is becoming not nice, and not clear
225 shop: actorName,
226 scrapedAt: new Date().toISOString().split(`T`)[0],
227 }))
228
229 const columns = getColumns(objsPg)
230 const values = getValues(objsPg)
231 const queryString = `
232 INSERT INTO public."${process.env.PG_DATA_TABLE}" (${columns})
233 VALUES (${values})
234 `
235 try {
236 const { rowCount } = await pgClient.query(queryString)
237 console.log(`[save] saved to database: ${JSON.stringify(rowCount)}`)
238 } catch (err) {
239 if (err.message.includes(`violates unique constraint`)) console.warn(`PostgresSQL: violates unique constraint`)
240 else throw err
241 }
242 }
243
244 // Only make sense for HlidacShopu
245 if (pgClientNormalized) {
246 const objsPgData = objs.map((obj) => ({
247 shop: actorName,
248 pid: obj.pid,
249 name: obj.name,
250 url: obj.url,
251 img: obj.img,
252 }))
253
254 const objsPgDataPrice = objs.map((obj) => ({
255 shop: actorName,
256 pid: obj.pid,
257 scrapedAt: new Date().toISOString().split(`T`)[0],
258 currentPrice: obj.currentPrice,
259 originalPrice: obj.originalPrice,
260 inStock: obj.inStock,
261 }))
262
263 const queryString = `
264 INSERT INTO public."${process.env.PG_DATA_TABLE}" (${getColumns(objsPgData)})
265 VALUES (${getValues(objsPgData)})
266 ON CONFLICT DO NOTHING
267 `
268 try {
269 const { rowCount } = await pgClientNormalized.query(queryString)
270 console.log(`[save] saved to database (data): ${JSON.stringify(rowCount)}`)
271 } catch (err) {
272 if (err.message.includes(`violates unique constraint`)) console.warn(`PostgresSQL: violates unique constraint`)
273 else throw err
274 }
275
276 const queryStringPrice = `
277 INSERT INTO public."${process.env.PG_DATA_PRICE_TABLE}" (${getColumns(objsPgDataPrice)})
278 VALUES (${getValues(objsPgDataPrice)})
279 ON CONFLICT DO NOTHING
280 `
281 try {
282 const { rowCount } = await pgClientNormalized.query(queryStringPrice)
283 console.log(`[save] saved to database (price): ${JSON.stringify(rowCount)}`)
284 } catch (err) {
285 if (err.message.includes(`violates unique constraint`)) console.warn(`PostgresSQL: violates unique constraint`)
286 else throw err
287 }
288 }
289
290 if (elasticClient) {
291 // .index creates or updates the document
292 // .create creates a new document if it doesn't exist, 409 if it does
293 // try {
294 // const res = await elasticClient.index({
295 // index: `actors-monorepo-shops`, // TODO: Consider using actorName
296 // id, // foo-bar
297 // document: objExtended, // {...}
298 // })
299 // } catch (err) {
300 // // https://discuss.elastic.co/t/elasticsearch-503-ok-false-message-the-requested-deployment-is-currently-unavailable/200583
301 // if (err.message.includes(`requested resource is currently unavailable`)) console.log(`Elasticsearch is unavailable, skipping, but not aborting`)
302 // else throw err
303 // }
304 }
305}
306
307function getColumns (objs) {
308 return Object.keys(objs[0]).map((key) => `"${key}"`).join(`, `)
309}
310
311function getValues (objs) {
312 return objs.map(objPg => Object.values(objPg).map((value) => {
313 // escape strings to prevent SQL injection
314 if (typeof value === `string`) return `'${value.replace(/'/g, `''`)}'`
315 // convert to DB specific null
316 if (typeof value === `undefined` || value === null) return `NULL`
317 return value
318 }).join(`, `)).join(`), (`)
319}
320
321export function parseEnvFromInput (input) {
322 const env = {}
323 for (const key in input) {
324 if (key === key.toUpperCase()) env[key] = input[key]
325 }
326 console.log(`[parseEnvFromInput] ${JSON.stringify(env)}`)
327 Object.assign(process.env, env)
328}
Pricing
Pricing model
Pay per usageThis Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage.