Tradeinn (tradeinn.com) scraper
Go to Store
This Actor is unavailable because the developer has decided to deprecate it. Would you like to try a similar Actor instead?
See alternative ActorsTradeinn (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