-
Notifications
You must be signed in to change notification settings - Fork 0
/
quizziz-scraper.js
287 lines (260 loc) · 7.49 KB
/
quizziz-scraper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
const puppeteer = require("puppeteer");
const fs = require("fs");
const QUIZZIZ_URL =
"https://quizizz.com/join/quiz/64ba12becb79ee001dffa347/start?fbclid=IwAR1tAT1J4dp2RRhyIJZlcEpmeVshbOQuzT-8RUxc_vlEQ50ipv5MLd5P0SQ";
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function indexToLetter(index) {
if (index == "1") {
return "A";
} else if (index == "2") {
return "B";
} else if (index == "3") {
return "C";
} else if (index == "4") {
return "D";
} else {
return "E";
}
}
async function retry(action, retries = 3, waitTime = 3000) {
for (let i = 0; i < retries; i++) {
try {
return await action();
} catch (error) {
console.log(
`Retry ${i + 1}/${retries} failed: ${error.message}`
);
if (i < retries - 1) {
await wait(waitTime);
}
}
}
throw new Error(`Action failed after ${retries} retries`);
}
async function getQuestionAndAnswers(page, retries = 10) {
while (retries > 0) {
console.log(
"Try to take question and answer, try:",
retries
);
const questionData = await page.evaluate(() => {
try {
// extract the question
function extractQuestion() {
const questionContainer = document.querySelector(
".question-text-color"
);
return questionContainer
? questionContainer.innerText.trim()
: null;
}
// extract the answers
function extractOptions() {
const optionDivs = Array.from(
document.querySelectorAll(".option.is-mcq")
);
const sortedOptions = optionDivs.sort((a, b) => {
const aIndex =
a.className.match(/option-(\d+)/)[1];
const bIndex =
b.className.match(/option-(\d+)/)[1];
return aIndex - bIndex;
});
return sortedOptions.map((div) => ({
option: div
.querySelector(".resizeable")
.innerText.trim(),
id: div.getAttribute("data-cy"),
}));
}
const question = extractQuestion();
const options = extractOptions();
if (question && options.length > 0) {
return { question, options };
} else {
throw new Error("Questions or answers not found");
}
} catch (error) {
return null;
}
});
if (questionData) {
return questionData;
}
retries--;
await wait(3000);
}
throw new Error(
"Question or answers not found after 12 retries."
);
}
// Function to extract the correct answer
async function getCorrectAnswer(page) {
return await retry(async () => {
const correctAnswerElement = await page.$(
".is-correct"
);
if (correctAnswerElement) {
const correctClass =
await correctAnswerElement.evaluate(
(el) => el.className.match(/option-(\d+)/)[0]
);
const correctIndex =
correctClass.match(/option-(\d+)/)[1];
return indexToLetter(correctIndex);
} else {
console.log(
"Correct answer element not found, retrying..."
);
throw new Error("Correct answer element not found");
}
});
}
(async () => {
const browser = await puppeteer.launch({
headless: false,
});
const page = await browser.newPage();
await page.goto(QUIZZIZ_URL, {
waitUntil: "networkidle2",
});
await wait(5000);
// Click the Start button
await page.click('button[data-cy="start-solo-game"]');
console.log("Clicked on the start solo game button");
const quizData = [];
await wait(6000); // Wait for the quiz to load
while (true) {
try {
// Extract the question and options with the retry mechanism
const questionData = await retry(() =>
getQuestionAndAnswers(page)
);
// Check if the first option is available
const firstOptionSelector = ".option-1";
await retry(async () => {
const firstOption = await page.$(
firstOptionSelector
);
if (firstOption) {
// Click on the first available option to reveal the answer
await wait(3000);
await firstOption.click();
console.log("Clicked the first option");
// Extract the correct answer
const correctAnswer = await getCorrectAnswer(
page
);
// Store the result
quizData.push({
question: questionData.question,
options: questionData.options
.map((o) => o.option)
.join("\n"),
answer: correctAnswer,
});
await wait(3000);
const nextButtonClicked =
await clickTheNextButton(page);
if (!nextButtonClicked) {
console.log(
"Next button not found, continuing with next question..."
);
}
const selectorsContainerClicked =
await checkAndClickSelectorsContainer(page);
if (!selectorsContainerClicked) {
console.log(
"Selectors container not found, continuing with next question..."
);
}
} else {
console.log(
"First option not found, retrying..."
);
throw new Error("First option not found");
}
});
} catch (error) {
console.error("Error extracting quiz data:", error);
break;
}
}
await browser.close();
// Formatting data for CSV
const csvContent = quizData
.map(({ question, options, answer }) => {
return `"${question}\n${options}","${answer}"`;
})
.join("\n");
// Writing data to CSV
fs.writeFileSync("quiz_data.csv", csvContent);
console.log("Quiz data saved to quiz_data.csv");
})();
const clickTheNextButton = async (page) => {
try {
await retry(async () => {
const nextButtonElement = await page.$(
".right-navigator"
);
if (nextButtonElement) {
await nextButtonElement.click();
console.log("Clicked the next button");
return true;
}
console.log("Next button not found, retrying...");
throw new Error("Next button not found");
});
return true;
} catch (error) {
console.error(
"Failed to click the next button after 3 retries:",
error
);
return false;
}
};
const checkAndClickSelectorsContainer = async (page) => {
try {
await retry(async () => {
const selectorsContainer = await page.$(
'.selectors-container[role="group"]'
);
if (selectorsContainer) {
console.log("Selectors container found");
const options = await selectorsContainer.$$(
"button.selector-item"
);
if (options.length > 0) {
await options[0].click(); // Click the first available option
console.log(
"Clicked an option within the selectors container"
);
await wait(3000);
return true;
} else {
console.log(
"No options found within the selectors container, retrying..."
);
throw new Error(
"No options found within the selectors container"
);
}
} else {
console.log(
"Selectors container not found, retrying..."
);
throw new Error("Selectors container not found");
}
});
return true;
} catch (error) {
console.error(
"Failed to click the selectors container after 3 retries:",
error
);
return false;
}
};