Greasy Fork 还支持 简体中文。

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.3
  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. }
  26. }
  27.  
  28. const banner = document.createElement('div');
  29. Object.assign(banner.style, {
  30. position: 'fixed',
  31. top: '90px',
  32. right: '10px',
  33. zIndex: '10001',
  34. backgroundColor: 'rgba(255, 165, 0, 0.9)',
  35. color: 'white',
  36. padding: '10px',
  37. borderRadius: '5px',
  38. display: 'none'
  39. });
  40. document.body.appendChild(banner);
  41.  
  42. const style = document.createElement('style');
  43. style.textContent = `
  44. @keyframes rgbFlow {
  45. 0% { background-position: 0% 50%; }
  46. 100% { background-position: 100% 50%; }
  47. }
  48. `;
  49. document.head.appendChild(style);
  50.  
  51. const timerDisplay = document.createElement('div');
  52. Object.assign(timerDisplay.style, {
  53. position: 'fixed',
  54. top: '50px',
  55. right: '10px',
  56. zIndex: '10000',
  57. color: 'white',
  58. backgroundColor: 'rgba(0,0,0,0.7)',
  59. padding: '5px 10px',
  60. borderRadius: '5px',
  61. fontFamily: 'Arial, sans-serif',
  62. fontSize: '14px'
  63. });
  64. document.body.appendChild(timerDisplay);
  65.  
  66. function startTimer() {
  67. if (timerInterval) clearInterval(timerInterval);
  68. startTime = Date.now();
  69. timerInterval = setInterval(updateTimer, 1000);
  70. timerDisplay.textContent = '0s';
  71. }
  72.  
  73. function updateTimer() {
  74. const elapsed = Math.floor((Date.now() - startTime) / 1000);
  75. timerDisplay.textContent = `${elapsed}s`;
  76. }
  77.  
  78. function stopTimer() {
  79. clearInterval(timerInterval);
  80. const elapsed = Math.floor((Date.now() - startTime) / 1000);
  81. timerDisplay.textContent = `${elapsed}s (stopped)`;
  82. }
  83.  
  84. function extractUrls() {
  85. const results = document.querySelectorAll('article[data-testid="result"]');
  86. let newUrlsCount = 0;
  87. results.forEach(result => {
  88. const link = result.querySelector('a[data-testid="result-extras-url-link"]');
  89. if (link) {
  90. const url = link.href;
  91. const urlDomain = new URL(url).hostname.toLowerCase();
  92. if (targetSite) {
  93. if (!urlDomain.includes(targetSite)) return;
  94. }
  95. if (!collectedUrls.has(url)) {
  96. collectedUrls.add(url);
  97. newUrlsCount++;
  98. }
  99. }
  100. });
  101. return newUrlsCount;
  102. }
  103.  
  104. async function clickMoreResults() {
  105. isProcessing = true;
  106. btn.classList.add('processing');
  107. let iteration = 1;
  108. let moreResultsButton;
  109. let batchCount = 0;
  110. do {
  111. if (!isProcessing) break;
  112. if (batchCount >= 420) {
  113. banner.textContent = 'Taking 15s break to avoid limits';
  114. banner.style.display = 'block';
  115. await new Promise(resolve => setTimeout(resolve, 15000));
  116. banner.style.display = 'none';
  117. batchCount = 0;
  118. }
  119. moreResultsButton = document.getElementById('more-results');
  120. if (moreResultsButton) {
  121. moreResultsButton.click();
  122. await new Promise(resolve => setTimeout(resolve, 2000));
  123. batchCount += extractUrls();
  124. iteration++;
  125. }
  126. } while (moreResultsButton && isProcessing);
  127. isProcessing = false;
  128. btn.classList.remove('processing');
  129. GM_notification({
  130. title: 'Collection Complete',
  131. text: `Saved ${collectedUrls.size} URLs`,
  132. timeout: 5000
  133. });
  134. saveUrls();
  135. }
  136.  
  137. function saveUrls() {
  138. const blob = new Blob([Array.from(collectedUrls).join('\n')], {type: 'text/plain'});
  139. const url = URL.createObjectURL(blob);
  140. const a = document.createElement('a');
  141. a.href = url;
  142. a.download = 'urls.txt';
  143. document.body.appendChild(a);
  144. a.click();
  145. document.body.removeChild(a);
  146. URL.revokeObjectURL(url);
  147. stopTimer();
  148. }
  149.  
  150. const btn = document.createElement('button');
  151. btn.textContent = '🦆';
  152. Object.assign(btn.style, {
  153. position: 'fixed',
  154. top: '10px',
  155. right: '10px',
  156. zIndex: '10000',
  157. padding: '12px 24px',
  158. background: 'linear-gradient(90deg, #ff0000, #00ff00, #0000ff, #ff0000)',
  159. backgroundSize: '300% 100%',
  160. animation: 'rgbFlow 5s linear infinite',
  161. color: 'white',
  162. border: 'none',
  163. borderRadius: '25px',
  164. cursor: 'pointer',
  165. fontFamily: 'Arial, sans-serif',
  166. fontWeight: 'bold',
  167. boxShadow: '0 4px 15px rgba(0,0,0,0.2)',
  168. transition: 'transform 0.2s, box-shadow 0.2s'
  169. });
  170.  
  171. btn.addEventListener('mouseover', () => {
  172. btn.style.transform = 'scale(1.05)';
  173. btn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.25)';
  174. });
  175.  
  176. btn.addEventListener('mouseout', () => {
  177. btn.style.transform = 'scale(1)';
  178. btn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
  179. });
  180.  
  181. btn.addEventListener('click', () => {
  182. if (!isProcessing) {
  183. collectedUrls.clear();
  184. startTimer();
  185. clickMoreResults();
  186. } else {
  187. isProcessing = false;
  188. btn.classList.remove('processing');
  189. banner.style.display = 'none';
  190. stopTimer();
  191. saveUrls();
  192. }
  193. });
  194.  
  195. document.body.appendChild(btn);
  196. })();