# 8 Types of Data Extracted with Web Scraping
> Source: https://scrape.do/blog/web-scraping-data-types/
Published: 2025-06-02 · Updated: 2026-07-21 · Authors: Batuhan Ozyon · Categories: Scraping Basics
Web scraping extracts not just plain text, but **all kinds of content from websites**.
Scrapers may deal with HTML pages one day and PDF files or JSON structures the next.
Being efficient with the **type of data** you're dealing with is crucial, because each format requires a different approach.
In this guide, we'll explore the **different data types web scrapers encounter** and how to handle each one effectively:
## 1 - HTML (Basic Text)
HTML is the standard format of all web pages, containing the text content and structure of sites.
It is also **the crushing majority of data that is scraped from the web**, which is then parsed using libraries like BeautifulSoup or Cheerio to turn into structured data.
There are a few different approaches to scraping and parsing text from HTML:
### Static Pages
Static pages are the simple, unchanging pages on a site like blog posts, news articles, and documentation pages.
Their content doesn’t update frequently once published. You typically *scrape them once* and maybe check back occasionally for edits.
These pages have a fixed HTML structure (headers, paragraphs, etc.), making them straightforward to parse.
💡 For example, using **Python** and requests:
```python
import requests
url = "https://example.com/blog-post"
response = requests.get(url)
with open("page.html", "w", encoding="utf-8") as file:
file.write(response.text)
```
In the code above, we request the page and scrape its full HTML content and write it to a file.
Static pages are usually free of complex scripts, so a simple request is enough.
### Product & Listing Pages
Ever wondered how price trackers like CamelCamelCamel or travel fare aggregators like Skyscanner work?
They’re powered by scraping product, property, or car listing pages. These pages are **dynamic in content** (prices, stock levels, etc.) but often structured in a repeatable way. E-commerce product pages, real estate listings, job postings – they all present itemized data (name, price, description, etc.) that a scraper can regularly collect. The key here is scheduling: you might scrape *daily or hourly* to catch changes.
By regularly scraping competitor or market sites, businesses can monitor prices and inventory in real time.
Such pages may also require handling pagination (multiple pages of listings). A crawler might start at a category page, grab all item links, then scrape each item page for details. You should also be prepared for anti-scraping measures on these sites (CAPTCHAs or IP blocks), since price monitoring is common. Tools like Scrapy (with auto-throttle, proxies) or specialized scraping APIs can be very useful for these cases. The data itself is in HTML, so you’d parse it similar to static pages – just run the scraper on a loop or schedule to keep data fresh.
### User-Generated Content
User-generated content includes social media posts, forum threads, product reviews – basically *content that users continuously create*.
This data type is fast-moving and often requires continuous or frequent scraping.
***Why scrape it?***
Companies track **trends and sentiment** on social platforms to make decisions. Scraping social media can help monitor competitor activity, follow market trends, or analyze customer sentiment in real time.
For example, you might scrape tweets for a hashtag to see how people feel about a new product.
Because this content updates by the minute, scrapers need to run continuously or on short intervals.
**APIs are your friend** here: many platforms (Twitter/X, Reddit, etc.) offer APIs that return JSON data for posts, comments, etc. Using the official API when possible is ideal (it gives structured data and respects the platform's rules). If no API is available or sufficient, you may resort to HTML scraping or even automation (logging in, scrolling). Keep in mind that rate limits and terms of service are important considerations for user-generated data.
One trick for speed: scrape only recent items (e.g., the latest page of a forum or the newest tweets). In the race to catch the next viral trend, **speed is essential** – being first to spot a trending post can be a game changer.
## 2\. Markdown
Markdown is a lightweight text format that you might not *scrape* directly from websites, but it often comes into play.
Many documentation sites or developer content (like README files on GitHub) are written in Markdown. When scraping, you usually retrieve HTML, but you might convert that HTML to Markdown for easier downstream use. Why? Because Markdown is **clean and LLM-friendly** – it preserves structure (headings, lists, bold/italic) without the clutter of HTML tags.
Structured content in Markdown offers significant advantages for machine learning models, improving their accuracy and understanding.
Essentially, Markdown is simpler for both humans and AI to read compared to raw HTML or JSON.
If you plan to use scraped text for training a language model or simply want a cleaner look, you can use libraries to convert HTML to Markdown.
For example, Python’s `markdownify` library can do this in one call. Markdown keeps things like **headings**, **bullet points**, and *italics*, which is great for maintaining readability.
It’s commonly used to prepare datasets for AI: entire websites are scraped, then converted into Markdown files for training GPT models, because the structured simplicity of Markdown helps the model parse the content more effectively.
So while Markdown itself isn’t fetched from websites in most cases (unless the site explicitly serves it), it’s often the *output format* you’ll transform your scraped data into for certain applications.
## 3\. Links (Crawling)
Web scraping isn’t only about grabbing content from single pages – often, you need to discover **all the relevant pages** to scrape.
That's where crawling comes in. Crawling means systematically following links on web pages to find other pages. A web crawler (or spider) starts with a set of seed URLs, fetches them, then extracts the hyperlinks (``) in each page to continue the process.
In short, **web crawling discovers URLs or links** on websites, whereas web scraping is what you do when you extract data from those pages. Most scrapers have a crawling component that finds *what to scrape next*.
Handling links involves parsing HTML and collecting the `href` attributes. For example, to get all links from a page with BeautifulSoup:
```python
links = []
for a in soup.find_all('a', href=True):
links.append(a['href'])
```
This snippet gathers every hyperlink on the page. Of course, not all links are relevant and you’ll usually filter for certain domains or URL patterns (e.g., only crawl links that start with `/products/`).
Also beware of **infinite loops** or cyclic links. A naive crawler could bounce between pages endlessly. Using a framework like Scrapy can automate a lot of this, as it handles queueing URLs and avoiding repeats.
Some strategies for crawling include starting from a site’s sitemap (if available) or starting at a category page and working down to item pages.
## 4\. Media Files (Images, Videos, PDFs)
Not all data comes as text. Web scrapers often need to deal with **media files** like images, videos, and PDFs. These require a different approach: instead of parsing HTML text, you’re typically downloading files and possibly processing them with specialized libraries. Let’s break down each type of media and how to scrape it.
### Images
Images are everywhere online – product photos, profile pictures, infographics, you name it. To scrape images, you usually find their URLs in the HTML (commonly in `
` tags). Websites host images as static files accessible at specific URLs. The page’s HTML contains an `
` tag pointing to the image file, often with an `alt` attribute describing it. As a scraper, you gather those **`src` links** and then download the images via HTTP.
Downloading an image is straightforward with a GET request for the image URL, which returns binary data. For example:
```python
img_url = "https://example.com/image.jpg"
img_data = requests.get(img_url).content
with open("image.jpg", "wb") as f:
f.write(img_data) # save image to disk
```
This will save the image locally. You might do this for multiple images (e.g., scraping all product images from a page). Keep in mind the **file size** – images can be large, so consider throttling or checking the `Content-Length` header before downloading huge files. Also, some sites serve different image versions based on device (using the `srcset` attribute for responsive images). In those cases, you may need logic to pick the desired resolution.
Once you have the image, what next? Sometimes the goal is just to download and store it (for example, creating a dataset of pictures). Other times, you might **analyze** the image – e.g., using OCR to extract text from images or an image processing library to classify it. If needed, you can extract metadata like EXIF (camera info) from photos using libraries like Pillow. But that veers into image processing; from a scraping perspective, the main task is finding and retrieving the image files. Make sure to also scrape the context if needed – for instance, the `alt="Image description"` text is useful as it describes the image content, which can be important for labeling.
### Videos
Scraping videos is a bit more complex than images. Videos on web pages are often embedded players (YouTube, Vimeo, etc.) or HTML5 video tags. The video content might be served in chunks or streams (like `.m3u8` playlists for HLS streaming). As a scraper, you have a few options:
* **Direct download:** Some sites provide direct video file links (`.mp4`, `.webm`). If you find a direct URL to the video file, you can download it similar to an image (GET request and save). The challenge is finding that URL – you might need to inspect network calls or page scripts to locate it.
* **Use a tool:** For platforms like YouTube, it's easier to use an existing tool or API. Tools like `youtube-dl` (or its successor `yt-dlp`) can fetch videos from many sites by handling all the behind-the-scenes details. You can call these from your script or use their Python libraries to programmatically download videos.
* **Headless browser approach:** If videos load via JS and you can’t easily find the source, you might use a headless browser to run the page, then grab the video file once it starts playing. This is heavy and usually a last resort.
Scraping videos often means **large file downloads**, so plan for bandwidth and storage. It may also be time-consuming, so consider if you really need the whole video or just metadata. Speaking of metadata, you might not need the video content itself – maybe you just want the title, duration, view count, etc. Many video platforms provide this info via their pages or APIs. For example, YouTube’s pages embed JSON with video stats, or you can use YouTube’s Data API to get video details without downloading the video.
If you do download videos, you might subsequently run them through tools (like `ffprobe` or `mediainfo`) to extract metadata or snapshots. But at scraping time, it’s usually about getting the file. Here’s a simple conceptual example for a direct video URL:
```python
video_url = "https://example.com/video.mp4"
resp = requests.get(video_url)
with open("video.mp4", "wb") as f:
f.write(resp.content)
```
In reality, many sites won’t give you a plain MP4 link without some negotiation (maybe cookies, headers, or a token), so be prepared for that. Also, always check if downloading videos is allowed for the site – videos are often copyrighted content.
### PDFs
PDF files are a common format for documents – think research papers, reports, product specs, etc. Scraping PDFs involves two steps: **download the PDF**, then **parse its content**. The downloading part is straightforward (again a GET request to the file URL, like with images/videos). The real challenge is parsing, because PDFs are essentially binary documents that encode text and graphics in a complex layout. They are not as easily machine-readable as HTML or JSON.
Common issues with PDFs include:
* **Unstructured text:** Text in PDFs might not have a clear order (columns, footnotes, etc., can confuse the sequence).
* **Varied formatting:** PDFs can contain tables, images, and text with varying fonts/sizes. There’s no DOM like HTML; it's more like coordinates on a page.
* **Scanned pages:** Some PDFs are just scanned images of text – requiring OCR to get actual text.
* **No standard structure:** Unlike HTML which has tags, PDFs don’t have semantic tags to indicate what is a heading or a table, making it hard to consistently extract data.
Because of these challenges, specialized tools are used. Libraries in Python such as **PyMuPDF (fitz)**, **pdfplumber**, or **PDFMiner** can extract text. For tables, libraries like **Camelot** or **Tabula** can detect table structures in PDFs. If the PDF is scanned (images), you’d use an OCR solution like **pytesseract** after converting PDF pages to images. There’s rarely a one-size-fits-all; often a combination is needed. In practice, scraping PDFs might mean saving them and then running a parsing pipeline.
Notably, there are recommended tools for different tasks: PyMuPDF or pdfplumber for general text extraction, Camelot for table extraction, and pdf2image + OCR for image-based PDFs. For example, one approach is:
```python
import fitz # PyMuPDF
doc = fitz.open("report.pdf")
text = ""
for page in doc:
text += page.get_text()
print(text[:200]) # print first 200 chars of combined text
```
This would give you the raw text of a PDF (if it's text-based). But if you need structured data (like a specific table or field), you might have to analyze that text or use a more table-aware library. Be prepared for **cleanup** – PDF text often includes line breaks in weird places or headers/footers mixed in.
It’s worth noting that PDFs are often scraped for data in research and finance. But it’s one of the tougher formats to handle due to the lack of structure. Always verify the output – the scraper might think it got the data, but if columns got jumbled, you need to adjust your parsing logic. In summary: grab the PDF file, then leverage PDF parsers (or even AI in some cases) to extract the information you need. *Patience is key* with PDFs, as they can throw many curveballs during parsing.
## 5\. Tables
Tables are a special case of HTML content worth calling out. Data presented in rows and columns (like financial data, schedules, etc.) is often enclosed in `
` tags on a webpage. Scraping tables can be rewarding (you get nicely structured rows), but also frustrating when the HTML is complex or when tables are rendered via JavaScript.
### HTML Tables (Static)
HTML tables in static content are part of the page’s HTML source. You can parse them with an HTML parser. However, tables can be deeply nested or use spanning cells which break the neat row-column assumption. For instance, a table might have `` which causes misalignment if not accounted for. Despite these challenges, libraries make table scraping easier. The **pandas** library, for example, has a `read_html` function that can automatically grab tables from HTML into a DataFrame. This works well if the table is well-formed. Under the hood it uses libraries like lxml, but for a scraper it means less manual parsing. BeautifulSoup is also commonly used to extract table rows and cells if you need custom handling.
A quick way to get tables:
```python
import pandas as pd
tables = pd.read_html(html_content)
print(f"Found {len(tables)} tables")
df = tables[0] # first table as DataFrame
print(df.head())
```
The above will give you structured data if the table isn’t too tricky. If the table has headers, multi-level columns, or merged cells, you might need to post-process the DataFrame. In other cases, you may choose to parse manually: find the ``, iterate over each `` (table row), and then each `| ` or ` | ` cell. This lets you build a list of rows where each row is a list of cell values. Manual parsing is more work but gives you control, especially if you need to skip header rows or handle nested tables.
A big challenge is that HTML tables often contain *formatting* (like empty cells for spacing, or non-data rows). You’ll have to clean those out. Also, not all tables use proper ` | ` for headers – sometimes the first row is all ` | `. You, as the scraper, must infer what’s header vs data. Despite these issues, scraping static tables is typically easier than scraping data scattered through paragraphs, because tables are already in a structured format. It’s a matter of extracting that structure intact.
### JS-Rendered Tables
Modern web applications sometimes load table data on the fly with JavaScript. This means when you fetch the page HTML via a normal request, you might get an empty `` or just headers, and the rows are populated by a script after page load (for example, via an AJAX call). To scrape these tables, you have two main strategies:
#### 1\. Headless browser / automation:
Use a tool like Selenium, Puppeteer, or Playwright to actually render the page in a browser environment. The automation script can wait for the table to load, then either take the HTML of the filled table or directly read the data. Selenium is invaluable for scraping tables that are loaded via JavaScript.
It can simulate user actions (like clicking “load more” or navigating through a table UI). Once the table is visible, you can use Selenium's methods to iterate over the rows and cells. This approach guarantees you see what a user sees, but it's heavier on resources. Example (conceptual):
```python
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/dynamic-table")
# Wait for table to load (could use WebDriverWait here)
rows = driver.find_elements(By.TAG_NAME, "tr")
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
# ... extract text from cells
driver.quit()
```
This will load the page in Chrome and allow JavaScript to execute, so if the table data is fetched asynchronously, it should appear in `rows`. Selenium even allows waiting for specific conditions (like an element with certain text to appear) to ensure data is ready.
The downside is speed – a headless browser is much slower than direct HTML requests, especially if you have to scrape many pages.
#### API call inspection:
Often, the JavaScript that populates a table does so by calling a backend API (for example, a REST endpoint returning JSON, or a GraphQL query). If you can figure out that request (using developer tools to watch network XHR calls), you can skip the browser and call that API directly.
By finding the XHR that returns the data you need, you can retrieve the entire table in structured form without rendering the page.
This is a *huge* win when possible. For example, suppose a stock prices table on a site is fetched via an API call like `GET /api/prices?page=1&pageSize=100` which returns JSON data for 100 rows. If you call that URL with the correct headers (and authentication if needed), you get JSON that you can parse easily – no need to parse HTML at all. So, always check if dynamic content can be accessed via a hidden API. Many web apps separate data and presentation this way.
In summary, for JS-rendered tables: **try API-first**, and if that fails, use a headless browser. Either way, the goal is to end up with the table data. Once you have it (HTML or JSON), you treat it as you would any table: rows and columns of data to iterate through or convert to CSV/Excel, etc.
⚠ Keep in mind that dynamically loaded tables might also have pagination or “infinite scroll” which means your scraper needs to handle multiple requests or scroll events to get all data (or find an API parameter for page number).
## 6\. JSON (API Calls & Embedded Data)
JSON (JavaScript Object Notation) has become a **ubiquitous data format** on the web. It’s not something you *view* on a webpage (usually), but rather something transmitted behind the scenes or embedded in pages for scripts. Scrapers love JSON because it's structured data – no messy HTML parsing, just key-value pairs and arrays that can be directly read by code. There are two main scenarios where you'll deal with JSON while scraping:
### API Calls (External or Internal)
A lot of websites provide RESTful or GraphQL APIs. These APIs return data in JSON format (sometimes XML, but JSON is more common nowadays). Instead of scraping the website’s HTML, you can sometimes get the same or better data by calling these APIs. For example, an e-commerce site might have an API endpoint like `/api/products/12345` that returns a JSON with all the product details (name, price, stock, reviews, etc.). If you call that directly, you skip parsing the HTML product page altogether – you get structured data ready to use.
Even when sites don’t have "public" APIs, many web apps use JSON APIs internally. Single Page Applications (built with React, Angular, etc.) often load data via fetch/XHR calls. As a scraper, sniff those calls out (using your browser’s dev tools or a proxy) and see if you can replicate them. Usually it involves sending the right headers (often the `X-Requested-With` or auth tokens), but the result is a nicely formatted JSON. This approach is great for staying under the radar too, since you’re mimicking the site’s own requests. As a bonus, hitting a JSON endpoint is lightweight compared to loading a full page.
Handling JSON in code is straightforward. In Python, for instance:
```python
import requests, json
url = "https://api.example.com/data?item=123"
resp = requests.get(url, headers={"Authorization": "Bearer TOKEN"})
data = resp.json() # directly parse JSON response
print(data["itemName"], data["price"])
```
Here we send a request (with an auth header, as an example) and use `resp.json()` to get a Python dict. If the site expects certain headers like a custom User-Agent or API key, you'll need to include those. The key point is that **APIs return structured data**, which is a scrapper’s dream – no regex or HTML tree navigation needed.
One thing to watch: APIs might have rate limits or require API keys. Always check if you’re allowed to use them for scraping purposes. Some public APIs might be unofficial (undocumented but open) – use those carefully to avoid causing issues for the site.
### Embedded JSON in HTML
Sometimes, the data you need is neither in plain HTML text nor available from a separate API endpoint, but it might be sitting **right in the page source as JSON**. Web developers often embed JSON data in pages to pass initial state or configuration to front-end scripts. This can appear in two ways:
* **JSON-LD (Linked Data):** This is usually in ``. This is common in modern web apps – they deliver a blob of JSON to the client which the JS code then uses to render the page. You can grep the HTML for something like `__INITIAL_STATE__` or even `{` characters that look like JSON. If you find a JSON structure, you can pull it out with string operations or an HTML parser and then load it with a JSON library.
* **Framework hydration state:** This is the most common and most useful modern case, and it's worth knowing by name. JavaScript frameworks serialize the entire page's data into a single, predictably-named ` | |