Tradeinn (tradeinn.com) scraper avatar

Tradeinn (tradeinn.com) scraper

Deprecated
View all Actors
This Actor is deprecated

This Actor is unavailable because the developer has decided to deprecate it. Would you like to try a similar Actor instead?

See alternative Actors
Tradeinn (tradeinn.com) scraper

Tradeinn (tradeinn.com) scraper

strajk/tradeinn-tradeinn-com-scraper

Scrapes products titles, prices, images and availability. Does NOT scrape product details.

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}
Developer
Maintained by Community
Categories