Created
June 13, 2026 20:53
-
-
Save thelabcat/80e06903410e73310324b2662d2d5171 to your computer and use it in GitHub Desktop.
Gaming on Rumble viewers per stream calculator
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
| #!/usr/bin/env python3 | |
| """Gaming on Rumble viewers per stream calculator | |
| Do some scraping, and calculate a viewer-per-stream stat for gaming on Rumble, | |
| and format it to BewareTheMoon's X post style. | |
| Relies on Chrome or a compatible browser being installed and configured for | |
| Selenium puppeteering, plus the Python libraries `bs4` and `selenium` being | |
| installed. Both modules are available from PYPI. | |
| Copyright 2026 Wilbur Jaywright dba Marswide BGL | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| S.D.G. | |
| """ | |
| import bs4 | |
| from selenium import webdriver | |
| from selenium.webdriver.chrome.options import Options | |
| RUMBLE_BASE_URI = "https://rumble.com" | |
| CAT_SUB_URI = "/category/gaming" # The sub URI pointing at the category page | |
| PAGE_PARAM = "?page=" # The parameter at the end of the category page URI that specifies with number page we are on | |
| VIEW_MULTS = "KMGT" # Suffix multiples of 1000 views | |
| def get_category_page(driver: webdriver.Chrome, pagenum: int = 1) -> bs4.BeautifulSoup: | |
| """ | |
| Load a numbered page of the category. | |
| Args: | |
| driver (webdriver.Chrome): A running Selenium driver. | |
| pagenum (int): The page number to load. | |
| Defaults to 1. | |
| Returns: | |
| page (bs4.BeautifulSoup): HTML soup of the page. | |
| """ | |
| url = RUMBLE_BASE_URI + CAT_SUB_URI + PAGE_PARAM + str(pagenum) | |
| print(f"Loading {url} ...", end=" ") | |
| driver.get(url) | |
| print("Done.") | |
| return bs4.BeautifulSoup(driver.page_source, features="html.parser") | |
| def count_videos_on_page(page: bs4.BeautifulSoup) -> int: | |
| """Count the videos on a single category page""" | |
| return len(page.find_all("rum-video-thumbnail")) | |
| def get_page_ref(link_bttn: bs4.Tag) -> int: | |
| """For a number page button, find which page it is pointing at""" | |
| assert link_bttn.get("href").startswith(CAT_SUB_URI), f"Expected page link button, got {link_bttn}" | |
| # The button has no page parameter, so it will go to the first page | |
| if "?page=" not in link_bttn["href"]: | |
| return 1 | |
| # Parse the value of the page parameter | |
| return int(link_bttn["href"].split(PAGE_PARAM)[1]) | |
| # Start the browser | |
| print("Starting web browser driver...", end=" ") | |
| chrome_options = Options() | |
| chrome_options.add_argument("--headless=new") | |
| driver = webdriver.Chrome(options=chrome_options) | |
| print("Started.") | |
| # Start with the first page of the category | |
| first_page = get_category_page(driver) | |
| # Find the highest valued numbered page button, and store what number it is | |
| page_count = get_page_ref( | |
| max( | |
| # Use a CSS selector to find all links that point to this same category | |
| # Most of them are the numbered page buttons at the bottom... | |
| first_page.select(f"a[href^={repr(CAT_SUB_URI)}]"), | |
| # ...then find the maximum page parameter among them | |
| key=get_page_ref | |
| ) | |
| ) | |
| print(f"There are {page_count} total pages of game streams.") | |
| # The total number of videos on the first page is either the max there can be, or all there are. | |
| # In the second case, the math later still works. | |
| max_on_page = count_videos_on_page(first_page) | |
| print("There are", max_on_page, "streams on the first page, so that's the most there will be on each.") | |
| # If we have more than one page, the first page is full | |
| if page_count != 1: | |
| # Find the number of videos on the last page | |
| last_page = get_category_page(driver, page_count) | |
| on_last_page = count_videos_on_page(last_page) | |
| print("There are", on_last_page, "streams on the last page.") | |
| else: | |
| # The first page is the only one, that's all the videos | |
| on_last_page = max_on_page | |
| # We are done requesting pages I think | |
| print("Shutting down web browser driver.") | |
| driver.quit() | |
| # Even if the first page is the only one, that just means this equals on_last_page, which will be correct | |
| total_streams = max_on_page * (page_count - 1) + on_last_page | |
| print() # Value is calculated, but to fit tweet form, we want it later... | |
| # Time to parse the viewer count! | |
| viewcount_elem = first_page.select_one(".header__viewers") | |
| viewcount_text = viewcount_elem.get_text().strip() # "6.7K Viewers" | |
| assert viewcount_text.endswith( | |
| "Viewers" | |
| ), f"Error finding view count element, found instead: {viewcount_elem}" | |
| viewcount_numstr = viewcount_text.removesuffix("Viewers").strip() # "6.7K" | |
| # Is the view count multiplied by a suffix? | |
| if not viewcount_numstr[-1].isnumeric(): | |
| viewcount = int( | |
| # Get the numeric half... | |
| float(viewcount_numstr[:-1]) | |
| # ...then parse the multiplier suffix half and apply it | |
| * (1000 ** (VIEW_MULTS.index(viewcount_numstr[-1]) + 1)) | |
| ) | |
| # The viewcount is direct, with no multiplier suffix | |
| else: | |
| viewcount = int(viewcount_numstr) | |
| # Finally, print the tweet format | |
| print(f"There are currently {viewcount_numstr} ppl viewing gaming on Rumble") | |
| print(f"There are currently {total_streams} ppl streaming games on Rumble") | |
| print( | |
| f"Divided evenly, that would be {viewcount / total_streams:.2f} viewers per streamer" | |
| ) | |
| input("\nPress enter to exit.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment