Last active
May 27, 2025 17:35
-
-
Save ohnit/c74176ea03119ca8649ded529199088f to your computer and use it in GitHub Desktop.
Scrape comments from Figma
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
For when you only have 'viewer' access to a Figma project, but you'd really like a csv with all the top-level comments. Replies are not included. | |
The csv columns will be: "Identifier", "Author", "Message" | |
## To use: | |
Paste this code into your browser's devtools. | |
Creater a new scraper: | |
```js | |
let scraper = new FigmaCommentScraper(); | |
``` | |
Then slowly scroll through all the comments. When you are done, call: | |
```js | |
scraper.stop(); | |
``` | |
You can output the CSV string to the console using either of these: | |
```js | |
scraper.dump(); | |
// Doesn't dump comments with ids less than 3. | |
scraper.dumpAfter(3); | |
``` | |
You can copy and paste into the a spreadsheet. You may need to specify "text to columns" | |
I had a problem with newlines in comments. I used this formula. Then pasted as values back into the original column: | |
`=SUBSTITUTE(D67,"\n", CHAR(10))` | |
*/ | |
class FigmaCommentScraper { | |
allComments; | |
#interval; | |
constructor() { | |
this.allComments = {}; | |
this.scrape(); | |
this.start(); | |
} | |
/** | |
Collects comments are are visible in the sidebar. To get all comments, you need to scroll. (see `start()`) | |
*/ | |
scrape() { | |
Array.from(document.querySelectorAll("div[class^=comments_row_presentation--commentInner]")).map((elem) => { | |
const message = elem.querySelector(':scope div[class^=comment_message--commentMessage]')?.innerText; | |
const author = elem.querySelector(':scope div[class^=comment_metadata--author]')?.innerText; | |
const identifier = elem.querySelector(':scope span[class^=comment_metadata--parentName]')?.innerText.replace("#", "").split(" ")[0]; | |
// console.log(message, author, identifier); | |
if (identifier in this.allComments === false) { | |
this.allComments[identifier] = [identifier, author, message]; | |
} | |
}) | |
} | |
/** | |
Scrapes comments periodically so you can scroll through all the comments. | |
*/ | |
start() { | |
this.#interval = setInterval(() => { | |
this.scrape(); | |
console.log("scraping"); | |
}, 500); | |
} | |
/** | |
Stops periodic scraping. | |
*/ | |
stop() { | |
clearInterval(this.#interval); | |
} | |
/** | |
Clear all scraped comments | |
*/ | |
clear() { | |
this.allComments = {}; | |
} | |
/** | |
Returns comments formatted as CSV. | |
*/ | |
dump() { | |
return this.#doDump(this.allComments); | |
} | |
/** | |
Like `dump()` but will skip any comments with an id number less than `identifier`. | |
This is useful adding new comments to your previous scraped ones. | |
*/ | |
dumpAfter(identifier) { | |
const comments = Object.fromEntries( | |
Object.entries(this.allComments).filter(([key, _value]) => Number(key) > identifier) | |
); | |
return this.#doDump(comments); | |
} | |
#doDump(comments) { | |
let lines = Object.values(comments).map(([identifier, author, message]) => { | |
// don't quote identifier so it gets treated as a number in spreadsheets | |
return `${identifier},"${this.#escapeCSVQuotes(author)}","${this.#escapeCSVQuotes(message)}"` | |
}); | |
lines.unshift(`"Identifier", "Author", "Message"`); | |
return lines.join("\n");; | |
} | |
#escapeCSVQuotes(text) { | |
return text.replaceAll(`"`, `""`); | |
} | |
} | |
let scraper = new FigmaCommentScraper(); | |
// scraper.stop(); | |
// scraper.dump(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment