├── vercel.json ├── .gitignore ├── package.json └── server.js /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "server.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "server.js" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /.env 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vercel 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pricechecker", 3 | "version": "1.0.0", 4 | "description": "Compare PC Part Prices Instantly", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^1.9.0", 14 | "axios-cookiejar-support": "^5.0.5", 15 | "cheerio": "^1.0.0", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.5.0", 18 | "express": "^4.21.2", 19 | "express-rate-limit": "^7.5.0", 20 | "express-sse": "^0.5.3", 21 | "mongodb": "^6.16.0", 22 | "node-cron": "^4.0.6", 23 | "nodemon": "^3.1.9", 24 | "puppeteer": "^22.11.0", 25 | "tough-cookie": "^5.1.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const axios = require("axios"); 4 | const cheerio = require("cheerio"); 5 | const crypto = require("crypto"); 6 | const rateLimit = require("express-rate-limit"); 7 | 8 | const app = express(); 9 | app.set("trust proxy", 1); 10 | const PORT = process.env.PORT || 3000; 11 | app.use(cors()); 12 | 13 | const limiter = rateLimit({ 14 | windowMs: 1 * 60 * 1000, 15 | max: 10, 16 | handler: (req, res) => { 17 | res.status(429).json({ 18 | success: false, 19 | message: 20 | "You're sending too many requests. Please wait a minute and try again.", 21 | }); 22 | }, 23 | }); 24 | 25 | app.use(limiter); 26 | // Scraper for StarTech 27 | const scrapeStarTech = async (product) => { 28 | try { 29 | const response = await axios.get( 30 | `https://www.startech.com.bd/product/search?search=${product}` 31 | // { 32 | // headers: { 33 | // "User-Agent": 34 | // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", 35 | // Accept: 36 | // "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 37 | // "Accept-Language": "en-US,en;q=0.9", 38 | // Referer: "https://www.startech.com.bd/", 39 | // Connection: "keep-alive", 40 | // "Upgrade-Insecure-Requests": "1", 41 | // }, 42 | // } 43 | ); 44 | 45 | const $ = cheerio.load(response.data); 46 | const products = []; 47 | const logo = $(".brand img").attr("src") || "logo not found"; 48 | $(".p-item").each((index, element) => { 49 | const name = 50 | $(element).find(".p-item-name").text().trim() || "Name not found"; 51 | const price = 52 | $(element).find(".price-new").text().trim() || 53 | $(element).find(".p-item-price").text().trim() || 54 | "Out Of Stock"; 55 | 56 | const img = 57 | $(element).find(".p-item-img img").attr("src") || "Image not found"; 58 | const link = 59 | $(element).find(".p-item-img a").attr("href") || "Link not found"; 60 | const id = crypto.randomUUID(); 61 | products.push({ id, name, price, img, link }); 62 | }); 63 | 64 | return { products, logo }; 65 | } catch (error) { 66 | console.error("Error scraping StarTech:", error); 67 | return { 68 | products: [], 69 | logo: "logo not found", 70 | }; 71 | } 72 | }; 73 | 74 | // Scraper for TechLand 75 | const scrapeTechLand = async (product) => { 76 | try { 77 | const response = await axios.get( 78 | `https://www.techlandbd.com/index.php?route=product/search&search=${product}` 79 | ); 80 | const $ = cheerio.load(response.data); 81 | const products = []; 82 | const logo = $("#logo img").attr("src") || "logo not found"; 83 | $(".product-layout").each((index, element) => { 84 | const name = $(element).find(".name").text().trim() || "Name not found"; 85 | const price = 86 | $(element).find(".price-new").text().trim() || "Out Of Stock"; 87 | const img = 88 | $(element).find(".image img").attr("src") || "Image not found"; 89 | const link = 90 | $(element).find(".product-img").attr("href") || "Link not found"; 91 | 92 | const id = crypto.randomUUID(); 93 | products.push({ id, name, price, img, link }); 94 | }); 95 | 96 | return { products, logo }; 97 | } catch (error) { 98 | console.error("Error scraping TechLand:", error); 99 | return { 100 | products: [], 101 | logo: "logo not found", 102 | }; 103 | } 104 | }; 105 | const scrapeRyans = async (product) => { 106 | try { 107 | const response = await axios.get( 108 | `https://www.ryans.com/search?q=${product}` 109 | ); 110 | const $ = cheerio.load(response.data); 111 | const products = []; 112 | const logo = $(".main-logo img").attr("src") || "logo not found"; 113 | $(".category-single-product").each((index, element) => { 114 | const name = 115 | $(element).find(".card-text a").text().trim() || "Name not found"; 116 | const price = $(element).find(".pr-text").text().trim() || "Out Of Stock"; 117 | const img = 118 | $(element).find(".image-box img").attr("src") || "Image not found"; 119 | const link = 120 | $(element).find(".image-box a").attr("href") || "Link not found"; 121 | 122 | const id = crypto.randomUUID(); 123 | products.push({ id, name, price, img, link }); 124 | }); 125 | 126 | return { products, logo }; 127 | } catch (error) { 128 | console.error("Error scraping TechLand:", error); 129 | return { 130 | products: [], 131 | logo: "logo not found", 132 | }; 133 | } 134 | }; 135 | const scrapeBinary = async (product) => { 136 | try { 137 | const response = await axios.get( 138 | `https://www.binarylogic.com.bd/search/${product}` 139 | ); 140 | const $ = cheerio.load(response.data); 141 | const products = []; 142 | const logo = $(".homepage_two__log svg").attr("src") || "logo not found"; 143 | $(".single_product").each((index, element) => { 144 | const name = 145 | $(element).find(".p-item-name").text().trim() || "Name not found"; 146 | const price = 147 | $(element).find(".current_price").text().trim() || "Out Of Stock"; 148 | const img = 149 | $(element).find(".p-item-img img").attr("src") || "Image not found"; 150 | const link = 151 | $(element).find(".p-item-img a").attr("href") || "Link not found"; 152 | 153 | const id = crypto.randomUUID(); 154 | products.push({ id, name, price, img, link }); 155 | }); 156 | 157 | return { products, logo }; 158 | } catch (error) { 159 | console.error("Error scraping TechLand:", error); 160 | return { 161 | products: [], 162 | logo: "logo not found", 163 | }; 164 | } 165 | }; 166 | const scrapePcHouse = async (product) => { 167 | try { 168 | const response = await axios.get( 169 | `https://www.pchouse.com.bd/index.php?route=product/search&search=${product}` 170 | ); 171 | const $ = cheerio.load(response.data); 172 | const products = []; 173 | const logo = $("#logo img").attr("src") || "logo not found"; 174 | $(".product-layout").each((index, element) => { 175 | const name = $(element).find(".name").text().trim() || "Name not found"; 176 | const price = 177 | $(element).find(".price-new").text().trim() || "Out Of Stock"; 178 | const img = 179 | $(element).find(".product-img img").attr("src") || "Image not found"; 180 | const link = 181 | $(element).find(".product-img").attr("href") || "Link not found"; 182 | 183 | const id = crypto.randomUUID(); 184 | products.push({ id, name, price, img, link }); 185 | }); 186 | 187 | return { products, logo }; 188 | } catch (error) { 189 | console.error("Error scraping TechLand:", error); 190 | return { 191 | products: [], 192 | logo: "logo not found", 193 | }; 194 | } 195 | }; 196 | 197 | const scrapeUltraTech = async (product) => { 198 | try { 199 | const response = await axios.get( 200 | `https://www.ultratech.com.bd/index.php?route=product/search&search=${product}` 201 | ); 202 | const $ = cheerio.load(response.data); 203 | const products = []; 204 | const logo = $("#logo img").attr("src") || "logo not found"; 205 | $(".product-layout").each((index, element) => { 206 | const name = $(element).find(".name").text().trim() || "Name not found"; 207 | const price = 208 | $(element).find(".price-new").text().trim() || "Out Of Stock"; 209 | const img = 210 | $(element).find(".product-img img").attr("src") || "Image not found"; 211 | const link = 212 | $(element).find(".product-img").attr("href") || "Link not found"; 213 | 214 | const id = crypto.randomUUID(); 215 | products.push({ id, name, price, img, link }); 216 | }); 217 | 218 | return { products, logo }; 219 | } catch (error) { 220 | console.error("Error scraping TechLand:", error); 221 | return { 222 | products: [], 223 | logo: "logo not found", 224 | }; 225 | } 226 | }; 227 | // const scrapeVibeGaming = async (product) => { 228 | // try { 229 | // const response = await axios.get( 230 | // `https://vibegaming.com.bd/?s=${product}&post_type=product`, 231 | // { 232 | // headers: { 233 | // "X-Forwarded-For": "119.30.41.88", 234 | // }, 235 | // } 236 | // ); 237 | 238 | // const $ = cheerio.load(response.data); 239 | // const products = []; 240 | // const logo = $(".sticky-logo").attr("data-src") || "logo not found"; 241 | // $(".product").each((index, element) => { 242 | // const name = 243 | // $(element).find(".product-name").text().trim() || "Name not found"; 244 | 245 | // let lowestPrice = 0; 246 | // const priceElements = $(element).find(".amount"); 247 | 248 | // if (priceElements.length > 0) { 249 | // let min = Infinity; 250 | // priceElements.each((i, el) => { 251 | // const text = $(el) 252 | // .text() 253 | // .replace(/[^\d.]/g, ""); 254 | // const value = parseFloat(text); 255 | // if (!isNaN(value) && value < min) { 256 | // min = value; 257 | // } 258 | // }); 259 | // if (min !== Infinity) { 260 | // lowestPrice = `৳${min.toLocaleString()}`; 261 | // } 262 | // } 263 | 264 | // const img = 265 | // $(element).find(".no-back-image img").attr("data-src") || 266 | // "Image not found"; 267 | // const link = 268 | // $(element).find(".thumbnail-wrapper a").attr("href") || 269 | // "Link not found"; 270 | // const id = crypto.randomUUID(); 271 | 272 | // products.push({ id, name, price: lowestPrice, img, link }); 273 | // }); 274 | 275 | // return { products, logo }; 276 | // } catch (error) { 277 | // console.error("Error scraping TechLand:", error); 278 | // return { 279 | // products: [], 280 | // logo: "logo not found" 281 | // }; 282 | // } 283 | // }; 284 | const scrapeSkyLand = async (product) => { 285 | try { 286 | const response = await axios.get( 287 | `https://www.skyland.com.bd/index.php?route=product/search&search=${product}` 288 | ); 289 | const $ = cheerio.load(response.data); 290 | const products = []; 291 | const logo = $("#logo img").attr("src") || "logo not found"; 292 | $(".product-layout").each((index, element) => { 293 | const name = $(element).find(".name").text().trim() || "Name not found"; 294 | const price = 295 | $(element).find(".price-new").text().trim() || "Out Of Stock"; 296 | const img = 297 | $(element).find(".product-img img").attr("src") || "Image not found"; 298 | const link = 299 | $(element).find(".product-img").attr("href") || "Link not found"; 300 | 301 | const id = crypto.randomUUID(); 302 | products.push({ id, name, price, img, link }); 303 | }); 304 | 305 | return { products, logo }; 306 | } catch (error) { 307 | console.error("Error scraping TechLand:", error); 308 | return { 309 | products: [], 310 | logo: "logo not found", 311 | }; 312 | } 313 | }; 314 | const scrapePotakaIT = async (product) => { 315 | try { 316 | const response = await axios.get( 317 | `https://www.potakait.com/index.php?route=product/search&search=${product}` 318 | // { 319 | // headers: { 320 | // "User-Agent": 321 | // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", 322 | // Accept: "application/json, text/plain, */*", 323 | // "Accept-Encoding": "gzip, deflate, br", 324 | // Connection: "keep-alive", 325 | // "Upgrade-Insecure-Requests": "1", 326 | // "Cache-Control": "max-age=0", 327 | // TE: "Trailers", 328 | // DNT: "1", // Do Not Track header 329 | // Referer: "https://www.potakait.com", // Referer header may help 330 | // Origin: "https://www.potakait.com", // Sometimes needed 331 | // Cookie: "ar_debug=1", 332 | // }, 333 | // } 334 | ); 335 | 336 | const $ = cheerio.load(response.data); 337 | const products = []; 338 | const logo = $(".brand-logo img").attr("src") || "logo not found"; 339 | $(".product-item").each((index, element) => { 340 | const name = 341 | $(element).find(".title a").text().trim() || "Name not found"; 342 | const price = 343 | $(element).find(".price:not(.old)").text().trim() || "Out Of Stock"; 344 | const img = 345 | $(element).find(".product-img img").attr("data-src") || 346 | $(element).find(".product-img img").attr("src") || 347 | "Image not found"; 348 | const link = $(element).find(".title a").attr("href") || "Link not found"; 349 | 350 | const id = crypto.randomUUID(); 351 | products.push({ id, name, price, img, link }); 352 | }); 353 | 354 | return { products, logo }; 355 | } catch (error) { 356 | console.error("Error scraping PotakaIt:", error); 357 | return { 358 | products: [], 359 | logo: "logo not found", 360 | }; 361 | } 362 | }; 363 | 364 | app.get("/scrape/:product", async (req, res) => { 365 | let product = req.params.product; 366 | product = product.replace(/\s+/g, "%20"); 367 | 368 | const [ 369 | starTechProducts, 370 | techLandProducts, 371 | skyLandProducts, 372 | ryansProducts, 373 | pchouseProducts, 374 | ultraProducts, 375 | binaryProducts, 376 | // VibeGamingProducts, 377 | potakaProducts, 378 | ] = await Promise.all([ 379 | scrapeStarTech(product), 380 | scrapeTechLand(product), 381 | scrapeSkyLand(product), 382 | scrapeRyans(product), 383 | scrapePcHouse(product), 384 | scrapeUltraTech(product), 385 | scrapeBinary(product), 386 | // scrapeVibeGaming(product), 387 | scrapePotakaIT(product), 388 | ]); 389 | const allShops = [ 390 | { 391 | name: "StarTech", 392 | products: starTechProducts.products, 393 | logo: starTechProducts.logo, 394 | }, 395 | { 396 | name: "TechLand", 397 | products: techLandProducts.products, 398 | logo: techLandProducts.logo, 399 | }, 400 | { 401 | name: "SkyLand", 402 | products: skyLandProducts.products, 403 | logo: skyLandProducts.logo, 404 | }, 405 | { 406 | name: "Ryans", 407 | products: ryansProducts.products, 408 | logo: ryansProducts.logo, 409 | }, 410 | 411 | { 412 | name: "PcHouse", 413 | products: pchouseProducts.products, 414 | logo: pchouseProducts.logo, 415 | }, 416 | { 417 | name: "UltraTech", 418 | products: ultraProducts.products, 419 | logo: ultraProducts.logo, 420 | }, 421 | // { 422 | // name: "Vibe Gaming", 423 | // products: VibeGamingProducts.products, 424 | // logo: VibeGamingProducts.logo, 425 | // }, 426 | { 427 | name: "Binary", 428 | products: binaryProducts.products, 429 | logo: binaryProducts.logo, 430 | }, 431 | { 432 | name: "PotakaIT", 433 | products: potakaProducts.products, 434 | logo: potakaProducts.logo, 435 | }, 436 | // TechLand: techLandProducts, 437 | // Ryans: ryansProducts, 438 | // Binary: binaryProducts, 439 | // PotakaIT: potakaProducts, 440 | // PcHouse: pchouseProducts, 441 | // UltraTech: ultraProducts, 442 | // SkyLand: skyLandProducts, 443 | ]; 444 | const shopsWithResults = allShops.filter((shop) => shop.products.length > 0); 445 | res.json(shopsWithResults); 446 | }); 447 | 448 | // Start the server 449 | app.listen(PORT, () => 450 | console.log(`Server running at http://localhost:${PORT}`) 451 | ); 452 | app.get("/", (req, res) => { 453 | res.send("Welcome to the PricePoka's Scraper API!"); 454 | }); 455 | --------------------------------------------------------------------------------