DuckDuckGo URL Collector

Collects URLs from DuckDuckGo with optional site: filtering and rate limiting

  1. // ==UserScript==
  2. // @name DuckDuckGo URL Collector
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.2
  5. // @description Collects URLs from DuckDuckGo with optional site: filtering and rate limiting
  6. // @author Ghosty-Tongue
  7. // @match *://duckduckgo.com/*
  8. // @grant GM_notification
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const collectedUrls = new Set();
  16. let isProcessing = false;
  17. let startTime, timerInterval;
  18. let targetSite = null;
  19.  
  20. const searchForm = document.getElementById('search_form_input');
  21. if (searchForm && searchForm.value.includes('site:')) {
  22. const match = searchForm.value.match(/site:([^\s]+)/);
  23. if (match) {
  24. targetSite = match[1].toLowerCase();
  25. console.log(`Filtering for site: ${targetSite}`);
  26. }
  27. } else {
  28. console.log('No site: filter detected, collecting all URLs');
  29. }
  30.  
  31. const banner = document.createElement('div');
  32. Object.assign(banner.style, {
  33. position: 'fixed',
  34. top: '90px',
  35. right: '10px',
  36. zIndex: '10001',
  37. backgroundColor: 'rgba(255, 165, 0, 0.9)',
  38. color: 'white',
  39. padding: '10px',
  40. borderRadius: '5px',
  41. display: 'none'
  42. });
  43. document.body.appendChild(banner);
  44.  
  45. const style = document.createElement('style');
  46. style.textContent = `
  47. @keyframes rgbFlow {
  48. 0% { background-position: 0% 50%; }
  49. 100% { background-position: 100% 50%; }
  50. }
  51. @keyframes pulse {
  52. 0% { transform: scale(1); }
  53. 50% { transform: scale(1.1); }
  54. 100% { transform: scale(1); }
  55. }
  56. `;
  57. document.head.appendChild(style);
  58.  
  59. const timerDisplay = document.createElement('div');
  60. Object.assign(timerDisplay.style, {
  61. position: 'fixed',
  62. top: '50px',
  63. right: '10px',
  64. zIndex: '10000',
  65. color: 'white',
  66. backgroundColor: 'rgba(0,0,0,0.7)',
  67. padding: '5px 10px',
  68. borderRadius: '5px',
  69. fontFamily: 'Arial, sans-serif',
  70. fontSize: '14px'
  71. });
  72. document.body.appendChild(timerDisplay);
  73.  
  74. function startTimer() {
  75. if (timerInterval) clearInterval(timerInterval);
  76. startTime = Date.now();
  77. timerInterval = setInterval(updateTimer, 1000);
  78. timerDisplay.textContent = '0s';
  79. }
  80.  
  81. function updateTimer() {
  82. const elapsed = Math.floor((Date.now() - startTime) / 1000);
  83. timerDisplay.textContent = `${elapsed}s`;
  84. }
  85.  
  86. function stopTimer() {
  87. clearInterval(timerInterval);
  88. const elapsed = Math.floor((Date.now() - startTime) / 1000);
  89. timerDisplay.textContent = `${elapsed}s (stopped)`;
  90. }
  91.  
  92. function extractUrls() {
  93. console.log('Extracting URLs from current page...');
  94. const results = document.querySelectorAll('article[data-testid="result"]');
  95. let newUrlsCount = 0;
  96.  
  97. results.forEach(result => {
  98. const link = result.querySelector('a[data-testid="result-extras-url-link"]');
  99. if (link) {
  100. const url = link.href;
  101. const urlDomain = new URL(url).hostname.toLowerCase();
  102.  
  103. if (targetSite) {
  104. if (!urlDomain.includes(targetSite)) {
  105. console.log(`Skipping ${url} - doesn't match ${targetSite}`);
  106. return;
  107. }
  108. }
  109.  
  110. if (!collectedUrls.has(url)) {
  111. collectedUrls.add(url);
  112. newUrlsCount++;
  113. }
  114. }
  115. });
  116.  
  117. console.log(`Added ${newUrlsCount} new URLs from this batch. Total: ${collectedUrls.size}`);
  118. return newUrlsCount;
  119. }
  120.  
  121. async function clickMoreResults() {
  122. isProcessing = true;
  123. btn.classList.add('processing');
  124. console.log('Starting URL collection process...');
  125.  
  126. let iteration = 1;
  127. let moreResultsButton;
  128. let batchCount = 0;
  129.  
  130. do {
  131. if (batchCount >= 420) {
  132. banner.textContent = 'Taking 15s break to avoid limits';
  133. banner.style.display = 'block';
  134. await new Promise(resolve => setTimeout(resolve, 15000));
  135. banner.style.display = 'none';
  136. batchCount = 0;
  137. console.log('Batch counter reset after 420 URLs');
  138. }
  139.  
  140. console.log(`Looking for "More Results" button (Attempt ${iteration})...`);
  141. moreResultsButton = document.getElementById('more-results');
  142.  
  143. if (moreResultsButton) {
  144. console.log('Clicking "More Results" button...');
  145. moreResultsButton.click();
  146. await new Promise(resolve => setTimeout(resolve, 2000));
  147. batchCount += extractUrls();
  148. iteration++;
  149. }
  150. } while (moreResultsButton);
  151.  
  152. console.log(`Finished collecting URLs. Total unique URLs: ${collectedUrls.size}`);
  153. isProcessing = false;
  154. btn.classList.remove('processing');
  155.  
  156. GM_notification({
  157. title: 'Collection Complete',
  158. text: `Saved ${collectedUrls.size} URLs`,
  159. timeout: 5000
  160. });
  161.  
  162. saveUrls();
  163. }
  164.  
  165. function saveUrls() {
  166. console.log('Preparing to save URLs to file...');
  167. const blob = new Blob([Array.from(collectedUrls).join('\n')], {type: 'text/plain'});
  168. const url = URL.createObjectURL(blob);
  169. const a = document.createElement('a');
  170. a.href = url;
  171. a.download = 'urls.txt';
  172. document.body.appendChild(a);
  173. a.click();
  174. document.body.removeChild(a);
  175. URL.revokeObjectURL(url);
  176. console.log(`File saved with ${collectedUrls.size} URLs`);
  177. stopTimer();
  178. }
  179.  
  180. const btn = document.createElement('button');
  181. btn.textContent = '🦆';
  182. Object.assign(btn.style, {
  183. position: 'fixed',
  184. top: '10px',
  185. right: '10px',
  186. zIndex: '10000',
  187. padding: '12px 24px',
  188. background: 'linear-gradient(90deg, #ff0000, #00ff00, #0000ff, #ff0000)',
  189. backgroundSize: '300% 100%',
  190. animation: 'rgbFlow 5s linear infinite',
  191. color: 'white',
  192. border: 'none',
  193. borderRadius: '25px',
  194. cursor: 'pointer',
  195. fontFamily: 'Arial, sans-serif',
  196. fontWeight: 'bold',
  197. boxShadow: '0 4px 15px rgba(0,0,0,0.2)',
  198. transition: 'transform 0.2s, box-shadow 0.2s'
  199. });
  200.  
  201. btn.addEventListener('mouseover', () => {
  202. btn.style.transform = 'scale(1.05)';
  203. btn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.25)';
  204. });
  205.  
  206. btn.addEventListener('mouseout', () => {
  207. btn.style.transform = 'scale(1)';
  208. btn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
  209. });
  210.  
  211. btn.addEventListener('click', () => {
  212. if (!isProcessing) {
  213. console.log('----- New Collection Started -----');
  214. collectedUrls.clear();
  215. startTimer();
  216. clickMoreResults();
  217. }
  218. });
  219.  
  220. document.body.appendChild(btn);
  221. console.log('URL Collector button initialized');
  222. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址