1import axios from 'axios';
2import * as cheerio from 'cheerio';
3import { Actor } from 'apify';
4
5await Actor.init();
6
7interface Input {
8 pageNumber: number;
9}
10
11
12const input = await Actor.getInput<Input>();
13if (!input) throw new Error("Input is missing! Please provide a JSON object with a 'pageNumber' property.");
14const pageNumber: number = input.pageNumber || 1;
15console.log(`Scraping page number: ${pageNumber}`);
16
17
18const targetUrl = `https://xplate.com/en/numbers/license-plates?page=${pageNumber}`;
19
20
21const XPLATES_SELECTORS = {
22 SOURCE_NAME: 'xplate',
23 ERROR_MESSAGE_SELECTOR: 'div.alert.alert-warning.text-center.m-0.rounded-3',
24 ALL_PLATES: 'div[class="number-card"]',
25 PLATE_PRICE: 'span.custom-red.dm-white',
26 PLATE_DURATION: 'div.d-flex.align-items-center.meta > div > span',
27 PLATE_LINK: 'a[class="p-0 m-0 lower-part default-dark-btn px-1 text-center bordered dm-bordered"]',
28 URL: targetUrl,
29 SKIP_CONFIGURATION: {
30 CALL_FOR_PRICE: 'Call For Price',
31 FEATURED: 'featured',
32 CHARACTER_HAS_NOC: 'noc',
33 },
34};
35
36interface Plate {
37 image: string;
38 price: string;
39 duration: string;
40 emirate: string;
41 character: string;
42 number: string;
43 source: string;
44}
45
46
47const response = await axios.get(targetUrl, {
48 headers: {
49
50 'Cookie': 'XSRF-TOKEN=...; xplate_session=...'
51 }
52});
53const html = response.data;
54const $ = cheerio.load(html);
55
56
57if ($(XPLATES_SELECTORS.ERROR_MESSAGE_SELECTOR).length) {
58 console.error('Error message found on the page, aborting.');
59 await Actor.exit();
60}
61
62
63const plateElements = Array.from($(XPLATES_SELECTORS.ALL_PLATES)).slice(1);
64console.log(`Found ${plateElements.length} plate elements.`);
65
66const plates: Plate[] = [];
67
68
69for (const plateEl of plateElements) {
70 const plateElement = $(plateEl);
71 const imgSrc = plateElement.find('img').attr('data-src') || '';
72 const price = plateElement.find(XPLATES_SELECTORS.PLATE_PRICE).text().trim() || '';
73 const duration = plateElement.find(XPLATES_SELECTORS.PLATE_DURATION).text().trim() || '';
74 const url = plateElement.find(XPLATES_SELECTORS.PLATE_LINK).attr('href') || '';
75
76
77 const emirateMatch = url.match(/\/(\d+)-(.+?)-code-/);
78 const characterMatch = url.match(/-code-(.+?)-plate-number-/);
79 const numberMatch = url.match(/plate-number-(\d+)/);
80 const emirate = emirateMatch ? emirateMatch[2] : '';
81 const character = characterMatch ? characterMatch[1] : '';
82 const number = numberMatch ? numberMatch[1] : '';
83
84 const newPlate: Plate = {
85 image: imgSrc,
86 price,
87 duration,
88 emirate,
89 character,
90 number,
91 source: XPLATES_SELECTORS.SOURCE_NAME,
92 };
93
94 plates.push(newPlate);
95}
96
97
98console.log(JSON.stringify(plates, null, 2));
99
100
101await Actor.pushData(plates);
102
103await Actor.exit();