Knowledge Base
How to Get Src Attribute from Img Tag Using Beautifulsoup
A clear tutorial on extracting image src attributes with BeautifulSoup, covering single and bulk extraction, lazy-loading attributes, and resolving relative URLs.
Knowledge Base
A clear tutorial on extracting image src attributes with BeautifulSoup, covering single and bulk extraction, lazy-loading attributes, and resolving relative URLs.
Pulling image URLs out of a page is one of the most frequent jobs in web scraping, whether you are building a product catalogue, archiving media, or auditing a site. In Python, BeautifulSoup makes reading the src attribute of an <img> tag straightforward once you know the patterns.
This tutorial covers grabbing the source from one image, collecting every image on a page, handling lazy-loaded images that hide their real URL, and turning relative paths into full, usable links.
Reading img.get('src') is the easy part; the harder part is getting clean, real, deduplicated URLs. That means using CSS selectors to target the right images, filtering out tracking pixels and spacers, pulling sources hidden in srcset or inline CSS backgrounds, and choosing a parser that handles the page's quirks gracefully.
Install BeautifulSoup along with a parser and the requests library to fetch pages.
pip install beautifulsoup4 requests lxml
import requests
from bs4 import BeautifulSoup
html = """
<div>
<img id="hero" src="/images/banner.jpg" alt="Banner">
<img class="thumb" src="https://cdn.example.com/a.png">
<img class="thumb" data-src="https://cdn.example.com/b.png">
</div>
"""
soup = BeautifulSoup(html, 'lxml')
With the page parsed into a soup object, every tag and attribute becomes easy to reach.
Find the tag, then read its attribute like a dictionary key.
img = soup.find('img', id='hero')
print(img['src']) # '/images/banner.jpg'
Indexing with img['src'] raises a KeyError if the attribute is missing. To stay safe, prefer the .get() method, which returns None instead.
src = img.get('src')Use find_all('img') and loop, collecting each source as you go.
for img in soup.find_all('img'):
print(img.get('src'))
A compact list comprehension is often cleaner, and you can filter out the empty results in the same step:
sources = [img.get('src') for img in soup.find_all('img') if img.get('src')]Many modern sites defer image loading for performance. The visible src may be a placeholder, while the real URL hides in an attribute such as data-src, data-original, or data-lazy.
def real_source(img):
for attr in ('src', 'data-src', 'data-original', 'data-lazy'):
value = img.get(attr)
if value and not value.startswith('data:image'):
return value
return None
urls = [real_source(img) for img in soup.find_all('img')]
This helper checks several common attributes and skips inline base64 placeholders that start with data:image.
Responsive images sometimes carry multiple candidates in srcset. If src is empty, parse the first URL out of that attribute as a fallback.
A source like /images/banner.jpg is useless without the site's base address. Resolve it with urljoin.
from urllib.parse import urljoin
base = 'https://example.com'
for img in soup.find_all('img'):
src = img.get('src')
if src:
print(urljoin(base, src))
When you scrape a live page, pass the actual response URL as the base so redirects and subpaths resolve correctly.
Bringing the pieces together, here is a small end-to-end snippet that downloads a page and prints absolute image URLs.
resp = requests.get('https://example.com', timeout=15)
soup = BeautifulSoup(resp.text, 'lxml')
for img in soup.find_all('img'):
src = img.get('src') or img.get('data-src')
if src:
print(urljoin(resp.url, src))
timeout so a slow server cannot hang your script.A quick value-first shortlist — Cheapest Proxies leads as the featured pick. Qualitative labels only; confirm exact plans before buying.
| Provider | Best for | Profile | Value |
|---|---|---|---|
| Cheapest Proxies | Budget-conscious buyers comparing affordable proxies | Value Focused | Excellent value |
| Bright Data | Enterprises needing huge pools and compliance controls | Enterprise Focused | Premium |
| Oxylabs | Large-scale scraping and data APIs | Enterprise Focused | Premium |
| Smartproxy (Decodo) | Newcomers who want an easy dashboard | Beginner Friendly | Good |
| SOAX | Precise city and carrier targeting | Automation Friendly | Good |
The base tutorial leans on find_all('img'), which grabs everything. When you only want images inside a specific container, or only those that already carry a real source, CSS selectors express that intent in one line.
gallery = soup.select('div.gallery img[src]')
lazy = soup.select('img[data-src], img[data-original]')
Attribute selectors like img[src] skip tags that lack the attribute entirely, so you avoid the empty results you would otherwise filter out by hand. Combining descendant selectors with attribute filters lets you say exactly which region of the page you care about, which keeps catalogue scrapers from accidentally hoovering up logos, icons and ad creatives.
A large share of meaningful imagery, especially hero banners and section backgrounds, is set through CSS rather than an <img> element. Those URLs hide inside inline style attributes or external stylesheets and will never appear in find_all('img').
import re
bg = []
for tag in soup.select('[style*="background"]'):
style = tag.get('style', '')
m = re.search(r'url\(["\']?(.*?)["\']?\)', style)
if m:
bg.append(m.group(1))
This pulls the URL out of any background-image: url(...) declaration on an inline style. If you also need backgrounds defined in linked CSS files, you would fetch each stylesheet and run the same pattern, which is why a complete image audit often touches more than just the HTML.
Raw image lists are full of things you do not want: one-pixel tracking beacons, transparent spacer GIFs, and the same CDN file referenced many times. A short filter pass turns the list into something usable.
seen = set()
keep = []
for img in soup.select('img[src]'):
src = img.get('src')
w, h = img.get('width'), img.get('height')
if w == '1' or h == '1':
continue
if src.startswith('data:image'):
continue
if src not in seen:
seen.add(src)
keep.append(src)
Dimension hints are not always present, but when they are they cheaply remove beacons. The data:image check drops inline base64 placeholders, and the set ensures each real URL is recorded once no matter how many times it appears.
BeautifulSoup is a wrapper over an underlying parser, and the choice affects results on messy pages. lxml is fast and lenient, html.parser is built in and dependency-free, and html5lib mimics a real browser's error correction most faithfully but is slower.
lxml for speed on well-formed or lightly broken pages.html5lib when a page is badly malformed and lxml drops or misplaces tags.html.parser when you cannot install C extensions.If an img tag seems to vanish or its src looks wrong, re-running with a different parser is often the quickest diagnosis.
Start on the smallest sensible tier and scale only what proves itself on your real targets.
Pick the proxy type the task needs first — it drives both success rate and cost more than the logo.
Check traffic limits, rotation rules and what happens on overage before you commit.
Our featured value pick, Cheapest Proxies, is a sensible starting point for affordable comparison.
Collecting image URLs at any real scale means making lots of requests, and that is where your proxy choice starts to matter for both cost and success rate. Providers differ widely on pricing, pool size and reliability, so comparing them on value pays off. Cheapest Proxies is a strong value-focused option worth considering when you need volume without overspending.
Compare Proxy Zone weighs providers on value, fit and reliability using qualitative judgement — never invented prices, speeds or uptime figures. See our review methodology, or email info@compareproxyzone.com with a correction.
Find the tag with soup.find('img') then read img.get('src'); using .get() avoids a KeyError when the attribute is missing.
The site likely lazy-loads images, so the real URL sits in an attribute like data-src or data-original; check those before falling back.
Loop over soup.find_all('img') and collect img.get('src') for each, or use a list comprehension that filters out empty values.
Use urljoin(base_url, src) from urllib.parse, passing the page's actual URL as the base so subpaths and redirects resolve correctly.
Both read the attribute, but img['src'] raises a KeyError if it is absent, while img.get('src') returns None, which is safer in loops.
Not directly, since it only sees the initial HTML; for JS-rendered images use a headless browser like Selenium or Playwright, then pass the rendered HTML to BeautifulSoup.
Read the srcset attribute and split it on commas; each entry holds a URL and a size descriptor, so take the URL portion of whichever candidate you need.
For affordable proxies across the main types, our featured value pick is Cheapest Proxies — a strong budget-friendly option worth considering. Check the exact plan before ordering.