├── clip.png
├── manifest.json
├── popup.html
└── popup.js
/clip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsmikepowers/apollo-easy-scrape/49ed1f2e432183830c74d578a9324c1b33a29da6/clip.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Apollo Easy Scrape",
3 | "description": "Easily scrape data from web pages with a single click.",
4 | "version": "1.0",
5 | "manifest_version": 3,
6 | "permissions": ["activeTab", "scripting"],
7 | "action": {
8 | "default_popup": "popup.html"
9 | },
10 | "icons": {
11 | "16": "clip.png",
12 | "48": "clip.png",
13 | "128": "clip.png"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 📋 Apollo Easy Scrape
7 |
8 |
71 |
72 |
73 | 📋 Apollo Easy Scrape
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/popup.js:
--------------------------------------------------------------------------------
1 | var pageUrls = [];
2 |
3 | document.addEventListener('DOMContentLoaded', function() {
4 | var actionButton = document.getElementById('displayTableButton');
5 | var downloadCsvButton = document.getElementById('downloadCsvButton');
6 |
7 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
8 | var currentTab = tabs[0];
9 | var statusElement = document.getElementById('status');
10 | var totalElement = document.getElementById('total');
11 | var baseUrl = currentTab.url;
12 |
13 |
14 | if (baseUrl.includes('https://app.apollo.io/#/people?finderViewId')) {
15 | statusElement.textContent = "You found a list!";
16 | statusElement.style.fontSize = "20px";
17 | actionButton.disabled = false;
18 | actionButton.classList.add("activeButton");
19 | actionButton.style.backgroundColor = "#007BFF";
20 | actionButton.style.color = "white";
21 | actionButton.style.cursor = "pointer";
22 | actionButton.addEventListener('mouseenter', function() {
23 | this.style.backgroundColor = "#0056b3";
24 | });
25 | actionButton.addEventListener('mouseleave', function() {
26 | this.style.backgroundColor = "#007BFF";
27 | });
28 |
29 | chrome.scripting.executeScript({
30 | target: {tabId: tabs[0].id},
31 | function: function() {
32 | // Find the span element by its class name
33 | function findTotalInSpanWithSpace() {
34 | // Initialize totalNumber to 0 by default
35 | let totalText = 0;
36 |
37 | // Get all elements in the document
38 | const spanElements = document.querySelectorAll('span');
39 |
40 | // Iterate through each to find the matching text
41 | for (let i = 0; i < spanElements.length; i++) {
42 | const textContent = spanElements[i].textContent.trim();
43 | // Adjusted regex to ensure there's a space after 'of'
44 | const match = textContent.match(/of\s([\d,]+)/);
45 |
46 | // If a match is found, extract the number, remove commas, and convert to an integer
47 | if (match && match[1]) {
48 | totalText = parseInt(match[1].replace(/,/g, ''), 10);
49 | break; // Stop searching once a match is found
50 | }
51 | }
52 |
53 | return totalText;
54 | }
55 |
56 | const totalText = findTotalInSpanWithSpace();
57 | console.log(totalText); // This will display the number found after "of ", e.g., 1026
58 |
59 |
60 | return totalText;
61 | },
62 | }, function(results) {
63 | if (chrome.runtime.lastError) {
64 | totalElement.textContent = `Error: ${chrome.runtime.lastError.message}`;
65 | } else {
66 | const total = results[0].result;
67 | let pages = Math.ceil(total / 25);
68 | pages = pages > 100 ? 100 : pages;
69 | let totalText = `${total} total contacts found`;
70 | let pagesText = ` on ${pages} pages.`;
71 |
72 | totalElement.innerHTML = totalText + pagesText;
73 | totalElement.style.fontSize = "20px";
74 |
75 | // Generate page URLs and store them in the array
76 | for (let i = 1; i <= pages; i++) {
77 | baseUrl = baseUrl.replace(/&page=\d+/, '');
78 | const pageUrl = `${baseUrl}&page=${i}`;
79 | pageUrls.push(pageUrl);
80 | }
81 | }
82 | });
83 | } else {
84 | statusElement.textContent = "Please go to an Apollo.io List URL";
85 | statusElement.style.fontSize = "20px";
86 | actionButton.disabled = true;
87 | actionButton.classList.remove("activeButton");
88 | actionButton.style.backgroundColor = "grey";
89 | actionButton.style.color = "white";
90 | actionButton.style.cursor = "not-allowed";
91 | }
92 | });
93 |
94 | downloadCsvButton.addEventListener('click', function() {
95 | downloadTableAsCsv();
96 | });
97 | });
98 |
99 | var allTableData = [];
100 |
101 | document.getElementById('displayTableButton').addEventListener('click', function() {
102 | // Clear the data
103 | allTableData = [];
104 | document.getElementById('tableContainer').innerHTML = '';
105 |
106 | // Then fetch all tables
107 | getAllTables();
108 | });
109 |
110 | function getAllTables() {
111 | const pageLinks = pageUrls;
112 | const timeBetweenPages = document.getElementById('timeBetweenPages').value * 1000;
113 | pageLinks.forEach((pageLink, index) => {
114 | setTimeout(() => {
115 | chrome.tabs.update({url: pageLink}, function(tab) {
116 | chrome.tabs.onUpdated.addListener(function listener(tabId, info) {
117 | if (info.status === 'complete' && tabId === tab.id) {
118 | chrome.tabs.onUpdated.removeListener(listener);
119 | chrome.scripting.executeScript({
120 | target: {tabId: tab.id},
121 | function: findAndSendTableData,
122 | }, function(results) {
123 | if (chrome.runtime.lastError) {
124 | console.error(`Error: ${chrome.runtime.lastError.message}`);
125 | } else {
126 | allTableData.push(results[0].result);
127 | if (index === pageLinks.length - 1) {
128 | document.getElementById('tableContainer').innerHTML = allTableData.join('');
129 | }
130 | }
131 | });
132 | }
133 | });
134 | });
135 | }, index * timeBetweenPages); // Wait 5 seconds between each page to avoid overloading the server
136 | });
137 | }
138 |
139 | function findAndSendTableData() {
140 | const table = document.querySelector('table');
141 | if (!table) {
142 | chrome.runtime.sendMessage({error: "No table found on the page."});
143 | return '';
144 | }
145 |
146 | const clonedTable = table.cloneNode(true);
147 | const elementsToRemove = clonedTable.querySelectorAll('svg, img, button, input[type="checkbox"]');
148 | elementsToRemove.forEach(el => el.parentNode.removeChild(el));
149 |
150 | const phoneRegex = /\+\d{11}/g;
151 | const cells = clonedTable.querySelectorAll('td');
152 | cells.forEach(cell => {
153 | let text = cell.textContent;
154 | const matches = text.match(phoneRegex);
155 | if (matches) {
156 | matches.forEach(match => {
157 | const formatted = match.replace(/(\+\d{1})(\d{3})(\d{3})(\d{4})/, '$1 ($2) $3-$4');
158 | text = text.replace(match, formatted);
159 | });
160 | }
161 |
162 | text = text.replace(/[^a-zA-Z0-9\s,.@-]/g, '').replace(/Â/g, '');
163 | cell.textContent = text;
164 | });
165 |
166 | return clonedTable.outerHTML;
167 | }
168 |
169 | function downloadTableAsCsv() {
170 | const tableContainer = document.getElementById('tableContainer');
171 | if (!tableContainer) {
172 | console.error("No table container found on the page to download.");
173 | return;
174 | }
175 |
176 | let csvContent = "\uFEFF";
177 | let headerProcessed = false;
178 |
179 | const rows = tableContainer.querySelectorAll("table tr");
180 | let nameIndex = -1;
181 | let quickActionsIndex = -1;
182 | for (const row of rows) {
183 | let rowData = [];
184 | const cells = row.querySelectorAll("th, td");
185 | for (let i = 0; i < cells.length; i++) {
186 | if (row === rows[0]) {
187 | if (!headerProcessed) {
188 | if (cells[i].innerText === "Name") {
189 | nameIndex = i;
190 | } else if (cells[i].innerText === "Quick Actions") {
191 | quickActionsIndex = i;
192 | continue;
193 | }
194 | } else {
195 | continue;
196 | }
197 | }
198 |
199 | if (i === quickActionsIndex) continue;
200 | let cellText = cells[i].innerText;
201 | if (i === nameIndex) {
202 | if (row === rows[0] && !headerProcessed) {
203 | rowData.push(`"First Name"`, `"Last Name"`, `"Full Name"`);
204 | } else {
205 | const names = cellText.split(' ');
206 | const firstName = names[0] || '';
207 | const lastName = names.slice(1).join(' ') || '';
208 | const fullName = cellText;
209 | rowData.push(`"${firstName}"`, `"${lastName}"`, `"${fullName}"`);
210 | }
211 | continue;
212 | }
213 |
214 | if (cellText === "No email" || cellText === "Request Mobile Number" || cellText === "NA") {
215 | cellText = " ";
216 | }
217 |
218 | cellText = cellText.replace(/[^a-zA-Z0-9\s,.@-]/g, '').replace(/Â/g, '');
219 | cellText = cellText.replace(/"/g, '""').replace(/#/g, '');
220 | cellText = cellText.trim();
221 | rowData.push(`"${cellText}"`);
222 | }
223 |
224 | // Skip the row if it contains the word "Name" in a single cell after the first row
225 | if (row !== rows[0] && rowData.some(cell => cell.includes("Name"))) {
226 | continue;
227 | }
228 |
229 | csvContent += rowData.join(",") + "\r\n";
230 | if (row === rows[0]) {
231 | headerProcessed = true;
232 | }
233 | }
234 | const fileNameInput = document.getElementById('fileName');
235 | const fileName = fileNameInput.value || 'tableData';
236 | const encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvContent);
237 | const link = document.createElement("a");
238 | link.setAttribute("href", encodedUri);
239 | link.setAttribute("download", fileName + ".csv");
240 | document.body.appendChild(link);
241 |
242 | link.click();
243 | document.body.removeChild(link);
244 | }
245 |
246 |
247 | chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
248 | if (message.tableData) {
249 | allTableData.push(message.tableData);
250 | document.getElementById('tableContainer').innerHTML = allTableData.join('');
251 | } else if (message.error) {
252 | document.getElementById('tableContainer').textContent = message.error;
253 | }
254 | });
255 |
--------------------------------------------------------------------------------