{
  "apiVersion": 1,
  "generatedAt": "2026-09-14T00:00:00+00:00",
  "slug": "youtube-metrics-comments-collector",
  "botId": "youtube-metrics-comments-collector",
  "configEncoding": "plain",
  "configDigest": "a331f5367f9707ecdbf27df35c164756245cbd99c2ec534746235fb4e5562454",
  "configBytes": 21709,
  "project": {
    "name": "YouTube Metrics and Comments Collector",
    "settings": {
      "urls": [
        "${video_url}"
      ],
      "headless": false,
      "navigation_timeout": 60000,
      "humanoidEnabled": false,
      "proxyEnabled": false,
      "captchaEnabled": false,
      "matchTimezoneAndLocale": false,
      "block_resources": false,
      "get_urls_from_sitemap": false,
      "respect_robots_txt": false,
      "skip_initial_navigation": true,
      "variables": [
        {
          "name": "video_url",
          "value": "",
          "required": true,
          "is_list": false
        },
        {
          "name": "comment_count",
          "value": "50",
          "required": true,
          "is_list": false
        },
        {
          "name": "sort_order",
          "value": "top",
          "required": true,
          "is_list": false
        }
      ]
    },
    "datasets": {
      "video_metrics": {
        "item_selector": "css=html",
        "properties": [
          {
            "name": "video_id",
            "type": "text",
            "selector": "variable=m_video_id"
          },
          {
            "name": "channel_id",
            "type": "text",
            "selector": "variable=m_channel_id"
          },
          {
            "name": "title",
            "type": "text",
            "selector": "variable=m_title"
          },
          {
            "name": "channel",
            "type": "text",
            "selector": "variable=m_channel"
          },
          {
            "name": "views",
            "type": "text",
            "selector": "variable=m_views"
          },
          {
            "name": "likes",
            "type": "text",
            "selector": "variable=m_likes"
          },
          {
            "name": "published",
            "type": "text",
            "selector": "variable=m_published"
          },
          {
            "name": "duration_seconds",
            "type": "text",
            "selector": "variable=m_duration"
          },
          {
            "name": "description",
            "type": "attribute",
            "selector": "css=meta[property='og:description']",
            "attribute": "content"
          },
          {
            "name": "page_url",
            "type": "page_url"
          }
        ],
        "required_fields": [
          "title"
        ]
      },
      "comments": {
        "item_selector": "css=ytd-comment-thread-renderer",
        "properties": [
          {
            "name": "author",
            "type": "text",
            "selector": "css=#author-text",
            "transformations": [
              {
                "type": "trim"
              }
            ]
          },
          {
            "name": "author_url",
            "type": "attribute",
            "selector": "css=#author-text",
            "attribute": "href",
            "transformations": [
              {
                "type": "script",
                "code": "if(!value) return ''; var v=String(value); return v.indexOf('http')===0 ? v : 'https://www.youtube.com'+(v.charAt(0)==='/'?'':'/')+v;"
              }
            ]
          },
          {
            "name": "text",
            "type": "text",
            "selector": "css=#content-text",
            "transformations": [
              {
                "type": "trim"
              }
            ]
          },
          {
            "name": "likes",
            "type": "text",
            "selector": "css=#vote-count-middle",
            "transformations": [
              {
                "type": "script",
                "code": "if(!value) return '0'; var m=String(value).match(/([\\d.,]+)\\s*([KM]?)/i); if(!m) return '0'; var n=parseFloat(m[1].replace(/,/g,'')); if(isNaN(n)) return '0'; var s=(m[2]||'').toUpperCase(); if(s==='K')n*=1e3; if(s==='M')n*=1e6; return String(Math.round(n));"
              }
            ]
          },
          {
            "name": "published",
            "type": "text",
            "selector": "css=.published-time-text, #published-time-text",
            "transformations": [
              {
                "type": "trim"
              }
            ]
          },
          {
            "name": "replies",
            "type": "text",
            "selector": "css=#more-replies, #more-text",
            "transformations": [
              {
                "type": "trim"
              }
            ]
          },
          {
            "name": "page_url",
            "type": "page_url"
          }
        ]
      }
    },
    "commands": [
      {
        "command": "set_cookie",
        "params": {
          "name": "SOCS",
          "value": "CAI",
          "url": "https://www.youtube.com"
        }
      },
      {
        "command": "set_cookie",
        "params": {
          "name": "CONSENT",
          "value": "YES+cb",
          "url": "https://www.youtube.com"
        }
      },
      {
        "command": "set_cookie",
        "params": {
          "name": "PREF",
          "value": "hl=en",
          "url": "https://www.youtube.com"
        }
      },
      {
        "command": "set_viewport_size",
        "params": {
          "width": 480,
          "height": 940
        }
      },
      {
        "command": "goto",
        "params": {
          "url": "${last_target_url}"
        }
      },
      {
        "command": "try_catch",
        "params": {
          "try": [
            {
              "command": "wait_for_selector",
              "params": {
                "selector": "css=button[aria-label*='Reject all'], button[aria-label*='Accept all']",
                "timeout": 4000
              }
            },
            {
              "command": "click",
              "params": {
                "selector": "css=button[aria-label*='Reject all'], button[aria-label*='Accept all']",
                "timeout": 5000
              }
            }
          ],
          "catch": [
            {
              "command": "comment",
              "params": {
                "text": "No consent interstitial (cookies worked or non-EU)."
              }
            }
          ]
        }
      },
      {
        "command": "wait_for_selector",
        "params": {
          "selector": "css=ytd-watch-flexy",
          "timeout": 45000
        }
      },
      {
        "command": "set_viewport_size",
        "params": {
          "width": 480,
          "height": 940
        }
      },
      {
        "command": "comment",
        "params": {
          "text": "PHASE 1: metrics from embedded JSON (stable route)."
        }
      },
      {
        "command": "try_catch",
        "params": {
          "try": [
            {
              "command": "execute_script",
              "output_variable": "m",
              "params": {
                "script": "var p=window.ytInitialPlayerResponse||{};var v=p.videoDetails||{};var mf=(p.microformat||{}).playerMicroformatRenderer||{};var d=window.ytInitialData||{};var likes='';(function w(o,dep){if(likes||!o||typeof o!=='object'||dep>12)return;for(var k in o){if(/likeCount/i.test(k)&&typeof o[k]==='string'&&/[0-9]/.test(o[k])){likes=o[k];return;}w(o[k],dep+1);}})(d,0);return {video_id:String(v.videoId||''),channel_id:String(mf.channelId||''),title:String(v.title||document.title||''),channel:String(v.author||mf.ownerChannelName||''),views:String(v.viewCount||''),likes:String(likes||''),published:String(mf.publishDate||''),duration:String(v.lengthSeconds||'')};"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_video_id",
                "value": "${m.video_id}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_channel_id",
                "value": "${m.channel_id}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_title",
                "value": "${m.title}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_channel",
                "value": "${m.channel}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_views",
                "value": "${m.views}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_likes",
                "value": "${m.likes}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_published",
                "value": "${m.published}"
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "m_duration",
                "value": "${m.duration}"
              }
            },
            {
              "command": "extract_data",
              "params": {
                "dataset": "video_metrics"
              }
            }
          ],
          "catch": [
            {
              "command": "comment",
              "params": {
                "text": "METRICS_DEGRADED: embedded JSON unavailable; comments will still be collected."
              }
            }
          ]
        }
      },
      {
        "command": "comment",
        "params": {
          "text": "PHASE 2: strip related-videos column + footer so the comments section owns the document bottom. This is what makes infinite_scroll's bottom-teleport land ON the comment continuation spinner instead of overshooting into lazily-loaded related videos."
        }
      },
      {
        "command": "execute_script",
        "params": {
          "script": "var removed=[];var sec=document.querySelector('ytd-watch-flexy #secondary')||document.querySelector('#secondary');if(sec){sec.remove();removed.push('secondary');}var rel=document.querySelector('ytd-watch-next-secondary-results-renderer');if(rel){rel.remove();removed.push('related');}var foot=document.querySelector('tp-yt-app #footer')||document.querySelector('ytd-app #footer');if(foot){foot.remove();removed.push('footer');}return removed.join(',');"
        },
        "output_variable": "stripped"
      },
      {
        "command": "comment",
        "params": {
          "text": "PHASE 2b: teleport to true document bottom ONCE so every comment-section sentinel intersects (full hydration + interactive sort header + armed continuation spinner). Safe only because #secondary was just stripped — bottom == end of comments."
        }
      },
      {
        "command": "scroll",
        "params": {
          "selector": "css=html",
          "direction": "bottom",
          "delay": 0
        }
      },
      {
        "command": "wait",
        "params": {
          "timeout": 1500
        }
      },
      {
        "command": "comment",
        "params": {
          "text": "PHASE 3: force-mount lazy comment section. Gradual 600px steps (never teleport) so YouTube's IntersectionObserver samples the sentinel; dual visibility gates exit on first thread OR on 'comments off' message."
        }
      },
      {
        "command": "set_variable",
        "params": {
          "name": "threads_visible",
          "value": "false"
        }
      },
      {
        "command": "set_variable",
        "params": {
          "name": "comments_off",
          "value": "false"
        }
      },
      {
        "command": "set_variable",
        "params": {
          "name": "mount_tries",
          "value": "0"
        }
      },
      {
        "command": "while",
        "params": {
          "condition": {
            "type": "script",
            "expression": "String(variables.threads_visible) !== 'true' && String(variables.comments_off) !== 'true' && Number(variables.mount_tries) < 40"
          },
          "do": [
            {
              "command": "scroll",
              "params": {
                "direction": "by_pixels",
                "amount": 600,
                "delay": 0
              }
            },
            {
              "command": "wait",
              "params": {
                "timeout": 450
              }
            },
            {
              "command": "is_visible",
              "params": {
                "selector": "css=ytd-comment-thread-renderer"
              },
              "output_variable": "threads_visible"
            },
            {
              "command": "is_visible",
              "params": {
                "selector": "css=ytd-comments #message, ytd-comments ytd-message-renderer"
              },
              "output_variable": "comments_off"
            },
            {
              "command": "set_variable",
              "params": {
                "name": "mount_tries",
                "value": "${Number(mount_tries) + 1}"
              }
            }
          ]
        }
      },
      {
        "command": "set_variable",
        "params": {
          "name": "comments_unavailable",
          "value": "false"
        }
      },
      {
        "command": "try_catch",
        "params": {
          "try": [
            {
              "command": "wait_for_selector",
              "params": {
                "selector": "css=ytd-comment-thread-renderer",
                "timeout": 10000
              }
            }
          ],
          "catch": [
            {
              "command": "set_variable",
              "params": {
                "name": "comments_unavailable",
                "value": "true"
              }
            }
          ]
        }
      },
      {
        "command": "if",
        "params": {
          "condition": {
            "type": "script",
            "expression": "String(variables.comments_unavailable) !== 'true'"
          },
          "then": [
            {
              "command": "comment",
              "params": {
                "text": "PHASE 4: explicit sort selection (both branches click — never trust the channel's default ordering). Position-based nth click first (language-neutral), exact-text fallback second (guaranteed English via PREF hl=en cookie)."
              }
            },
            {
              "command": "try_catch",
              "params": {
                "try": [
                  {
                    "command": "scroll_into_view",
                    "params": {
                      "selector": "css=ytd-comments-header-renderer",
                      "timeout": 4000
                    }
                  },
                  {
                    "command": "if",
                    "params": {
                      "condition": {
                        "type": "script",
                        "expression": "String(variables.sort_order || '').toLowerCase().indexOf('recent') > -1 || String(variables.sort_order || '').toLowerCase().indexOf('new') > -1"
                      },
                      "then": [
                        {
                          "command": "click",
                          "params": {
                            "selector": "css=ytd-comments-header-renderer tp-yt-paper-dropdown-menu-trigger #icon-button, ytd-comments-header-renderer tp-yt-paper-dropdown-menu-trigger, ytd-comments-header-renderer button[aria-label*='Sort'], #sort-menu #icon-button",
                            "timeout": 8000
                          }
                        },
                        {
                          "command": "wait",
                          "params": {
                            "timeout": 1200
                          }
                        },
                        {
                          "command": "try_catch",
                          "params": {
                            "try": [
                              {
                                "command": "click",
                                "params": {
                                  "selector": "css=ytd-comments-header-renderer tp-yt-paper-listbox a, ytd-comments-header-renderer tp-yt-paper-listbox tp-yt-paper-item >> nth=1",
                                  "timeout": 5000
                                }
                              }
                            ],
                            "catch": [
                              {
                                "command": "click",
                                "params": {
                                  "selector": "text=Newest first",
                                  "timeout": 5000
                                }
                              }
                            ]
                          }
                        },
                        {
                          "command": "wait",
                          "params": {
                            "timeout": 2500
                          }
                        },
                        {
                          "command": "wait_for_selector",
                          "params": {
                            "selector": "css=ytd-comment-thread-renderer",
                            "timeout": 20000
                          }
                        }
                      ],
                      "else": [
                        {
                          "command": "click",
                          "params": {
                            "selector": "css=ytd-comments-header-renderer tp-yt-paper-dropdown-menu-trigger #icon-button, ytd-comments-header-renderer tp-yt-paper-dropdown-menu-trigger, ytd-comments-header-renderer button[aria-label*='Sort'], #sort-menu #icon-button",
                            "timeout": 8000
                          }
                        },
                        {
                          "command": "wait",
                          "params": {
                            "timeout": 1200
                          }
                        },
                        {
                          "command": "try_catch",
                          "params": {
                            "try": [
                              {
                                "command": "click",
                                "params": {
                                  "selector": "css=ytd-comments-header-renderer tp-yt-paper-listbox a, ytd-comments-header-renderer tp-yt-paper-listbox tp-yt-paper-item >> nth=0",
                                  "timeout": 5000
                                }
                              }
                            ],
                            "catch": [
                              {
                                "command": "click",
                                "params": {
                                  "selector": "text=Top comments",
                                  "timeout": 5000
                                }
                              }
                            ]
                          }
                        },
                        {
                          "command": "wait",
                          "params": {
                            "timeout": 2500
                          }
                        },
                        {
                          "command": "wait_for_selector",
                          "params": {
                            "selector": "css=ytd-comment-thread-renderer",
                            "timeout": 20000
                          }
                        }
                      ]
                    }
                  }
                ],
                "catch": [
                  {
                    "command": "comment",
                    "params": {
                      "text": "SORT_DEGRADED: menu interaction failed; continuing with default ordering."
                    }
                  }
                ]
              }
            },
            {
              "command": "comment",
              "params": {
                "text": "PHASE 5: collect. infinite_scroll on css=html (the standards-mode scrollingElement; body.scrollHeight is unreliable on YouTube). do-block: re-strip insurance, deduped extract, exact-N break. Current engine checkpoints variables+memory every cycle, so crash-resume never duplicates rows."
              }
            },
            {
              "command": "set_variable",
              "params": {
                "name": "total",
                "value": "0"
              }
            },
            {
              "command": "infinite_scroll",
              "params": {
                "selector": "css=html",
                "max_scrolls": "${Math.max(12, Math.ceil(Number(comment_count) / 10) + 8)}",
                "scroll_delay": 2000,
                "do": [
                  {
                    "command": "execute_script",
                    "params": {
                      "script": "var s=document.querySelector('ytd-watch-flexy #secondary')||document.querySelector('#secondary');if(s)s.remove();var f=document.querySelector('tp-yt-app #footer')||document.querySelector('ytd-app #footer');if(f)f.remove();return true;"
                    }
                  },
                  {
                    "command": "extract_data",
                    "params": {
                      "dataset": "comments"
                    },
                    "output_variable": "fresh"
                  },
                  {
                    "command": "set_variable",
                    "params": {
                      "name": "total",
                      "value": "${Number(total) + (Array.isArray(fresh) ? fresh.length : 0)}"
                    }
                  },
                  {
                    "command": "if",
                    "params": {
                      "condition": {
                        "type": "script",
                        "expression": "Number(variables.total) >= Number(variables.comment_count)"
                      },
                      "then": [
                        {
                          "command": "break",
                          "params": {}
                        }
                      ],
                      "else": []
                    }
                  }
                ]
              }
            },
            {
              "command": "comment",
              "params": {
                "text": "Collection finished: total=${total} requested=${comment_count} sort=${sort_order} mount_tries=${mount_tries} stripped=${stripped}"
              }
            }
          ],
          "else": [
            {
              "command": "comment",
              "params": {
                "text": "COMMENTS_UNAVAILABLE: comment section never rendered (disabled, restricted, bot-walled, or blocked). Metrics were still collected."
              }
            }
          ]
        }
      }
    ],
    "authors": []
  }
}