# Complete X/Twitter Scraping Guide for 2026: Tweets, Profile Info, Trending Topics > Source: https://scrape.do/blog/twitter-x-scraping/ Published: 2026-05-08 · Updated: 2026-05-08 · Authors: Antonello Zanini · Categories: Scraping Use Cases Is the X API too expensive, or are you concerned they may become even more costly in the future or restrict access to essential data fields? Scraping Twitter is the practical alternative! Still, you may have already tried and been blocked by X’s anti-scraping measures… In this detailed tutorial, you will learn everything you need to build an effective, scalable, production-ready Twitter scraper. This will be able to automatically retrieve Tweets, X profile information, and trending topics. Follow the instructions below and become a Twitter scraping champion! ## Why Scrape Twitter When There Is an Official API? [X comes with official APIs](https://docs.x.com/x-api/introduction) to access posts, users, Spaces, lists, trends, and media. However, usage is constrained by rate limits. Also, pricing follows a pay-per-use structure, with a lack of flat-rate subscription options. That can make large-scale Twitter data collection quite expensive… In addition, API access is not fully stable or future-proof. X can modify response structures or remove specific data fields without notice. More importantly, it may also place relevant data behind paywalls, further limiting what can be accessed. Compared to the X API, Twitter scraping offers greater flexibility and scalability. It enables access to all publicly visible data at a lower cost and without strict rate limits. Compared to the official APIs, web scraping provides more control over what is collected and how it is structured. That makes it a more adaptable alternative in several real-world use cases. ## Data You Can Retrieve Via Twitter Scraping The main data that you can retrieve from Twitter via web scraping is: - **Tweets**: Post text, publication timestamp, engagement metrics (likes, replies, reposts, etc.), media, embedded links, author metadata, and more. - **Profile information**: Name, handle, bio, location, profile and header images, verification status, follower/following counts, join date, etc. - **Trending topics**: Current trending hashtags and topics, along with contextual labels like region or category. ## Main Challenges of Scraping X/Twitter [To keep web scraping legal and ethical](https://scrape.do/blog/ethics-of-web-scraping-detailed-review/), you should only target publicly available information. As a rule of thumb, avoid accessing any data behind a login wall. Now, one of the first challenges in Twitter scraping is its notoriously aggressive login wall practices. If you visit X in a browser while not logged in and try to interact with page elements, most actions will trigger a login modal: ![The X login modal](/uploads/blog/twitter-x-scraping-x-login-modal.png) This significantly limits the amount of publicly accessible Twitter information and, consequently, what can be scraped directly from the site. However, the main challenge is actually X’s [anti-scraping mechanisms](https://scrape.do/blog/prevent-web-scraping/). These include IP blocks for excessive requests, as well as heavy reliance on JavaScript rendering. Try accessing Twitter/X through a simple HTTP client, and you will encounter an error page like this: ![The X “JavaScript is not available” error page](/uploads/blog/twitter-x-scraping-javascript-not-available.png) These protections are designed to block automated bots and scraping scripts. Still, there is a solution! ## How to Build an Effective Twitter Scraper in Python To build a working Twitter scraper, you need a way to handle X’s anti-scraping protections. This is where a solution like the [Scrape.do Web Scraping API](https://scrape.do/products/web-scraping-api/) comes in! Scrape.do acts as a cloud-based scraping layer that handles proxies, CAPTCHA solving, TLS fingerprinting, and headless browser execution. It allows you to reliably retrieve the fully rendered HTML of a Twitter/X page without having to manage scraping infrastructure yourself. [Learn more in the docs](https://scrape.do/documentation). Scrape.do’s Web Scraping API gives you access to that scraping infrastructure through a single endpoint. You can [start for free with 1,000 monthly credits](https://scrape.do/pricing/). At a high level, the architecture for a [Python scraping script](https://scrape.do/blog/how-to-scrape-data-with-python-detailed-preview/) targeting Twitter is straightforward: 1. You use Python’s [`requests`](https://pypi.org/project/requests/) library to call the Scrape.do API endpoint and retrieve the unlocked X page content. 2. You parse the returned HTML using a lightweight HTML parser such as [`beautifulsoup`](https://pypi.org/project/beautifulsoup4/). This approach is way simpler and more resource-efficient than running a full headless browser operation (e.g., [via Playwright](https://scrape.do/blog/web-scraping-with-playwright/) or [Selenium](https://scrape.do/blog/selenium-web-scraping/)). It combines the speed of static HTTP clients with the ability to access JavaScript-rendered content, making it well-suited for scalable Twitter scraping. Now, go through the common required steps to build such a Twitter scraper! ### Prerequisites To follow along with this tutorial, make sure you have: - A [Scrape.do account](https://dashboard.scrape.do/sign-up). - Python installed locally, with a project set up and [a virtual environment configured](https://docs.python.org/3/library/venv.html). - Requests and Beautiful Soup installed in your venv: pip install requests beautifulsoup4 ### Get Started with Scrape.do Web Scraping API [Log in to your Scrape.do account](https://dashboard.scrape.do/login), or [create a new one](https://dashboard.scrape.do/sign-up) if you have not before. You will be redirected to the Playground section of your account. Here, you can get familiar with Scrape.do’s Web Scraping API by experimenting with the available arguments in an interactive web app. You can also get runnable snippets in multiple programming languages. Continue by copying your Scrape.do API token by clicking the “Copy to clipboard” button in the “Your API Token” section: ![Copying your Scrape.do API token](/uploads/blog/twitter-x-scraping-copy-api-token.png) The Scrape.do API token is required to authenticate requests to the Web Scraping API. Store it in a safe place, as you will need it soon in your Python Twitter scraping script. **Note**: The API token is automatically generated for you when you subscribe. If you want to manage your API tokens or create a new one, go to the “API Token” section in the “Settings” page: ![Managing the Scrape.do API tokens in the dashboard](/uploads/blog/twitter-x-scraping-manage-api-tokens.png) [Explore](https://scrape.do/documentation/) the documentation to learn how to connect to the Web Scraping API, including supported parameters, available options, and usage examples. ### Configuring Scrape.do Web Scraping API for Twitter Scraping The Scrape.do Web Scraping API [supports several arguments](https://scrape.do/documentation/#api-parameters-overview) and must be configured correctly to retrieve the unlocked HTML of the target page. To configure it properly, you first need to understand how the target website works. Visit an X profile (e.g., the [National Geographic X profile](https://x.com/NatGeo)) in incognito mode (to ensure a fresh session) and observe how the page renders: ![How a Twitter/X profile page renders](/uploads/blog/twitter-x-scraping-profile-page-render.gif) You will notice that the page dynamically loads its data. First, it retrieves and renders the layout, and then it loads the Tweets. So, as expected, the target website is dynamic and [requires JavaScript execution](https://scrape.do/blog/how-to-scrape-javascript-rendered-web-pages-with-python/). Scrape.do's Web Scraping API supports JavaScript rendering, so that is not a problem. Now, your goal is to scrape Tweets (as well as other data). Thus, you must ensure that Tweet HTML elements are present on the page before applying the parsing logic. Instead of waiting a fixed amount of time for the page to load (which is an option supported by the Web Scraping API), waiting for a selector is more robust. This option allows you to instruct the cloud browser used by Scrape.do to wait for a specific element to be on the page before returning the HTML. In other words, it ensures you receive a fully populated Twitter page with Tweets loaded. To identify the correct selector, start by inspecting a Tweet element in your browser: ![The HTML of a Tweet node](/uploads/blog/twitter-x-scraping-tweet-node-html.png) Note that Tweet nodes can be selected with: ```css article[data-testid="tweet"] ``` **Pro tip**: When writing a selector for web scraping, referencing an HTML attribute like `data-testid` is ideal. That is because [`data-*`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-*) are custom HTML attributes that are often used in E2E tests. That mean they tend to remain stable over time across releases. So, configure the Web Scraping API in the playground as follows: ![Configuring the Scrape.do Web Scraping API for Twitter scraping](/uploads/blog/twitter-x-scraping-api-config-twitter.png) Enable the “Render JavaScript” option and set `article[data-testid="tweet"]` in the “Wait Selector” field (*Note*: “Block resources” is enabled by default to save resources). These are the required settings for Twitter scraping. Copy the Python snippet provided in the playground to test the setup. Alternatively, run the equivalent, more readable version below: ```python import requests from bs4 import BeautifulSoup import csv # The target Twitter URL to scrape target_url = "https://x.com/NatGeo" # Replace with your actual Scrape.do API token SCRAPE_DO_API_TOKEN = "" # The required parameters to scrape Twitter params = { "url": target_url, "token": SCRAPE_DO_API_TOKEN, "render": "true", "waitSelector": 'article[data-testid="tweet"]' } # Perform a request to the Scrape.do Web Scraping API response = requests.get( "http://api.scrape.do/", params=params ) # Raise an exception if the request was unsuccessful response.raise_for_status() print(f"Status code:\n{response.status_code}\n") print(f"HTML:\n {response.text}") ``` **Pro tip**: In a production-ready script, avoid embedding the Scrape.do API token directly in the code. Instead, load it from an environment variable or a `.env` file. Execute it, and you will get: ![The output produced by the test script](/uploads/blog/twitter-x-scraping-test-script-output.png) Note the [`200` response code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200), which indicates the page was scraped successfully. The response body from the Web Scraping API contains the fully rendered Twitter HTML page, including loaded Tweets. That means Scrape.do was able to bypass all Twitter anti-scraping mechanisms. Great! ### What to Expect Next In the following chapters, you will see how to build Twitter scrapers to fetch: - Tweets - X profile information - X trending topics In each case, you will be guided through all the necessary steps. Let’s get started! ## Scraping Tweets In this step-by-step section, you will see how to build a Python script for scraping Tweets. The target profile will be National Geographic, but any other X profile will work just as well. ### Step #1: Retrieve and Parse the Target Page To access the X profile page with Tweets fully loaded, use the Scrape.do Web Scraping API as explained earlier. Then, pass the unlocked HTML to BeautifulSoup for parsing: ```python from bs4 import BeautifulSoup # Retrieve the X profile page via Scrape.do Web Scraping API... html = response.text soup = BeautifulSoup(html, "html.parser") ``` Through the `soup` object, you now have access to the BeautifulSoup API for node selection and data parsing. Well done! ### Step #2: Prepare to Scrape All Tweets Since the target page contains multiple Tweets, start by defining a data structure where to store the scraped data. A list will work perfectly: ```python tweets = [] ``` Use [Beautiful Soup’ `select()`](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#css-selectors) method to select all Tweet HTML nodes. Then, iterate over them and prepare to extract data from each one: ```python # Select all tweet elements tweet_elements = soup.select('article[data-testid="tweet"]') for tweet_element in tweet_elements: # Scrape data from each tweet... ``` Amazing! Time to understand which data fields you can extract from a single Tweet node. ### Step #3: Define the Tweet Data Scraping Strategy Before jumping straight into scraping data from X, you need a strategy. Take a look at a Tweet card: ![](/uploads/blog/twitter-x-scraping-tweet-card-overview.png) Inspect it in your browser using DevTools and get familiar with its HTML structure. In particular, you will notice that you can retrieve: - The X account profile name and the author's handle. - The Tweet URL, text, and publication time. - Any images, embedded links, or media. - Tweet metrics such as replies, likes, and more. ![Note the randomly generated classes](/uploads/blog/twitter-x-scraping-randomly-generated-classes.png) You will also notice that most HTML classes appear to be random and generated at build time. This means you cannot rely on them for node selection. Instead, it is better to focus on `data-*` attributes, as mentioned earlier. Great! Below, you will see how to scrape each of these data fields, populating the data parsing logic in the loop accordingly. ### Step #4: Scrape the Tweet Profile Info and URL Start by inspecting the upper section of a Tweet, which includes the author’s information: ![Inspecting the Tweet’s author profile name element](/uploads/blog/twitter-x-scraping-tweet-author-name.png) ![Inspecting the Tweet’s handle element](/uploads/blog/twitter-x-scraping-tweet-handle.png) The author’s profile name and handle are contained in a `div[data-testid="User-Name"]` HTML element. Scrape both as follows: ```python profile_name_element = tweet_element.select_one('div[data-testid="User-Name"] div span') profile_name = profile_name_element.get_text(strip=True) if profile_name_element else None handle_element = tweet_element.select_one('div[data-testid="User-Name"] div:nth-child(2) a span') handle = handle_element.get_text(strip=True) if handle_element else None ``` In the snippet above, the `select_one()` method returns the HTML element matching the specified CSS selector (or `None` if it is not present). Then, you can extract its content using the [`get_text()`](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#get-text) method. Now, focus on the Tweet publication date: ![Inspecting the Tweet publication date HTML node](/uploads/blog/twitter-x-scraping-tweet-publication-date.png) Notice how the complete timestamp is specified in the `datetime` attribute of the `