├── 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 |

By Mike Buy Me A Coffee :)

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 | --------------------------------------------------------------------------------