

# Export Public Instagram Comments to Excel for a Thesis

Export public top-level comments under rules you write down, then open them in Excel. Keep each source ID as text. Write down what those comments can show before you code them.

A top-level comment is a comment on the post itself. It is not a reply under another comment. In this article, a sample is the public comments you saved under your written rules. It is not every comment on Instagram.

Export means you download the saved rows as a file. An Actor is a hosted program that collects rows. A Task is a saved input preset for that Actor. A run is one collection pass with one input. The Actor saves source rows. You do the counts and the coding.

## What each saved row contains

These field rules were checked on 2026-10-13 for public build 0.1.21 of the [Instagram Comments Scraper Actor](https://apify.com/chronometrica/instagram-comments-scraper). A field is one named value on a row. This build saves one public top-level comment on each row.

Each row has `postUrl`, `postId`, `postShortCode`, `commentId`, `text`, `createdAt`, `likesCount`, `repliesCount`, `ownerId`, `ownerUsername`, `ownerVerified`, and `collectedAt`.

`postUrl` is the post link you entered. `postId` is the source identity of that post. `postShortCode` is the short code inside that link. The short code is the part after `/p/`, `/reel/`, or `/tv/`. `commentId` is the source identity of one comment. `text` is the comment words. `createdAt` is the comment time from the source. `likesCount` is the like count the source displayed. `repliesCount` is the reply count the source displayed. It is not the reply text. `ownerId` is the source identity of the account that posted the comment. `ownerUsername` is the public name of that account. `ownerVerified` says whether the source showed a verification mark on that account. `collectedAt` is the time the Actor saved the row.

Null means the value was not available. An empty text has no characters. Empty text is not the same as null. Zero means the number was zero. False means the verification mark was no. Keep null, zero, and false as different values. Do not turn null into zero or false.

This build does not put a status field on each row. It does not save `parentId`. It does not save reply text. It does not save the post caption. It does not save follower counts. It does not save a bot score. A bot score would guess whether an account is automatic. It does not save a sentiment label. Sentiment would be a label such as praise or complaint. It does not mark who counts as a full member of a group. An owner ID can be null.

There is no reply row to code. A null `repliesCount` is not a reply with empty text. It is not a sentiment.

## 1. Write the frame before you collect

The frame is the list of posts you chose and the rules for comments you keep. Write the frame before you run the Actor.

Write these items:

- The known public post links.
- The rule that put those posts on the list.
- The date window, or a note that you will not filter by date.
- The collection time you will copy from `collectedAt`.
- The language rule.
- The thing you count: comments, accounts, or posts.

A date window is the first day and the last day you choose to keep. The thing you count is your unit. [A comment, an account, and a post are different units](/en/articles/reddit-research-count-units/). Write the collection time as `collectedAt`, not the day you start coding.

Write the post list before you read the comments. Then you will not add a post because its comments fit your idea. Write the date window before the run, or write that you will not use one. Write the unit in the same note. Later counts are easy to mix up. The made-up file below shows why. The Actor does not add a language field. Write a language rule you can follow. For example, keep every saved text and mark language in a new column. If you drop a row, write the reason.

Use a public link you already know. The link must contain `/p/`, `/reel/`, or `/tv/`. A profile link is an account page. A Story is a short-lived story item. A hashtag link is a tag page. Search and Explore are browse pages. Those routes are not supported. Use public posts only. Do not collect private accounts or hidden comments.

The sample can describe the public comment text you saved. It cannot stand in for all Instagram users. A claim about all users needs a separate plan that selects people. This article does not write that plan.

If you use a date filter, the end dates are inclusive. Inclusive means a comment on the first day or the last day can stay. A comment with no `createdAt` is skipped while a date filter is on. A date filter looks at no more than 250 public comments per post, unless you remove the result limit. Oldest-first uses that same look limit. Removing the result limit still does not collect the full public feed. Write this limit in your notes if you use dates or oldest-first.

## 2. Check one post with a cap of 3

A pilot is a very small first run. You use it to check the file shape. Start with one known public post and a cap of 3. A cap is the highest number of comments you ask to save from one post. The cap is a maximum, not a promised count.

The saved [export to CSV Task](https://apify.com/chronometrica/instagram-comments-scraper/examples/export-instagram-comments-to-csv) was checked on 2026-10-13. Its saved input is:

```json
{
  "postUrls": ["https://www.instagram.com/reel/DZu6dkVyImf/"],
  "maxCommentsPerPost": 250,
  "commentSortMode": "default",
  "collectAllAvailable": false
}
```

2. Check one post with a cap of 3

That Reel is a starting input. A starting input is not your study. A run does not do your coding. Replace the Reel with your own public post link.

Use this shape for the pilot:

```json
{
  "postUrls": ["https://www.instagram.com/p/YOURPUBLICPOST/"],
  "maxCommentsPerPost": 3,
  "commentSortMode": "default",
  "collectAllAvailable": false
}
```

2. Check one post with a cap of 3

`YOURPUBLICPOST` is a placeholder. A placeholder is a stand-in you must replace. Do not run those letters as if they were a real post.

`commentSortMode` set to `default` keeps the source order for that request. Default order is not a promise of the newest comments. It is not a promise of the oldest comments. It is not a random draw of all users. If you later choose newest or oldest, that choice sorts the comments the run collected. It does not by itself pass the look limit described in step 1.

`collectAllAvailable` set to false keeps your numeric cap. If you set it to true, the numeric cap is removed. The run can still stop when the public feed ends, when time runs out, or when a source fails. That stop is still not every public comment. It is not every Instagram user. Keep `collectAllAvailable` false for the pilot.

One post keeps the first check small. If two posts fail at once, the cause is harder to see. A cap of 3 lets you read every saved row by eye. The Task value 250 is only a saved maximum. It is not a promise of 250 comments. An older public question may talk about 100 comments as a tool limit. That old number is not the current Actor ceiling. Your written cap is the maximum you set. A higher cap is still not every comment.

After the run, compare `postShortCode` with your URL. Then read OUTPUT. OUTPUT is the run report saved with the rows. Read `requestedTargets`, `savedRows`, and `cappedTargets` at the top. For each target, read `resultState`, `sampleHealth`, and `stopReason`.

A checked run saved 3 rows for the requested short code. Those rows had comment IDs, text, author IDs, and dates. OUTPUT showed capped, stop reason `max_comments_reached`, and `savedRows` 3. `repliesCount` was null. That run shows the field shape on that source. It does not show all comments on the post. It does not prove which comment is oldest. It does not list reply text or other posts. Author IDs were present in that run. A later post can still return a null owner ID.

The cap stopped that run. A capped result does not mean the post has only 3 comments. Choose your study cap only after this shape check passes. Keep `collectAllAvailable` false until you understand the other stops.

If the pilot returns no rows, do not write that the post has no comments. Source visibility can fail. Read `resultState`, `sampleHealth`, and `stopReason` before you decide. Empty rows are not the same as a coded sentiment.

## 3. Keep the original JSON

JSON is a text format that can keep nested values and the word null. JSONL means one JSON object on each line. A dataset is the saved set of rows from a run. You can export it as described in the [Apify dataset guide](https://docs.apify.com/platform/storage/dataset).

Save the original JSON or JSONL and the OUTPUT report first. Make the CSV or Excel file as a working copy after that. CSV means a text table that separates columns with commas. Also save the input you used. Save the post links, the cap, the sort, `collectAllAvailable`, and any dates.

The comment row has no status field for the whole dataset. Excel will not hold OUTPUT inside the comment cells. If you keep only the working file, you can lose the rules that made it.

Keep JSON because CSV can hide the difference between null and empty. These two fictional values are not a study count:

```json
{"text": null, "likesCount": null, "ownerVerified": null}
```

3. Keep the original JSON

```json
{"text": "", "likesCount": 0, "ownerVerified": false}
```

3. Keep the original JSON

A CSV cell can look blank for null and for empty text. Only the original JSON tells you which one you had. Zero likes are real. False is a real verification value. Null is not zero, and null is not false.

## 4. Import CSV with IDs stored as text

Download the working copy as CSV or Excel from the dataset. For CSV, open Excel and use Data, then From Text/CSV. Review each column type before you load the file. Microsoft explains that import path in [Import or export text or CSV files](https://support.microsoft.com/en-us/office/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba).

Set `postId`, `commentId`, and `ownerId` to Text. Real source IDs often look like long numbers. A number column can drop digits from a long ID. Keep `text` as text. Keep `createdAt` and `collectedAt` as date and time only if they still match the JSON. UTC is the shared world clock. It does not use your local time zone. If Excel shifts the clock, keep the time as text instead. Do not trust the type Excel guesses.

After import, compare one `commentId` and one `createdAt` with the untouched JSON. Compare the row count too. The next section gives you a made-up text test for this check.

## 5. Count rows before you code

Check the working file against the original JSON. Count rows. Count different comment IDs that are not blank. Different means you count the same ID once. Find copied rows. Find rows with no comment ID. Use the JSON to tell null from empty.

### Fictional import test

The next file is fictional. Fictional means we made it up for teaching. It is not an Actor result. It is not part of the eight-row count below. It is not a result from a real Excel run.

The test ID is `1234567890123456789`. That ID has 19 digits. Store it as text. The expected ID is exactly those 19 digits. This article does not invent a wrong number from Excel. It tells you the digits you must still see.

The test text has a comma, a line break, quote marks, and the word café. The letter é is not in the basic English A-to-Z set. The file must keep é.

This JSON is the fictional source:

```json
{
  "commentId": "1234567890123456789",
  "text": "Nice, work\nShe said \"hello\" at the café"
}
```

5. Count rows before you code

In JSON, `\n` means one line break. This CSV is the same fictional row. The quoted text contains a real line break. Inner quote marks are doubled. That is normal CSV writing.

```csv
commentId,text
"1234567890123456789","Nice, work
She said ""hello"" at the café"
```

5. Count rows before you code

After a correct import, the ID is still:

```text
1234567890123456789
```

5. Count rows before you code

The text is still:

```text
Nice, work
She said "hello" at the café
```

5. Count rows before you code

Those are the expected kept values. They are not a report from your computer. If the ID column is a number, or café loses é, stop and fix the import. Do not start coding on a damaged file.

### Fictional eight-row study file

The next eight rows are also fictional. They are not an Actor result. They are not the checked three-row run. They are one teaching file from one made-up post. Do not copy these counts into a real thesis.

Every row shares these post values:

- `postUrl`: `https://www.instagram.com/p/EXAMPLE0001/`
- `postId`: `1000000000000000001`
- `postShortCode`: `EXAMPLE0001`
- `collectedAt`: `2026-10-13T12:00:00Z`

`postId` has 19 digits. In a real import it must be Text. Eight comment rows still belong to one post. Do not count eight posts.

In the table, null means unavailable. Empty means the text has no characters. The short owner labels A, B, C, and D stand in for source IDs. Real owner IDs can be long. Store them as text too.

| row | commentId | text | ownerId | ownerUsername | likesCount | repliesCount | ownerVerified | createdAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| R1 | C1 | Great post | A | name_a | 2 | null | false | 2026-10-01T10:00:00Z |
| R2 | C1 | Great post | A | name_a | 2 | null | false | 2026-10-01T10:00:00Z |
| R3 | C2 | Great post | A | name_a | 1 | null | false | 2026-10-02T10:00:00Z |
| R4 | C3 | Nice pic | B | name_b | 0 | 0 | false | 2026-10-03T10:00:00Z |
| R5 | C4 | Love this | C | name_c | 4 | null | false | 2026-10-04T10:00:00Z |
| R6 | C5 | Awesome | D | name_d | 3 | null | false | 2026-10-05T10:00:00Z |
| R7 | C6 | empty | null | null | null | null | null | 2026-10-06T10:00:00Z |
| R8 | null | Cool | null | null | null | null | null | 2026-10-07T10:00:00Z |

R1 and R2 have the same source values. R2 is a copied export row. A copied row repeats the same comment ID and the same source values. R3 uses the same words, Great post, but its comment ID is C2. C2 is a different comment. Its time and like count also differ. Those differences help you see it. The comment ID would be enough even if the other fields matched.

On R4, `likesCount` 0 means zero likes. `repliesCount` 0 means the source displayed zero replies. You still have no reply text. `ownerVerified` false means the source showed the account as not verified. On R7, null does not mean zero or false. C6 has empty text. This story does not use null text for C6. A CSV working copy can make that empty text, the null owner, the null likes, the null replies, and the null verification mark all look blank. Use the original JSON before you code C6.

Count the file in this order:

Raw rows are every line, including the copy. Raw rows = 8.

R2 is the extra copy. Extra copied rows = 1.

R8 has no comment ID. Rows with no comment ID = 1.

Distinct known comments = 8 - 1 - 1. That is 6. The six IDs are C1, C2, C3, C4, C5, and C6.

C6 has empty text. Known comments with empty text = 1.

The text base = 6 - 1. That is 5. The text base is the known comments with text you can code. Those comments are C1, C2, C3, C4, and C5.

You can also start from rows that contain words. R1, R2, R3, R4, R5, R6, and R8 have words. That is 7 rows. Remove the copy R2 and the unsettled row R8. 7 - 1 - 1 = 5. The same text base appears either way.

| Count | How it is reached | Result |
| --- | --- | --- |
| Raw rows | R1 through R8 | 8 |
| Extra copied rows | R2 copies R1 | 1 |
| Rows with no comment ID | R8 | 1 |
| Distinct known comments | 8 - 1 - 1 | 6 |
| Known comments with empty text | C6 | 1 |
| Text base | 6 - 1 | 5 |
| Known comments with an owner | C1 through C5 | 5 |
| Distinct known accounts | A, B, C, and D | 4 |

R1, R2, and R3 all say Great post. Count C1 once because R1 and R2 share an ID. Keep C2 because its ID is different. If you drop repeated words, you lose a real comment. If you keep every matching line, you count C1 twice.

Owner values on the six known comments are A, A, B, C, D, and null. Five known comments have an owner: C1 through C5. Account A wrote both C1 and C2. Distinct known accounts = 5 - 1. That is 4. The four accounts are A, B, C, and D. Five owner values are five comments, not five accounts, and not five people.

Raw rows with a non-null owner are R1, R2, R3, R4, R5, and R6. That count is 6. Six is the wrong comment total because R2 is a copy. After you remove the copy, five owner values remain. Five is still the wrong account total because A appears twice.

The file has no person field. One person could use two accounts. Two people could share one account. Report 4 known accounts. Do not report 4 people. Do not report 5 people. C6 is a known comment with a null owner. Do not write owner 0 for C6. Six known comments and four known accounts are both true in this file. They are not the same count.

R8 has the text Cool, a time, and no comment ID. No other row says Cool. That still does not make R8 a known comment. A time does not replace a comment ID. Do not merge R8 with C6 because both owners are null. A shared null is not a match. R8 is not proved to be a new comment. It is not proved to be a copy. Keep it unsettled until you review the original JSON. Do not merge on a blank comment ID.

Use 5 only as the base for a measure that needs known comments with text. Keep C6 in the availability count. Availability means whether the value was there. For a measure on all known comments, the base is 6. Write the base beside the measure. Do not use 8, 6, and 5 as if they were one base. This story does not calculate a praise share or any other result.

Known comments with `createdAt` = 6 of 6. Known comments with no date = 0 in this story. R8 has a time, but that time does not give it a comment ID. A real run can differ. If you used a date filter, comments with null `createdAt` would already have been skipped.

## 6. Add codes beside the raw text

Keep the source words in `raw_text`. Do not fix spelling there. Do not remove a line break there. If you need cleaned words, put them in a new column such as `coding_text`. Put every choice in new columns: `coding_unit`, `code`, `coder`, `decision_note`, and `coding_version`.

`coding_unit` names the thing you code. In this exercise the unit is the comment. `code` is your manual label. `coder` names the person who chose it. `decision_note` says why. `coding_version` names the rule set. If the rule changes, write a new version. Do not overwrite the old note in silence.

[A second person can repeat the choice](/en/articles/reddit-comment-coder-agreement/) only if those columns stay filled. The Actor does not fill them.

The same fictional file continues here. These labels are practice marks by a fictional coder named casey. They are not Actor fields. They are not sentiment scores. They are not findings about real people.

| commentId | raw_text | coding_unit | code | coder | decision_note | coding_version |
| --- | --- | --- | --- | --- | --- | --- |
| C1 | Great post | comment | in_text_base | casey | Same source values as the copied row. Count C1 once. | v1 |
| C2 | Great post | comment | in_text_base | casey | Words match C1. The comment ID differs, so keep both. | v1 |
| C3 | Nice pic | comment | in_text_base | casey | Different comment ID and account B. | v1 |
| C4 | Love this | comment | in_text_base | casey | Different comment ID and account C. | v1 |
| C5 | Awesome | comment | in_text_base | casey | Different comment ID and account D. | v1 |

C1 and C2 stay two units because their comment IDs differ. Account A wrote both. The account unit would group them. The comment unit does not.

C6 stays out of this practice table because its text is empty. Keep C6 in the availability count. R8 stays out because it has no comment ID. Do not give R8 a code to force it into the text base.

If the source post disappears later, keep the dated file as the observation from `collectedAt`. Do not edit the old words to match a missing page. A retention rule is a separate written choice for how long you keep the file and who can see it. This article does not set that rule.

## 7. Write the sample ledger

A ledger is a written count of what you asked for, what you saved, and what you left out. Build it from the original JSON and OUTPUT. The fictional ledger below uses only the made-up rows. This story has no OUTPUT report. Where a real run must copy OUTPUT, the story says unknown.

| Ledger line | Value in the fictional file |
| --- | --- |
| Requested posts | 1 made-up post |
| Saved rows | 8 |
| Extra copied rows | 1 |
| Distinct known comments | 6 |
| Text base | 5 |
| Known comments with empty text | 1 |
| Unsettled rows | 1 |
| Distinct known accounts | 4 |
| Known comments with a date | 6 |
| Known comments with no date | 0 |
| Useful targets | Unknown in this story. Read OUTPUT. |
| Failed targets | Unknown in this story. Read OUTPUT. |
| Empty targets | Unknown in this story. Read OUTPUT. |
| Capped targets | Unknown in this story. Read OUTPUT. |

On a real run, copy `requestedTargets`, `savedRows`, and `cappedTargets` from OUTPUT. Copy `resultState`, `sampleHealth`, and `stopReason` for each post. Then count distinct comment IDs yourself. Do not replace a report value with a guess. Do not paste the numbers from this story into that report.

| Item | What you do | Reason |
| --- | --- | --- |
| R2 | Leave it out of comment totals | Copied row of C1 with the same source values |
| C6 | Leave it out of the text base only | Empty text. Keep it in the availability count |
| R8 | Keep it on a separate unsettled list | No comment ID. Do not merge on a blank ID |

Write each exclusion reason next to the row. Default order and public visibility can change which voices appear. Say that in the ledger note. A capped target hit the cap you set. It does not by itself name every comment that stayed out.

The source displayed total is the comment count shown on the post page. Keep it separate from saved rows. You will use both numbers in the methods note.

## 8. Write a methods note that matches the file

Write what you actually saved. Say that the rows are publicly shown top-level comments. Name the post links, the order, the cap, the date window, and the collection time. Keep the source displayed total separate from saved rows.

If those two numbers differ, the cause is not known from the difference alone. The gap does not prove a collection bug. The gap also does not prove the collection was complete or faultless. A cap, a feed stop, a time stop, a source failure, hidden comments, or replies that were never rows can all change the counts. Your OUTPUT note is the place to record the stop you actually saw.

If saved rows are zero, do not write that the post had no comments. Read the target states first.

These words are a template. Replace each bracket with your own run. Do not copy the fictional counts into it.

```text
We saved publicly shown top-level comments.
The posts were the public URLs we listed before the run.
The order was default.
The cap per post was [your cap].
The collection time was [collectedAt].
The date window was [your dates], or we did not use a date filter.
We saved [saved rows] rows.
The source displayed total was [source total], or we did not record one.
A difference between those numbers is not proof of a bug.
That difference is also not proof that the file is complete.
We did not export reply text.
Null means a value was unavailable.
We kept zero and false as real values.
We counted comments, accounts, and posts separately.
This file describes the saved public comments only.
It does not describe all Instagram users.
```

8. Write a methods note that matches the file

Leave the source displayed total blank if you did not record it. Do not invent it. A second person should be able to trace each coded comment to a comment ID, a post link, and `collectedAt`.

## Next action

Replace the sample Reel with one public post you already know. Use a cap of 3, default order, and `collectAllAvailable` set to false. Compare the IDs and text with the original JSON before you raise the cap. Start from the [Instagram Comments Scraper Actor](https://apify.com/chronometrica/instagram-comments-scraper) and the [export to CSV Task](https://apify.com/chronometrica/instagram-comments-scraper/examples/export-instagram-comments-to-csv).

## Sources

- [Instagram Comments Scraper Actor](https://apify.com/chronometrica/instagram-comments-scraper)
- [Export Instagram comments to CSV Task](https://apify.com/chronometrica/instagram-comments-scraper/examples/export-instagram-comments-to-csv)
- [Download Instagram comments to Excel question](https://www.reddit.com/r/InstagramMarketing/comments/15m86x1/download_instagram_comments_to_excel/)
- [Import or export text or CSV files](https://support.microsoft.com/en-us/office/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba)
- [Apify dataset storage](https://docs.apify.com/platform/storage/dataset)