├── LICENSE ├── README.md ├── images ├── ico128.png ├── ico16.png ├── ico19.png ├── ico38.png └── ico48.png ├── manifest.json ├── yt-comments-crawler-animation.gif └── yt-comments-crawler.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Roman Davydov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Icon](https://github.com/rdavydov/yt-comments-crawler/blob/main/images/ico19.png?raw=true) YouTube Comments Crawler 2 | 3 | ![](https://img.shields.io/github/license/rdavydov/yt-comments-crawler?style=for-the-badge&logo=github&color=purple&logoColor=thistle) 4 | ![](https://img.shields.io/github/stars/rdavydov/yt-comments-crawler?style=for-the-badge&logo=github&color=darkblue&logoColor=aquamarine) 5 | ![](https://img.shields.io/github/forks/rdavydov/yt-comments-crawler?style=for-the-badge&logo=github&color=darkblue&logoColor=aquamarine) 6 | ![](https://img.shields.io/github/watchers/rdavydov/yt-comments-crawler?style=for-the-badge&logo=github&color=darkblue&logoColor=aquamarine) 7 | ![](https://img.shields.io/github/last-commit/rdavydov/yt-comments-crawler?style=for-the-badge&logo=github&color=darkgreen&logoColor=lightgreen) 8 | 9 | Browser extension that extracts all comments from the YouTube video page, sorts them by the amount of likes and saves them to a csv file. 10 | 11 | ![Demo](https://github.com/rdavydov/yt-comments-crawler/blob/main/yt-comments-crawler-animation.gif?raw=true) 12 | 13 | ## How to Use 14 | 15 | Open any YouTube video page, scroll down to the comments and then click on the blue icon with white arrows at the left bottom corner. It will crawl through all comments and download a csv file with them. 16 | 17 | ## How to Install 18 | 19 | | Browser | | 20 | |---------|--------------------------| 21 | | Edge | [Link](https://microsoftedge.microsoft.com/addons/detail/youtube-comments-crawler/bghlionfnlnijejpfnbjknloaipfnkod) | 22 | | Firefox | [Link](https://addons.mozilla.org/en-US/firefox/addon/youtube-comments-crawler/) | 23 | 24 | OR (Chromium-based browsers) 25 | 26 | 1. Clone this repo 27 | 2. Go to your browser's extensions `chrome://extensions` 28 | 3. Enable Developer mode 29 | 4. Click "Load unpacked" (extension) and select this cloned repo's folder 30 | -------------------------------------------------------------------------------- /images/ico128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/images/ico128.png -------------------------------------------------------------------------------- /images/ico16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/images/ico16.png -------------------------------------------------------------------------------- /images/ico19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/images/ico19.png -------------------------------------------------------------------------------- /images/ico38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/images/ico38.png -------------------------------------------------------------------------------- /images/ico48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/images/ico48.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "YouTube Comments Crawler", 4 | "description": "Extracts all comments from the YouTube video page, sorts them by the amount of likes and saves them to a csv file.", 5 | "version": "0.4", 6 | "icons": { 7 | "16": "images/ico16.png", 8 | "19": "images/ico19.png", 9 | "38": "images/ico38.png", 10 | "48": "images/ico48.png", 11 | "128": "images/ico128.png" 12 | }, 13 | "content_scripts": [ 14 | { 15 | "matches": [ 16 | "*://www.youtube.com/*" 17 | ], 18 | "js": [ 19 | "yt-comments-crawler.js" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /yt-comments-crawler-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdavydov/yt-comments-crawler/1cabfa58ab6f866d35c69d013810156f98f7df6f/yt-comments-crawler-animation.gif -------------------------------------------------------------------------------- /yt-comments-crawler.js: -------------------------------------------------------------------------------- 1 | // Write a message to the console 2 | console.log('%c🧑‍💻 YT Comments Crawler: Extension loaded.', 'background-color: lightblue;'); 3 | 4 | // Create a button element 5 | const button = document.createElement('button'); 6 | 7 | // Add an ID attribute to the button 8 | button.setAttribute('id', 'btn-crawl-comments'); 9 | 10 | button.innerText = '⏬'; 11 | button.title = 'Crawl comments'; 12 | 13 | // Style the button 14 | button.style.position = 'fixed'; 15 | button.style.opacity = 0.7; 16 | button.style.bottom = 0; 17 | button.style.left = 0; 18 | button.style.background = 'var(--yt-spec-brand-background-secondary)'; 19 | button.style.color = 'var(--yt-spec-icon-active-other)'; 20 | button.style.border = '1px solid var(--yt-spec-brand-background-primary)'; 21 | button.style.borderRadius = '2px'; 22 | button.style.padding = '4px 4px'; 23 | button.style.cursor = 'pointer'; 24 | button.style.fontSize = '2em'; 25 | button.style.transition = 'background 0.2s ease-in-out'; 26 | button.style.textDecoration = 'none'; 27 | 28 | document.body.appendChild(button); 29 | 30 | if (document.addEventListener) { 31 | document.addEventListener('fullscreenchange', toggleBtn, false); 32 | document.addEventListener('mozfullscreenchange', toggleBtn, false); 33 | document.addEventListener('MSFullscreenChange', toggleBtn, false); 34 | document.addEventListener('webkitfullscreenchange', toggleBtn, false); 35 | } else { 36 | console.log("%c🧑‍💻 YT Comments Crawler: Can't add event listener!', 'background-color: darkred;"); 37 | } 38 | 39 | function toggleBtn() { 40 | if (document.fullscreenElement || document.mozFullScreenElement || 41 | document.webkitFullscreenElement || document.webkitIsFullScreen || 42 | document.mozFullScreen || document.msFullscreenElement) { 43 | console.log('%c🧑‍💻 YT Comments Crawler: Went full screen, hiding the button.', 'background-color: lightblue;'); 44 | button.hidden = true; 45 | } else { 46 | console.log('%c🧑‍💻 YT Comments Crawler: Exited full screen, showing the button.', 'background-color: lightblue;'); 47 | button.hidden = false; 48 | } 49 | } 50 | 51 | // Attach a click listener to a button 52 | document.querySelector('#btn-crawl-comments').addEventListener('click', async () => { 53 | // Write a warning message to the console 54 | console.log('%c🧑‍💻 YT Comments Crawler: Button clicked.', 'background-color: lightblue;'); 55 | 56 | // Get the video ID from the current URL 57 | const videoId = window.location.search.split('v=')[1].split(/[&#]/)[0]; 58 | console.log(`%c🧑‍💻 YT Comments Crawler: videoId: ${videoId}`, 'background-color: lightblue;'); 59 | 60 | // Get the video title from the page title and remove the " - YouTube" suffix 61 | const videoTitle = document.title.replace(' - YouTube', '').trim().replace(/[^ \p{L}0-9-_.]/gu, '').replace(/\s+/g, ' '); 62 | console.log(`%c🧑‍💻 YT Comments Crawler: videoTitle: ${videoTitle}`, 'background-color: lightblue;'); 63 | 64 | button.innerText = '⏳'; 65 | button.title = 'Crawling...'; 66 | 67 | // Scroll to the bottom of the page to load all comments 68 | let lastHeight = 0; 69 | while (true) { 70 | window.scrollTo(0, document.documentElement.scrollHeight); 71 | await new Promise(resolve => setTimeout(resolve, 1000)); 72 | const newHeight = document.documentElement.scrollHeight; 73 | if (newHeight === lastHeight) { 74 | break; 75 | } 76 | lastHeight = newHeight; 77 | } 78 | 79 | // Comments extraction and saving into csv 80 | 81 | // Get the comment elements from the current page DOM 82 | const commentElements = document.querySelectorAll('#contents #content-text'); 83 | 84 | // Get the total number of comments 85 | const totalComments = commentElements.length; 86 | // console.log(`%c🧑‍💻 YT Comments Crawler: totalComments: ${totalComments}`, 'background-color: lightblue;'); 87 | 88 | // Convert the comment elements to an array of comment objects 89 | const comments = []; 90 | for (const commentElement of commentElements) { 91 | const comment = {}; 92 | comment.text = commentElement.textContent.trim().replace(/\n/g, ' ').replace(/"/g, '""').replace(/'/g, "''"); 93 | 94 | const commentBody = commentElement.closest('#comment'); 95 | const commentAuthor = commentBody.querySelector('#author-text'); 96 | comment.author = commentAuthor.textContent.trim().replace(/"/g, '""').replace(/'/g, "''"); 97 | 98 | const commentInfo = commentBody.querySelector('#vote-count-middle'); 99 | comment.likes = commentInfo.textContent.trim().replace(/"/g, '""').replace(/'/g, "''"); 100 | 101 | comments.push(comment); 102 | } 103 | 104 | // Convert the comment likes to sortable format 105 | function getSortableLikes(likes) { 106 | let multiplier = 1; 107 | if (likes.endsWith("K")) { 108 | multiplier = 1000; 109 | likes = likes.slice(0, -1); 110 | } else if (likes.endsWith("M")) { 111 | multiplier = 1000000; 112 | likes = likes.slice(0, -1); 113 | } 114 | return parseFloat(likes) * multiplier; 115 | } 116 | 117 | // Sort the comments by likes 118 | comments.sort((a, b) => getSortableLikes(b.likes) - getSortableLikes(a.likes)); 119 | 120 | // Convert the comments to a CSV string 121 | const headers = ['Text', 'Author', 'Likes']; 122 | const rows = comments.map(comment => [comment.text, comment.author, comment.likes]); 123 | const csv = [headers, ...rows].map(row => row.map(value => `"${value}"`).join(',')).join('\n'); 124 | // console.log(`csv: ${csv}`); 125 | 126 | // Create a Blob from the CSV string 127 | const blob = new Blob([csv], { type: 'text/csv' }); 128 | 129 | // Create a link element to download the CSV file 130 | const link = document.createElement('a'); 131 | const fileName = `${videoTitle} [${videoId}]`; 132 | link.download = `${fileName}.csv`; 133 | link.href = URL.createObjectURL(blob); 134 | 135 | // Click the link to download the CSV file 136 | link.click(); 137 | button.innerText = `🔃`; 138 | button.title = `Crawl again`; 139 | }); --------------------------------------------------------------------------------